diff --git a/data/scripts/lib/register_monster_type.lua b/data/scripts/lib/register_monster_type.lua index ed5de6421d9..be8f9b4bcd1 100644 --- a/data/scripts/lib/register_monster_type.lua +++ b/data/scripts/lib/register_monster_type.lua @@ -347,140 +347,88 @@ function SortLootByChance(loot) end) end -registerMonsterType.loot = function(mtype, mask) - if type(mask.loot) == "table" then - SortLootByChance(mask.loot) - local lootError = false - for _, loot in pairs(mask.loot) do - local parent = Loot() - if loot.name then - if not parent:setIdFromName(loot.name) then - lootError = true - end - else - if not isInteger(loot.id) or loot.id < 1 then - lootError = true - end - parent:setId(loot.id) - end - if loot.subType or loot.charges then - parent:setSubType(loot.subType or loot.charges) - else - local lType = ItemType(loot.name and loot.name or loot.id) - if lType and lType:getCharges() > 1 then - parent:setSubType(lType:getCharges()) - end - end - if loot.chance then - parent:setChance(loot.chance) - end - if loot.minCount then - parent:setMinCount(loot.minCount) - end - if loot.maxCount then - parent:setMaxCount(loot.maxCount) - end - if loot.aid or loot.actionId then - parent:setActionId(loot.aid or loot.actionId) - end - if loot.text or loot.description then - parent:setText(loot.text or loot.description) - end - if loot.name then - parent:setNameItem(loot.name) - end - if loot.article then - parent:setArticle(loot.article) - end - if loot.attack then - parent:setAttack(loot.attack) - end - if loot.defense then - parent:setDefense(loot.defense) - end - if loot.extraDefense or loot.extraDef then - parent:setExtraDefense(loot.extraDefense or loot.extraDef) - end - if loot.armor then - parent:setArmor(loot.armor) +local function configureLootAttributes(lootObject, lootProperties) + if lootProperties.subType or lootProperties.charges then + lootObject:setSubType(lootProperties.subType or lootProperties.charges) + else + local itemType = ItemType(lootProperties.name and lootProperties.name or lootProperties.itemId) + if itemType and itemType:getCharges() > 1 then + lootObject:setSubType(itemType:getCharges()) + end + end + + lootObject:setChance(lootProperties.chance or 0) + lootObject:setMinCount(lootProperties.minCount or 0) + lootObject:setMaxCount(lootProperties.maxCount or 0) + lootObject:setActionId(lootProperties.aid or lootProperties.actionId or 0) + lootObject:setText(lootProperties.text or lootProperties.description or "") + lootObject:setNameItem(lootProperties.name or "") + lootObject:setArticle(lootProperties.article or "") + lootObject:setAttack(lootProperties.attack or 0) + lootObject:setDefense(lootProperties.defense or 0) + lootObject:setExtraDefense(lootProperties.extraDefense or lootProperties.extraDef or 0) + lootObject:setArmor(lootProperties.armor or 0) + lootObject:setShootRange(lootProperties.shootRange or lootProperties.range or 0) + lootObject:setUnique(lootProperties.unique or false) +end + +local function addChildrenLoot(parent, childrenLoot) + SortLootByChance(childrenLoot) + for _, child in pairs(childrenLoot) do + local childLoot = Loot() + if child.name then + if not childLoot:setIdFromName(child.name) then + return true end - if loot.shootRange or loot.range then - parent:setShootRange(loot.shootRange or loot.range) + else + if not isInteger(child.id) or child.id < 1 then + return true end - if loot.unique then - parent:setUnique(loot.unique) + childLoot:setId(child.id) + end + configureLootAttributes(childLoot, child) + parent:addChildLoot(childLoot) + end + return false +end + +function MonsterType:createLoot(lootTable) + SortLootByChance(lootTable) + local lootError = false + + for _, loot in pairs(lootTable) do + local parent = Loot() + if loot.name then + if not parent:setIdFromName(loot.name) then + lootError = true end - if loot.child then - SortLootByChance(loot.child) - for _, children in pairs(loot.child) do - local child = Loot() - if children.name then - if not child:setIdFromName(children.name) then - lootError = true - end - else - if not isInteger(children.id) or children.id < 1 then - lootError = true - end - child:setId(children.id) - end - if children.subType or children.charges then - child:setSubType(children.subType or children.charges) - else - local cType = ItemType(children.name and children.name or children.id) - if cType and cType:getCharges() > 1 then - child:setSubType(cType:getCharges()) - end - end - if children.chance then - child:setChance(children.chance) - end - if children.minCount then - child:setMinCount(children.minCount) - end - if children.maxCount then - child:setMaxCount(children.maxCount) - end - if children.aid or children.actionId then - child:setActionId(children.aid or children.actionId) - end - if children.text or children.description then - child:setText(children.text or children.description) - end - if loot.name then - child:setNameItem(loot.name) - end - if children.article then - child:setArticle(children.article) - end - if children.attack then - child:setAttack(children.attack) - end - if children.defense then - child:setDefense(children.defense) - end - if children.extraDefense or children.extraDef then - child:setExtraDefense(children.extraDefense or children.extraDef) - end - if children.armor then - child:setArmor(children.armor) - end - if children.shootRange or children.range then - child:setShootRange(children.shootRange or children.range) - end - if children.unique then - child:setUnique(children.unique) - end - parent:addChildLoot(child) - end + else + if not isInteger(loot.id) or loot.id < 1 then + lootError = true end - mtype:addLoot(parent) + parent:setId(loot.id) end - if lootError then - logger.warn("[registerMonsterType.loot] - Monster: {} loot could not correctly be load", mtype:name()) + + configureLootAttributes(parent, loot) + + if loot.child and addChildrenLoot(parent, loot.child) then + lootError = true end + + self:addLoot(parent) + end + + if lootError then + logger.warn("[MonsterType:createLoot] - Monster: {} loot could not be loaded correctly", self:getName()) end end + +registerMonsterType.loot = function(mtype, mask) + if type(mask.loot) == "table" then + mtype:createLoot(mask.loot) + end +end + local playerElements = { COMBAT_PHYSICALDAMAGE, COMBAT_ENERGYDAMAGE, COMBAT_EARTHDAMAGE, COMBAT_FIREDAMAGE, COMBAT_ICEDAMAGE, COMBAT_HOLYDAMAGE, COMBAT_DEATHDAMAGE } registerMonsterType.elements = function(mtype, mask) local min = configManager.getNumber(configKeys.MIN_ELEMENTAL_RESISTANCE) diff --git a/data/scripts/systems/custom_monster_loot.lua b/data/scripts/systems/custom_monster_loot.lua new file mode 100644 index 00000000000..36d4794980c --- /dev/null +++ b/data/scripts/systems/custom_monster_loot.lua @@ -0,0 +1,38 @@ +-- Drops custom loot for all monsters +local allLootConfig = { + { id = 6526, chance = 100000, minCount = 1, maxCount = 10 }, -- Example of loot (100% chance) +} + +-- Custom loot for specific monsters (this has the same usage options as normal monster loot) +local customLootConfig = { + ["Dragon"] = { items = { + { name = "platinum coin", chance = 1000, maxCount = 1 }, + } }, +} + +local customMonsterLoot = GlobalEvent("CreateCustomMonsterLoot") + +function customMonsterLoot.onStartup() + for monsterName, lootTable in pairs(customLootConfig) do + local mtype = Game.getMonsterTypeByName(monsterName) + if mtype then + if lootTable and lootTable.items and #lootTable.items > 0 then + mtype:createLoot(lootTable.items) + logger.debug("[customMonsterLoot.onStartup] - Custom loot registered for monster: {}", mtype:getName()) + end + else + logger.error("[customMonsterLoot.onStartup] - Monster type not found: {}", monsterName) + end + end + + if #allLootConfig > 0 then + for monsterName, mtype in pairs(Game.getMonsterTypes()) do + if mtype then + mtype:createLoot(allLootConfig) + logger.debug("[customMonsterLoot.onStartup] - Global loot registered for monster: {}", mtype:getName()) + end + end + end +end + +customMonsterLoot:register() diff --git a/src/creatures/monsters/monsters.cpp b/src/creatures/monsters/monsters.cpp index cd33e12e248..b90a5f6323f 100644 --- a/src/creatures/monsters/monsters.cpp +++ b/src/creatures/monsters/monsters.cpp @@ -346,9 +346,7 @@ void Monsters::clear() { std::shared_ptr Monsters::getMonsterType(const std::string &name, bool silent /* = false*/) const { std::string lowerCaseName = asLowerCaseString(name); if (auto it = monsters.find(lowerCaseName); - it != monsters.end() - // We will only return the MonsterType if it match the exact name of the monster - && it->first.find(lowerCaseName) != std::basic_string::npos) { + it != monsters.end()) { return it->second; } if (!silent) { diff --git a/src/game/game.cpp b/src/game/game.cpp index 1f6ce9951f5..8e12bbb0003 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -457,7 +457,7 @@ void Game::loadBoostedCreature() { } const auto oldRace = result->getNumber("raceid"); - const auto monsterlist = getBestiaryList(); + const auto &monsterlist = getBestiaryList(); struct MonsterRace { uint16_t raceId { 0 }; diff --git a/src/io/iobestiary.cpp b/src/io/iobestiary.cpp index b18ed181467..a27f9d0a54a 100644 --- a/src/io/iobestiary.cpp +++ b/src/io/iobestiary.cpp @@ -193,7 +193,7 @@ uint16_t IOBestiary::getBestiaryRaceUnlocked(const std::shared_ptr &play } uint16_t count = 0; - std::map besty_l = g_game().getBestiaryList(); + const std::map &besty_l = g_game().getBestiaryList(); for (const auto &it : besty_l) { const auto mtype = g_monsters().getMonsterType(it.second); diff --git a/src/io/ioprey.cpp b/src/io/ioprey.cpp index 65838a1660c..22df49b39c2 100644 --- a/src/io/ioprey.cpp +++ b/src/io/ioprey.cpp @@ -146,7 +146,7 @@ void TaskHuntingSlot::reloadMonsterGrid(std::vector blackList, uint32_ // Disabling task hunting system if the server have less then 36 registered monsters on bestiary because: // - Impossible to generate random lists without duplications on slots. // - Stress the server with unnecessary loops. - std::map bestiary = g_game().getBestiaryList(); + const std::map &bestiary = g_game().getBestiaryList(); if (bestiary.size() < 36) { return; } @@ -574,7 +574,7 @@ void IOPrey::initializeTaskHuntOptions() { } msg.addByte(0xBA); - std::map bestiaryList = g_game().getBestiaryList(); + const std::map &bestiaryList = g_game().getBestiaryList(); msg.add(static_cast(bestiaryList.size())); std::for_each(bestiaryList.begin(), bestiaryList.end(), [&msg](auto mType) { const auto mtype = g_monsters().getMonsterType(mType.second); diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index aad4b71d06c..e84096f6e0a 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -36,6 +36,7 @@ void GameFunctions::init(lua_State* L) { Lua::registerMethod(L, "Game", "createNpcType", GameFunctions::luaGameCreateNpcType); Lua::registerMethod(L, "Game", "createMonsterType", GameFunctions::luaGameCreateMonsterType); + Lua::registerMethod(L, "Game", "getMonsterTypeByName", GameFunctions::luaGameGetMonsterTypeByName); Lua::registerMethod(L, "Game", "getSpectators", GameFunctions::luaGameGetSpectators); @@ -164,6 +165,24 @@ int GameFunctions::luaGameCreateNpcType(lua_State* L) { return NpcTypeFunctions::luaNpcTypeCreate(L); } +int GameFunctions::luaGameGetMonsterTypeByName(lua_State* L) { + if (!isString(L, 1)) { + reportErrorFunc("First argument must be a string"); + return 1; + } + + const auto name = getString(L, 1); + const auto &mType = g_monsters().getMonsterType(name); + if (!mType) { + reportErrorFunc(fmt::format("MonsterType with name {} not found", name)); + return 1; + } + + pushUserdata(L, mType); + setMetatable(L, -1, "MonsterType"); + return 1; +} + int GameFunctions::luaGameGetSpectators(lua_State* L) { // Game.getSpectators(position[, multifloor = false[, onlyPlayer = false[, minRangeX = 0[, maxRangeX = 0[, minRangeY = 0[, maxRangeY = 0]]]]]]) const Position &position = Lua::getPosition(L, 1); diff --git a/src/lua/functions/core/game/game_functions.hpp b/src/lua/functions/core/game/game_functions.hpp index 6d332face9f..cd81d59f954 100644 --- a/src/lua/functions/core/game/game_functions.hpp +++ b/src/lua/functions/core/game/game_functions.hpp @@ -16,6 +16,7 @@ class GameFunctions { private: static int luaGameCreateMonsterType(lua_State* L); static int luaGameCreateNpcType(lua_State* L); + static int luaGameGetMonsterTypeByName(lua_State* L); static int luaGameGetSpectators(lua_State* L); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 466a2413353..155bb5a2f92 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -2316,7 +2316,7 @@ void ProtocolGame::parseBestiarysendRaces() { NetworkMessage msg; msg.addByte(0xd5); msg.add(BESTY_RACE_LAST); - std::map mtype_list = g_game().getBestiaryList(); + const std::map &mtype_list = g_game().getBestiaryList(); for (uint8_t i = BESTY_RACE_FIRST; i <= BESTY_RACE_LAST; i++) { std::string BestClass; uint16_t count = 0; @@ -2359,7 +2359,7 @@ void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) { auto raceId = msg.get(); std::string Class; std::shared_ptr mtype = nullptr; - std::map mtype_list = g_game().getBestiaryList(); + const std::map &mtype_list = g_game().getBestiaryList(); auto ait = mtype_list.find(raceId); if (ait != mtype_list.end()) { @@ -2398,7 +2398,7 @@ void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) { newmsg.addByte(mtype->info.bestiaryStars); newmsg.addByte(mtype->info.bestiaryOccurrence); - std::vector lootList = mtype->info.lootItems; + const std::vector &lootList = mtype->info.lootItems; newmsg.addByte(lootList.size()); for (const LootBlock &loot : lootList) { int8_t difficult = g_iobestiary().calculateDifficult(loot.chance); @@ -2497,7 +2497,7 @@ void ProtocolGame::parseCyclopediaMonsterTracker(NetworkMessage &msg) { } // Bestiary tracker logic - const auto bestiaryMonsters = g_game().getBestiaryList(); + const auto &bestiaryMonsters = g_game().getBestiaryList(); auto it = bestiaryMonsters.find(monsterRaceId); if (it != bestiaryMonsters.end()) { const auto mtype = g_monsters().getMonsterType(it->second); @@ -2970,7 +2970,7 @@ void ProtocolGame::parseBestiarysendCreatures(NetworkMessage &msg) { if (search == 1) { auto monsterAmount = msg.get(); - std::map mtype_list = g_game().getBestiaryList(); + const std::map &mtype_list = g_game().getBestiaryList(); for (uint16_t monsterCount = 1; monsterCount <= monsterAmount; monsterCount++) { auto raceid = msg.get(); if (player->getBestiaryKillCount(raceid) > 0) { @@ -7559,7 +7559,7 @@ void ProtocolGame::sendPreyData(const std::unique_ptr &slot) { msg.addByte(outfit.lookAddons); } } else if (slot->state == PreyDataState_ListSelection) { - const std::map bestiaryList = g_game().getBestiaryList(); + const std::map &bestiaryList = g_game().getBestiaryList(); msg.add(static_cast(bestiaryList.size())); std::for_each(bestiaryList.begin(), bestiaryList.end(), [&msg](auto mType) { msg.add(mType.first); @@ -8168,7 +8168,7 @@ void ProtocolGame::sendTaskHuntingData(const std::unique_ptr &s }); } else if (slot->state == PreyTaskDataState_ListSelection) { std::shared_ptr user = player; - const std::map bestiaryList = g_game().getBestiaryList(); + const std::map &bestiaryList = g_game().getBestiaryList(); msg.add(static_cast(bestiaryList.size())); std::for_each(bestiaryList.begin(), bestiaryList.end(), [&msg, user](auto mType) { msg.add(mType.first);