diff --git a/changelog.txt b/changelog.txt index 04221cf97..fe35332b6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,16 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/25/2014 == +cavedude: Corrected a crash in spawn_conditions caused by NPCs on a one way path. +cavedude: Added strict column to spawn_events which will prevent an event from enabling if it's mid-cycle. +cavedude: Prevented disabled or strict spawn_events from enabling when the zone first boots. +cavedude: Fixed the quest function toggle_spawn_event under Perl. + +If you're using the quest function toggle_spawn_event (worked on Lua only) it has changed syntax to: +toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base) + +Required SQL: utils/sql/git/required/2014_04_25_spawn_events.sql + == 04/23/2014 == Kayen: Improved SE_LimitCombatSkills will now more accurately determine if a spell is a combat proc. Kayen: SE_LimitInstant will now also work when set to include instant spells. diff --git a/utils/sql/git/required/2014_04_25_spawn_events.sql b/utils/sql/git/required/2014_04_25_spawn_events.sql new file mode 100644 index 000000000..6a96444e9 --- /dev/null +++ b/utils/sql/git/required/2014_04_25_spawn_events.sql @@ -0,0 +1 @@ +alter table spawn_events add column `strict` tinyint(4) not null default 0; \ No newline at end of file diff --git a/zone/MobAI.cpp b/zone/MobAI.cpp index bcb4b131b..681d6031b 100644 --- a/zone/MobAI.cpp +++ b/zone/MobAI.cpp @@ -1627,11 +1627,32 @@ void NPC::AI_DoMovement() { if (gridno > 0 || cur_wp==-2) { if (movetimercompleted==true) { // time to pause at wp is over + + int32 spawn_id = this->GetSpawnPointID(); + LinkedListIterator iterator(zone->spawn2_list); + iterator.Reset(); + Spawn2 *found_spawn = nullptr; + + while(iterator.MoreElements()) + { + Spawn2* cur = iterator.GetData(); + iterator.Advance(); + if(cur->GetID() == spawn_id) + { + found_spawn = cur; + break; + } + } + if (wandertype == 4 && cur_wp == CastToNPC()->GetMaxWp()) { CastToNPC()->Depop(true); //depop and resart spawn timer + if(found_spawn) + found_spawn->SetNPCPointerNull(); } else if (wandertype == 6 && cur_wp == CastToNPC()->GetMaxWp()) { CastToNPC()->Depop(false);//depop without spawn timer + if(found_spawn) + found_spawn->SetNPCPointerNull(); } else { movetimercompleted=false; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 6a7114786..91b6d1836 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -1678,14 +1678,15 @@ XS(XS__toggle_spawn_event); XS(XS__toggle_spawn_event) { dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: toggle_spawn_event(event_id, enabled?, reset_base)"); + if (items != 4) + Perl_croak(aTHX_ "Usage: toggle_spawn_event(event_id, enabled?, strict, reset_base)"); uint32 event_id = (int)SvIV(ST(0)); bool enabled = ((int)SvIV(ST(1))) == 0?false:true; - bool reset_base = ((int)SvIV(ST(1))) == 0?false:true; + bool strict = ((int)SvIV(ST(2))) == 0?false:true; + bool reset_base = ((int)SvIV(ST(3))) == 0?false:true; - quest_manager.toggle_spawn_event(event_id, enabled, reset_base); + quest_manager.toggle_spawn_event(event_id, enabled, strict, reset_base); XSRETURN_EMPTY; } diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index c6e551d85..bd788695a 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -372,8 +372,8 @@ int lua_get_spawn_condition(const char *zone, uint32 instance_id, int condition_ return quest_manager.get_spawn_condition(zone, instance_id, condition_id); } -void lua_toggle_spawn_event(int event_id, bool enable, bool reset) { - quest_manager.toggle_spawn_event(event_id, enable, reset); +void lua_toggle_spawn_event(int event_id, bool enable, bool strict, bool reset) { + quest_manager.toggle_spawn_event(event_id, enable, strict, reset); } void lua_summon_burried_player_corpse(uint32 char_id, float x, float y, float z, float h) { diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 80fd1ef8c..ab5e79087 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -1733,8 +1733,8 @@ short QuestManager::get_spawn_condition(const char *zone_short, uint32 instance_ } //toggle a spawn event -void QuestManager::toggle_spawn_event(int event_id, bool enable, bool reset_base) { - zone->spawn_conditions.ToggleEvent(event_id, enable, reset_base); +void QuestManager::toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base) { + zone->spawn_conditions.ToggleEvent(event_id, enable, strict, reset_base); } bool QuestManager::has_zone_flag(int zone_id) { diff --git a/zone/questmgr.h b/zone/questmgr.h index 81ccc58c2..89a0eca78 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -150,7 +150,7 @@ public: void showgrid(int gridid); void spawn_condition(const char *zone_short, uint32 instance_id, uint16 condition_id, short new_value); short get_spawn_condition(const char *zone_short, uint32 instance_id, uint16 condition_id); - void toggle_spawn_event(int event_id, bool enable, bool reset_base); + void toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base); bool has_zone_flag(int zone_id); void set_zone_flag(int zone_id); void clear_zone_flag(int zone_id); diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index 9b4963fd4..8d2d47fc0 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -303,11 +303,13 @@ void Spawn2::ForceDespawn() { npcthis->Depop(true); IsDespawned = true; + npcthis = nullptr; return; } else { npcthis->Depop(false); + npcthis = nullptr; } } } @@ -555,6 +557,7 @@ SpawnEvent::SpawnEvent() { action = ActionSet; argument = 0; period = 0xFFFFFFFF; + strict = false; memset(&next, 0, sizeof(next)); } @@ -586,26 +589,28 @@ void SpawnConditionManager::Process() { for(; cur != end; ++cur) { SpawnEvent &cevent = *cur; - if(!cevent.enabled) - continue; + if(cevent.enabled) + { + if(EQTime::IsTimeBefore(&tod, &cevent.next)) { + //this event has been triggered. + //execute the event + if(!cevent.strict || (cevent.strict && cevent.next.hour == tod.hour && cevent.next.day == tod.day && cevent.next.month == tod.month && cevent.next.year == tod.year)) + ExecEvent(cevent, true); - if(EQTime::IsTimeBefore(&tod, &cevent.next)) { - //this event has been triggered. - //execute the event - ExecEvent(cevent, true); - //add the period of the event to the trigger time - EQTime::AddMinutes(cevent.period, &cevent.next); - std::string t; - EQTime::ToString(&cevent.next, t); - _log(SPAWNS__CONDITIONS, "Event %d: Will trigger again in %d EQ minutes at %s.", 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; + //add the period of the event to the trigger time + EQTime::AddMinutes(cevent.period, &cevent.next); + std::string t; + EQTime::ToString(&cevent.next, t); + _log(SPAWNS__CONDITIONS, "Event %d: Will trigger again in %d EQ minutes at %s.", 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; + } } } } @@ -619,6 +624,14 @@ void SpawnConditionManager::ExecEvent(SpawnEvent &event, bool send_update) { return; //unable to find the spawn condition to operate on } + TimeOfDay_Struct tod; + zone->zone_time.getEQTimeOfDay(&tod); + if(event.strict && (event.next.hour != tod.hour || event.next.day != tod.day || event.next.month != tod.month || event.next.year != tod.year)) + { + _log(SPAWNS__CONDITIONS, "Event %d: Unable to execute. Condition is strict, and event time has already passed.", event.id); + return; + } + SpawnCondition &cond = condi->second; int16 new_value = cond.value; @@ -666,10 +679,10 @@ void SpawnConditionManager::UpdateDBEvent(SpawnEvent &event) { len = MakeAnyLenString(&query, "UPDATE spawn_events SET " "next_minute=%d, next_hour=%d, next_day=%d, next_month=%d, " - "next_year=%d, enabled=%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.id + event.next.year, event.enabled?1:0, event.strict?1:0,event.id ); if(!database.RunQuery(query, len, errbuf)) { LogFile->write(EQEMuLog::Error, "Unable to update spawn event '%s': %s\n", query, errbuf); @@ -703,7 +716,7 @@ bool SpawnConditionManager::LoadDBEvent(uint32 event_id, SpawnEvent &event, std: bool ret = false; len = MakeAnyLenString(&query, - "SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,zone " + "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); if (database.RunQuery(query, len, errbuf, &result)) { safe_delete_array(query); @@ -721,12 +734,13 @@ bool SpawnConditionManager::LoadDBEvent(uint32 event_id, SpawnEvent &event, std: event.enabled = atoi(row[8])==0?false:true; event.action = (SpawnEvent::Action) atoi(row[9]); event.argument = atoi(row[10]); - zone_name = row[11]; + event.strict = atoi(row[11])==0?false:true; + zone_name = row[12]; std::string t; EQTime::ToString(&event.next, t); - _log(SPAWNS__CONDITIONS, "Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d. Will trigger at %s", - event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, t.c_str()); + _log(SPAWNS__CONDITIONS, "(LoadDBEvent) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d. Will trigger at %s", + event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict, t.c_str()); ret = true; } @@ -794,7 +808,7 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in //load spawn events SpawnEvent event; len = MakeAnyLenString(&query, - "SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument " + "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); if (database.RunQuery(query, len, errbuf, &result)) { safe_delete_array(query); @@ -816,10 +830,11 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in event.enabled = atoi(row[8])==0?false:true; event.action = (SpawnEvent::Action) atoi(row[9]); event.argument = atoi(row[10]); + event.strict = atoi(row[11])==0?false:true; spawn_events.push_back(event); - _log(SPAWNS__CONDITIONS, "Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d", - event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument); + _log(SPAWNS__CONDITIONS, "(LoadSpawnConditions) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d", + event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict); } mysql_free_result(result); } else { @@ -847,34 +862,48 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in for(; cur != end; ++cur) { SpawnEvent &cevent = *cur; - if(!cevent.enabled) - continue; + 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; - //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) { - _log(SPAWNS__CONDITIONS, "Initial next trigger time set for spawn event %d", 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. - } + //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); - ran = false; - while(EQTime::IsTimeBefore(&tod, &cevent.next)) { - _log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", cevent.id); - //this event has been triggered. - //execute the event - 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) { - //save the event in the DB - UpdateDBEvent(cevent); + if(cevent.enabled) + { + //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) { + _log(SPAWNS__CONDITIONS, "Initial next trigger time set for spawn event %d", 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. + } + + ran = false; + while(EQTime::IsTimeBefore(&tod, &cevent.next)) { + _log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", 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) { + //save the event in the DB + UpdateDBEvent(cevent); + } } } @@ -894,14 +923,14 @@ void SpawnConditionManager::FindNearestEvent() { int next_id = -1; for(; cur != end; ++cur) { SpawnEvent &cevent = *cur; - - if(!cevent.enabled) - continue; - - //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(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) @@ -1035,7 +1064,7 @@ void SpawnConditionManager::ReloadEvent(uint32 event_id) { FindNearestEvent(); } -void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool reset_base) { +void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool strict, bool reset_base) { _log(SPAWNS__CONDITIONS, "Request to %s spawn event %d %sresetting trigger time", enabled?"enable":"disable", event_id, reset_base?"":"without "); @@ -1048,8 +1077,9 @@ void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool rese if(cevent.id == event_id) { //make sure were actually changing something - if(cevent.enabled != enabled || reset_base) { + if(cevent.enabled != enabled || reset_base || cevent.strict != strict) { cevent.enabled = enabled; + cevent.strict = strict; if(reset_base) { _log(SPAWNS__CONDITIONS, "Spawn event %d located in this zone. State set. Trigger time reset (period %d).", event_id, cevent.period); //start with the time now diff --git a/zone/spawn2.h b/zone/spawn2.h index 42c2153f2..e529bb65e 100644 --- a/zone/spawn2.h +++ b/zone/spawn2.h @@ -67,6 +67,7 @@ public: bool NPCPointerValid() { return (npcthis!=nullptr); } void SetNPCPointer(NPC* n) { npcthis = n; } + void SetNPCPointerNull() { npcthis = nullptr; } void SetTimer(uint32 duration) { timer.Start(duration); } uint32 GetKillCount() { return killcount; } protected: @@ -134,6 +135,7 @@ public: bool enabled; Action action; int16 argument; + bool strict; uint32 period; //eq minutes (3 seconds) between events TimeOfDay_Struct next; //next time this event triggers @@ -148,7 +150,7 @@ public: int16 GetCondition(const char *zone_short, uint32 instance_id, uint16 condition_id); void SetCondition(const char *zone_short, uint32 instance_id, uint16 condition_id, int16 new_value, bool world_update = false); - void ToggleEvent(uint32 event_id, bool enabled, bool reset_base); + void ToggleEvent(uint32 event_id, bool enabled, bool strict, bool reset_base); bool Check(uint16 condition, int16 min_value); void ReloadEvent(uint32 event_id);