mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
* Added mintime and maxtime to spawnentry. This will prevent a NPC from spawning outside of the times specified. NPCs spawned in this way will then behave like normal NPCs. They will not despawn on their own, unlike spawn_events/spawn_conditions. NPCs using this that are alone in their spawngroup will attempt to spawn after their respawn timer has expired if the time of day is outside their range. Otherwise, another NPC in the spawngroup will be chosen to spawn. The normal rules (chance, spawn_limit) still apply to these NPCs, this is just another rule added to the system. mintime and maxtime both represent the in-game EQ Hour. Valid values are 1-24. If either or both of the values are 0, then the NPC will not have any time restriction. Added a new rule World:BootHour. This allows server admins to specify the EQ hour the server will boot to. Valid options are 1-24. Setting this rule to 0 (default) disables it and world will use whatever time is specified in the DB. * generated base_spawnentry_repository.h from script * removed the rule insert from database_update_manifest.cpp. * Add logging, initializers, minor cleanup * Remove if/else branch * Update eqtime.cpp * Initializers, logging --------- Co-authored-by: Akkadius <akkadius1@gmail.com>
1305 lines
40 KiB
C++
1305 lines
40 KiB
C++
/* EQEMu: Everquest Server Emulator
|
|
Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
|
|
|
|
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; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY except by those people which sell it, which
|
|
are required to give you total support for your newly bought product;
|
|
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, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "../common/global_define.h"
|
|
#include "../common/strings.h"
|
|
|
|
#include "client.h"
|
|
#include "entity.h"
|
|
#include "spawn2.h"
|
|
#include "spawngroup.h"
|
|
#include "worldserver.h"
|
|
#include "zone.h"
|
|
#include "zonedb.h"
|
|
#include "../common/repositories/criteria/content_filter_criteria.h"
|
|
#include "../common/repositories/spawn2_repository.h"
|
|
#include "../common/repositories/spawn2_disabled_repository.h"
|
|
|
|
extern EntityList entity_list;
|
|
extern Zone* zone;
|
|
|
|
extern WorldServer worldserver;
|
|
|
|
/*
|
|
|
|
CREATE TABLE spawn_conditions (
|
|
zone VARCHAR(16) NOT NULL,
|
|
id MEDIUMINT UNSIGNED NOT NULL DEFAULT '1',
|
|
value MEDIUMINT NOT NULL DEFAULT '0',
|
|
onchange TINYINT UNSIGNED NOT NULL DEFAULT '0',
|
|
name VARCHAR(255) NOT NULL DEFAULT '',
|
|
PRIMARY KEY(zone,id)
|
|
);
|
|
|
|
CREATE TABLE spawn_events (
|
|
#identifiers
|
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
zone VARCHAR(16) NOT NULL,
|
|
cond_id MEDIUMINT UNSIGNED NOT NULL,
|
|
name VARCHAR(255) NOT NULL DEFAULT '',
|
|
|
|
#timing information
|
|
period INT UNSIGNED NOT NULL,
|
|
next_minute TINYINT UNSIGNED NOT NULL,
|
|
next_hour TINYINT UNSIGNED NOT NULL,
|
|
next_day TINYINT UNSIGNED NOT NULL,
|
|
next_month TINYINT UNSIGNED NOT NULL,
|
|
next_year INT UNSIGNED NOT NULL,
|
|
enabled TINYINT NOT NULL DEFAULT '1',
|
|
|
|
#action:
|
|
action TINYINT UNSIGNED NOT NULL DEFAULT '0',
|
|
argument MEDIUMINT NOT NULL DEFAULT '0'
|
|
);
|
|
|
|
*/
|
|
|
|
Spawn2::Spawn2(uint32 in_spawn2_id, uint32 spawngroup_id,
|
|
float in_x, float in_y, float in_z, float in_heading,
|
|
uint32 respawn, uint32 variance, uint32 timeleft, uint32 grid,
|
|
bool in_path_when_zone_idle, uint16 in_cond_id, int16 in_min_value,
|
|
bool in_enabled, EmuAppearance anim)
|
|
: timer(100000), killcount(0)
|
|
{
|
|
spawn2_id = in_spawn2_id;
|
|
spawngroup_id_ = spawngroup_id;
|
|
x = in_x;
|
|
y = in_y;
|
|
z = in_z;
|
|
heading = in_heading;
|
|
respawn_ = respawn;
|
|
variance_ = variance;
|
|
grid_ = grid;
|
|
path_when_zone_idle = in_path_when_zone_idle;
|
|
condition_id = in_cond_id;
|
|
condition_min_value = in_min_value;
|
|
npcthis = nullptr;
|
|
enabled = in_enabled;
|
|
this->anim = anim;
|
|
|
|
if(timeleft == 0xFFFFFFFF) {
|
|
//special disable timeleft
|
|
timer.Disable();
|
|
} else if(timeleft != 0){
|
|
//we have a timeleft from the DB or something
|
|
timer.Start(timeleft);
|
|
} else {
|
|
//no timeleft at all, reset to
|
|
timer.Start(resetTimer());
|
|
timer.Trigger();
|
|
}
|
|
}
|
|
|
|
Spawn2::~Spawn2()
|
|
{
|
|
}
|
|
|
|
uint32 Spawn2::resetTimer()
|
|
{
|
|
uint32 rspawn = respawn_ * 1000;
|
|
|
|
if (variance_ != 0) {
|
|
int var_over_2 = (variance_ * 1000) / 2;
|
|
rspawn = zone->random.Int(rspawn - var_over_2, rspawn + var_over_2);
|
|
|
|
//put a lower bound on it, not a lot of difference below 100, so set that as the bound.
|
|
if(rspawn < 100)
|
|
rspawn = 100;
|
|
}
|
|
|
|
return (rspawn);
|
|
|
|
}
|
|
|
|
uint32 Spawn2::despawnTimer(uint32 despawn_timer)
|
|
{
|
|
uint32 dspawn = despawn_timer * 1000;
|
|
|
|
if (variance_ != 0) {
|
|
int var_over_2 = (variance_ * 1000) / 2;
|
|
dspawn = zone->random.Int(dspawn - var_over_2, dspawn + var_over_2);
|
|
|
|
//put a lower bound on it, not a lot of difference below 100, so set that as the bound.
|
|
if(dspawn < 100)
|
|
dspawn = 100;
|
|
}
|
|
|
|
return (dspawn);
|
|
|
|
}
|
|
|
|
bool Spawn2::Process() {
|
|
IsDespawned = false;
|
|
|
|
if (!Enabled())
|
|
return true;
|
|
|
|
//grab our spawn group
|
|
SpawnGroup *spawn_group = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_);
|
|
|
|
if (NPCPointerValid() && (spawn_group && spawn_group->despawn == 0 || condition_id != 0)) {
|
|
return true;
|
|
}
|
|
|
|
if (timer.Check()) {
|
|
timer.Disable();
|
|
|
|
LogSpawns("[{}]: Timer has triggered", spawn2_id);
|
|
|
|
//first check our spawn condition, if this isnt active
|
|
//then we reset the timer and try again next time.
|
|
if (condition_id != SC_AlwaysEnabled
|
|
&& !zone->spawn_conditions.Check(condition_id, condition_min_value)) {
|
|
LogSpawns("[{}]: spawning prevented by spawn condition [{}]", spawn2_id, condition_id);
|
|
Reset();
|
|
return (true);
|
|
}
|
|
|
|
/**
|
|
* Wait for init grids timer because we bulk load this data before trying to fetch it individually
|
|
*/
|
|
if (spawn_group == nullptr && zone->GetInitgridsTimer().Check()) {
|
|
content_db.LoadSpawnGroupsByID(spawngroup_id_, &zone->spawn_group_list);
|
|
spawn_group = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_);
|
|
}
|
|
|
|
if (spawn_group == nullptr) {
|
|
LogSpawns("Spawn2 [{}]: Unable to locate spawn group [{}]. Disabling", spawn2_id, spawngroup_id_);
|
|
|
|
return false;
|
|
}
|
|
|
|
uint16 condition_value=1;
|
|
|
|
if (condition_id > 0) {
|
|
condition_value = zone->spawn_conditions.GetCondition(zone->GetShortName(), zone->GetInstanceID(), condition_id);
|
|
}
|
|
|
|
//have the spawn group pick an NPC for us
|
|
uint32 npcid = spawn_group->GetNPCType(condition_value);
|
|
if (npcid == 0) {
|
|
LogSpawns("Spawn2 [{}]: Spawn group [{}] did not yeild an NPC! not spawning", spawn2_id, spawngroup_id_);
|
|
|
|
Reset(); //try again later (why?)
|
|
return (true);
|
|
}
|
|
|
|
//try to find our NPC type.
|
|
const NPCType *tmp = content_db.LoadNPCTypesData(npcid);
|
|
if (tmp == nullptr) {
|
|
LogSpawns("Spawn2 [{}]: Spawn group [{}] yeilded an invalid NPC type [{}]", spawn2_id, spawngroup_id_, npcid);
|
|
Reset(); //try again later
|
|
return (true);
|
|
}
|
|
|
|
if (tmp->npc_id == 0) {
|
|
LogError("NPC type did not load for npc_id [{}]", npcid);
|
|
return true;
|
|
}
|
|
|
|
if (tmp->unique_spawn_by_name) {
|
|
if (!entity_list.LimitCheckName(tmp->name)) {
|
|
LogSpawns("Spawn2 [{}]: Spawn group [{}] yeilded NPC type [{}], which is unique and one already exists", spawn2_id, spawngroup_id_, npcid);
|
|
timer.Start(5000); //try again in five seconds.
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
if (tmp->spawn_limit > 0) {
|
|
if (!entity_list.LimitCheckType(npcid, tmp->spawn_limit)) {
|
|
LogSpawns("Spawn2 [{}]: Spawn group [{}] yeilded NPC type [{}], which is over its spawn limit ([{}])", spawn2_id, spawngroup_id_, npcid, tmp->spawn_limit);
|
|
timer.Start(5000); //try again in five seconds.
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
bool ignore_despawn = false;
|
|
if (npcthis) {
|
|
ignore_despawn = npcthis->IgnoreDespawn();
|
|
}
|
|
|
|
if (ignore_despawn) {
|
|
return true;
|
|
}
|
|
|
|
if (spawn_group->despawn != 0 && condition_id == 0 && !ignore_despawn) {
|
|
zone->Despawn(spawn2_id);
|
|
}
|
|
|
|
if (IsDespawned) {
|
|
return true;
|
|
}
|
|
|
|
currentnpcid = npcid;
|
|
|
|
glm::vec4 loc(x, y, z, heading);
|
|
int starting_wp = 0;
|
|
if (spawn_group->wp_spawns && grid_ > 0)
|
|
{
|
|
glm::vec4 wploc;
|
|
starting_wp = content_db.GetRandomWaypointLocFromGrid(wploc, zone->GetZoneID(), grid_);
|
|
if (wploc.x != 0.0f || wploc.y != 0.0f || wploc.z != 0.0f)
|
|
{
|
|
loc = wploc;
|
|
Log(Logs::General, Logs::Spawns, "spawning at random waypoint #%i loc: (%.3f, %.3f, %.3f).", starting_wp , loc.x, loc.y, loc.z);
|
|
}
|
|
}
|
|
|
|
NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water);
|
|
|
|
npcthis = npc;
|
|
npc->AddLootTable();
|
|
if (npc->DropsGlobalLoot()) {
|
|
npc->CheckGlobalLootTables();
|
|
}
|
|
npc->SetSpawnGroupId(spawngroup_id_);
|
|
npc->SaveGuardPointAnim(anim);
|
|
npc->SetAppearance((EmuAppearance) anim);
|
|
entity_list.AddNPC(npc);
|
|
//this limit add must be done after the AddNPC since we need the entity ID.
|
|
entity_list.LimitAddNPC(npc);
|
|
|
|
/**
|
|
* Roambox init
|
|
*/
|
|
if (spawn_group->roamdist > 0) {
|
|
npc->AI_SetRoambox(
|
|
spawn_group->roamdist,
|
|
spawn_group->roambox[0],
|
|
spawn_group->roambox[1],
|
|
spawn_group->roambox[2],
|
|
spawn_group->roambox[3],
|
|
spawn_group->delay,
|
|
spawn_group->min_delay
|
|
);
|
|
}
|
|
|
|
if (zone->InstantGrids()) {
|
|
LogSpawns("Spawn2 [{}]: Group [{}] spawned [{}] ([{}]) at ([{}], [{}], [{}])",
|
|
spawn2_id,
|
|
spawngroup_id_,
|
|
npc->GetName(),
|
|
npcid,
|
|
x,
|
|
y,
|
|
z
|
|
);
|
|
|
|
LoadGrid(starting_wp);
|
|
}
|
|
else {
|
|
LogSpawns("Spawn2 [{}]: Group [{}] spawned [{}] ([{}]) at ([{}], [{}], [{}]). Grid loading delayed",
|
|
spawn2_id,
|
|
spawngroup_id_,
|
|
tmp->name,
|
|
npcid,
|
|
x,
|
|
y,
|
|
z
|
|
);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Spawn2::Disable()
|
|
{
|
|
if(npcthis)
|
|
{
|
|
npcthis->Depop();
|
|
}
|
|
enabled = false;
|
|
}
|
|
|
|
void Spawn2::LoadGrid(int start_wp) {
|
|
if (!npcthis)
|
|
return;
|
|
if (grid_ < 1)
|
|
return;
|
|
if (!entity_list.IsMobInZone(npcthis))
|
|
return;
|
|
//dont set an NPC's grid until its loaded for them.
|
|
npcthis->SetGrid(grid_);
|
|
npcthis->AssignWaypoints(grid_, start_wp);
|
|
LogSpawns("Spawn2 [{}]: Loading grid [{}] for [{}]; starting wp is [{}]", spawn2_id, grid_, npcthis->GetName(), start_wp);
|
|
}
|
|
|
|
/*
|
|
All three of these actions basically say that the mob which was
|
|
associated with this spawn point is no longer relavent.
|
|
*/
|
|
void Spawn2::Reset() {
|
|
timer.Start(resetTimer());
|
|
npcthis = nullptr;
|
|
LogSpawns("Spawn2 [{}]: Spawn reset, repop in [{}] ms", spawn2_id, timer.GetRemainingTime());
|
|
}
|
|
|
|
void Spawn2::Depop() {
|
|
timer.Disable();
|
|
LogSpawns("Spawn2 [{}]: Spawn reset, repop disabled", spawn2_id);
|
|
npcthis = nullptr;
|
|
}
|
|
|
|
void Spawn2::Repop(uint32 delay) {
|
|
if (delay == 0) {
|
|
timer.Trigger();
|
|
LogSpawns("Spawn2 [{}]: Spawn reset, repop immediately", spawn2_id);
|
|
} else {
|
|
LogSpawns("Spawn2 [{}]: Spawn reset for repop, repop in [{}] ms", spawn2_id, delay);
|
|
timer.Start(delay);
|
|
}
|
|
npcthis = nullptr;
|
|
}
|
|
|
|
void Spawn2::ForceDespawn()
|
|
{
|
|
SpawnGroup* sg = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_);
|
|
|
|
if(npcthis != nullptr)
|
|
{
|
|
if (npcthis->IgnoreDespawn())
|
|
return;
|
|
|
|
if(!npcthis->IsEngaged())
|
|
{
|
|
if(sg->despawn == 3 || sg->despawn == 4)
|
|
{
|
|
npcthis->Depop(true);
|
|
IsDespawned = true;
|
|
npcthis = nullptr;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
npcthis->Depop(false);
|
|
npcthis = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 cur = 100000;
|
|
uint32 dtimer = sg->despawn_timer;
|
|
|
|
if(sg->despawn == 1 || sg->despawn == 3)
|
|
{
|
|
cur = resetTimer();
|
|
}
|
|
|
|
if(sg->despawn == 2 || sg->despawn == 4)
|
|
{
|
|
cur = despawnTimer(dtimer);
|
|
}
|
|
|
|
LogSpawns("Spawn2 [{}]: Spawn group [{}] set despawn timer to [{}] ms", spawn2_id, spawngroup_id_, cur);
|
|
timer.Start(cur);
|
|
}
|
|
|
|
//resets our spawn as if we just died
|
|
void Spawn2::DeathReset(bool realdeath)
|
|
{
|
|
//get our reset based on variance etc and store it locally
|
|
uint32 cur = resetTimer();
|
|
//set our timer to our reset local
|
|
timer.Start(cur);
|
|
|
|
//zero out our NPC since he is now gone
|
|
npcthis = nullptr;
|
|
|
|
if(realdeath) { killcount++; }
|
|
|
|
//if we have a valid spawn id
|
|
if(spawn2_id)
|
|
{
|
|
database.UpdateRespawnTime(spawn2_id, zone->GetInstanceID(), (cur/1000));
|
|
LogSpawns("Spawn2 [{}]: Spawn reset by death, repop in [{}] ms", spawn2_id, timer.GetRemainingTime());
|
|
//store it to database too
|
|
}
|
|
}
|
|
|
|
bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList<Spawn2*> &spawn2_list, int16 version) {
|
|
|
|
std::unordered_map<uint32, uint32> spawn_times;
|
|
|
|
timeval tv;
|
|
gettimeofday(&tv, nullptr);
|
|
|
|
/* Bulk Load NPC Types Data into the cache */
|
|
content_db.LoadNPCTypesData(0, true);
|
|
|
|
std::string spawn_query = StringFormat(
|
|
"SELECT "
|
|
"respawn_times.id, "
|
|
"respawn_times.`start`, "
|
|
"respawn_times.duration "
|
|
"FROM "
|
|
"respawn_times "
|
|
"WHERE instance_id = %u",
|
|
zone->GetInstanceID()
|
|
);
|
|
auto results = database.QueryDatabase(spawn_query);
|
|
for (auto row = results.begin(); row != results.end(); ++row) {
|
|
uint32 start_duration = Strings::ToInt(row[1]) > 0 ? Strings::ToInt(row[1]) : 0;
|
|
uint32 end_duration = Strings::ToInt(row[2]) > 0 ? Strings::ToInt(row[2]) : 0;
|
|
|
|
/* Our current time was expired */
|
|
if ((start_duration + end_duration) <= tv.tv_sec) {
|
|
spawn_times[Strings::ToInt(row[0])] = 0;
|
|
}
|
|
/* We still have time left on this timer */
|
|
else {
|
|
spawn_times[Strings::ToInt(row[0])] = ((start_duration + end_duration) - tv.tv_sec) * 1000;
|
|
}
|
|
}
|
|
|
|
LogInfo("Loaded [{}] respawn timer(s)", Strings::Commify(results.RowCount()));
|
|
|
|
const char *zone_name = ZoneName(zoneid);
|
|
|
|
auto spawns = Spawn2Repository::GetWhere(
|
|
content_db, fmt::format(
|
|
"TRUE {} AND zone = '{}' AND (version = {} OR version = -1) ",
|
|
ContentFilterCriteria::apply(),
|
|
zone_name,
|
|
version
|
|
)
|
|
);
|
|
|
|
std::vector<uint32> spawn2_ids;
|
|
for (auto &s: spawns) {
|
|
spawn2_ids.push_back(s.id);
|
|
}
|
|
|
|
// we load spawn2_disabled entries for this zone
|
|
// if there are more specific entries for an instance of this zone, we load those instead
|
|
// if there are no entries for this zone, we load the default entries
|
|
std::vector<Spawn2DisabledRepository::Spawn2Disabled> disabled_spawns = {};
|
|
if (!spawn2_ids.empty()) {
|
|
disabled_spawns = Spawn2DisabledRepository::GetWhere(
|
|
database,
|
|
fmt::format(
|
|
"spawn2_id IN ({}) and (instance_id = {} OR instance_id = 0) ORDER BY instance_id",
|
|
Strings::Join(spawn2_ids, ","),
|
|
zone->GetInstanceID()
|
|
)
|
|
);
|
|
}
|
|
|
|
for (auto &s: spawns) {
|
|
uint32 spawn_time_left = 0;
|
|
if (spawn_times.count(s.id) != 0) {
|
|
spawn_time_left = spawn_times[s.id];
|
|
}
|
|
|
|
// load from spawn2_disabled
|
|
bool spawn_enabled = true;
|
|
|
|
// check if spawn is disabled
|
|
for (auto &ds: disabled_spawns) {
|
|
if (ds.spawn2_id == s.id) {
|
|
spawn_enabled = !ds.disabled;
|
|
}
|
|
}
|
|
|
|
auto new_spawn = new Spawn2(
|
|
s.id,
|
|
s.spawngroupID,
|
|
s.x,
|
|
s.y,
|
|
s.z,
|
|
s.heading,
|
|
s.respawntime,
|
|
s.variance,
|
|
spawn_time_left,
|
|
s.pathgrid,
|
|
(bool) s.path_when_zone_idle,
|
|
s._condition,
|
|
(int16) s.cond_value,
|
|
spawn_enabled,
|
|
(EmuAppearance) s.animation
|
|
);
|
|
|
|
spawn2_list.Insert(new_spawn);
|
|
}
|
|
|
|
LogInfo("Loaded [{}] spawn2 entries", Strings::Commify(results.RowCount()));
|
|
|
|
NPC::SpawnZoneController();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ZoneDatabase::CreateSpawn2(Client *client, uint32 spawngroup, const char* zone, const glm::vec4& position, uint32 respawn, uint32 variance, uint16 condition, int16 cond_value)
|
|
{
|
|
|
|
std::string query = StringFormat("INSERT INTO spawn2 (spawngroupID, zone, x, y, z, heading, "
|
|
"respawntime, variance, _condition, cond_value) "
|
|
"VALUES (%i, '%s', %f, %f, %f, %f, %i, %i, %u, %i)",
|
|
spawngroup, zone, position.x, position.y, position.z, position.w,
|
|
respawn, variance, condition, cond_value);
|
|
auto results = QueryDatabase(query);
|
|
if (!results.Success()) {
|
|
return false;
|
|
}
|
|
|
|
if (results.RowsAffected() != 1)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32 Zone::CountSpawn2() {
|
|
LinkedListIterator<Spawn2*> iterator(spawn2_list);
|
|
uint32 count = 0;
|
|
|
|
iterator.Reset();
|
|
while(iterator.MoreElements())
|
|
{
|
|
count++;
|
|
iterator.Advance();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void Zone::Despawn(uint32 spawn2ID) {
|
|
LinkedListIterator<Spawn2*> iterator(spawn2_list);
|
|
|
|
iterator.Reset();
|
|
while(iterator.MoreElements()) {
|
|
Spawn2 *cur = iterator.GetData();
|
|
if(spawn2ID == cur->spawn2_id)
|
|
cur->ForceDespawn();
|
|
iterator.Advance();
|
|
}
|
|
}
|
|
|
|
void Spawn2::SpawnConditionChanged(const SpawnCondition &c, int16 old_value) {
|
|
if(GetSpawnCondition() != c.condition_id)
|
|
return;
|
|
|
|
LogSpawns("Spawn2 [{}]: Notified that our spawn condition [{}] has changed from [{}] to [{}]. Our min value is [{}]", spawn2_id, c.condition_id, old_value, c.value, condition_min_value);
|
|
|
|
bool old_state = (old_value >= condition_min_value);
|
|
bool new_state = (c.value >= condition_min_value);
|
|
if(old_state == new_state) {
|
|
LogSpawns("Spawn2 [{}]: Our threshold for this condition was not crossed. Doing nothing", spawn2_id);
|
|
return; //no change
|
|
}
|
|
|
|
uint32 timer_remaining = 0;
|
|
switch(c.on_change) {
|
|
case SpawnCondition::DoNothing:
|
|
//that was easy.
|
|
LogSpawns("Spawn2 [{}]: Our condition is now [{}]. Taking no action on existing spawn", spawn2_id, new_state?"enabled":"disabled");
|
|
break;
|
|
case SpawnCondition::DoDepop:
|
|
LogSpawns("Spawn2 [{}]: Our condition is now [{}]. Depoping our mob", spawn2_id, new_state?"enabled":"disabled");
|
|
if(npcthis != nullptr)
|
|
npcthis->Depop(false); //remove the current mob
|
|
Reset(); //reset our spawn timer
|
|
break;
|
|
case SpawnCondition::DoRepop:
|
|
LogSpawns("Spawn2 [{}]: Our condition is now [{}]. Forcing a repop", spawn2_id, new_state?"enabled":"disabled");
|
|
if(npcthis != nullptr)
|
|
npcthis->Depop(false); //remove the current mob
|
|
Repop(); //repop
|
|
break;
|
|
case SpawnCondition::DoRepopIfReady:
|
|
LogSpawns("Spawn2 [{}]: Our condition is now [{}]. Forcing a repop if repsawn timer is expired", spawn2_id, new_state?"enabled":"disabled");
|
|
if(npcthis != nullptr) {
|
|
LogSpawns("Spawn2 [{}]: Our npcthis is currently not null. The zone thinks it is [{}]. Forcing a depop", spawn2_id, npcthis->GetName());
|
|
npcthis->Depop(false); //remove the current mob
|
|
npcthis = nullptr;
|
|
}
|
|
if(new_state) { // only get repawn timer remaining when the SpawnCondition is enabled.
|
|
timer_remaining = database.GetSpawnTimeLeft(spawn2_id,zone->GetInstanceID());
|
|
LogSpawns("Spawn2 [{}]: Our condition is now [{}]. The respawn timer_remaining is [{}]. Forcing a repop if it is <= 0", spawn2_id, new_state?"enabled":"disabled", timer_remaining);
|
|
if(timer_remaining <= 0)
|
|
Repop();
|
|
} else {
|
|
LogSpawns("Spawn2 [{}]: Our condition is now [{}]. Not checking respawn timer", spawn2_id, new_state?"enabled":"disabled");
|
|
}
|
|
break;
|
|
default:
|
|
if(c.on_change < SpawnCondition::DoSignalMin) {
|
|
LogSpawns("Spawn2 [{}]: Our condition is now [{}]. Invalid on-change action [{}]", spawn2_id, new_state?"enabled":"disabled", c.on_change);
|
|
return; //unknown onchange action
|
|
}
|
|
int signal_id = c.on_change - SpawnCondition::DoSignalMin;
|
|
LogSpawns("Spawn2 [{}]: Our condition is now [{}]. Signaling our mob with [{}]", spawn2_id, new_state?"enabled":"disabled", signal_id);
|
|
if(npcthis != nullptr)
|
|
npcthis->SignalNPC(signal_id);
|
|
}
|
|
}
|
|
|
|
void Zone::SpawnConditionChanged(const SpawnCondition &c, int16 old_value) {
|
|
LogSpawns("Zone notified that spawn condition [{}] has changed from [{}] to [{}]. Notifying all spawn points", c.condition_id, old_value, c.value);
|
|
|
|
LinkedListIterator<Spawn2*> iterator(spawn2_list);
|
|
|
|
iterator.Reset();
|
|
while(iterator.MoreElements()) {
|
|
Spawn2 *cur = iterator.GetData();
|
|
if(cur->GetSpawnCondition() == c.condition_id)
|
|
cur->SpawnConditionChanged(c, old_value);
|
|
iterator.Advance();
|
|
}
|
|
}
|
|
|
|
SpawnCondition::SpawnCondition() {
|
|
condition_id = 0;
|
|
value = 0;
|
|
on_change = DoNothing;
|
|
}
|
|
|
|
SpawnEvent::SpawnEvent() {
|
|
id = 0;
|
|
condition_id = 0;
|
|
enabled = false;
|
|
action = ActionSet;
|
|
argument = 0;
|
|
period = 0xFFFFFFFF;
|
|
strict = false;
|
|
memset(&next, 0, sizeof(next));
|
|
}
|
|
|
|
SpawnConditionManager::SpawnConditionManager()
|
|
: minute_timer(3000) //1 eq minute
|
|
{
|
|
memset(&next_event, 0, sizeof(next_event));
|
|
}
|
|
|
|
void SpawnConditionManager::Process() {
|
|
if(spawn_events.empty())
|
|
return;
|
|
|
|
if(minute_timer.Check()) {
|
|
//check each spawn event.
|
|
|
|
//get our current time
|
|
TimeOfDay_Struct tod{};
|
|
zone->zone_time.GetCurrentEQTimeOfDay(&tod);
|
|
|
|
//see if time is past our nearest event.
|
|
if(EQTime::IsTimeBefore(&next_event, &tod))
|
|
return;
|
|
|
|
//at least one event should get triggered,
|
|
std::vector<SpawnEvent>::iterator cur,end;
|
|
cur = spawn_events.begin();
|
|
end = spawn_events.end();
|
|
for(; cur != end; ++cur) {
|
|
SpawnEvent &cevent = *cur;
|
|
|
|
if(cevent.enabled)
|
|
{
|
|
if(EQTime::IsTimeBefore(&tod, &cevent.next)) {
|
|
//this event has been triggered.
|
|
//execute the event
|
|
uint8 min = cevent.next.minute + RuleI(Zone, SpawnEventMin);
|
|
if(!cevent.strict || (cevent.strict && tod.minute < min && cevent.next.hour == tod.hour && cevent.next.day == tod.day && cevent.next.month == tod.month && cevent.next.year == tod.year))
|
|
ExecEvent(cevent, true);
|
|
else
|
|
LogSpawns("Event {}: Is strict, ExecEvent is skipped.", cevent.id);
|
|
|
|
//add the period of the event to the trigger time
|
|
EQTime::AddMinutes(cevent.period, &cevent.next);
|
|
std::string t;
|
|
EQTime::ToString(&cevent.next, t);
|
|
LogSpawns("Event [{}]: Will trigger again in [{}] EQ minutes at [{}]", cevent.id, cevent.period, t.c_str());
|
|
//save the next event time in the DB
|
|
UpdateDBEvent(cevent);
|
|
//find the next closest event timer.
|
|
FindNearestEvent();
|
|
//minor optimization, if theres no more possible events,
|
|
//then stop trying... I dunno how worth while this is.
|
|
if(EQTime::IsTimeBefore(&next_event, &tod))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SpawnConditionManager::ExecEvent(SpawnEvent &event, bool send_update) {
|
|
std::map<uint16, SpawnCondition>::iterator condi;
|
|
condi = spawn_conditions.find(event.condition_id);
|
|
if(condi == spawn_conditions.end()) {
|
|
//If we're here, strict has already been checked. Check again in case hour has changed.
|
|
LogSpawns("Event [{}]: Unable to find condition [{}] to execute on", event.id, event.condition_id);
|
|
return; //unable to find the spawn condition to operate on
|
|
}
|
|
|
|
TimeOfDay_Struct tod{};
|
|
zone->zone_time.GetCurrentEQTimeOfDay(&tod);
|
|
if(event.strict && (event.next.hour != tod.hour || event.next.day != tod.day || event.next.month != tod.month || event.next.year != tod.year))
|
|
{
|
|
LogSpawns("Event [{}]: Unable to execute. Condition is strict, and event time has already passed", event.id);
|
|
return;
|
|
}
|
|
|
|
SpawnCondition &cond = condi->second;
|
|
|
|
int16 new_value = cond.value;
|
|
|
|
//we have our event and our condition, do our stuff.
|
|
switch(event.action) {
|
|
case SpawnEvent::ActionSet:
|
|
new_value = event.argument;
|
|
LogSpawns("Event [{}]: Executing. Setting condition [{}] to [{}]", event.id, event.condition_id, event.argument);
|
|
break;
|
|
case SpawnEvent::ActionAdd:
|
|
new_value += event.argument;
|
|
LogSpawns("Event [{}]: Executing. Adding [{}] to condition [{}], yielding [{}]", event.id, event.argument, event.condition_id, new_value);
|
|
break;
|
|
case SpawnEvent::ActionSubtract:
|
|
new_value -= event.argument;
|
|
LogSpawns("Event [{}]: Executing. Subtracting [{}] from condition [{}], yielding [{}]", event.id, event.argument, event.condition_id, new_value);
|
|
break;
|
|
case SpawnEvent::ActionMultiply:
|
|
new_value *= event.argument;
|
|
LogSpawns("Event [{}]: Executing. Multiplying condition [{}] by [{}], yielding [{}]", event.id, event.condition_id, event.argument, new_value);
|
|
break;
|
|
case SpawnEvent::ActionDivide:
|
|
new_value /= event.argument;
|
|
LogSpawns("Event [{}]: Executing. Dividing condition [{}] by [{}], yielding [{}]", event.id, event.condition_id, event.argument, new_value);
|
|
break;
|
|
default:
|
|
LogSpawns("Event [{}]: Invalid event action type [{}]", event.id, event.action);
|
|
return;
|
|
}
|
|
|
|
//now set the condition to the new value
|
|
if(send_update) //full blown update
|
|
SetCondition(zone->GetShortName(), zone->GetInstanceID(), cond.condition_id, new_value);
|
|
else //minor update done while loading
|
|
cond.value = new_value;
|
|
}
|
|
|
|
void SpawnConditionManager::UpdateDBEvent(SpawnEvent &event) {
|
|
|
|
std::string query = StringFormat("UPDATE spawn_events SET "
|
|
"next_minute = %d, next_hour = %d, "
|
|
"next_day = %d, next_month = %d, "
|
|
"next_year = %d, enabled = %d, "
|
|
"strict = %d WHERE id = %d",
|
|
event.next.minute, event.next.hour,
|
|
event.next.day, event.next.month,
|
|
event.next.year, event.enabled? 1: 0,
|
|
event.strict? 1: 0, event.id);
|
|
database.QueryDatabase(query);
|
|
}
|
|
|
|
void SpawnConditionManager::UpdateDBCondition(const char* zone_name, uint32 instance_id, uint16 cond_id, int16 value) {
|
|
|
|
std::string query = StringFormat("REPLACE INTO spawn_condition_values "
|
|
"(id, value, zone, instance_id) "
|
|
"VALUES( %u, %u, '%s', %u)",
|
|
cond_id, value, zone_name, instance_id);
|
|
database.QueryDatabase(query);
|
|
}
|
|
|
|
bool SpawnConditionManager::LoadDBEvent(uint32 event_id, SpawnEvent &event, std::string &zone_name) {
|
|
|
|
std::string query = StringFormat("SELECT id, cond_id, period, "
|
|
"next_minute, next_hour, next_day, "
|
|
"next_month, next_year, enabled, "
|
|
"action, argument, strict, zone "
|
|
"FROM spawn_events WHERE id = %d", event_id);
|
|
auto results = database.QueryDatabase(query);
|
|
if (!results.Success()) {
|
|
return false;
|
|
}
|
|
|
|
if (results.RowCount() == 0)
|
|
return false;
|
|
|
|
auto row = results.begin();
|
|
|
|
event.id = Strings::ToInt(row[0]);
|
|
event.condition_id = Strings::ToInt(row[1]);
|
|
event.period = Strings::ToInt(row[2]);
|
|
|
|
event.next.minute = Strings::ToInt(row[3]);
|
|
event.next.hour = Strings::ToInt(row[4]);
|
|
event.next.day = Strings::ToInt(row[5]);
|
|
event.next.month = Strings::ToInt(row[6]);
|
|
event.next.year = Strings::ToInt(row[7]);
|
|
|
|
event.enabled = Strings::ToInt(row[8]) != 0;
|
|
event.action = (SpawnEvent::Action) Strings::ToInt(row[9]);
|
|
event.argument = Strings::ToInt(row[10]);
|
|
event.strict = Strings::ToInt(row[11]) != 0;
|
|
zone_name = row[12];
|
|
|
|
std::string timeAsString;
|
|
EQTime::ToString(&event.next, timeAsString);
|
|
|
|
LogSpawns("(LoadDBEvent) Loaded [{}] spawn event [{}] on condition [{}] with period [{}], action [{}], argument [{}], strict [{}]. Will trigger at [{}]", event.enabled? "enabled": "disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict, timeAsString.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 instance_id)
|
|
{
|
|
//clear out old stuff..
|
|
spawn_conditions.clear();
|
|
|
|
std::string query = StringFormat("SELECT id, onchange, value "
|
|
"FROM spawn_conditions "
|
|
"WHERE zone = '%s'", zone_name);
|
|
auto results = content_db.QueryDatabase(query);
|
|
if (!results.Success()) {
|
|
return false;
|
|
}
|
|
|
|
for (auto row = results.begin(); row != results.end(); ++row) {
|
|
//load spawn conditions
|
|
SpawnCondition cond;
|
|
|
|
cond.condition_id = Strings::ToInt(row[0]);
|
|
cond.value = Strings::ToInt(row[2]);
|
|
cond.on_change = (SpawnCondition::OnChange) Strings::ToInt(row[1]);
|
|
spawn_conditions[cond.condition_id] = cond;
|
|
|
|
LogSpawns("Loaded spawn condition [{}] with value [{}] and on_change [{}]", cond.condition_id, cond.value, cond.on_change);
|
|
}
|
|
|
|
LogInfo("Loaded [{}] spawn_conditions", Strings::Commify(std::to_string(results.RowCount())));
|
|
|
|
//load values
|
|
query = StringFormat("SELECT id, value FROM spawn_condition_values "
|
|
"WHERE zone = '%s' AND instance_id = %u",
|
|
zone_name, instance_id);
|
|
results = database.QueryDatabase(query);
|
|
if (!results.Success()) {
|
|
spawn_conditions.clear();
|
|
return false;
|
|
}
|
|
|
|
for (auto row = results.begin(); row != results.end(); ++row) {
|
|
auto iter = spawn_conditions.find(Strings::ToInt(row[0]));
|
|
|
|
if(iter != spawn_conditions.end())
|
|
iter->second.value = Strings::ToInt(row[1]);
|
|
}
|
|
|
|
//load spawn events
|
|
query = StringFormat("SELECT id, cond_id, period, next_minute, next_hour, "
|
|
"next_day, next_month, next_year, enabled, action, argument, strict "
|
|
"FROM spawn_events WHERE zone = '%s'", zone_name);
|
|
results = database.QueryDatabase(query);
|
|
if (!results.Success()) {
|
|
return false;
|
|
}
|
|
|
|
LogInfo("Loaded [{}] spawn_events", Strings::Commify(std::to_string(results.RowCount())));
|
|
|
|
for (auto row = results.begin(); row != results.end(); ++row) {
|
|
SpawnEvent event;
|
|
|
|
event.id = Strings::ToInt(row[0]);
|
|
event.condition_id = Strings::ToInt(row[1]);
|
|
event.period = Strings::ToInt(row[2]);
|
|
|
|
if (event.period == 0) {
|
|
LogError("Refusing to load spawn event #[{}] because it has a period of 0\n", event.id);
|
|
continue;
|
|
}
|
|
|
|
event.next.minute = Strings::ToInt(row[3]);
|
|
event.next.hour = Strings::ToInt(row[4]);
|
|
event.next.day = Strings::ToInt(row[5]);
|
|
event.next.month = Strings::ToInt(row[6]);
|
|
event.next.year = Strings::ToInt(row[7]);
|
|
|
|
event.enabled = Strings::ToInt(row[8]) == 0 ? false : true;
|
|
event.action = (SpawnEvent::Action) Strings::ToInt(row[9]);
|
|
event.argument = Strings::ToInt(row[10]);
|
|
event.strict = Strings::ToInt(row[11]) == 0 ? false : true;
|
|
|
|
spawn_events.push_back(event);
|
|
|
|
LogSpawns(
|
|
"(LoadSpawnConditions) Loaded [{}] spawn event [{}] on condition [{}] with period [{}], action [{}], argument [{}], strict [{}]",
|
|
event.enabled ? "enabled" : "disabled",
|
|
event.id,
|
|
event.condition_id,
|
|
event.period,
|
|
event.action,
|
|
event.argument,
|
|
event.strict
|
|
);
|
|
}
|
|
|
|
//now we need to catch up on events that happened while we were away
|
|
//and use them to alter just the condition variables.
|
|
|
|
//each spawn2 will then use its correct condition value when
|
|
//it decides what to do. This essentially forces a 'depop' action
|
|
//on spawn points which are turned off, and a 'repop' action on
|
|
//spawn points which get turned on. Im too lazy to figure out a
|
|
//better solution, and I just dont care thats much.
|
|
//get our current time
|
|
TimeOfDay_Struct tod{};
|
|
zone->zone_time.GetCurrentEQTimeOfDay(&tod);
|
|
|
|
for(auto cur = spawn_events.begin(); cur != spawn_events.end(); ++cur) {
|
|
SpawnEvent &cevent = *cur;
|
|
|
|
bool StrictCheck = false;
|
|
if(cevent.strict &&
|
|
cevent.next.hour == tod.hour &&
|
|
cevent.next.day == tod.day &&
|
|
cevent.next.month == tod.month &&
|
|
cevent.next.year == tod.year)
|
|
StrictCheck = true;
|
|
|
|
//If event is disabled, or we failed the strict check, set initial spawn_condition to 0.
|
|
if(!cevent.enabled || !StrictCheck)
|
|
SetCondition(zone->GetShortName(), zone->GetInstanceID(),cevent.condition_id,0);
|
|
|
|
if(!cevent.enabled)
|
|
continue;
|
|
|
|
//watch for special case of all 0s, which means to reset next to now
|
|
if (cevent.next.year == 0 && cevent.next.month == 0 && cevent.next.day == 0 && cevent.next.hour == 0 &&
|
|
cevent.next.minute == 0) {
|
|
LogSpawns("Initial next trigger time set for spawn event [{}]", cevent.id);
|
|
memcpy(&cevent.next, &tod, sizeof(cevent.next));
|
|
//add one period
|
|
EQTime::AddMinutes(cevent.period, &cevent.next);
|
|
//save it in the db.
|
|
UpdateDBEvent(cevent);
|
|
continue; //were done with this event.
|
|
}
|
|
|
|
bool ran = false;
|
|
while (EQTime::IsTimeBefore(&tod, &cevent.next)) {
|
|
LogSpawns("Catch up triggering on event [{}]", cevent.id);
|
|
//this event has been triggered.
|
|
//execute the event
|
|
if (!cevent.strict || StrictCheck) {
|
|
ExecEvent(cevent, false);
|
|
}
|
|
|
|
//add the period of the event to the trigger time
|
|
EQTime::AddMinutes(cevent.period, &cevent.next);
|
|
ran = true;
|
|
}
|
|
|
|
//only write it out if the event actually ran
|
|
if(ran)
|
|
UpdateDBEvent(cevent); //save the event in the DB
|
|
}
|
|
|
|
//now our event timers are all up to date, find our closest event.
|
|
FindNearestEvent();
|
|
|
|
return true;
|
|
}
|
|
|
|
void SpawnConditionManager::FindNearestEvent() {
|
|
//set a huge year which should never get reached normally
|
|
next_event.year = 0xFFFFFF;
|
|
|
|
std::vector<SpawnEvent>::iterator cur,end;
|
|
cur = spawn_events.begin();
|
|
end = spawn_events.end();
|
|
int next_id = -1;
|
|
for(; cur != end; ++cur) {
|
|
SpawnEvent &cevent = *cur;
|
|
if(cevent.enabled)
|
|
{
|
|
//see if this event is before our last nearest
|
|
if(EQTime::IsTimeBefore(&next_event, &cevent.next))
|
|
{
|
|
memcpy(&next_event, &cevent.next, sizeof(next_event));
|
|
next_id = cevent.id;
|
|
}
|
|
}
|
|
}
|
|
if (next_id == -1) {
|
|
LogSpawns("No spawn events enabled. Disabling next event");
|
|
}
|
|
else {
|
|
LogSpawns("Next event determined to be event [{}]", next_id);
|
|
}
|
|
}
|
|
|
|
void SpawnConditionManager::SetCondition(const char *zone_short, uint32 instance_id, uint16 condition_id, int16 new_value, bool world_update)
|
|
{
|
|
if(world_update) {
|
|
//this is an update coming from another zone, they
|
|
//have allready updated the DB, just need to update our
|
|
//memory, and check for condition changes
|
|
std::map<uint16, SpawnCondition>::iterator condi;
|
|
condi = spawn_conditions.find(condition_id);
|
|
if(condi == spawn_conditions.end()) {
|
|
LogSpawns("Condition update received from world for [{}], but we do not have that conditon", condition_id);
|
|
return; //unable to find the spawn condition
|
|
}
|
|
|
|
SpawnCondition &cond = condi->second;
|
|
|
|
if(cond.value == new_value) {
|
|
LogSpawns("Condition update received from world for [{}] with value [{}], which is what we already have", condition_id, new_value);
|
|
return;
|
|
}
|
|
|
|
int16 old_value = cond.value;
|
|
|
|
//set our local value
|
|
cond.value = new_value;
|
|
|
|
LogSpawns("Condition update received from world for [{}] with value [{}]", condition_id, new_value);
|
|
|
|
//now we have to test each spawn point to see if it changed.
|
|
zone->SpawnConditionChanged(cond, old_value);
|
|
} else if(!strcasecmp(zone_short, zone->GetShortName()) && instance_id == zone->GetInstanceID())
|
|
{
|
|
//this is a local spawn condition, we need to update the DB,
|
|
//our memory, then notify spawn points of the change.
|
|
std::map<uint16, SpawnCondition>::iterator condi;
|
|
condi = spawn_conditions.find(condition_id);
|
|
if(condi == spawn_conditions.end()) {
|
|
LogSpawns("Local Condition update requested for [{}], but we do not have that conditon", condition_id);
|
|
return; //unable to find the spawn condition
|
|
}
|
|
|
|
SpawnCondition &cond = condi->second;
|
|
|
|
if(cond.value == new_value) {
|
|
LogSpawns("Local Condition update requested for [{}] with value [{}], which is what we already have", condition_id, new_value);
|
|
return;
|
|
}
|
|
|
|
int16 old_value = cond.value;
|
|
|
|
//set our local value
|
|
cond.value = new_value;
|
|
//save it in the DB too
|
|
UpdateDBCondition(zone_short, instance_id, condition_id, new_value);
|
|
|
|
LogSpawns("Local Condition update requested for [{}] with value [{}]", condition_id, new_value);
|
|
|
|
//now we have to test each spawn point to see if it changed.
|
|
zone->SpawnConditionChanged(cond, old_value);
|
|
}
|
|
else
|
|
{
|
|
//this is a remote spawn condition, update the DB and send
|
|
//an update packet to the zone if its up
|
|
|
|
LogSpawns("Remote spawn condition [{}] set to [{}]. Updating DB and notifying world", condition_id, new_value);
|
|
|
|
UpdateDBCondition(zone_short, instance_id, condition_id, new_value);
|
|
|
|
auto pack = new ServerPacket(ServerOP_SpawnCondition, sizeof(ServerSpawnCondition_Struct));
|
|
ServerSpawnCondition_Struct* ssc = (ServerSpawnCondition_Struct*)pack->pBuffer;
|
|
|
|
ssc->zoneID = ZoneID(zone_short);
|
|
ssc->instanceID = instance_id;
|
|
ssc->condition_id = condition_id;
|
|
ssc->value = new_value;
|
|
|
|
worldserver.SendPacket(pack);
|
|
safe_delete(pack);
|
|
}
|
|
}
|
|
|
|
void SpawnConditionManager::ReloadEvent(uint32 event_id) {
|
|
std::string zone_short_name;
|
|
|
|
LogSpawns("Requested to reload event [{}] from the database", event_id);
|
|
|
|
//first look for the event in our local event list
|
|
std::vector<SpawnEvent>::iterator cur,end;
|
|
cur = spawn_events.begin();
|
|
end = spawn_events.end();
|
|
for(; cur != end; ++cur) {
|
|
SpawnEvent &cevent = *cur;
|
|
|
|
if(cevent.id == event_id) {
|
|
//load the event into the old event slot
|
|
if(!LoadDBEvent(event_id, cevent, zone_short_name)) {
|
|
//unable to find the event in the database...
|
|
LogSpawns("Failed to reload event [{}] from the database", event_id);
|
|
return;
|
|
}
|
|
//sync up our nearest event
|
|
FindNearestEvent();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//if we get here, it is a new event...
|
|
SpawnEvent e;
|
|
if(!LoadDBEvent(event_id, e, zone_short_name)) {
|
|
//unable to find the event in the database...
|
|
LogSpawns("Failed to reload event [{}] from the database", event_id);
|
|
return;
|
|
}
|
|
|
|
//we might want to check the next timer like we do on
|
|
//regular load events, but we are assuming this is a new event
|
|
//and anyways, this will get handled (albeit not optimally)
|
|
//naturally by the event handling code.
|
|
|
|
spawn_events.push_back(e);
|
|
|
|
//sync up our nearest event
|
|
FindNearestEvent();
|
|
}
|
|
|
|
void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool strict, bool reset_base) {
|
|
|
|
LogSpawns("Request to [{}] spawn event [{}] [{}]resetting trigger time", enabled?"enable":"disable", event_id, reset_base?"":"without ");
|
|
|
|
//first look for the event in our local event list
|
|
std::vector<SpawnEvent>::iterator cur,end;
|
|
cur = spawn_events.begin();
|
|
end = spawn_events.end();
|
|
for(; cur != end; ++cur) {
|
|
SpawnEvent &cevent = *cur;
|
|
|
|
if(cevent.id == event_id) {
|
|
//make sure were actually changing something
|
|
if(cevent.enabled != enabled || reset_base || cevent.strict != strict) {
|
|
cevent.enabled = enabled;
|
|
cevent.strict = strict;
|
|
if(reset_base) {
|
|
LogSpawns("Spawn event [{}] located in this zone. State set. Trigger time reset (period [{}])", event_id, cevent.period);
|
|
//start with the time now
|
|
zone->zone_time.GetCurrentEQTimeOfDay(&cevent.next);
|
|
//advance the next time by our period
|
|
EQTime::AddMinutes(cevent.period, &cevent.next);
|
|
} else {
|
|
LogSpawns("Spawn event [{}] located in this zone. State changed", event_id);
|
|
}
|
|
|
|
//save the event in the DB
|
|
UpdateDBEvent(cevent);
|
|
|
|
//sync up our nearest event
|
|
FindNearestEvent();
|
|
} else {
|
|
LogSpawns("Spawn event [{}] located in this zone but no change was needed", event_id);
|
|
}
|
|
//even if we dont change anything, we still found it
|
|
return;
|
|
}
|
|
}
|
|
|
|
//if we get here, the condition must be in another zone.
|
|
//first figure out what zone it applies to.
|
|
//update the DB and send and send an update packet to
|
|
//the zone if its up
|
|
|
|
//we need to load the event from the DB and then update
|
|
//the values in the DB, because we do not know if the zone
|
|
//is up or not. The message to the zone will just tell it to
|
|
//update its in-memory event list
|
|
SpawnEvent e;
|
|
std::string zone_short_name;
|
|
if(!LoadDBEvent(event_id, e, zone_short_name)) {
|
|
LogSpawns("Unable to find spawn event [{}] in the database", event_id);
|
|
//unable to find the event in the database...
|
|
return;
|
|
}
|
|
if(e.enabled == enabled && !reset_base) {
|
|
LogSpawns("Spawn event [{}] is not located in this zone but no change was needed", event_id);
|
|
return; //no changes.
|
|
}
|
|
|
|
e.enabled = enabled;
|
|
if(reset_base) {
|
|
LogSpawns("Spawn event [{}] is in zone [{}]. State set. Trigger time reset (period [{}]). Notifying world", event_id, zone_short_name.c_str(), e.period);
|
|
//start with the time now
|
|
zone->zone_time.GetCurrentEQTimeOfDay(&e.next);
|
|
//advance the next time by our period
|
|
EQTime::AddMinutes(e.period, &e.next);
|
|
} else {
|
|
LogSpawns("Spawn event [{}] is in zone [{}]. State changed. Notifying world", event_id, zone_short_name.c_str(), e.period);
|
|
}
|
|
//save the event in the DB
|
|
UpdateDBEvent(e);
|
|
|
|
|
|
//now notify the zone
|
|
auto pack = new ServerPacket(ServerOP_SpawnEvent, sizeof(ServerSpawnEvent_Struct));
|
|
ServerSpawnEvent_Struct* sse = (ServerSpawnEvent_Struct*)pack->pBuffer;
|
|
|
|
sse->zoneID = ZoneID(zone_short_name.c_str());
|
|
sse->event_id = event_id;
|
|
|
|
worldserver.SendPacket(pack);
|
|
safe_delete(pack);
|
|
}
|
|
|
|
int16 SpawnConditionManager::GetCondition(const char *zone_short, uint32 instance_id, uint16 condition_id) {
|
|
if(!strcasecmp(zone_short, zone->GetShortName()) && instance_id == zone->GetInstanceID())
|
|
{
|
|
//this is a local spawn condition
|
|
std::map<uint16, SpawnCondition>::iterator condi;
|
|
condi = spawn_conditions.find(condition_id);
|
|
if(condi == spawn_conditions.end())
|
|
{
|
|
LogSpawns("Unable to find local condition [{}] in Get request", condition_id);
|
|
return(0); //unable to find the spawn condition
|
|
}
|
|
|
|
SpawnCondition &cond = condi->second;
|
|
return cond.value;
|
|
}
|
|
|
|
//this is a remote spawn condition, grab it from the DB
|
|
//load spawn conditions
|
|
std::string query = StringFormat(
|
|
"SELECT value FROM spawn_condition_values "
|
|
"WHERE zone = '%s' AND instance_id = %u AND id = %d",
|
|
zone_short, instance_id, condition_id
|
|
);
|
|
auto results = database.QueryDatabase(query);
|
|
if (!results.Success()) {
|
|
LogSpawns("Unable to query remote condition [{}] from zone [{}] in Get request", condition_id, zone_short);
|
|
return 0; //dunno a better thing to do...
|
|
}
|
|
|
|
if (results.RowCount() == 0) {
|
|
LogSpawns("Unable to load remote condition [{}] from zone [{}] in Get request", condition_id, zone_short);
|
|
return 0; //dunno a better thing to do...
|
|
}
|
|
|
|
auto row = results.begin();
|
|
|
|
return Strings::ToInt(row[0]);
|
|
}
|
|
|
|
bool SpawnConditionManager::Check(uint16 condition, int16 min_value) {
|
|
std::map<uint16, SpawnCondition>::iterator condi;
|
|
condi = spawn_conditions.find(condition);
|
|
if(condi == spawn_conditions.end())
|
|
return(false); //unable to find the spawn condition
|
|
|
|
SpawnCondition &cond = condi->second;
|
|
|
|
return(cond.value >= min_value);
|
|
}
|
|
|