Skip to content

Commit

Permalink
feat: added outfit and mount preview feature to the store (#773)
Browse files Browse the repository at this point in the history
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.