Skip to content
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
2 changes: 1 addition & 1 deletion sql/mob_groups.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11773,7 +11773,7 @@ INSERT INTO `mob_groups` VALUES (8,771,173,'Combat',792,0,386,0,0,27,30,0);
INSERT INTO `mob_groups` VALUES (9,2146,173,'Jelly',660,0,1407,0,0,23,27,0);
INSERT INTO `mob_groups` VALUES (10,3912,173,'Thunder_Elemental',792,4,2410,0,0,32,34,0);
INSERT INTO `mob_groups` VALUES (11,3523,173,'Sea_Monk',792,0,504,0,0,32,35,0);
INSERT INTO `mob_groups` VALUES (12,1807,173,'Greater_Pugil',0,128,279,0,0,25,32,0);
INSERT INTO `mob_groups` VALUES (12,1807,173,'Greater_Pugil',792,0,279,0,0,25,32,0);
INSERT INTO `mob_groups` VALUES (13,743,173,'Clipper',792,0,483,0,0,29,32,0);
INSERT INTO `mob_groups` VALUES (14,4309,173,'Water_Elemental',792,4,2629,0,0,32,34,0);
INSERT INTO `mob_groups` VALUES (15,639,173,'Cargo_Crab_Colin',0,32,416,0,0,34,37,0);
Expand Down
171,417 changes: 85,709 additions & 85,708 deletions sql/mob_spawn_points.sql

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions sql/mob_spawn_sets.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

DROP TABLE IF EXISTS `mob_spawn_sets`;
CREATE TABLE IF NOT EXISTS `mob_spawn_sets` (
`zoneid` SMALLINT(3) NOT NULL DEFAULT 0,
`spawnsetid` TINYINT(3) NOT NULL DEFAULT 0,
`maxspawns` TINYINT(4) NOT NULL DEFAULT 0,
PRIMARY KEY (`zoneid`, `spawnsetid`) USING BTREE
)
ENGINE=Aria TRANSACTIONAL=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

-- AlTaieu
-- INSERT INTO `mob_spawn_sets` VALUES (33, 1, 7);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 2, 1);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 3, 3);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 4, 7);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 5, 3);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 6, 4);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 7, 9);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 8, 3);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 9, 2);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 10, 3);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 11, 2);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 12, 1);
-- INSERT INTO `mob_spawn_sets` VALUES (33, 13, 1);

INSERT INTO `mob_spawn_sets` VALUES (173, 1, 10);
2 changes: 2 additions & 0 deletions src/map/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/status_effect_container.h
${CMAKE_CURRENT_SOURCE_DIR}/status_effect.cpp
${CMAKE_CURRENT_SOURCE_DIR}/status_effect.h
${CMAKE_CURRENT_SOURCE_DIR}/spawn_group.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spawn_group.h
${CMAKE_CURRENT_SOURCE_DIR}/time_server.cpp
${CMAKE_CURRENT_SOURCE_DIR}/time_server.h
${CMAKE_CURRENT_SOURCE_DIR}/timetriggers.cpp
Expand Down
2 changes: 1 addition & 1 deletion src/map/ai/states/respawn_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ bool CRespawnState::Update(timer::time_point tick)
auto* PMob = dynamic_cast<CMobEntity*>(m_PEntity);
if (PMob)
{
if (!WasExitDelayed() && !PMob->m_AllowRespawn)
if ((!WasExitDelayed() && !PMob->m_AllowRespawn) || (PMob->m_spawnGroup && !PMob->CanSpawnFromGroup()))
{
if (m_spawnTime > 0s)
{
Expand Down
35 changes: 35 additions & 0 deletions src/map/entities/mobentity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "ai/helpers/targetfind.h"
#include "ai/states/attack_state.h"
#include "ai/states/mobskill_state.h"
#include "ai/states/respawn_state.h"
#include "ai/states/weaponskill_state.h"
#include "battlefield.h"
#include "common/timer.h"
Expand Down Expand Up @@ -142,6 +143,7 @@ CMobEntity::CMobEntity()
, m_Pool(0)
, m_flags(0)
, m_name_prefix(0)
, m_spawnGroup(nullptr)
, m_unk0(0)
, m_unk1(8)
, m_unk2(0)
Expand Down Expand Up @@ -591,6 +593,16 @@ bool CMobEntity::ValidTarget(CBattleEntity* PInitiator, uint16 targetFlags)
return false;
}

bool CMobEntity::CanSpawnFromGroup()
{
if (!m_spawnGroup)
{
return true;
}

return m_spawnGroup->isInSpawnPool(this->targid);
}

void CMobEntity::Spawn()
{
TracyZoneScoped;
Expand Down Expand Up @@ -1157,6 +1169,29 @@ void CMobEntity::OnDespawn(CDespawnState& /*unused*/)
{
TracyZoneScoped;
FadeOut();

if (m_spawnGroup)
{
auto replacementTargID = m_spawnGroup->removeAndReplaceWithRandomMember(this->targid);
if (replacementTargID != this->targid) // Respawn normally if we got selected again, otherwise poke the replacement to do so
{
auto PMob = this->loc.zone->GetEntity(replacementTargID);
if (PMob && PMob->PAI)
{
// Check if replacement can switch into respawn state with our current respawn time
// if Internal_Respawn returns true, the mob will switch into respawn state with m_RespawnTime (should it be the target mob's respawn time?)
if (!PMob->PAI->Internal_Respawn(m_RespawnTime))
{
// If they're already in the respawn state...
if (PMob->PAI->IsCurrentState<CRespawnState>())
{
PMob->PAI->GetCurrentState()->ResetEntryTime(); // Reset their despawn time
}
}
}
}
}

PAI->Internal_Respawn(m_RespawnTime);
luautils::OnMobDespawn(this);
// #event despawn
Expand Down
4 changes: 4 additions & 0 deletions src/map/entities/mobentity.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
class CMobSpellContainer;
class CMobSpellList;
class CEnmityContainer;
class spawnGroup;

enum SPAWNTYPE
{
Expand Down Expand Up @@ -181,6 +182,7 @@ class CMobEntity : public CBattleEntity

virtual void OnDespawn(CDespawnState&) override;

bool CanSpawnFromGroup();
virtual void Spawn() override;
virtual void FadeOut() override;
virtual bool isWideScannable() override;
Expand Down Expand Up @@ -255,6 +257,8 @@ class CMobEntity : public CBattleEntity
uint32 m_flags; // includes the CFH flag and whether the HP bar should be shown or not (e.g. Yilgeban doesnt)
uint8 m_name_prefix; // The ding bats VS Ding bats

spawnGroup* m_spawnGroup; // spawn group this mob Belongs to

uint8 m_unk0; // possibly campaign related (entity 0x24)
uint8 m_unk1; // (entity_update 0x25)
uint8 m_unk2; // (entity_update 0x26)
Expand Down
128 changes: 128 additions & 0 deletions src/map/spawn_group.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
===========================================================================

Copyright (c) 2025 LandSandBoat Dev Team

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/

===========================================================================
*/
#include "spawn_group.h"

#include "navmesh.h"

#include "common/xirand.h"
#include "entities/baseentity.h"
#include "utils/zoneutils.h"

#include <cstdlib>

spawnGroup::spawnGroup(uint32_t _maxSpawns, uint16_t _zoneId, uint32_t _groupId)
{
maxSpawns = _maxSpawns;
zoneId = _zoneId;
groupId = _groupId;
}

// Add member, used on db load
void spawnGroup::addMember(uint16_t targid)
{
members.push_back(targid);
}

// Used when a mob despawns. A mob will feed in its own targid
// The targid will be removed from the list of mobs that are allowed to spawn, and then replaced with another random available targid (including itself)
uint16_t spawnGroup::removeAndReplaceWithRandomMember(uint16_t targid)
{
mobsInPoolAllowedToSpawn.erase(targid); // Remove input targid

return fillSpawnPool(); // Return random member re-added back to pool (which can include self)
}

// Fill spawn pool until full.
// Randomly select from the members vector after shuffling but only unique members (uniqueness is checked)
// return the last targid filled in so the CMobEntity can know which mob to eventually try to respawn
// return value is not used on db load
uint16_t spawnGroup::fillSpawnPool()
{
std::shuffle(members.begin(), members.end(), xirand::rng());
uint32_t lastTargId = 0;

for (auto member : members)
{
// We don't support duplicates. Skip.
if (mobsInPoolAllowedToSpawn.contains(member))
{
continue;
}

// Add in a new mob to the allowed spawn list if we're not already full
if (mobsInPoolAllowedToSpawn.size() < maxSpawns)
{
lastTargId = member;
mobsInPoolAllowedToSpawn.insert(member);
}
}

return lastTargId; // return the last targid inserted
}

// Check if targid is in spawn pool.
// CMobEntity will use this to check if it can respawn, or the zone time/day change for night/day only mobs.
bool spawnGroup::isInSpawnPool(uint16_t targid) const
{
return mobsInPoolAllowedToSpawn.contains(targid);
}

// If there's less total spawns than members then this group is not valid
// if mob spawn total spawns is the same as member size then this group is not valid
// if a mob isn't in a valid position then this group is not valid
bool spawnGroup::isValid(CZone* zone)
{
if (members.size() < maxSpawns)
{
ShowError(fmt::format("Mob spawn group {} in zone {} has less members than it does max spawns.", groupId, zoneId));
return false;
}

if (members.size() == maxSpawns)
{
ShowError(fmt::format("Mob spawn group {} in zone {} has the same size of members as it does max spawn mobs.", groupId, zoneId));

return false;
}

for (const auto& member : members)
{
if (const CBaseEntity* PMob = zone->GetEntity(member, TYPE_MOB))
{
auto PNavMesh = PMob->loc.zone->m_navMesh.get();
if (PNavMesh && !PNavMesh->validPosition(PMob->loc.p))
{
ShowError(fmt::format("Mob {} ID {} in zone {} is in a spawn group without a valid position. This could have very bad side effects!", PMob->packetName, PMob->id, zone->GetID()));

return false;
}

if (PMob->loc.p.x == 0 && PMob->loc.p.y == 0 && PMob->loc.p.z == 0)
{
ShowError(fmt::format("Mob {} ID {} in zone {} is in a spawn group with a position of (0,0,0). That is probably not valid!", PMob->packetName, PMob->id, zone->GetID()));

return false;
}
}
}

return true;
}
45 changes: 45 additions & 0 deletions src/map/spawn_group.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
===========================================================================

Copyright (c) 2025 LandSandBoat Dev Team

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/

===========================================================================
*/
#pragma once
#include <cstdint>

class CZone;

class spawnGroup
{
public:
spawnGroup(uint32_t _maxSpawns, uint16_t _zoneId, uint32_t _groupId);

void addMember(uint16_t targid);

uint16_t removeAndReplaceWithRandomMember(uint16_t targid); // called on despawn to remove self from current queue
uint16_t fillSpawnPool(); // fill queue back up to size of maxSpawns, return targid of last mob inserted
bool isInSpawnPool(uint16_t targid) const; // returns true if targid is in queue, used in respawn state to check if this mob should spawn

bool isValid(CZone* zone); // returns false if group is not valid
private:
uint8_t maxSpawns;
uint16_t zoneId;
uint32_t groupId;

std::vector<uint16_t> members; // short form targid, all mobs available to spawn in the group
std::unordered_set<uint16_t> mobsInPoolAllowedToSpawn; // short form targid, all mobs in current spawn queue of the group
};
Loading