From 6cc8336263e419988907ae26f4a9eac35a296634 Mon Sep 17 00:00:00 2001 From: 0x05010705 <0x05010705@protonmail.ch> Date: Wed, 4 Dec 2024 22:27:00 -0800 Subject: [PATCH] Adds Support for Multiple Mob Pets --- .../The_Garden_of_RuHmet/mobs/Ixaern_DRG.lua | 38 +++++++------------ .../mobs/Ixaern_DRGs_Wynav.lua | 10 ++++- sql/mob_pets.sql | 6 ++- src/map/ai/controllers/mob_controller.cpp | 29 ++++++++++---- src/map/entities/battleentity.h | 3 +- src/map/lua/lua_baseentity.cpp | 25 +++++++----- src/map/utils/zoneutils.cpp | 22 ++++++++++- 7 files changed, 88 insertions(+), 45 deletions(-) diff --git a/scripts/zones/The_Garden_of_RuHmet/mobs/Ixaern_DRG.lua b/scripts/zones/The_Garden_of_RuHmet/mobs/Ixaern_DRG.lua index d4e7b784a8f..f274e237bdb 100644 --- a/scripts/zones/The_Garden_of_RuHmet/mobs/Ixaern_DRG.lua +++ b/scripts/zones/The_Garden_of_RuHmet/mobs/Ixaern_DRG.lua @@ -7,24 +7,22 @@ local ID = zones[xi.zone.THE_GARDEN_OF_RUHMET] ---@type TMobEntity local entity = {} +entity.onMobSpawn = function(mob) + mob:setLocalVar('petCount', 0) + mob:setLocalVar('petTimer', 0) +end + entity.onMobFight = function(mob, target) - -- Spawn the pets if they are despawned - -- TODO: summon animations? - local mobId = mob:getID() - local x = mob:getXPos() - local y = mob:getYPos() - local z = mob:getZPos() + local petCount = mob:getLocalVar('petCount') + local petTimer = mob:getLocalVar('petTimer') - for i = mobId + 1, mobId + 3 do - local wynav = GetMobByID(i) - if wynav and not wynav:isSpawned() then - local repopWynavs = wynav:getLocalVar('repop') -- see Wynav script - if mob:getBattleTime() - repopWynavs > 10 then - wynav:setSpawn(x + math.random(1, 5), y, z + math.random(1, 5)) - wynav:spawn() - wynav:updateEnmity(target) - end - end + if + petCount < 3 and + petTimer < os.time() + 30 + then + mob:useMobAbility(xi.jsa.CALL_WYVERN) + mob:setLocalVar('petCount', 3) + mob:setLocalVar('petTimer', os.time()) end end @@ -39,14 +37,6 @@ entity.onMobDeath = function(mob, player, optParams) end entity.onMobDespawn = function(mob) - -- despawn pets - local mobId = mob:getID() - for i = mobId + 1, mobId + 3 do - if GetMobByID(i):isSpawned() then - DespawnMob(i) - end - end - -- Pick a new PH for Ix'Aern (DRG) local groups = ID.mob.AWAERN_DRG_GROUPS SetServerVariable('[SEA]IxAernDRG_PH', groups[math.random(1, #groups)] + math.random(0, 2)) diff --git a/scripts/zones/The_Garden_of_RuHmet/mobs/Ixaern_DRGs_Wynav.lua b/scripts/zones/The_Garden_of_RuHmet/mobs/Ixaern_DRGs_Wynav.lua index fa9dd1de819..ececbcf6bb8 100644 --- a/scripts/zones/The_Garden_of_RuHmet/mobs/Ixaern_DRGs_Wynav.lua +++ b/scripts/zones/The_Garden_of_RuHmet/mobs/Ixaern_DRGs_Wynav.lua @@ -2,6 +2,8 @@ -- Area: The Garden of Ru'Hmet -- Mob: Ix'aern DRG's Wynav ----------------------------------- +local ID = zones[xi.zone.THE_GARDEN_OF_RUHMET] +----------------------------------- ---@type TMobEntity local entity = {} @@ -41,7 +43,13 @@ entity.onMobDeath = function(mob, player, optParams) end entity.onMobDespawn = function(mob) - mob:setLocalVar('repop', mob:getBattleTime()) -- This get erased on respawn automatic. + local ixaern = GetMobByID(ID.mob.IXAERN_DRG) + + if ixaern then + local petCount = ixaern:getLocalVar('petCount') + + ixaern:setLocalVar('petCount', petCount - 1) + end end return entity diff --git a/sql/mob_pets.sql b/sql/mob_pets.sql index 1c1ac907dab..ff5f24ff436 100644 --- a/sql/mob_pets.sql +++ b/sql/mob_pets.sql @@ -22,7 +22,8 @@ CREATE TABLE `mob_pets` ( `job` tinyint(4) DEFAULT '9', `mobname` varchar(24) DEFAULT NULL, `petname` varchar(24) DEFAULT NULL, - PRIMARY KEY (`mob_mobid`) + PRIMARY KEY (`mob_mobid`, `pet_offset`), + INDEX (`mob_mobid`) ) ENGINE=Aria TRANSACTIONAL=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; @@ -261,6 +262,9 @@ INSERT INTO `mob_pets` VALUES (16920662,1,14,'Awaern','Aerns_Wynav'); INSERT INTO `mob_pets` VALUES (16920779,1,9,'Awaern','Aerns_Euvhi'); INSERT INTO `mob_pets` VALUES (16920783,1,14,'Awaern','Aerns_Wynav'); INSERT INTO `mob_pets` VALUES (16920787,1,15,'Awaern','Aerns_Elemental'); +INSERT INTO `mob_pets` VALUES (16921022,1,14,'Ixaern_DRG','Ixaern_DRGs_Wynav'); +INSERT INTO `mob_pets` VALUES (16921022,2,14,'Ixaern_DRG','Ixaern_DRGs_Wynav'); +INSERT INTO `mob_pets` VALUES (16921022,3,14,'Ixaern_DRG','Ixaern_DRGs_Wynav'); -- ------------------------------------------------------------ -- Temenos (Zone 37) diff --git a/src/map/ai/controllers/mob_controller.cpp b/src/map/ai/controllers/mob_controller.cpp index 4cbf1c84218..a2f28d01525 100644 --- a/src/map/ai/controllers/mob_controller.cpp +++ b/src/map/ai/controllers/mob_controller.cpp @@ -200,10 +200,21 @@ void CMobController::TryLink() } } - // my pet should help as well - if (PMob->PPet != nullptr && PMob->PPet->PAI->IsRoaming()) + // my pets should help as well + if (PMob->PPet != nullptr) { - PMob->PPet->PAI->Engage(PTarget->targid); + CBattleEntity* PPet = PMob->PPet; + // Iterate over all my pets + while (PPet != nullptr) + { + // Check if they are roaming + if (PPet->PAI->IsRoaming()) + { + // Have them attack my target + PPet->PAI->Engage(PTarget->targid); + } + PPet = PPet->PPetNext; + } } // Handle monster linking if they are close enough @@ -1086,12 +1097,16 @@ void CMobController::FollowRoamPath() PMob->PAI->PathFind->FollowPath(m_Tick); CBattleEntity* PPet = PMob->PPet; - if (PPet != nullptr && PPet->PAI->IsSpawned() && !PPet->PAI->IsEngaged()) + while (PPet != nullptr) { - // pet should follow me if roaming - position_t targetPoint = nearPosition(PMob->loc.p, 2.1f, (float)M_PI); + if (PPet->PAI->IsSpawned() && !PPet->PAI->IsEngaged()) + { + // pet should follow me if roaming + position_t targetPoint = nearPosition(PMob->loc.p, 2.1f, (float)M_PI); - PPet->PAI->PathFind->PathTo(targetPoint); + PPet->PAI->PathFind->PathTo(targetPoint); + } + PPet = PPet->PPetNext; } // if I just finished reset my last action time diff --git a/src/map/entities/battleentity.h b/src/map/entities/battleentity.h index fa05d11f99f..5d216c9c626 100644 --- a/src/map/entities/battleentity.h +++ b/src/map/entities/battleentity.h @@ -771,7 +771,8 @@ class CBattleEntity : public CBaseEntity CParty* PParty; CBattleEntity* PPet; - CBattleEntity* PMaster; // Owner/owner of the entity (applies to all combat entities) + CBattleEntity* PPetNext; // Support for multiple pets on same master + CBattleEntity* PMaster; // Owner/owner of the entity (applies to all combat entities) CBattleEntity* PLastAttacker; time_point LastAttacked; battlehistory_t BattleHistory{}; // Stores info related to most recent combat actions taken towards this entity. diff --git a/src/map/lua/lua_baseentity.cpp b/src/map/lua/lua_baseentity.cpp index 56cca9d8d3f..65b8bee076f 100644 --- a/src/map/lua/lua_baseentity.cpp +++ b/src/map/lua/lua_baseentity.cpp @@ -14632,19 +14632,26 @@ void CLuaBaseEntity::spawnPet(sol::object const& arg0) return; } - CMobEntity* PPet = static_cast(PMob->PPet); + CMobEntity* PPetTemp = static_cast(PMob->PPet); - // if a number is given its an avatar or elemental spawn - if ((arg0 != sol::lua_nil) && arg0.is()) + while (PPetTemp != nullptr) { - petutils::SpawnMobPet(PMob, arg0.as()); - } + CMobEntity* PPet = static_cast(PPetTemp); - // always spawn on master - PPet->m_SpawnPoint = nearPosition(PMob->loc.p, 2.2f, static_cast(M_PI)); + // if a number is given its an avatar or elemental spawn + if ((arg0 != sol::lua_nil) && arg0.is()) + { + petutils::SpawnMobPet(PMob, arg0.as()); + } - // setup AI - PPet->Spawn(); + // always spawn on master + PPet->m_SpawnPoint = nearPosition(PMob->loc.p, 2.2f, static_cast(M_PI)); + + // setup AI + PPet->Spawn(); + + PPetTemp = static_cast(PPetTemp->PPetNext); + } } } diff --git a/src/map/utils/zoneutils.cpp b/src/map/utils/zoneutils.cpp index bd82e523004..097ddb6d57e 100644 --- a/src/map/utils/zoneutils.cpp +++ b/src/map/utils/zoneutils.cpp @@ -635,10 +635,28 @@ namespace zoneutils // pet is always spawned by master PPet->m_AllowRespawn = false; PPet->m_SpawnType = SPAWNTYPE_SCRIPTED; + PPet->PPetNext = nullptr; PPet->SetDespawnTime(0s); - PMaster->PPet = PPet; - PPet->PMaster = PMaster; + if (PMaster->PPet == nullptr) + { + PMaster->PPet = PPet; + PPet->PMaster = PMaster; + } + else + { + // We know that PMaster->PPet is not nullptr at least once + // Iterate until we find end of Pet Linked List + CMobEntity* PPetTemp = static_cast(PMaster->PPet); + while (PPetTemp->PPetNext != nullptr) + { + PPetTemp = static_cast(PPetTemp->PPetNext); + } + // Attach the new pet (PPet) to the last pet (PPetTemp) + // Link the new pet to the Master + PPetTemp->PPetNext = PPet; + PPet->PMaster = PMaster; + } } } }