Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add varying monster spawn #3605

Merged
merged 1 commit into from
Aug 29, 2021
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
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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little bit of a lie, since the set is not empty, but contains non existent monster(s) only 🤔

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