Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add podium of tenacity and fix bugs related to podium #1379

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ local transformItems = {
[42348] = 42349, [42349] = 42348, -- opulent floor lamp
[42363] = 42364, -- djinn lamp
[42365] = 42366, -- djinn lamp
[42367] = 42368, [42368] = 42367, -- podium of tenacity
}

local transformTo = Action()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ function bestiaryOnKill.onKill(player, creature, lastHit)
if not player:isPlayer() or not creature:isMonster() or creature:hasBeenSummoned() or creature:isPlayer() then
return true
end
local mType = MonsterType(creature:getName())
if not mType or mType:bossRace() then
return true
end

local bestiaryBetterment = Concoction.find(Concoction.Ids.BestiaryBetterment)
if not bestiaryBetterment then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ function bosstiaryOnKill.onKill(player, creature, lastHit)
if not player:isPlayer() or not creature:isMonster() or creature:hasBeenSummoned() or creature:isPlayer() then
return true
end
local mType = MonsterType(creature:getName())
if not mType or not mType:bossRace() then
return true
end

local bosstiaryMultiplier = (configManager.getNumber(configKeys.BOSSTIARY_KILL_MULTIPLIER) or 1)
local killBonus = (configManager.getNumber(configKeys.BOOSTED_BOSS_KILL_BONUS) or 3)
Expand Down
4 changes: 4 additions & 0 deletions data/items/items.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52605,9 +52605,11 @@
<attribute key="decayTo" value="35969"/>
</item>
<item id="35973" article="a" name="podium of renown">
<attribute key="transformOnUse" value="35974" />
<attribute key="wrapableto" value="23398"/>
</item>
<item id="35974" article="a" name="podium of renown">
<attribute key="transformOnUse" value="35973" />
<attribute key="wrapableto" value="23398"/>
</item>
<item fromid="35977" toid="35978" name="sarcophagus"/>
Expand Down Expand Up @@ -59051,9 +59053,11 @@
<attribute key="wrapableto" value="23398"/>
</item>
<item id="42367" name="podium of tenacity">
<attribute key="transformOnUse" value="42368" />
<attribute key="wrapableto" value="23398"/>
</item>
<item id="42368" name="podium of tenacity">
<attribute key="transformOnUse" value="42367" />
<attribute key="wrapableto" value="23398"/>
</item>
<item id="43510" name="tibiora's box replica">
Expand Down
38 changes: 38 additions & 0 deletions data/scripts/talkactions/god/add_bosstiary_kills.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
local talkaction = TalkAction("/addbosskill")

function talkaction.onSay(player, words, param)
local usage = "Usage: /addbosskill <kills>,<monster name>,<optional target name>"
if not HasValidTalkActionParams(player, param, usage) then
return false
end

local split = param:split(",")
local kills = tonumber(split[1])
local monsterName = string.capitalize(string.trimSpace(tostring(split[2])))
local targetName = string.capitalize(string.trimSpace(tostring(split[3])))

if not kills or kills < 1 then
player:sendCancelMessage("Invalid kill count.")
return false
end

local target = targetName ~= "" and Player(targetName) or player
if not target then
player:sendCancelMessage("Target player not found.")
return false
end

local message = "Added received kills: " .. kills .. ", for boss: " .. monsterName
if target == player then
player:sendTextMessage(MESSAGE_ADMINISTRADOR, message .. " to yourself.")
else
player:sendTextMessage(MESSAGE_ADMINISTRADOR, message .. " to player: " .. targetName)
target:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received kills: " .. kills .. ", to boss: " .. monsterName)
end
target:addBosstiaryKill(monsterName, kills)
return true
end

talkaction:separator(" ")
talkaction:groupType("god")
talkaction:register()
17 changes: 11 additions & 6 deletions src/creatures/monsters/monsters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,14 +309,19 @@ MonsterType* Monsters::getMonsterType(const std::string &name) {
return nullptr;
}

MonsterType* Monsters::getMonsterTypeByRaceId(uint16_t thisrace) {
phmap::btree_map<uint16_t, std::string> raceid_list = g_game().getBestiaryList();
auto it = raceid_list.find(thisrace);
if (it == raceid_list.end()) {
MonsterType* Monsters::getMonsterTypeByRaceId(uint16_t raceId, bool isBoss /* = false*/) {
MonsterType* bossType = g_ioBosstiary().getMonsterTypeByBossRaceId(raceId);
if (isBoss && bossType) {
return bossType;
}

auto monster_race_map = g_game().getBestiaryList();
auto it = monster_race_map.find(raceId);
if (it == monster_race_map.end()) {
return nullptr;
}
MonsterType* mtype = g_monsters().getMonsterType(it->second);
return (mtype ? mtype : nullptr);

return g_monsters().getMonsterType(it->second);
}

void Monsters::addMonsterType(const std::string &name, MonsterType* mType) {
Expand Down
9 changes: 4 additions & 5 deletions src/creatures/monsters/monsters.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ class MonsterType {
BestiaryType_t bestiaryRace = BESTY_RACE_NONE; // Number (addByte)

// Bosstiary
uint32_t bossRaceId = 0;
uint32_t bossStorageCooldown = 0;
BosstiaryRarity_t bosstiaryRace;
std::string bosstiaryClass;
Expand Down Expand Up @@ -185,15 +184,15 @@ class MonsterType {
}

float getHealthMultiplier() const {
return info.bossRaceId > 0 ? g_configManager().getFloat(RATE_BOSS_HEALTH) : g_configManager().getFloat(RATE_MONSTER_HEALTH);
return info.bosstiaryClass.empty() ? g_configManager().getFloat(RATE_MONSTER_HEALTH) : g_configManager().getFloat(RATE_BOSS_HEALTH);
}

float getAttackMultiplier() const {
return info.bossRaceId > 0 ? g_configManager().getFloat(RATE_BOSS_ATTACK) : g_configManager().getFloat(RATE_MONSTER_ATTACK);
return info.bosstiaryClass.empty() ? g_configManager().getFloat(RATE_MONSTER_ATTACK) : g_configManager().getFloat(RATE_BOSS_ATTACK);
}

float getDefenseMultiplier() const {
return info.bossRaceId > 0 ? g_configManager().getFloat(RATE_BOSS_DEFENSE) : g_configManager().getFloat(RATE_MONSTER_DEFENSE);
return info.bosstiaryClass.empty() ? g_configManager().getFloat(RATE_MONSTER_DEFENSE) : g_configManager().getFloat(RATE_BOSS_DEFENSE);
}

void loadLoot(MonsterType* monsterType, LootBlock lootblock);
Expand Down Expand Up @@ -264,7 +263,7 @@ class Monsters {
}

MonsterType* getMonsterType(const std::string &name);
MonsterType* getMonsterTypeByRaceId(uint16_t thisrace);
MonsterType* getMonsterTypeByRaceId(uint16_t raceId, bool isBoss = false);
void addMonsterType(const std::string &name, MonsterType* mType);
bool deserializeSpell(MonsterSpell* spell, spellBlock_t &sb, const std::string &description = "");

Expand Down
4 changes: 2 additions & 2 deletions src/creatures/players/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -2385,9 +2385,9 @@ class Player final : public Creature, public Cylinder {
return bossRemoveTimes;
}

void sendBossPodiumWindow(const Item* podium, const Position &position, uint16_t itemId, uint8_t stackpos) const {
void sendMonsterPodiumWindow(const Item* podium, const Position &position, uint16_t itemId, uint8_t stackpos) const {
if (client) {
client->sendBossPodiumWindow(podium, position, itemId, stackpos);
client->sendMonsterPodiumWindow(podium, position, itemId, stackpos);
}
}

Expand Down
111 changes: 75 additions & 36 deletions src/game/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ namespace InternalGame {
return true;
}

template <typename T>
T getCustomAttributeValue(const Item* item, const std::string &attributeName) {
static_assert(std::is_integral<T>::value, "T must be an integral type");

auto attribute = item->getCustomAttribute(attributeName);
if (!attribute) {
return 0;
}

int64_t value = attribute->getInteger();
if (value < std::numeric_limits<T>::min() || value > std::numeric_limits<T>::max()) {
spdlog::error("[{}] value is out of range for the specified type", __FUNCTION__);
return 0;
}

return static_cast<T>(value);
}

} // Namespace InternalGame

Game::Game() {
Expand Down Expand Up @@ -3566,15 +3584,16 @@ void Game::playerConfigureShowOffSocket(uint32_t playerId, const Position &pos,
return;
}

bool isPodiumOfRenown = itemId == ITEM_PODIUM_OF_RENOWN1 || itemId == ITEM_PODIUM_OF_RENOWN2;
if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
std::forward_list<Direction> listDir;
if (player->getPathTo(pos, listDir, 0, 1, true, false)) {
g_dispatcher().addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
SchedulerTask* task;
if (itemId != ITEM_PODIUM_OF_VIGOUR) {
if (isPodiumOfRenown) {
task = createSchedulerTask(400, std::bind_front(&Player::sendPodiumWindow, player, item, pos, itemId, stackPos));
} else {
task = createSchedulerTask(400, std::bind_front(&Player::sendBossPodiumWindow, player, item, pos, itemId, stackPos));
task = createSchedulerTask(400, std::bind_front(&Player::sendMonsterPodiumWindow, player, item, pos, itemId, stackPos));
}
player->setNextWalkActionTask(task);
} else {
Expand All @@ -3583,10 +3602,10 @@ void Game::playerConfigureShowOffSocket(uint32_t playerId, const Position &pos,
return;
}

if (itemId != ITEM_PODIUM_OF_VIGOUR) {
if (isPodiumOfRenown) {
player->sendPodiumWindow(item, pos, itemId, stackPos);
} else {
player->sendBossPodiumWindow(item, pos, itemId, stackPos);
player->sendMonsterPodiumWindow(item, pos, itemId, stackPos);
}
}

Expand All @@ -3607,7 +3626,7 @@ void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t &outfit, const Pos
return;
}

Tile* tile = dynamic_cast<Tile*>(item->getParent());
const auto tile = item->getParent() ? item->getParent()->getTile() : nullptr;
if (!tile) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
return;
Expand Down Expand Up @@ -3674,7 +3693,7 @@ void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t &outfit, const Pos
// Change Podium name
if (outfit.lookType != 0 || outfit.lookMount != 0) {
std::ostringstream name;
name << "podium of renown displaying the ";
name << item->getName() << " displaying the ";
bool outfited = false;
if (outfit.lookType != 0) {
const Outfit* outfitInfo = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType);
Expand Down Expand Up @@ -8868,7 +8887,7 @@ void Game::playerBosstiarySlot(uint32_t playerId, uint8_t slotId, uint32_t selec
player->setSlotBossId(slotId, selectedBossId);
}

void Game::playerSetBossPodium(uint32_t playerId, uint32_t bossRaceId, const Position &pos, uint8_t stackPos, const uint16_t itemId, uint8_t direction, uint8_t podiumVisible, uint8_t bossVisible) {
void Game::playerSetMonsterPodium(uint32_t playerId, uint32_t monsterRaceId, const Position &pos, uint8_t stackPos, const uint16_t itemId, uint8_t direction, const std::pair<uint8_t, uint8_t> &podiumAndMonsterVisible) {
Player* player = getPlayerByID(playerId);
if (!player || pos.x == 0xFFFF) {
return;
Expand All @@ -8885,21 +8904,20 @@ void Game::playerSetBossPodium(uint32_t playerId, uint32_t bossRaceId, const Pos
return;
}

if (bossRaceId != 0) {
item->setCustomAttribute("PodiumBossId", static_cast<int64_t>(bossRaceId));
} else {
auto podiumBoss = item->getCustomAttribute("PodiumBossId");
if (podiumBoss) {
bossRaceId = static_cast<uint32_t>(podiumBoss->getInteger());
}
if (monsterRaceId != 0) {
item->setCustomAttribute("PodiumMonsterRaceId", static_cast<int64_t>(monsterRaceId));
} else if (auto podiumMonsterRace = item->getCustomAttribute("PodiumMonsterRaceId")) {
monsterRaceId = static_cast<uint32_t>(podiumMonsterRace->getInteger());
}

const MonsterType* mType = g_ioBosstiary().getMonsterTypeByBossRaceId(bossRaceId);
const MonsterType* mType = g_monsters().getMonsterTypeByRaceId(monsterRaceId, itemId == ITEM_PODIUM_OF_VIGOUR);
if (!mType) {
player->sendCancelMessage(RETURNVALUE_CONTACTADMINISTRATOR);
spdlog::error("[{}] player {} is trying to add invalid monster to podium {}", __FUNCTION__, player->getName(), item->getName());
return;
}

const auto tile = dynamic_cast<Tile*>(item->getParent());
const auto tile = item->getParent() ? item->getParent()->getTile() : nullptr;
if (!tile) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
return;
Expand All @@ -8917,27 +8935,28 @@ void Game::playerSetBossPodium(uint32_t playerId, uint32_t bossRaceId, const Pos
return;
}

if (auto bossOutfit = mType->info.outfit;
bossOutfit.lookType != 0 && bossVisible) {
item->setCustomAttribute("LookTypeEx", static_cast<int64_t>(bossOutfit.lookTypeEx));
item->setCustomAttribute("LookType", static_cast<int64_t>(bossOutfit.lookType));
item->setCustomAttribute("LookHead", static_cast<int64_t>(bossOutfit.lookHead));
item->setCustomAttribute("LookBody", static_cast<int64_t>(bossOutfit.lookBody));
item->setCustomAttribute("LookLegs", static_cast<int64_t>(bossOutfit.lookLegs));
item->setCustomAttribute("LookFeet", static_cast<int64_t>(bossOutfit.lookFeet));
item->setCustomAttribute("LookAddons", static_cast<int64_t>(bossOutfit.lookAddons));
const auto &[podiumVisible, monsterVisible] = podiumAndMonsterVisible;
if (auto monsterOutfit = mType->info.outfit;
monsterOutfit.lookType != 0 && monsterVisible) {
item->setCustomAttribute("LookTypeEx", static_cast<int64_t>(monsterOutfit.lookTypeEx));
item->setCustomAttribute("LookType", static_cast<int64_t>(monsterOutfit.lookType));
item->setCustomAttribute("LookHead", static_cast<int64_t>(monsterOutfit.lookHead));
item->setCustomAttribute("LookBody", static_cast<int64_t>(monsterOutfit.lookBody));
item->setCustomAttribute("LookLegs", static_cast<int64_t>(monsterOutfit.lookLegs));
item->setCustomAttribute("LookFeet", static_cast<int64_t>(monsterOutfit.lookFeet));
item->setCustomAttribute("LookAddons", static_cast<int64_t>(monsterOutfit.lookAddons));
} else {
item->removeCustomAttribute("LookType");
}

item->setCustomAttribute("PodiumVisible", static_cast<int64_t>(podiumVisible));
item->setCustomAttribute("LookDirection", static_cast<int64_t>(direction));
item->setCustomAttribute("BossVisible", static_cast<int64_t>(bossVisible));
item->setCustomAttribute("MonsterVisible", static_cast<int64_t>(monsterVisible));

// Change Podium name
if (bossVisible) {
if (monsterVisible) {
std::ostringstream name;
name << "podium of vigour displaying " << mType->name;
name << item->getName() << " displaying " << mType->name;
item->setAttribute(ItemAttribute_t::NAME, name.str());
} else {
item->removeAttribute(ItemAttribute_t::NAME);
Expand Down Expand Up @@ -8984,27 +9003,47 @@ void Game::playerRotatePodium(uint32_t playerId, const Position &pos, uint8_t st
return;
}

auto podiumBoss = item->getCustomAttribute("PodiumBossId");
auto podiumRaceIdAttribute = item->getCustomAttribute("PodiumMonsterRaceId");
auto lookDirection = item->getCustomAttribute("LookDirection");
auto podiumVisible = item->getCustomAttribute("PodiumVisible");
auto bossVisible = item->getCustomAttribute("BossVisible");
auto monsterVisible = item->getCustomAttribute("MonsterVisible");

auto podiumBossId = static_cast<uint8_t>(podiumBoss ? podiumBoss->getInteger() : 0);
auto podiumRaceId = podiumRaceIdAttribute ? static_cast<uint16_t>(podiumRaceIdAttribute->getInteger()) : 0;
uint8_t directionValue;
if (lookDirection) {
directionValue = static_cast<uint8_t>(lookDirection->getInteger() >= 3 ? 0 : lookDirection->getInteger() + 1);
} else {
directionValue = 2;
}
auto isPodiumVisible = static_cast<uint8_t>(podiumVisible ? podiumVisible->getInteger() : 1);
bool isBossVisible = bossVisible ? bossVisible->getInteger() : false;
auto isPodiumVisible = podiumVisible ? static_cast<bool>(podiumVisible->getInteger()) : false;
bool isMonsterVisible = monsterVisible ? static_cast<bool>(monsterVisible->getInteger()) : false;

if (!isBossVisible || podiumBossId == 0) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
// Rotate monster podium (bestiary or bosstiary) to the new direction
bool isPodiumOfRenown = itemId == ITEM_PODIUM_OF_RENOWN1 || itemId == ITEM_PODIUM_OF_RENOWN2;
if (!isPodiumOfRenown) {
if (!isMonsterVisible || podiumRaceId == 0) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
return;
}

playerSetMonsterPodium(playerId, podiumRaceId, pos, stackPos, itemId, directionValue, std::make_pair(isPodiumVisible, isMonsterVisible));
return;
}

playerSetBossPodium(playerId, podiumBossId, pos, stackPos, itemId, directionValue, isPodiumVisible, isBossVisible);
// We retrieve the outfit information to be able to rotate in the new direction
Outfit_t newOutfit;
newOutfit.lookType = InternalGame::getCustomAttributeValue<uint16_t>(item, "LookType");
newOutfit.lookHead = InternalGame::getCustomAttributeValue<uint8_t>(item, "LookHead");
newOutfit.lookBody = InternalGame::getCustomAttributeValue<uint8_t>(item, "LookBody");
newOutfit.lookLegs = InternalGame::getCustomAttributeValue<uint8_t>(item, "LookLegs");
newOutfit.lookFeet = InternalGame::getCustomAttributeValue<uint8_t>(item, "LookFeet");

newOutfit.lookMount = InternalGame::getCustomAttributeValue<uint16_t>(item, "LookMount");
newOutfit.lookMountHead = InternalGame::getCustomAttributeValue<uint8_t>(item, "LookMountHead");
newOutfit.lookMountBody = InternalGame::getCustomAttributeValue<uint8_t>(item, "LookMountBody");
newOutfit.lookMountLegs = InternalGame::getCustomAttributeValue<uint8_t>(item, "LookMountLegs");
newOutfit.lookMountFeet = InternalGame::getCustomAttributeValue<uint8_t>(item, "LookMountFeet");
playerSetShowOffSocket(player->getID(), newOutfit, pos, stackPos, itemId, isPodiumVisible, directionValue);
}

void Game::playerRequestInventoryImbuements(uint32_t playerId, bool isTrackerOpen) {
Expand Down
2 changes: 1 addition & 1 deletion src/game/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ class Game {
void playerBrowseForgeHistory(uint32_t playerId, uint8_t page);

void playerBosstiarySlot(uint32_t playerId, uint8_t slotId, uint32_t selectedBossId);
void playerSetBossPodium(uint32_t playerId, uint32_t bossRaceId, const Position &pos, uint8_t stackPos, const uint16_t itemId, uint8_t direction, uint8_t podiumVisible, uint8_t bossVisible);
void playerSetMonsterPodium(uint32_t playerId, uint32_t monsterRaceId, const Position &pos, uint8_t stackPos, const uint16_t itemId, uint8_t direction, const std::pair<uint8_t, uint8_t> &podiumAndMonsterVisible);
void playerRotatePodium(uint32_t playerId, const Position &pos, uint8_t stackPos, const uint16_t itemId);

void playerRequestInventoryImbuements(uint32_t playerId, bool isTrackerOpen);
Expand Down
Loading