From 9b5c173d214f0c6fc328d4de89174809322412db Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Sun, 10 Sep 2023 00:52:12 -0700 Subject: [PATCH 1/2] feat: mutliple creature icons --- .../magma_bubble_fight.lua | 66 +++++++++++---- data/libs/functions/creature.lua | 9 +-- src/creatures/creature.cpp | 3 +- src/creatures/creature.hpp | 38 +++++++-- src/creatures/monsters/monster.cpp | 6 +- src/creatures/monsters/monster.hpp | 15 ++-- src/creatures/players/player.cpp | 4 +- .../creatures/creature_functions.cpp | 81 +++++++++++++++---- .../creatures/creature_functions.hpp | 6 ++ .../creatures/monster/monster_functions.cpp | 7 +- src/server/network/protocol/protocolgame.cpp | 12 +-- 11 files changed, 176 insertions(+), 71 deletions(-) diff --git a/data-otservbr-global/scripts/quests/primal_ordeal_quest/magma_bubble_fight.lua b/data-otservbr-global/scripts/quests/primal_ordeal_quest/magma_bubble_fight.lua index f91f8df93e8..f13c55d89e4 100644 --- a/data-otservbr-global/scripts/quests/primal_ordeal_quest/magma_bubble_fight.lua +++ b/data-otservbr-global/scripts/quests/primal_ordeal_quest/magma_bubble_fight.lua @@ -3,9 +3,9 @@ local chargedFlameId = 39230 local heatedCrystalId = 39168 local cooledCrystalId = 39169 -local overheatedZone = Zone('fight.magma-bubble.overheated') +local overheatedZone = Zone("fight.magma-bubble.overheated") local bossZone = Zone("boss.magma-bubble") -local spawnZone = Zone('fight.magma-bubble.spawn') +local spawnZone = Zone("fight.magma-bubble.spawn") -- top left overheatedZone:addArea({ x = 33634, y = 32891, z = 15 }, { x = 33645, y = 32898, z = 15 }) @@ -19,7 +19,6 @@ overheatedZone:addArea({ x = 33664, y = 32896, z = 15 }, { x = 33671, y = 32899, overheatedZone:addArea({ x = 33635, y = 32911, z = 15 }, { x = 33643, y = 32929, z = 15 }) overheatedZone:addArea({ x = 33644, y = 32921, z = 15 }, { x = 33647, y = 32928, z = 15 }) - -- central area where monsters/boss spawns spawnZone:addArea({ x = 33647, y = 32900, z = 15 }, { x = 33659, y = 32913, z = 15 }) @@ -96,25 +95,25 @@ end encounter:register() local function addShieldStack(player) - local currentIcon = player:getIcon() + local currentIcon = player:getIcon("magma-bubble") if not currentIcon or currentIcon.category ~= CreatureIconCategory_Quests or currentIcon.icon ~= CreatureIconQuests_GreenShield then - player:setIcon(CreatureIconCategory_Quests, CreatureIconQuests_GreenShield, 5) + player:setIcon("magma-bubble", CreatureIconCategory_Quests, CreatureIconQuests_GreenShield, 5) return true end - player:setIcon(CreatureIconCategory_Quests, CreatureIconQuests_GreenShield, currentIcon.count + 5) + player:setIcon("magma-bubble", CreatureIconCategory_Quests, CreatureIconQuests_GreenShield, currentIcon.count + 5) end local function tickShields(player) - local currentIcon = player:getIcon() + local currentIcon = player:getIcon("magma-bubble") if not currentIcon or currentIcon.category ~= CreatureIconCategory_Quests or currentIcon.icon ~= CreatureIconQuests_GreenShield then return 0 end if currentIcon.count <= 0 then - player:clearIcon() + player:removeIcon("magma-bubble") return 0 end local newCount = currentIcon.count - 1 - player:setIcon(CreatureIconCategory_Quests, CreatureIconQuests_GreenShield, newCount) + player:setIcon("magma-bubble", CreatureIconCategory_Quests, CreatureIconQuests_GreenShield, newCount) return newCount end @@ -122,7 +121,9 @@ local overheatedDamage = GlobalEvent("self.magma-bubble.overheated.onThink") function overheatedDamage.onThink(interval, lastExecution) local players = overheatedZone:getPlayers() for _, player in ipairs(players) do - if player:getHealth() <= 0 then goto continue end + if player:getHealth() <= 0 then + goto continue + end local shields = tickShields(player) if shields > 0 then local effect = CONST_ME_BLACKSMOKE @@ -201,9 +202,15 @@ end local chargedFlameAction = Action() function chargedFlameAction.onUse(player, item, fromPosition, target, toPosition, isHotkey) - if not player then return false end - if not target or not target:isItem() then return false end - if target:getId() ~= cooledCrystalId then return false end + if not player then + return false + end + if not target or not target:isItem() then + return false + end + if target:getId() ~= cooledCrystalId then + return false + end target:transform(heatedCrystalId) local positions = { Position(toPosition.x - 1, toPosition.y, toPosition.z), @@ -227,8 +234,12 @@ chargedFlameAction:register() local shieldField = MoveEvent() function shieldField.onStepIn(creature, item, position, fromPosition) local player = creature:getPlayer() - if not player then return false end - if not encounter:isInZone(player:getPosition()) then return false end + if not player then + return false + end + if not encounter:isInZone(player:getPosition()) then + return false + end item:remove() addShieldStack(player) end @@ -239,7 +250,9 @@ shieldField:register() local theEndOfDaysHealth = CreatureEvent("fight.magma-bubble.TheEndOfDaysHealth") function theEndOfDaysHealth.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType) - if not creature then return primaryDamage, primaryType, secondaryDamage, secondaryType end + if not creature then + return primaryDamage, primaryType, secondaryDamage, secondaryType + end local newHealth = creature:getHealth() - primaryDamage - secondaryDamage if newHealth <= creature:getMaxHealth() * 0.5 then creature:setHealth(creature:getMaxHealth()) @@ -281,3 +294,24 @@ function magmaBubbleDeath.onDeath() end magmaBubbleDeath:register() + +local zoneEvent = ZoneEvent(bossZone) +function zoneEvent.afterEnter(_zone, creature) + local player = creature:getPlayer() + if not player then + return false + end + + player:setIcon("magma-bubble", CreatureIconCategory_Quests, CreatureIconQuests_GreenShield, 0) +end + +function zoneEvent.afterLeave(_zone, creature) + local player = creature:getPlayer() + if not player then + return false + end + + player:removeIcon("magma-bubble") +end + +zoneEvent:register() diff --git a/data/libs/functions/creature.lua b/data/libs/functions/creature.lua index ff44e87d55a..86245cc2491 100644 --- a/data/libs/functions/creature.lua +++ b/data/libs/functions/creature.lua @@ -19,8 +19,7 @@ function Creature.getClosestFreePosition(self, position, maxRadius, mustBeReacha end local tile = Tile(checkPosition) - if tile and tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) and - (not mustBeReachable or self:getPathTo(checkPosition)) then + if tile and tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) and (not mustBeReachable or self:getPathTo(checkPosition)) then return checkPosition end end @@ -90,7 +89,7 @@ function Creature:setItemOutfit(item, time) local condition = Condition(CONDITION_OUTFIT) condition:setOutfit({ - lookTypeEx = itemType:getId() + lookTypeEx = itemType:getId(), }) condition:setTicks(time) self:addCondition(condition) @@ -204,7 +203,3 @@ function Creature:addEventStamina(target) end end end - -function Creature:clearIcon() - self:setIcon(0, 0) -end diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index bc5fda492e7..78d7720bf57 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -1788,8 +1788,7 @@ const phmap::parallel_flat_hash_set> Creature::getZones() return Zone::getZones(getPosition()); } -void Creature::setIcon(CreatureIcon icon) { - creatureIcon = icon; +void Creature::iconChanged() const { if (!tile) { return; } diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index 9070ae41230..6571604bba4 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -222,16 +222,41 @@ class Creature : virtual public Thing { varBuffs[buff] += modifier; } - virtual CreatureIcon getIcon() const { - return creatureIcon; + virtual const std::vector getIcons() const { + std::vector icons; + icons.reserve(creatureIcons.size()); + for (const auto &[_, icon] : creatureIcons) { + if (icon.isSet()) { + icons.push_back(icon); + } + } + return icons; + } + + virtual CreatureIcon getIcon(const std::string &key) const { + if (!creatureIcons.contains(key)) { + return CreatureIcon(); + } + return creatureIcons.at(key); } - void setIcon(CreatureIcon icon); + void setIcon(const std::string &key, CreatureIcon icon) { + creatureIcons[key] = icon; + iconChanged(); + } + + void removeIcon(const std::string &key) { + creatureIcons.erase(key); + iconChanged(); + } - void clearIcon() { - setIcon(CreatureIcon()); + void clearIcons() { + creatureIcons.clear(); + iconChanged(); } + void iconChanged() const; + const Outfit_t getCurrentOutfit() const { return currentOutfit; } @@ -726,7 +751,8 @@ class Creature : virtual public Thing { uint8_t wheelOfDestinyDrainBodyDebuff = 0; - CreatureIcon creatureIcon = CreatureIcon(); + // use map here instead of phmap to keep the keys in a predictable order + std::map creatureIcons = {}; // creature script events bool hasEventRegistered(CreatureEventType_t event) const { diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index f344529a4d2..43d41567bfa 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -2122,12 +2122,12 @@ void Monster::configureForgeSystem() { if (monsterForgeClassification == ForgeClassifications_t::FORGE_FIENDISH_MONSTER) { setForgeStack(15); - setIcon(CreatureIcon(CreatureIconModifications_t::Fiendish, 0 /* don't show stacks on fiends */)); + setIcon("forge", CreatureIcon(CreatureIconModifications_t::Fiendish, 0 /* don't show stacks on fiends */)); g_game().updateCreatureIcon(this); } else if (monsterForgeClassification == ForgeClassifications_t::FORGE_INFLUENCED_MONSTER) { auto stack = static_cast(normal_random(1, 5)); setForgeStack(stack); - setIcon(CreatureIcon(CreatureIconModifications_t::Influenced, stack)); + setIcon("forge", CreatureIcon(CreatureIconModifications_t::Influenced, stack)); g_game().updateCreatureIcon(this); } @@ -2153,7 +2153,7 @@ void Monster::clearFiendishStatus() { health = mType->info.health * mType->getHealthMultiplier(); healthMax = mType->info.healthMax * mType->getHealthMultiplier(); - clearIcon(); + removeIcon("forge"); g_game().updateCreatureIcon(this); g_game().sendUpdateCreature(this); } diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index f1c966fcbc3..a32d2a737e4 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -160,18 +160,19 @@ class Monster final : public Creature { return challengeFocusDuration > 0; } - CreatureIcon getIcon() const override { - if (creatureIcon.isSet()) { - return creatureIcon; + const std::vector getIcons() const override { + const auto creatureIcons = Creature::getIcons(); + if (!creatureIcons.empty()) { + return creatureIcons; } if (challengeMeleeDuration > 0 && mType->info.targetDistance > targetDistance) { - return CreatureIcon(CreatureIconModifications_t::TurnedMelee); + return { CreatureIcon(CreatureIconModifications_t::TurnedMelee) }; } else if (varBuffs[BUFF_DAMAGERECEIVED] > 100) { - return CreatureIcon(CreatureIconModifications_t::HigherDamageReceived); + return { CreatureIcon(CreatureIconModifications_t::HigherDamageReceived) }; } else if (varBuffs[BUFF_DAMAGEDEALT] < 100) { - return CreatureIcon(CreatureIconModifications_t::LowerDamageDealt); + return { CreatureIcon(CreatureIconModifications_t::LowerDamageDealt) }; } - return CreatureIcon(); + return {}; } void setNormalCreatureLight() override; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 756e256fcba..c4be5639ef4 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -7567,9 +7567,9 @@ void Player::setHazardSystemPoints(int32_t count) { addStorageValue(STORAGEVALUE_HAZARDCOUNT, std::max(0, std::min(0xFFFF, count)), true); reloadHazardSystemPointsCounter = true; if (count > 0) { - setIcon(CreatureIcon(CreatureIconQuests_t::Hazard, count)); + setIcon("hazard", CreatureIcon(CreatureIconQuests_t::Hazard, count)); } else { - clearIcon(); + removeIcon("hazard"); } } diff --git a/src/lua/functions/creatures/creature_functions.cpp b/src/lua/functions/creatures/creature_functions.cpp index b97f3eff491..b9260eb206e 100644 --- a/src/lua/functions/creatures/creature_functions.cpp +++ b/src/lua/functions/creatures/creature_functions.cpp @@ -982,46 +982,95 @@ int CreatureFunctions::luaCreatureGetZones(lua_State* L) { } int CreatureFunctions::luaCreatureSetIcon(lua_State* L) { - // creature:setIcon(category, icon[, number]) - Creature* creature = getUserdata(L, 1); + // creature:setIcon(key, category, icon[, number]) + auto creature = getUserdata(L, 1); if (!creature) { reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); pushBoolean(L, false); return 1; } - auto category = getNumber(L, 2); - auto count = getNumber(L, 4, 0); + const auto key = getString(L, 2); + auto category = getNumber(L, 3); + auto count = getNumber(L, 5, 0); CreatureIcon creatureIcon; if (category == CreatureIconCategory_t::Modifications) { - auto icon = getNumber(L, 3); + auto icon = getNumber(L, 5); creatureIcon = CreatureIcon(icon, count); } else { - auto icon = getNumber(L, 3); + auto icon = getNumber(L, 4); creatureIcon = CreatureIcon(icon, count); } - creature->setIcon(creatureIcon); + creature->setIcon(key, creatureIcon); pushBoolean(L, true); return 1; } -int CreatureFunctions::luaCreatureGetIcon(lua_State* L) { - // creature:getIcon() - Creature* creature = getUserdata(L, 1); +int CreatureFunctions::luaCreatureGetIcons(lua_State* L) { + // creature:getIcons() + const auto creature = getUserdata(L, 1); if (!creature) { reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); pushBoolean(L, false); return 1; } - auto creatureIcon = creature->getIcon(); - if (creatureIcon.isNone()) { + auto icons = creature->getIcons(); + lua_createtable(L, static_cast(icons.size()), 0); + for (auto &icon : icons) { + lua_createtable(L, 0, 3); + setField(L, "category", static_cast(icon.category)); + setField(L, "icon", icon.serialize()); + setField(L, "count", icon.count); + lua_rawseti(L, -2, static_cast(icon.category)); + } + return 1; +} + +int CreatureFunctions::luaCreatureGetIcon(lua_State* L) { + // creature:getIcon(key) + const auto creature = getUserdata(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + const auto key = getString(L, 2); + auto icon = creature->getIcon(key); + if (icon.isSet()) { + lua_createtable(L, 0, 3); + setField(L, "category", static_cast(icon.category)); + setField(L, "icon", icon.serialize()); + setField(L, "count", icon.count); + } else { lua_pushnil(L); + } + return 1; +} + +int CreatureFunctions::luaCreatureRemoveIcon(lua_State* L) { + // creature:removeIcon(key) + auto creature = getUserdata(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); return 1; } - lua_createtable(L, 0, 3); - setField(L, "category", static_cast(creatureIcon.category)); - setField(L, "icon", creatureIcon.serialize()); - setField(L, "count", creatureIcon.count); + const auto key = getString(L, 2); + creature->removeIcon(key); + pushBoolean(L, true); + return 1; +} + +int CreatureFunctions::luaCreatureClearIcons(lua_State* L) { + // creature:clearIcons() + auto creature = getUserdata(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + creature->clearIcons(); + pushBoolean(L, true); return 1; } diff --git a/src/lua/functions/creatures/creature_functions.hpp b/src/lua/functions/creatures/creature_functions.hpp index 1d1b22a9f71..426e4859104 100644 --- a/src/lua/functions/creatures/creature_functions.hpp +++ b/src/lua/functions/creatures/creature_functions.hpp @@ -82,6 +82,9 @@ class CreatureFunctions final : LuaScriptInterface { registerMethod(L, "Creature", "getZones", CreatureFunctions::luaCreatureGetZones); registerMethod(L, "Creature", "setIcon", CreatureFunctions::luaCreatureSetIcon); registerMethod(L, "Creature", "getIcon", CreatureFunctions::luaCreatureGetIcon); + registerMethod(L, "Creature", "getIcons", CreatureFunctions::luaCreatureGetIcons); + registerMethod(L, "Creature", "removeIcon", CreatureFunctions::luaCreatureRemoveIcon); + registerMethod(L, "Creature", "clearIcons", CreatureFunctions::luaCreatureClearIcons); CombatFunctions::init(L); MonsterFunctions::init(L); @@ -178,5 +181,8 @@ class CreatureFunctions final : LuaScriptInterface { static int luaCreatureGetZones(lua_State* L); static int luaCreatureSetIcon(lua_State* L); + static int luaCreatureGetIcons(lua_State* L); static int luaCreatureGetIcon(lua_State* L); + static int luaCreatureRemoveIcon(lua_State* L); + static int luaCreatureClearIcons(lua_State* L); }; diff --git a/src/lua/functions/creatures/monster/monster_functions.cpp b/src/lua/functions/creatures/monster/monster_functions.cpp index 0989be8449c..bca5a2e421e 100644 --- a/src/lua/functions/creatures/monster/monster_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_functions.cpp @@ -470,12 +470,7 @@ int MonsterFunctions::luaMonsterSetForgeStack(lua_State* L) { auto icon = stack < 15 ? CreatureIconModifications_t::Influenced : CreatureIconModifications_t::Fiendish; - monster->setIcon(CreatureIcon( - icon, - icon == CreatureIconModifications_t::Influenced - ? static_cast(stack) - : 0 // don't show the stack for fiendish - )); + monster->setIcon("forge", CreatureIcon(icon, icon == CreatureIconModifications_t::Influenced ? static_cast(stack) : 0)); g_game().updateCreatureIcon(monster); g_game().sendUpdateCreature(monster); return 1; diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 4dbb0d33056..8d95f6969fe 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -3134,12 +3134,12 @@ void ProtocolGame::addCreatureIcon(NetworkMessage &msg, const Creature* creature return; } - const auto icon = creature->getIcon(); - // 0 = no icon, 1 = we'll send an icon - if (icon.isNone()) { - msg.addByte(0); - } else { - msg.addByte(1); + const auto icons = creature->getIcons(); + // client only supports 3 icons, otherwise it will crash + const auto count = icons.size() > 3 ? 3 : icons.size(); + msg.addByte(count); + for (uint8_t i = 0; i < count; ++i) { + const auto &icon = icons[i]; msg.addByte(icon.serialize()); msg.addByte(static_cast(icon.category)); msg.add(icon.count); From 39190f95b0c0d82ff1bf4a192b43419cef901d56 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Mon, 11 Sep 2023 22:51:34 -0700 Subject: [PATCH 2/2] sonar --- src/creatures/creature.hpp | 2 +- src/creatures/monsters/monster.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index 6571604bba4..3349ddb83a9 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -222,7 +222,7 @@ class Creature : virtual public Thing { varBuffs[buff] += modifier; } - virtual const std::vector getIcons() const { + virtual std::vector getIcons() const { std::vector icons; icons.reserve(creatureIcons.size()); for (const auto &[_, icon] : creatureIcons) { diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index a32d2a737e4..a479b494a9a 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -160,7 +160,7 @@ class Monster final : public Creature { return challengeFocusDuration > 0; } - const std::vector getIcons() const override { + std::vector getIcons() const override { const auto creatureIcons = Creature::getIcons(); if (!creatureIcons.empty()) { return creatureIcons;