Skip to content

Commit

Permalink
Add varying monster spawn
Browse files Browse the repository at this point in the history
  • Loading branch information
marmichalski committed Aug 27, 2021
1 parent 89ef7b0 commit 851f3ca
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 15 deletions.
5 changes: 4 additions & 1 deletion data/world/forgotten-spawn.xml
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,10 @@
<monster name="Troll" x="-4" y="0" z="7" spawntime="60" />
</spawn>
<spawn centerx="103" centery="120" centerz="7" radius="5">
<monster name="Rabbit" x="-1" y="1" z="7" spawntime="180" />
<monsters x="-1" y="1" z="7" spawntime="180">
<monster name="Rabbit" chance="70" />
<monster name="Skunk" chance="30" />
</monsters>
</spawn>
<spawn centerx="86" centery="122" centerz="7" radius="1">
<npc name="Riona" x="0" y="0" z="7" spawntime="60" direction="3" />
Expand Down
115 changes: 103 additions & 12 deletions src/spawn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,78 @@ bool Spawns::loadFromXml(const std::string& filename)
Spawn& spawn = spawnList.front();

for (auto childNode : spawnNode.children()) {
if (strcasecmp(childNode.name(), "monster") == 0) {
if (strcasecmp(childNode.name(), "monsters") == 0) {
Position pos(
centerPos.x + pugi::cast<uint16_t>(childNode.attribute("x").value()),
centerPos.y + pugi::cast<uint16_t>(childNode.attribute("y").value()),
centerPos.z
);

int32_t interval = pugi::cast<int32_t>(childNode.attribute("spawntime").value()) * 1000;
if (interval < MINSPAWN_INTERVAL) {
std::cout << "[Warning - Spawns::loadFromXml] " << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl;
continue;
} else if (interval > MAXSPAWN_INTERVAL) {
std::cout << "[Warning - Spawns::loadFromXml] " << pos << " spawntime can not be more than " << MAXSPAWN_INTERVAL / 1000 << " seconds." << std::endl;
continue;
}

size_t monstersCount = std::distance(childNode.children().begin(), childNode.children().end());
if (monstersCount == 0) {
std::cout << "[Warning - Spawns::loadFromXml] " << pos << " empty monsters set." << std::endl;
continue;
}

uint16_t totalChance = 0;
spawnBlock_t sb;
sb.pos = pos;
sb.direction = DIRECTION_NORTH;
sb.interval = interval;
sb.lastSpawn = 0;

for (auto monsterNode : childNode.children()) {
pugi::xml_attribute nameAttribute = monsterNode.attribute("name");
if (!nameAttribute) {
continue;
}

MonsterType* mType = g_monsters.getMonsterType(nameAttribute.as_string());
if (!mType) {
std::cout << "[Warning - Spawn::loadFromXml] " << pos << " can not find " << nameAttribute.as_string() << std::endl;
continue;
}

uint16_t chance = 100 / monstersCount;
pugi::xml_attribute chanceAttribute = monsterNode.attribute("chance");
if (chanceAttribute) {
chance = pugi::cast<uint16_t>(chanceAttribute.value());
}

if (chance + totalChance > 100) {
chance = 100 - totalChance;
totalChance = 100;
std::cout << "[Warning - Spawns::loadFromXml] " << mType->name << ' ' << pos << " total chance for set can not be higher than 100." << std::endl;
} else {
totalChance += chance;
}

sb.mTypes.push_back({mType, chance});
}

if (sb.mTypes.empty()) {
std::cout << "[Warning - Spawns::loadFromXml] " << pos << " empty monsters set." << std::endl;
continue;
}

sb.mTypes.shrink_to_fit();
if (sb.mTypes.size() > 1) {
std::sort(sb.mTypes.begin(), sb.mTypes.end(), [](std::tuple<MonsterType*, uint16_t> a, std::tuple<MonsterType*, uint16_t> b) {
return std::get<1>(a) > std::get<1>(b);
});
}

spawn.addBlock(sb);
} else if (strcasecmp(childNode.name(), "monster") == 0) {
pugi::xml_attribute nameAttribute = childNode.attribute("name");
if (!nameAttribute) {
continue;
Expand Down Expand Up @@ -214,7 +285,23 @@ bool Spawn::isInSpawnZone(const Position& pos)
return Spawns::isInZone(centerPos, radius, pos);
}

bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup /*= false*/)
bool Spawn::spawnMonster(uint32_t spawnId, spawnBlock_t sb, bool startup/* = false*/)
{
if (sb.mTypes.size() == 1) {
return spawnMonster(spawnId, std::get<0>(sb.mTypes.front()), sb.pos, sb.direction, startup);
}

for (std::tuple<MonsterType*, uint16_t> tuple : sb.mTypes) {
if (std::get<1>(tuple) >= normal_random(1, 100) && spawnMonster(spawnId, std::get<0>(tuple), sb.pos, sb.direction, startup)) {
return true;
}
}

// Just spawn the one with highest chance
return spawnMonster(spawnId, std::get<0>(sb.mTypes.front()), sb.pos, sb.direction, startup);
}

bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup/*= false*/)
{
std::unique_ptr<Monster> monster_ptr(new Monster(mType));
if (!g_events->eventMonsterOnSpawn(monster_ptr.get(), pos, startup, false)) {
Expand All @@ -239,7 +326,7 @@ bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& p
monster->setMasterPos(pos);
monster->incrementReferenceCounter();

spawnedMap.insert(spawned_pair(spawnId, monster));
spawnedMap.insert({spawnId, monster});
spawnMap[spawnId].lastSpawn = OTSYS_TIME();
return true;
}
Expand All @@ -249,7 +336,7 @@ void Spawn::startup()
for (const auto& it : spawnMap) {
uint32_t spawnId = it.first;
const spawnBlock_t& sb = it.second;
spawnMonster(spawnId, sb.mType, sb.pos, sb.direction, true);
spawnMonster(spawnId, sb, true);
}
}

Expand All @@ -274,7 +361,7 @@ void Spawn::checkSpawn()
continue;
}

spawnMonster(spawnId, sb.mType, sb.pos, sb.direction);
spawnMonster(spawnId, sb);
if (++spawnCount >= static_cast<uint32_t>(g_config.getNumber(ConfigManager::RATE_SPAWN))) {
break;
}
Expand All @@ -300,14 +387,22 @@ void Spawn::cleanup()
monster->decrementReferenceCounter();
it = spawnedMap.erase(it);
} else if (!isInSpawnZone(monster->getPosition()) && spawnId != 0) {
spawnedMap.insert(spawned_pair(0, monster));
spawnedMap.insert({0, monster});
it = spawnedMap.erase(it);
} else {
++it;
}
}
}

bool Spawn::addBlock(spawnBlock_t sb)
{
interval = std::min(interval, sb.interval);
spawnMap[spawnMap.size() + 1] = sb;

return true;
}

bool Spawn::addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval)
{
MonsterType* mType = g_monsters.getMonsterType(name);
Expand All @@ -316,18 +411,14 @@ bool Spawn::addMonster(const std::string& name, const Position& pos, Direction d
return false;
}

this->interval = std::min(this->interval, interval);

spawnBlock_t sb;
sb.mType = mType;
sb.mTypes.push_back({mType, 100});
sb.pos = pos;
sb.direction = dir;
sb.interval = interval;
sb.lastSpawn = 0;

uint32_t spawnId = spawnMap.size() + 1;
spawnMap[spawnId] = sb;
return true;
return addBlock(sb);
}

void Spawn::removeMonster(Monster* monster)
Expand Down
8 changes: 6 additions & 2 deletions src/spawn.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@
#include "tile.h"
#include "position.h"

#include <tuple>
#include <vector>

class Monster;
class MonsterType;
class Npc;

struct spawnBlock_t {
Position pos;
MonsterType* mType;
std::vector<std::tuple<MonsterType*, uint16_t>> mTypes;
int64_t lastSpawn;
uint32_t interval;
Direction direction;
Expand All @@ -45,6 +48,7 @@ class Spawn
Spawn(const Spawn&) = delete;
Spawn& operator=(const Spawn&) = delete;

bool addBlock(spawnBlock_t sb);
bool addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval);
void removeMonster(Monster* monster);

Expand All @@ -62,7 +66,6 @@ class Spawn
private:
//map of the spawned creatures
using SpawnedMap = std::multimap<uint32_t, Monster*>;
using spawned_pair = SpawnedMap::value_type;
SpawnedMap spawnedMap;

//map of creatures in the spawn
Expand All @@ -75,6 +78,7 @@ class Spawn
uint32_t checkSpawnEvent = 0;

static bool findPlayer(const Position& pos);
bool spawnMonster(uint32_t spawnId, spawnBlock_t sb, bool startup = false);
bool spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup = false);
void checkSpawn();
};
Expand Down

0 comments on commit 851f3ca

Please sign in to comment.