diff --git a/src/const.h b/src/const.h index b098ced652..c7dc5cfc55 100644 --- a/src/const.h +++ b/src/const.h @@ -683,6 +683,46 @@ enum ReloadTypes_t : uint8_t RELOAD_TYPE_WEAPONS, }; +enum MonsterIcon_t : uint8_t +{ + MONSTER_ICON_VULNERABLE = 1, + MONSTER_ICON_WEAKENED, + MONSTER_ICON_MELEE, + MONSTER_ICON_INFLUENCED, + MONSTER_ICON_FIENDISH, + + MONSTER_ICON_FIRST = MONSTER_ICON_VULNERABLE, + MONSTER_ICON_LAST = MONSTER_ICON_FIENDISH +}; + +enum CreatureIcon_t : uint8_t +{ + CREATURE_ICON_CROSS_WHITE = 1, + CREATURE_ICON_CROSS_WHITE_RED, + CREATURE_ICON_ORB_RED, + CREATURE_ICON_ORB_GREEN, + CREATURE_ICON_ORB_RED_GREEN, + CREATURE_ICON_GEM_GREEN, + CREATURE_ICON_GEM_YELLOW, + CREATURE_ICON_GEM_BLUE, + CREATURE_ICON_GEM_PURPLE, + CREATURE_ICON_GEM_RED, + CREATURE_ICON_PIGEON, + CREATURE_ICON_ENERGY, + CREATURE_ICON_POISON, + CREATURE_ICON_WATER, + CREATURE_ICON_FIRE, + CREATURE_ICON_ICE, + CREATURE_ICON_ARROW_UP, + CREATURE_ICON_ARROW_DOWN, + CREATURE_ICON_WARNING, + CREATURE_ICON_QUESTION, + CREATURE_ICON_CROSS_RED, + + CREATURE_ICON_FIRST = CREATURE_ICON_CROSS_WHITE, + CREATURE_ICON_LAST = CREATURE_ICON_CROSS_RED +}; + static constexpr int32_t CHANNEL_GUILD = 0x00; static constexpr int32_t CHANNEL_PARTY = 0x01; static constexpr int32_t CHANNEL_PRIVATE = 0xFFFF; diff --git a/src/creature.cpp b/src/creature.cpp index d224aeb837..65f62eb5a4 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -318,6 +318,16 @@ void Creature::stopEventWalk() } } +void Creature::updateIcons() const +{ + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, true, true); + for (Creature* spectator : spectators) { + assert(dynamic_cast(spectator) != nullptr); + static_cast(spectator)->sendUpdateCreatureIcons(this); + } +} + void Creature::updateMapCache() { Tile* tile; diff --git a/src/creature.h b/src/creature.h index 170a4b5abe..361afa0ac7 100644 --- a/src/creature.h +++ b/src/creature.h @@ -20,6 +20,7 @@ class Player; using ConditionList = std::list; using CreatureEventList = std::list; +using CreatureIconHashMap = std::unordered_map; enum slots_t : uint8_t { @@ -168,6 +169,11 @@ class Creature : virtual public Thing bool isInvisible() const; ZoneType_t getZone() const { return getTile()->getZone(); } + // creature icons + CreatureIconHashMap& getIcons() { return creatureIcons; } + const CreatureIconHashMap& getIcons() const { return creatureIcons; } + void updateIcons() const; + // walk functions void startAutoWalk(); void startAutoWalk(Direction direction); @@ -370,6 +376,7 @@ class Creature : virtual public Thing std::list summons; CreatureEventList eventsList; ConditionList conditions; + CreatureIconHashMap creatureIcons; std::vector listWalkDir; diff --git a/src/luascript.cpp b/src/luascript.cpp index 97eedbad67..404eea158c 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -2114,6 +2114,38 @@ void LuaScriptInterface::registerFunctions() registerEnum(DECAYING_TRUE); registerEnum(DECAYING_PENDING); + registerEnum(CREATURE_ICON_CROSS_WHITE); + registerEnum(CREATURE_ICON_CROSS_WHITE_RED); + registerEnum(CREATURE_ICON_ORB_RED); + registerEnum(CREATURE_ICON_ORB_GREEN); + registerEnum(CREATURE_ICON_ORB_RED_GREEN); + registerEnum(CREATURE_ICON_GEM_GREEN); + registerEnum(CREATURE_ICON_GEM_YELLOW); + registerEnum(CREATURE_ICON_GEM_BLUE); + registerEnum(CREATURE_ICON_GEM_PURPLE); + registerEnum(CREATURE_ICON_GEM_RED); + registerEnum(CREATURE_ICON_PIGEON); + registerEnum(CREATURE_ICON_ENERGY); + registerEnum(CREATURE_ICON_POISON); + registerEnum(CREATURE_ICON_WATER); + registerEnum(CREATURE_ICON_FIRE); + registerEnum(CREATURE_ICON_ICE); + registerEnum(CREATURE_ICON_ARROW_UP); + registerEnum(CREATURE_ICON_ARROW_DOWN); + registerEnum(CREATURE_ICON_WARNING); + registerEnum(CREATURE_ICON_QUESTION); + registerEnum(CREATURE_ICON_CROSS_RED); + registerEnum(CREATURE_ICON_FIRST); + registerEnum(CREATURE_ICON_LAST); + + registerEnum(MONSTER_ICON_VULNERABLE); + registerEnum(MONSTER_ICON_WEAKENED); + registerEnum(MONSTER_ICON_MELEE); + registerEnum(MONSTER_ICON_INFLUENCED); + registerEnum(MONSTER_ICON_FIENDISH); + registerEnum(MONSTER_ICON_FIRST); + registerEnum(MONSTER_ICON_LAST); + // _G registerGlobalVariable("INDEX_WHEREEVER", INDEX_WHEREEVER); registerGlobalBoolean("VIRTUAL_PARENT", true); @@ -2581,6 +2613,11 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "getZone", LuaScriptInterface::luaCreatureGetZone); + registerMethod("Creature", "hasIcon", LuaScriptInterface::luaCreatureHasIcon); + registerMethod("Creature", "setIcon", LuaScriptInterface::luaCreatureSetIcon); + registerMethod("Creature", "getIcon", LuaScriptInterface::luaCreatureGetIcon); + registerMethod("Creature", "removeIcon", LuaScriptInterface::luaCreatureRemoveIcon); + registerMethod("Creature", "getStorageValue", LuaScriptInterface::luaCreatureGetStorageValue); registerMethod("Creature", "setStorageValue", LuaScriptInterface::luaCreatureSetStorageValue); @@ -2816,6 +2853,11 @@ void LuaScriptInterface::registerFunctions() registerMethod("Monster", "isWalkingToSpawn", LuaScriptInterface::luaMonsterIsWalkingToSpawn); registerMethod("Monster", "walkToSpawn", LuaScriptInterface::luaMonsterWalkToSpawn); + registerMethod("Monster", "hasSpecialIcon", LuaScriptInterface::luaMonsterHasIcon); + registerMethod("Monster", "setSpecialIcon", LuaScriptInterface::luaMonsterSetIcon); + registerMethod("Monster", "getSpecialIcon", LuaScriptInterface::luaMonsterGetIcon); + registerMethod("Monster", "removeSpecialIcon", LuaScriptInterface::luaMonsterRemoveIcon); + // Npc registerClass("Npc", "Creature", LuaScriptInterface::luaNpcCreate); registerMetaMethod("Npc", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -8704,6 +8746,84 @@ int LuaScriptInterface::luaCreatureGetZone(lua_State* L) return 1; } +int LuaScriptInterface::luaCreatureHasIcon(lua_State* L) +{ + // creature:hasIcon(iconId) + const Creature* creature = getUserdata(L, 1); + if (creature) { + auto iconId = getNumber(L, 2); + pushBoolean(L, creature->getIcons().contains(iconId)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetIcon(lua_State* L) +{ + // creature:setIcon(iconId, value) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + auto iconId = getNumber(L, 2); + if (iconId > CREATURE_ICON_LAST) { + reportErrorFunc(L, "Invalid Creature Icon Id"); + pushBoolean(L, false); + return 1; + } + + creature->getIcons()[iconId] = getNumber(L, 3); + creature->updateIcons(); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureGetIcon(lua_State* L) +{ + // creature:getIcon(iconId) + const Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + auto iconId = getNumber(L, 2); + const auto& icons = creature->getIcons(); + auto it = icons.find(iconId); + if (it != icons.end()) { + lua_pushinteger(L, it->second); + } else { + lua_pushinteger(L, 0); + } + return 1; +} + +int LuaScriptInterface::luaCreatureRemoveIcon(lua_State* L) +{ + // creature:removeIcon(iconId) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + auto iconId = getNumber(L, 2); + auto& icons = creature->getIcons(); + auto it = icons.find(iconId); + if (it != icons.end()) { + icons.erase(it); + creature->updateIcons(); + pushBoolean(L, true); + } else { + pushBoolean(L, false); + } + + return 1; +} + int LuaScriptInterface::luaCreatureGetStorageValue(lua_State* L) { // creature:getStorageValue(key) @@ -11478,6 +11598,83 @@ int LuaScriptInterface::luaMonsterWalkToSpawn(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterHasIcon(lua_State* L) +{ + // monster:hasSpecialIcon(iconId) + const Monster* monster = getUserdata(L, 1); + if (monster) { + auto iconId = getNumber(L, 2); + pushBoolean(L, monster->getSpecialIcons().contains(iconId)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSetIcon(lua_State* L) +{ + // monster:setSpecialIcon(iconId, value) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + auto iconId = getNumber(L, 2); + if (iconId > MONSTER_ICON_LAST) { + reportErrorFunc(L, "Invalid Monster Icon Id"); + pushBoolean(L, false); + return 1; + } + + monster->getSpecialIcons()[iconId] = getNumber(L, 3); + monster->updateIcons(); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaMonsterGetIcon(lua_State* L) +{ + // monster:getSpecialIcon(iconId) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + auto iconId = getNumber(L, 2); + const auto& icons = monster->getSpecialIcons(); + auto it = icons.find(iconId); + if (it != icons.end()) { + lua_pushinteger(L, it->second); + } else { + lua_pushinteger(L, 0); + } + return 1; +} + +int LuaScriptInterface::luaMonsterRemoveIcon(lua_State* L) +{ + // monster:removeSpecialIcon(iconId) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + auto iconId = getNumber(L, 2); + auto& icons = monster->getSpecialIcons(); + auto it = icons.find(iconId); + if (it != icons.end()) { + icons.erase(it); + monster->updateIcons(); + pushBoolean(L, true); + } else { + pushBoolean(L, false); + } + return 1; +} + // Npc int LuaScriptInterface::luaNpcCreate(lua_State* L) { diff --git a/src/luascript.h b/src/luascript.h index 167f5770b5..bac407d70e 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -847,6 +847,11 @@ class LuaScriptInterface static int luaCreatureGetZone(lua_State* L); + static int luaCreatureHasIcon(lua_State* L); + static int luaCreatureSetIcon(lua_State* L); + static int luaCreatureGetIcon(lua_State* L); + static int luaCreatureRemoveIcon(lua_State* L); + static int luaCreatureGetStorageValue(lua_State* L); static int luaCreatureSetStorageValue(lua_State* L); @@ -1079,6 +1084,11 @@ class LuaScriptInterface static int luaMonsterIsWalkingToSpawn(lua_State* L); static int luaMonsterWalkToSpawn(lua_State* L); + static int luaMonsterHasIcon(lua_State* L); + static int luaMonsterSetIcon(lua_State* L); + static int luaMonsterGetIcon(lua_State* L); + static int luaMonsterRemoveIcon(lua_State* L); + // Npc static int luaNpcCreate(lua_State* L); diff --git a/src/monster.h b/src/monster.h index 1ac5a46893..e5763c867e 100644 --- a/src/monster.h +++ b/src/monster.h @@ -14,6 +14,7 @@ class Tile; using CreatureHashSet = std::unordered_set; using CreatureList = std::list; +using MonsterIconHashMap = std::unordered_map; enum TargetSearchType_t { @@ -128,11 +129,16 @@ class Monster final : public Creature BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, bool checkDefense = false, bool checkArmor = false, bool field = false, bool ignoreResistances = false) override; + // monster icons + MonsterIconHashMap& getSpecialIcons() { return monsterIcons; } + const MonsterIconHashMap& getSpecialIcons() const { return monsterIcons; } + static uint32_t monsterAutoID; private: CreatureHashSet friendList; CreatureList targetList; + MonsterIconHashMap monsterIcons; std::string name; std::string nameDescription; diff --git a/src/player.h b/src/player.h index 74f2ad1142..9ed554c87e 100644 --- a/src/player.h +++ b/src/player.h @@ -575,6 +575,12 @@ class Player final : public Creature, public Cylinder client->sendUpdateTile(tile, pos); } } + void sendUpdateCreatureIcons(const Creature* creature) + { + if (client) { + client->sendUpdateCreatureIcons(creature); + } + } void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) { diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index 17f3933b89..b1bc5d5c3a 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -13,6 +13,7 @@ #include "inbox.h" #include "iologindata.h" #include "iomarket.h" +#include "monster.h" #include "npc.h" #include "outfit.h" #include "outputmessage.h" @@ -2726,6 +2727,21 @@ void ProtocolGame::sendUpdateTile(const Tile* tile, const Position& pos) writeToOutputBuffer(msg); } +void ProtocolGame::sendUpdateCreatureIcons(const Creature* creature) +{ + if (!canSee(creature->getPosition())) { + return; + } + + NetworkMessage msg; + msg.addByte(0x8B); + msg.add(creature->getID()); + msg.addByte(14); // event player icons + + AddCreatureIcons(msg, creature); + writeToOutputBuffer(msg); +} + void ProtocolGame::sendPendingStateEntered() { NetworkMessage msg; @@ -3522,6 +3538,28 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); } +void ProtocolGame::AddCreatureIcons(NetworkMessage& msg, const Creature* creature) +{ + const auto& creatureIcons = creature->getIcons(); + if (const Monster* monster = creature->getMonster()) { + const auto& monsterIcons = monster->getSpecialIcons(); + msg.addByte(creatureIcons.size() + monsterIcons.size()); + for (const auto& [iconId, level] : monsterIcons) { + msg.addByte(iconId); + msg.addByte(1); + msg.add(level); + } + } else { + msg.addByte(creatureIcons.size()); + } + + for (const auto& [iconId, level] : creatureIcons) { + msg.addByte(iconId); + msg.addByte(0); + msg.add(level); + } +} + void ProtocolGame::AddPlayerStats(NetworkMessage& msg) { msg.addByte(0xA0); diff --git a/src/protocolgame.h b/src/protocolgame.h index 1948502616..7b38125d2f 100644 --- a/src/protocolgame.h +++ b/src/protocolgame.h @@ -252,6 +252,8 @@ class ProtocolGame final : public Protocol void sendRemoveTileCreature(const Creature* creature, const Position& pos, uint32_t stackpos); void sendUpdateTile(const Tile* tile, const Position& pos); + void sendUpdateCreatureIcons(const Creature* creature); + void sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, MagicEffectClasses magicEffect = CONST_ME_NONE); void sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, @@ -289,6 +291,7 @@ class ProtocolGame final : public Protocol void GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, NetworkMessage& msg); void AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove); + void AddCreatureIcons(NetworkMessage& msg, const Creature* creature); void AddPlayerStats(NetworkMessage& msg); void AddOutfit(NetworkMessage& msg, const Outfit_t& outfit); void AddPlayerSkills(NetworkMessage& msg);