Skip to content

Commit

Permalink
feat: added outfit and mount preview feature to the store (opentibiab…
Browse files Browse the repository at this point in the history
…r#773)

This change adds the new outfit and mount preview feature in the Tibia game store. In addition to the new interface, which offers a better structured and layout, players can now try out all the available outfits and mounts before purchasing them.
  • Loading branch information
murilo09 authored Feb 6, 2023
1 parent 27737e6 commit 5c50c5e
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 27 deletions.
2 changes: 1 addition & 1 deletion data-otservbr-global/migrations/16.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function onUpdateDatabase()
print("> Updating database to version 17 (Tutorial support)")
print("Updating database to version 17 (Tutorial support)")
db.query("ALTER TABLE `players` ADD `istutorial` SMALLINT(1) NOT NULL DEFAULT '0'")
return true -- true = There are others migrations file | false = this is the last migration file
end
4 changes: 3 additions & 1 deletion data-otservbr-global/migrations/24.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
function onUpdateDatabase()
return false -- true = There are others migrations file | false = this is the last migration file
Spdlog.info("Updating database to version 25 (random mount outfit window)")
db.query("ALTER TABLE `players` ADD `randomize_mount` SMALLINT(1) NOT NULL DEFAULT '0'")
return true
end
3 changes: 3 additions & 0 deletions data-otservbr-global/migrations/25.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function onUpdateDatabase()
return false -- true = There are others migrations file | false = this is the last migration file
end
3 changes: 2 additions & 1 deletion schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS `server_config` (
CONSTRAINT `server_config_pk` PRIMARY KEY (`config`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '24'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '25'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');

-- Table structure `accounts`
CREATE TABLE IF NOT EXISTS `accounts` (
Expand Down Expand Up @@ -142,6 +142,7 @@ CREATE TABLE IF NOT EXISTS `players` (
`istutorial` tinyint(1) NOT NULL DEFAULT '0',
`forge_dusts` bigint(21) NOT NULL DEFAULT '0',
`forge_dust_level` bigint(21) NOT NULL DEFAULT '100',
`randomize_mount` tinyint(1) NOT NULL DEFAULT '0',
INDEX `account_id` (`account_id`),
INDEX `vocation` (`vocation`),
CONSTRAINT `players_pk` PRIMARY KEY (`id`),
Expand Down
32 changes: 32 additions & 0 deletions src/creatures/players/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5256,6 +5256,33 @@ void Player::setCurrentMount(uint8_t mount)
addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mount);
}

bool Player::hasAnyMount() const
{
for (const auto& mounts = g_game().mounts.getMounts();
const Mount& mount : mounts) {
if (hasMount(&mount)) {
return true;
}
}
return false;
}

uint8_t Player::getRandomMountId() const
{
std::vector<uint8_t> playerMounts;

for (const auto& mounts = g_game().mounts.getMounts();
const Mount& mount : mounts) {
if (hasMount(&mount)) {
playerMounts.push_back(mount.id);
}
}

auto playerMountsSize = static_cast<int32_t>(playerMounts.size() - 1);
auto randomIndex = uniform_random(0, std::max<int32_t>(0, playerMountsSize));
return playerMounts.at(randomIndex);
}

bool Player::toggleMount(bool mount)
{
if ((OTSYS_TIME() - lastToggleMount) < 3000 && !wasMounted) {
Expand Down Expand Up @@ -5284,6 +5311,10 @@ bool Player::toggleMount(bool mount)
return false;
}

if (isRandomMounted()) {
currentMountId = getRandomMountId();
}

const Mount* currentMount = g_game().mounts.getMountByID(currentMountId);
if (!currentMount) {
return false;
Expand All @@ -5306,6 +5337,7 @@ bool Player::toggleMount(bool mount)
}

defaultOutfit.lookMount = currentMount->clientId;
setCurrentMount(currentMount->id);

if (currentMount->speed != 0) {
g_game().changeSpeed(this, currentMount->speed);
Expand Down
10 changes: 10 additions & 0 deletions src/creatures/players/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,17 @@ class Player final : public Creature, public Cylinder
bool tameMount(uint8_t mountId);
bool untameMount(uint8_t mountId);
bool hasMount(const Mount* mount) const;
bool hasAnyMount() const;
uint8_t getRandomMountId() const;
void dismount();

uint8_t isRandomMounted() const {
return randomMount;
}
void setRandomMount(uint8_t isMountRandomized) {
randomMount = isMountRandomized;
}

void sendFYIBox(const std::string& message) {
if (client) {
client->sendFYIBox(message);
Expand Down Expand Up @@ -2385,6 +2394,7 @@ class Player final : public Creature, public Cylinder
int32_t idleTime = 0;
uint32_t coinBalance = 0;
uint16_t expBoostStamina = 0;
uint8_t randomMount = 0;

uint16_t lastStatsTrainingTime = 0;
uint16_t staminaMinutes = 2520;
Expand Down
25 changes: 19 additions & 6 deletions src/game/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4908,7 +4908,7 @@ void Game::playerToggleMount(uint32_t playerId, bool mount)
player->toggleMount(mount);
}

void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, uint8_t isMountRandomized/* = 0*/)
{
if (!g_configManager().getBoolean(ALLOW_CHANGEOUTFIT)) {
return;
Expand All @@ -4919,6 +4919,13 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
return;
}

player->setRandomMount(isMountRandomized);

if (isMountRandomized && outfit.lookMount != 0 && player->hasAnyMount()) {
auto randomMount = mounts.getMountByID(player->getRandomMountId());
outfit.lookMount = randomMount->clientId;
}

const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType);
if (!playerOutfit) {
outfit.lookMount = 0;
Expand All @@ -4934,17 +4941,23 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
return;
}

const Tile* playerTile = player->getTile();
if (!playerTile) {
return;
}

if (playerTile->hasFlag(TILESTATE_PROTECTIONZONE)) {
outfit.lookMount = 0;
}

if (player->isMounted()) {
Mount* prevMount = mounts.getMountByID(player->getCurrentMount());
if (prevMount) {
changeSpeed(player, mount->speed - prevMount->speed);
}

player->setCurrentMount(mount->id);
} else {
player->setCurrentMount(mount->id);
outfit.lookMount = 0;
}

player->setCurrentMount(mount->id);
} else if (player->isMounted()) {
player->dismount();
}
Expand Down
2 changes: 1 addition & 1 deletion src/game/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ class Game
void playerShowQuestLine(uint32_t playerId, uint16_t questId);
void playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type,
const std::string& receiver, const std::string& text);
void playerChangeOutfit(uint32_t playerId, Outfit_t outfit);
void playerChangeOutfit(uint32_t playerId, Outfit_t outfit, uint8_t isMountRandomized = 0);
void playerInviteToParty(uint32_t playerId, uint32_t invitedId);
void playerJoinParty(uint32_t playerId, uint32_t leaderId);
void playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId);
Expand Down
10 changes: 6 additions & 4 deletions src/io/iologindata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ bool IOLoginData::preloadPlayer(Player* player, const std::string& name)
player->setGUID(result->getNumber<uint32_t>("id"));
Group* group = g_game().groups.getGroup(result->getNumber<uint16_t>("group_id"));
if (!group) {
SPDLOG_ERROR("Player {} has group id {} whitch doesn't exist", player->name,
SPDLOG_ERROR("Player {} has group id {} which doesn't exist", player->name,
result->getNumber<uint16_t>("group_id"));
return false;
}
Expand Down Expand Up @@ -164,7 +164,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)

Group* group = g_game().groups.getGroup(result->getNumber<uint16_t>("group_id"));
if (!group) {
SPDLOG_ERROR("Player {} has group id {} whitch doesn't exist", player->name, result->getNumber<uint16_t>("group_id"));
SPDLOG_ERROR("Player {} has group id {} which doesn't exist", player->name, result->getNumber<uint16_t>("group_id"));
return false;
}
player->setGroup(group);
Expand Down Expand Up @@ -216,7 +216,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
}

if (!player->setVocation(result->getNumber<uint16_t>("vocation"))) {
SPDLOG_ERROR("Player {} has vocation id {} whitch doesn't exist",
SPDLOG_ERROR("Player {} has vocation id {} which doesn't exist",
player->name, result->getNumber<uint16_t>("vocation"));
return false;
}
Expand Down Expand Up @@ -282,6 +282,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
player->addTaskHuntingPoints(result->getNumber<uint64_t>("task_points"));
player->addForgeDusts(result->getNumber<uint64_t>("forge_dusts"));
player->addForgeDustLevel(result->getNumber<uint64_t>("forge_dust_level"));
player->setRandomMount(result->getNumber<uint16_t>("randomize_mount"));

player->lastLoginSaved = result->getNumber<time_t>("lastlogin");
player->lastLogout = result->getNumber<time_t>("lastlogout");
Expand All @@ -292,7 +293,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)

Town* town = g_game().map.towns.getTown(result->getNumber<uint32_t>("town_id"));
if (!town) {
SPDLOG_ERROR("Player {} has town id {} whitch doesn't exist", player->name,
SPDLOG_ERROR("Player {} has town id {} which doesn't exist", player->name,
result->getNumber<uint16_t>("town_id"));
return false;
}
Expand Down Expand Up @@ -891,6 +892,7 @@ bool IOLoginData::savePlayer(Player* player)
query << "`task_points` = " << player->getTaskHuntingPoints() << ',';
query << "`forge_dusts` = " << player->getForgeDusts() << ',';
query << "`forge_dust_level` = " << player->getForgeDustLevel() << ',';
query << "`randomize_mount` = " << static_cast<uint16_t>(player->isRandomMounted()) << ',';

query << "`cap` = " << (player->capacity / 100) << ',';
query << "`sex` = " << static_cast<uint16_t>(player->sex) << ',';
Expand Down
54 changes: 41 additions & 13 deletions src/server/network/protocol/protocolgame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,8 @@ void ProtocolGame::parseSetOutfit(NetworkMessage &msg)
newOutfit.lookMountLegs = std::min<uint8_t>(132, msg.getByte());
newOutfit.lookMountFeet = std::min<uint8_t>(132, msg.getByte());
newOutfit.lookFamiliarsType = msg.get<uint16_t>();
g_game().playerChangeOutfit(player->getID(), newOutfit);
uint8_t isMountRandomized = msg.getByte();
g_game().playerChangeOutfit(player->getID(), newOutfit, isMountRandomized);
}
else if (outfitType == 1)
{
Expand Down Expand Up @@ -5938,15 +5939,34 @@ void ProtocolGame::sendOutfitWindow()

for (const Outfit& outfit : outfits) {
uint8_t addons;
if (!player->getOutfitAddons(outfit, addons)) {
continue;
if (player->getOutfitAddons(outfit, addons)) {
msg.add<uint16_t>(outfit.lookType);
msg.addString(outfit.name);
msg.addByte(addons);
msg.addByte(0x00);
++outfitSize;
} else if (outfit.lookType == 1210 || outfit.lookType == 1211) {
msg.add<uint16_t>(outfit.lookType);
msg.addString(outfit.name);
msg.addByte(3);
msg.addByte(0x02);
++outfitSize;
} else if (outfit.lookType == 1456 || outfit.lookType == 1457) {
msg.add<uint16_t>(outfit.lookType);
msg.addString(outfit.name);
msg.addByte(3);
msg.addByte(0x03);
++outfitSize;
} else if (outfit.from == "store") {
msg.add<uint16_t>(outfit.lookType);
msg.addString(outfit.name);
msg.addByte(outfit.lookType >= 962 && outfit.lookType <= 975 ? 0 : 3);
msg.addByte(0x01);
msg.add<uint32_t>(0x00);
++outfitSize;
}

msg.add<uint16_t>(outfit.lookType);
msg.addString(outfit.name);
msg.addByte(addons);
msg.addByte(0x00);
if (++outfitSize == limitOutfits) {
if (outfitSize == limitOutfits) {
break;
}
}
Expand All @@ -5967,9 +5987,17 @@ void ProtocolGame::sendOutfitWindow()
msg.add<uint16_t>(mount.clientId);
msg.addString(mount.name);
msg.addByte(0x00);
if (++mountSize == limitMounts) {
break;
}
++mountSize;
} else if (mount.type == "store") {
msg.add<uint16_t>(mount.clientId);
msg.addString(mount.name);
msg.addByte(0x01);
msg.add<uint32_t>(0x00);
++mountSize;
}

if (mountSize == limitMounts) {
break;
}
}

Expand Down Expand Up @@ -6006,8 +6034,8 @@ void ProtocolGame::sendOutfitWindow()
msg.addByte(0x00); //Try outfit
msg.addByte(mounted ? 0x01 : 0x00);

// Version 12.81 - Random outfit 'bool'
msg.addByte(0);
// Version 12.81 - Random mount 'bool'
msg.addByte(player->isRandomMounted() ? 0x01 : 0x00);

writeToOutputBuffer(msg);
}
Expand Down

0 comments on commit 5c50c5e

Please sign in to comment.