From 54c53ce3899142f7e314019a3b0f55b918c2034a Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Thu, 22 May 2025 23:41:47 -0400 Subject: [PATCH] [Feature] Add Zone Scripting Capabilities --- zone/embparser.cpp | 380 ++++++++++++++++--------------- zone/embparser.h | 88 +++---- zone/perl_zone.cpp | 54 +++++ zone/quest_interface.h | 45 ++++ zone/quest_parser_collection.cpp | 219 ++++++++++++++++++ zone/quest_parser_collection.h | 39 ++++ zone/zone.cpp | 197 ++++++++++++++++ zone/zone.h | 26 +++ 8 files changed, 823 insertions(+), 225 deletions(-) diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 16a662482..5a7f58136 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -278,6 +278,7 @@ int PerlembParser::EventCommon( EQ::ItemInstance* inst, const SPDat_Spell_Struct* spell, Mob* mob, + Zone* zone, uint32 extra_data, bool is_global, std::vector* extra_pointers @@ -287,52 +288,22 @@ int PerlembParser::EventCommon( return 0; } - bool is_player_quest = false; - bool is_global_player_quest = false; - bool is_global_npc_quest = false; - bool is_bot_quest = false; - bool is_global_bot_quest = false; - bool is_merc_quest = false; - bool is_global_merc_quest = false; - bool is_item_quest = false; - bool is_spell_quest = false; - - std::string package_name; - - GetQuestTypes( - is_player_quest, - is_global_player_quest, - is_bot_quest, - is_global_bot_quest, - is_merc_quest, - is_global_merc_quest, - is_global_npc_quest, - is_item_quest, - is_spell_quest, + QuestType quest_type = GetQuestTypes( event_id, npc_mob, inst, mob, + zone, is_global ); - GetQuestPackageName( - is_player_quest, - is_global_player_quest, - is_bot_quest, - is_global_bot_quest, - is_merc_quest, - is_global_merc_quest, - is_global_npc_quest, - is_item_quest, - is_spell_quest, - package_name, + std::string package_name = GetQuestPackageName( + quest_type, event_id, object_id, data, npc_mob, - inst, - is_global + inst ); const std::string& sub_name = QuestEventSubroutines[event_id]; @@ -348,15 +319,7 @@ int PerlembParser::EventCommon( /* Check for QGlobal export event enable */ if (parse->perl_event_export_settings[event_id].qglobals) { ExportQGlobals( - is_player_quest, - is_global_player_quest, - is_bot_quest, - is_global_bot_quest, - is_merc_quest, - is_global_merc_quest, - is_global_npc_quest, - is_item_quest, - is_spell_quest, + quest_type, package_name, npc_mob, mob, @@ -367,15 +330,7 @@ int PerlembParser::EventCommon( /* Check for Mob export event enable */ if (parse->perl_event_export_settings[event_id].mob) { ExportMobVariables( - is_player_quest, - is_global_player_quest, - is_bot_quest, - is_global_bot_quest, - is_merc_quest, - is_global_merc_quest, - is_global_npc_quest, - is_item_quest, - is_spell_quest, + quest_type, package_name, mob, npc_mob @@ -397,19 +352,24 @@ int PerlembParser::EventCommon( ExportEventVariables(package_name, event_id, object_id, data, npc_mob, inst, mob, extra_data, extra_pointers); } - if (is_player_quest || is_global_player_quest) { + if (quest_type == QuestType::Player || quest_type == QuestType::PlayerGlobal) { return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, mob, mob, nullptr, nullptr); - } else if (is_bot_quest || is_global_bot_quest || is_merc_quest || is_global_merc_quest) { + } else if ( + quest_type == QuestType::Bot || + quest_type == QuestType::BotGlobal || + quest_type == QuestType::Merc || + quest_type == QuestType::MercGlobal + ) { return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, npc_mob, mob, nullptr, nullptr); - } else if (is_item_quest) { + } else if (quest_type == QuestType::Item || quest_type == QuestType::ItemGlobal) { return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, mob, mob, inst, nullptr); - } else if (is_spell_quest) { + } else if (quest_type == QuestType::Spell || quest_type == QuestType::SpellGlobal) { if (mob) { return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, mob, mob, nullptr, spell); } else { return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, npc_mob, mob, nullptr, spell); } - } else { + } else if (quest_type == QuestType::NPC || quest_type == QuestType::NPCGlobal) { return SendCommands( package_name.c_str(), QuestEventSubroutines[event_id], @@ -439,6 +399,7 @@ int PerlembParser::EventNPC( nullptr, nullptr, mob, + nullptr, extra_data, false, extra_pointers @@ -462,6 +423,7 @@ int PerlembParser::EventGlobalNPC( nullptr, nullptr, mob, + nullptr, extra_data, true, extra_pointers @@ -484,6 +446,7 @@ int PerlembParser::EventPlayer( nullptr, nullptr, client, + nullptr, extra_data, false, extra_pointers @@ -506,6 +469,7 @@ int PerlembParser::EventGlobalPlayer( nullptr, nullptr, client, + nullptr, extra_data, true, extra_pointers @@ -534,6 +498,7 @@ int PerlembParser::EventItem( inst, nullptr, client, + nullptr, extra_data, false, extra_pointers @@ -558,6 +523,7 @@ int PerlembParser::EventSpell( nullptr, &spells[spell_id], client, + nullptr, extra_data, false, extra_pointers @@ -1192,20 +1158,12 @@ void PerlembParser::MapFunctions() #endif // EMBPERL_XS_CLASSES } -void PerlembParser::GetQuestTypes( - bool& is_player_quest, - bool& is_global_player_quest, - bool& is_bot_quest, - bool& is_global_bot_quest, - bool& is_merc_quest, - bool& is_global_merc_quest, - bool& is_global_npc_quest, - bool& is_item_quest, - bool& is_spell_quest, +QuestType PerlembParser::GetQuestTypes( QuestEventID event_id, Mob* npc_mob, EQ::ItemInstance* inst, Mob* mob, + Zone* zone, bool is_global ) { @@ -1219,100 +1177,72 @@ void PerlembParser::GetQuestTypes( event_id == EVENT_SPELL_FADE || event_id == EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE ) { - is_spell_quest = true; + return is_global ? QuestType::SpellGlobal : QuestType::Spell; } else { if (npc_mob) { if (!inst) { - if (is_global) { - if (npc_mob->IsBot()) { - is_global_bot_quest = true; - } else if (npc_mob->IsMerc()) { - is_global_merc_quest = true; - } - } else { - if (npc_mob->IsBot()) { - is_bot_quest = true; - } else if (npc_mob->IsMerc()) { - is_merc_quest = true; - } + if (npc_mob->IsBot()) { + return is_global ? QuestType::BotGlobal : QuestType::Bot; + } else if (npc_mob->IsMerc()) { + return is_global ? QuestType::MercGlobal : QuestType::Merc; } } else { - is_item_quest = true; + return is_global ? QuestType::ItemGlobal : QuestType::Item; } } else if (!npc_mob && mob) { if (!inst) { - if (is_global) { - if (mob->IsClient()) { - is_global_player_quest = true; - } - } else { - if (mob->IsClient()) { - is_player_quest = true; - } + if (mob->IsClient()) { + return is_global ? QuestType::PlayerGlobal : QuestType::Player; } } else { - is_item_quest = true; + return is_global ? QuestType::ItemGlobal : QuestType::Item; } + } else if (zone) { + return is_global ? QuestType::ZoneGlobal : QuestType::Zone; } } } -void PerlembParser::GetQuestPackageName( - bool& is_player_quest, - bool& is_global_player_quest, - bool& is_bot_quest, - bool& is_global_bot_quest, - bool& is_merc_quest, - bool& is_global_merc_quest, - bool& is_global_npc_quest, - bool& is_item_quest, - bool& is_spell_quest, - std::string& package_name, +std::string PerlembParser::GetQuestPackageName( + QuestType quest_type, QuestEventID event_id, uint32 object_id, const char* data, Mob* npc_mob, - EQ::ItemInstance* inst, - bool is_global + EQ::ItemInstance* inst ) { - if ( - !is_player_quest && - !is_global_player_quest && - !is_bot_quest && - !is_global_bot_quest && - !is_merc_quest && - !is_global_merc_quest && - !is_item_quest && - !is_spell_quest - ) { - if (is_global) { - is_global_npc_quest = true; - package_name = "qst_global_npc"; - } else { - package_name = fmt::format("qst_npc_{}", npc_mob->GetNPCTypeID()); - } - } else if (is_item_quest) { + if (quest_type == QuestType::NPC) { + return fmt::format("qst_npc_{}", npc_mob->GetNPCTypeID()); + } else if (quest_type == QuestType::NPCGlobal) { + return "qst_global_npc"; + } else if (quest_type == QuestType::Item || quest_type == QuestType::ItemGlobal) { if (!inst) { - return; + return ""; } - package_name = fmt::format("qst_item_{}", inst->GetID()); - } else if (is_player_quest) { - package_name = "qst_player"; - } else if (is_global_player_quest) { - package_name = "qst_global_player"; - } else if (is_bot_quest) { - package_name = "qst_bot"; - } else if (is_global_bot_quest) { - package_name = "qst_global_bot"; - } else if (is_merc_quest) { - package_name = "qst_merc"; - } else if (is_global_merc_quest) { - package_name = "qst_global_merc"; - } else { - package_name = fmt::format("qst_spell_{}", object_id); + return fmt::format("qst_item_{}", inst->GetID()); + } else if (quest_type == QuestType::Player) { + return "qst_player"; + } else if (quest_type == QuestType::PlayerGlobal) { + return "qst_global_player"; + } else if (quest_type == QuestType::Bot) { + return "qst_bot"; + } else if (quest_type == QuestType::BotGlobal) { + return "qst_global_bot"; + } else if (quest_type == QuestType::Merc) { + return "qst_merc"; + } else if (quest_type == QuestType::MercGlobal) { + return "qst_global_merc"; + } else if (quest_type == QuestType::Spell || quest_type == QuestType::SpellGlobal) { + return fmt::format("qst_spell_{}", object_id); + } else if (quest_type == QuestType::Zone) { + return "qst_zone"; + } else if (quest_type == QuestType::ZoneGlobal) { + return "qst_global_zone"; } + + return ""; } void PerlembParser::ExportCharID(const std::string& package_name, int& char_id, Mob* npc_mob, Mob* mob) @@ -1331,15 +1261,7 @@ void PerlembParser::ExportCharID(const std::string& package_name, int& char_id, } void PerlembParser::ExportQGlobals( - bool is_player_quest, - bool is_global_player_quest, - bool is_bot_quest, - bool is_global_bot_quest, - bool is_merc_quest, - bool is_global_merc_quest, - bool is_global_npc_quest, - bool is_item_quest, - bool is_spell_quest, + QuestType quest_type, std::string& package_name, Mob* npc_mob, Mob* mob, @@ -1347,16 +1269,7 @@ void PerlembParser::ExportQGlobals( ) { //NPC quest - if ( - !is_player_quest && - !is_global_player_quest && - !is_bot_quest && - !is_global_bot_quest && - !is_merc_quest && - !is_global_merc_quest && - !is_item_quest && - !is_spell_quest - ) { + if (quest_type == QuestType::NPC || quest_type == QuestType::NPCGlobal) { //only export for npcs that are global enabled. if (npc_mob && npc_mob->GetQglobal()) { std::map globhash; @@ -1485,15 +1398,7 @@ void PerlembParser::ExportQGlobals( } void PerlembParser::ExportMobVariables( - bool is_player_quest, - bool is_global_player_quest, - bool is_bot_quest, - bool is_global_bot_quest, - bool is_merc_quest, - bool is_global_merc_quest, - bool is_global_npc_quest, - bool is_item_quest, - bool is_spell_quest, + QuestType quest_type, std::string& package_name, Mob* mob, Mob* npc_mob @@ -1511,15 +1416,7 @@ void PerlembParser::ExportMobVariables( ExportVar(package_name.c_str(), "bot_owner_char_id", mob->CastToBot()->GetBotOwnerCharacterID()); } - if ( - !is_player_quest && - !is_global_player_quest && - !is_bot_quest && - !is_global_bot_quest && - !is_merc_quest && - !is_global_merc_quest && - !is_item_quest - ) { + if (quest_type == QuestType::NPC || quest_type == QuestType::NPCGlobal) { if (mob && mob->IsClient() && npc_mob && npc_mob->IsNPC()) { Client* c = mob->CastToClient(); @@ -1543,16 +1440,7 @@ void PerlembParser::ExportMobVariables( ExportVar(package_name.c_str(), "userid", mob->GetID()); } - if ( - !is_player_quest && - !is_global_player_quest && - !is_bot_quest && - !is_global_bot_quest && - !is_merc_quest && - !is_global_merc_quest && - !is_item_quest && - !is_spell_quest - ) { + if (quest_type == QuestType::NPC || quest_type == QuestType::NPCGlobal) { if (npc_mob->IsNPC()) { ExportVar(package_name.c_str(), "mname", npc_mob->GetName()); ExportVar(package_name.c_str(), "mobid", npc_mob->GetID()); @@ -2648,6 +2536,7 @@ int PerlembParser::EventBot( nullptr, nullptr, mob, + nullptr, extra_data, false, extra_pointers @@ -2671,6 +2560,7 @@ int PerlembParser::EventGlobalBot( nullptr, nullptr, mob, + nullptr, extra_data, true, extra_pointers @@ -2768,6 +2658,7 @@ int PerlembParser::EventMerc( nullptr, nullptr, mob, + nullptr, extra_data, false, extra_pointers @@ -2791,6 +2682,127 @@ int PerlembParser::EventGlobalMerc( nullptr, nullptr, mob, + nullptr, + extra_data, + true, + extra_pointers + ); +} + +void PerlembParser::LoadZoneScript(std::string filename) +{ + if (!perl || zone_quest_status_ != questUnloaded) { + return; + } + + try { + perl->eval_file("qst_zone", filename.c_str()); + } catch (std::string e) { + AddError( + fmt::format( + "Error Compiling Zone Quest File [{}] Error [{}]", + filename, + e + ) + ); + + zone_quest_status_ = questFailedToLoad; + return; + } + + zone_quest_status_ = questLoaded; +} + +void PerlembParser::LoadGlobalZoneScript(std::string filename) +{ + if (!perl || global_zone_quest_status_ != questUnloaded) { + return; + } + + try { + perl->eval_file("qst_global_zone", filename.c_str()); + } catch (std::string e) { + AddError( + fmt::format( + "Error Compiling Global Zone uest File [{}] Error [{}]", + filename, + e + ) + ); + + global_zone_quest_status_ = questFailedToLoad; + return; + } + + global_zone_quest_status_ = questLoaded; +} + +bool PerlembParser::ZoneHasQuestSub(QuestEventID event_id) +{ + if ( + !perl || + zone_quest_status_ != questLoaded || + event_id >= _LargestEventID + ) { + return false; + } + + return perl->SubExists("qst_zone", QuestEventSubroutines[event_id]); +} + +bool PerlembParser::GlobalZoneHasQuestSub(QuestEventID event_id) +{ + if ( + !perl || + global_zone_quest_status_ != questLoaded || + event_id >= _LargestEventID + ) { + return false; + } + + return perl->SubExists("qst_global_zone", QuestEventSubroutines[event_id]); +} + +int PerlembParser::EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + return EventCommon( + event_id, + 0, + data.c_str(), + nullptr, + nullptr, + nullptr, + nullptr, + zone, + extra_data, + false, + extra_pointers + ); +} + +int PerlembParser::EventGlobalZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + return EventCommon( + event_id, + 0, + data.c_str(), + nullptr, + nullptr, + nullptr, + nullptr, + zone, extra_data, true, extra_pointers diff --git a/zone/embparser.h b/zone/embparser.h index dd4ffe8d8..769828d58 100644 --- a/zone/embparser.h +++ b/zone/embparser.h @@ -41,6 +41,23 @@ typedef enum { questFailedToLoad } PerlQuestStatus; +enum class QuestType { + Bot, + BotGlobal, + Item, + ItemGlobal, + Merc, + MercGlobal, + NPC, + NPCGlobal, + Player, + PlayerGlobal, + Spell, + SpellGlobal, + Zone, + ZoneGlobal +}; + class PerlembParser : public QuestInterface { public: PerlembParser(); @@ -136,6 +153,22 @@ public: std::vector* extra_pointers ); + virtual int EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + + virtual int EventGlobalZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + virtual bool HasQuestSub(uint32 npc_id, QuestEventID event_id); virtual bool HasGlobalQuestSub(QuestEventID event_id); virtual bool PlayerHasQuestSub(QuestEventID event_id); @@ -146,6 +179,8 @@ public: virtual bool GlobalBotHasQuestSub(QuestEventID event_id); virtual bool MercHasQuestSub(QuestEventID event_id); virtual bool GlobalMercHasQuestSub(QuestEventID event_id); + virtual bool ZoneHasQuestSub(QuestEventID event_id); + virtual bool GlobalZoneHasQuestSub(QuestEventID event_id); virtual void LoadNPCScript(std::string filename, int npc_id); virtual void LoadGlobalNPCScript(std::string filename); @@ -157,6 +192,8 @@ public: virtual void LoadGlobalBotScript(std::string filename); virtual void LoadMercScript(std::string filename); virtual void LoadGlobalMercScript(std::string filename); + virtual void LoadZoneScript(std::string filename); + virtual void LoadGlobalZoneScript(std::string filename); virtual void AddVar(std::string name, std::string val); virtual std::string GetVar(std::string name); @@ -182,6 +219,7 @@ private: EQ::ItemInstance* inst, const SPDat_Spell_Struct* spell, Mob* mob, + Zone* zone, uint32 extra_data, bool is_global, std::vector* extra_pointers @@ -199,54 +237,28 @@ private: void MapFunctions(); - void GetQuestTypes( - bool& is_player_quest, - bool& is_global_player_quest, - bool& is_bot_quest, - bool& is_global_bot_quest, - bool& is_merc_quest, - bool& is_global_merc_quest, - bool& is_global_npc_quest, - bool& is_item_quest, - bool& is_spell_quest, + QuestType GetQuestTypes( QuestEventID event, Mob* npc_mob, EQ::ItemInstance* inst, Mob* mob, + Zone* zone, bool is_global ); - void GetQuestPackageName( - bool& is_player_quest, - bool& is_global_player_quest, - bool& is_bot_quest, - bool& is_global_bot_quest, - bool& is_merc_quest, - bool& is_global_merc_quest, - bool& is_global_npc_quest, - bool& is_item_quest, - bool& is_spell_quest, - std::string& package_name, + std::string GetQuestPackageName( + QuestType quest_type, QuestEventID event, uint32 object_id, const char* data, Mob* npc_mob, - EQ::ItemInstance* inst, - bool is_global + EQ::ItemInstance* inst ); void ExportCharID(const std::string& package_name, int& char_id, Mob* npc_mob, Mob* mob); void ExportQGlobals( - bool is_player_quest, - bool is_global_player_quest, - bool is_bot_quest, - bool is_global_bot_quest, - bool is_merc_quest, - bool is_global_merc_quest, - bool is_global_npc_quest, - bool is_item_quest, - bool is_spell_quest, + QuestType quest_type, std::string& package_name, Mob* npc_mob, Mob* mob, @@ -254,15 +266,7 @@ private: ); void ExportMobVariables( - bool is_player_quest, - bool is_global_player_quest, - bool is_bot_quest, - bool is_global_bot_quest, - bool is_merc_quest, - bool is_global_merc_quest, - bool is_global_npc_quest, - bool is_item_quest, - bool is_spell_quest, + QuestType quest_type, std::string& package_name, Mob* mob, Mob* npc_mob @@ -295,6 +299,8 @@ private: PerlQuestStatus global_bot_quest_status_; PerlQuestStatus merc_quest_status_; PerlQuestStatus global_merc_quest_status_; + PerlQuestStatus zone_quest_status_; + PerlQuestStatus global_zone_quest_status_; SV* _empty_sv; diff --git a/zone/perl_zone.cpp b/zone/perl_zone.cpp index 6cb49f557..26696a9a5 100644 --- a/zone/perl_zone.cpp +++ b/zone/perl_zone.cpp @@ -598,6 +598,51 @@ bool Perl_Zone_VariableExists(Zone* self, const std::string variable_name) return self->VariableExists(variable_name); } +uint32 Perl_Zone_GetTimerDuration(Zone* self, std::string name) +{ + return self->GetTimerDuration(name); +} + +uint32 Perl_Zone_GetTimerRemainingTime(Zone* self, std::string name) +{ + return self->GetTimerRemainingTime(name); +} + +bool Perl_Zone_HasTimer(Zone* self, std::string name) +{ + return self->HasTimer(name); +} + +bool Perl_Zone_IsPausedTimer(Zone* self, std::string name) +{ + return self->IsPausedTimer(name); +} + +void Perl_Zone_PauseTimer(Zone* self, std::string name) +{ + self->PauseTimer(name); +} + +void Perl_Zone_ResumeTimer(Zone* self, std::string name) +{ + self->ResumeTimer(name); +} + +void Perl_Zone_SetTimer(Zone* self, std::string name, uint32 duration) +{ + self->SetTimer(name, duration); +} + +void Perl_Zone_StopTimer(Zone* self, std::string name) +{ + self->StopTimer(name); +} + +void Perl_Zone_StopAllTimers(Zone* self) +{ + self->StopAllTimers(); +} + void perl_register_zone() { perl::interpreter perl(PERL_GET_THX); @@ -689,6 +734,8 @@ void perl_register_zone() package.add("GetSnowDuration", (int(*)(Zone*, uint8))&Perl_Zone_GetSnowDuration); package.add("GetTimeType", &Perl_Zone_GetTimeType); package.add("GetTimeZone", &Perl_Zone_GetTimeZone); + package.add("GetTimerDuration", &Perl_Zone_GetTimerDuration); + package.add("GetTimerRemainingTime", &Perl_Zone_GetTimerRemainingTime); package.add("GetZoneDescription", &Perl_Zone_GetZoneDescription); package.add("GetZoneID", &Perl_Zone_GetZoneID); package.add("GetZoneType", &Perl_Zone_GetZoneType); @@ -701,12 +748,14 @@ void perl_register_zone() package.add("GetZoneTotalBlockedSpells", &Perl_Zone_GetZoneTotalBlockedSpells); package.add("HasGraveyard", &Perl_Zone_HasGraveyard); package.add("HasMap", &Perl_Zone_HasMap); + package.add("HasTimer", &Perl_Zone_HasTimer); package.add("HasWaterMap", &Perl_Zone_HasWaterMap); package.add("HasWeather", &Perl_Zone_HasWeather); package.add("IsCity", &Perl_Zone_IsCity); package.add("IsHotzone", &Perl_Zone_IsHotzone); package.add("IsInstancePersistent", &Perl_Zone_IsInstancePersistent); package.add("IsIdleWhenEmpty", &Perl_Zone_IsIdleWhenEmpty); + package.add("IsPausedTimer", &Perl_Zone_IsPausedTimer); package.add("IsPVPZone", &Perl_Zone_IsPVPZone); package.add("IsRaining", &Perl_Zone_IsRaining); package.add("IsSnowing", &Perl_Zone_IsSnowing); @@ -715,8 +764,10 @@ void perl_register_zone() package.add("IsStaticZone", &Perl_Zone_IsStaticZone); package.add("IsUCSServerAvailable", &Perl_Zone_IsUCSServerAvailable); package.add("IsWaterZone", &Perl_Zone_IsWaterZone); + package.add("PauseTimer", &Perl_Zone_PauseTimer); package.add("Repop", (void(*)(Zone*))&Perl_Zone_Repop); package.add("Repop", (void(*)(Zone*, bool))&Perl_Zone_Repop); + package.add("ResumeTimer", &Perl_Zone_ResumeTimer); package.add("SetAAEXPModifier", &Perl_Zone_SetAAEXPModifier); package.add("SetAAEXPModifierByCharacterID", &Perl_Zone_SetAAEXPModifierByCharacterID); package.add("SetBucket", (void(*)(Zone*, const std::string, const std::string))&Perl_Zone_SetBucket); @@ -726,7 +777,10 @@ void perl_register_zone() package.add("SetInstanceTimer", &Perl_Zone_SetInstanceTimer); package.add("SetInstanceTimeRemaining", &Perl_Zone_SetInstanceTimeRemaining); package.add("SetIsHotzone", &Perl_Zone_SetIsHotzone); + package.add("SetTimer", &Perl_Zone_SetTimer); package.add("SetVariable", &Perl_Zone_SetVariable); + package.add("StopTimer", &Perl_Zone_StopTimer); + package.add("StopAllTimers", &Perl_Zone_StopAllTimers); package.add("ShowZoneGlobalLoot", &Perl_Zone_ShowZoneGlobalLoot); package.add("VariableExists", &Perl_Zone_VariableExists); } diff --git a/zone/quest_interface.h b/zone/quest_interface.h index 687a90f58..f18e1dbec 100644 --- a/zone/quest_interface.h +++ b/zone/quest_interface.h @@ -163,6 +163,28 @@ public: return 0; } + virtual int EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ) + { + return 0; + } + + virtual int EventGlobalZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ) + { + return 0; + } + virtual bool HasQuestSub(uint32 npc_id, QuestEventID event_id) { return false; @@ -223,6 +245,16 @@ public: return false; } + virtual bool ZoneHasQuestSub(QuestEventID event_id) + { + return false; + } + + virtual bool GlobalZoneHasQuestSub(QuestEventID event_id) + { + return false; + } + virtual void LoadNPCScript(std::string filename, int npc_id) { } virtual void LoadGlobalNPCScript(std::string filename) { } virtual void LoadPlayerScript(std::string filename) { } @@ -234,6 +266,8 @@ public: virtual void LoadGlobalBotScript(std::string filename) { } virtual void LoadMercScript(std::string filename) { } virtual void LoadGlobalMercScript(std::string filename) { } + virtual void LoadZoneScript(std::string filename) { } + virtual void LoadGlobalZoneScript(std::string filename) { } virtual int DispatchEventNPC( QuestEventID event_id, @@ -308,6 +342,17 @@ public: return 0; } + virtual int DispatchEventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ) + { + return 0; + } + virtual void AddVar(std::string name, std::string val) { } virtual std::string GetVar(std::string name) { diff --git a/zone/quest_parser_collection.cpp b/zone/quest_parser_collection.cpp index 2ab537c64..540f5f54e 100644 --- a/zone/quest_parser_collection.cpp +++ b/zone/quest_parser_collection.cpp @@ -49,6 +49,8 @@ QuestParserCollection::QuestParserCollection() _global_bot_quest_status = QuestUnloaded; _merc_quest_status = QuestUnloaded; _global_merc_quest_status = QuestUnloaded; + _zone_quest_status = QuestUnloaded; + _global_zone_quest_status = QuestUnloaded; } QuestParserCollection::~QuestParserCollection() { } @@ -98,6 +100,8 @@ void QuestParserCollection::ReloadQuests(bool reset_timers) _global_bot_quest_status = QuestUnloaded; _merc_quest_status = QuestUnloaded; _global_merc_quest_status = QuestUnloaded; + _zone_quest_status = QuestUnloaded; + _global_zone_quest_status = QuestUnloaded; _spell_quest_status.clear(); _item_quest_status.clear(); @@ -426,6 +430,49 @@ bool QuestParserCollection::MercHasQuestSub(QuestEventID event_id) return MercHasQuestSubLocal(event_id) || MercHasQuestSubGlobal(event_id); } +bool QuestParserCollection::ZoneHasQuestSubLocal(QuestEventID event_id) +{ + if (_zone_quest_status == QuestUnloaded) { + std::string filename; + auto qi = GetQIByZoneQuest(filename); + + if (qi) { + _zone_quest_status = qi->GetIdentifier(); + qi->LoadZoneScript(filename); + return qi->ZoneHasQuestSub(event_id); + } + } else if (_zone_quest_status != QuestFailedToLoad) { + auto iter = _interfaces.find(_zone_quest_status); + return iter->second->ZoneHasQuestSub(event_id); + } + + return false; +} + +bool QuestParserCollection::ZoneHasQuestSubGlobal(QuestEventID event_id) +{ + if (_global_zone_quest_status == QuestUnloaded) { + std::string filename; + auto qi = GetQIByGlobalZoneQuest(filename); + + if (qi) { + _global_zone_quest_status = qi->GetIdentifier(); + qi->LoadGlobalZoneScript(filename); + return qi->GlobalZoneHasQuestSub(event_id); + } + } else if (_global_zone_quest_status != QuestFailedToLoad) { + auto iter = _interfaces.find(_global_zone_quest_status); + return iter->second->GlobalZoneHasQuestSub(event_id); + } + + return false; +} + +bool QuestParserCollection::ZoneHasQuestSub(QuestEventID event_id) +{ + return ZoneHasQuestSubLocal(event_id) || ZoneHasQuestSubGlobal(event_id); +} + int QuestParserCollection::EventNPC( QuestEventID event_id, NPC* npc, @@ -924,6 +971,83 @@ int QuestParserCollection::EventMercGlobal( return 0; } +int QuestParserCollection::EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + const int local_return = EventZoneLocal(event_id, zone, data, extra_data, extra_pointers); + const int global_return = EventZoneGlobal(event_id, zone, data, extra_data, extra_pointers); + const int default_return = DispatchEventZone(event_id, zone, data, extra_data, extra_pointers); + + if (local_return != 0) { + return local_return; + } else if (global_return != 0) { + return global_return; + } else if (default_return != 0) { + return default_return; + } + + return 0; +} + +int QuestParserCollection::EventZoneLocal( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + if (_zone_quest_status == QuestUnloaded) { + std::string filename; + auto qi = GetQIByZoneQuest(filename); + + if (qi) { + _zone_quest_status = qi->GetIdentifier(); + qi->LoadZoneScript(filename); + return qi->EventZone(event_id, zone, data, extra_data, extra_pointers); + } + } else { + if (_zone_quest_status != QuestFailedToLoad) { + auto iter = _interfaces.find(_zone_quest_status); + return iter->second->EventZone(event_id, zone, data, extra_data, extra_pointers); + } + } + + return 0; +} + +int QuestParserCollection::EventZoneGlobal( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + if (_global_zone_quest_status == QuestUnloaded) { + std::string filename; + auto qi = GetQIByGlobalZoneQuest(filename); + + if (qi) { + _global_zone_quest_status = qi->GetIdentifier(); + qi->LoadGlobalZoneScript(filename); + return qi->EventGlobalZone(event_id, zone, data, extra_data, extra_pointers); + } + } else { + if (_global_zone_quest_status != QuestFailedToLoad) { + auto iter = _interfaces.find(_global_zone_quest_status); + return iter->second->EventGlobalZone(event_id, zone, data, extra_data, extra_pointers); + } + } + + return 0; +} + QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::string& filename) { if (!zone) { @@ -1425,6 +1549,81 @@ QuestInterface* QuestParserCollection::GetQIByGlobalMercQuest(std::string& filen return nullptr; } +QuestInterface* QuestParserCollection::GetQIByZoneQuest(std::string& filename) +{ + if (!zone || !zone->IsLoaded()) { + return nullptr; + } + + const std::string& global_path = fmt::format( + "{}/{}", + path.GetQuestsPath(), + QUEST_GLOBAL_DIRECTORY + ); + + const std::string& zone_path = fmt::format( + "{}/{}", + path.GetQuestsPath(), + zone->GetShortName() + ); + + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}", + path.GetQuestsPath(), + zone->GetShortName(), + zone->GetInstanceVersion() + ); + + std::vector file_names = { + fmt::format("{}/zone", zone_versioned_path), // Local versioned by Instance Version ./quests/zone/v0/zone.ext + fmt::format("{}/zone_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version + fmt::format("{}/zone", zone_path), // Local + fmt::format("{}/zone", global_path) // Global + }; + + std::string file_name; + for (auto & file : file_names) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}.{}", + file, + _extensions.find(e->GetIdentifier())->second + ); + + if (File::Exists(file_name)) { + filename = file_name; + return e; + } + } + } + + return nullptr; +} + +QuestInterface* QuestParserCollection::GetQIByGlobalZoneQuest(std::string& filename) +{ + if (!zone) { + return nullptr; + } + + std::string file_name; + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}/{}/global_zone.{}", + path.GetQuestsPath(), + QUEST_GLOBAL_DIRECTORY, + _extensions.find(e->GetIdentifier())->second + ); + + if (File::Exists(file_name)) { + filename = file_name; + return e; + } + } + + return nullptr; +} + void QuestParserCollection::GetErrors(std::list& quest_errors) { quest_errors.clear(); @@ -1561,6 +1760,26 @@ int QuestParserCollection::DispatchEventMerc( return ret; } +int QuestParserCollection::DispatchEventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + int ret = 0; + + for (const auto& e: _load_precedence) { + int i = e->DispatchEventZone(event_id, zone, data, extra_data, extra_pointers); + if (i != 0) { + ret = i; + } + } + + return ret; +} + void QuestParserCollection::LoadPerlEventExportSettings(PerlEventExportSettings* s) { for (int i = 0; i < _LargestEventID; i++) { diff --git a/zone/quest_parser_collection.h b/zone/quest_parser_collection.h index cb0a5e8d1..5d7d1d3c0 100644 --- a/zone/quest_parser_collection.h +++ b/zone/quest_parser_collection.h @@ -72,6 +72,7 @@ public: bool ItemHasQuestSub(EQ::ItemInstance* inst, QuestEventID event_id); bool BotHasQuestSub(QuestEventID event_id); bool MercHasQuestSub(QuestEventID event_id); + bool ZoneHasQuestSub(QuestEventID event_id); int EventNPC( QuestEventID event_id, @@ -172,6 +173,14 @@ public: std::vector* extra_pointers = nullptr ); + int EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data = 0, + std::vector* extra_pointers = nullptr + ); + void GetErrors(std::list &quest_errors); /* @@ -209,6 +218,8 @@ private: bool BotHasQuestSubGlobal(QuestEventID event_id); bool MercHasQuestSubLocal(QuestEventID event_id); bool MercHasQuestSubGlobal(QuestEventID event_id); + bool ZoneHasQuestSubLocal(QuestEventID event_id); + bool ZoneHasQuestSubGlobal(QuestEventID event_id); int EventNPCLocal( QuestEventID event_id, @@ -280,6 +291,22 @@ private: std::vector* extra_pointers ); + int EventZoneLocal( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + + int EventZoneGlobal( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + QuestInterface* GetQIByNPCQuest(uint32 npc_id, std::string& filename); QuestInterface* GetQIByGlobalNPCQuest(std::string& filename); QuestInterface* GetQIByPlayerQuest(std::string& filename); @@ -291,6 +318,8 @@ private: QuestInterface* GetQIByGlobalBotQuest(std::string& filename); QuestInterface* GetQIByMercQuest(std::string& filename); QuestInterface* GetQIByGlobalMercQuest(std::string& filename); + QuestInterface* GetQIByZoneQuest(std::string& filename); + QuestInterface* GetQIByGlobalZoneQuest(std::string& filename); int DispatchEventNPC( QuestEventID event_id, @@ -347,6 +376,14 @@ private: std::vector* extra_pointers ); + int DispatchEventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + std::map _interfaces; std::map _extensions; std::list _load_precedence; @@ -359,6 +396,8 @@ private: uint32 _global_bot_quest_status; uint32 _merc_quest_status; uint32 _global_merc_quest_status; + uint32 _zone_quest_status; + uint32 _global_zone_quest_status; std::map _spell_quest_status; std::map _item_quest_status; std::map _encounter_quest_status; diff --git a/zone/zone.cpp b/zone/zone.cpp index 37216b046..ac43e766b 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1693,6 +1693,17 @@ bool Zone::Process() { } } + const bool has_timer_event = parse->ZoneHasQuestSub(EVENT_TIMER); + + for (auto e : zone_timers) { + LogError("has_timer_event [{}]", has_timer_event ? "y" : "n"); + if (e.timer_.Enabled() && e.timer_.Check()) { + if (has_timer_event) { + parse->EventZone(EVENT_TIMER, this, e.name); + } + } + } + mMovementManager->Process(); return true; @@ -3301,4 +3312,190 @@ void Zone::ReloadMaps() pathing = IPathfinder::Load(map_name); } +uint32 Zone::GetTimerDuration(std::string name) +{ + const auto& e = std::find_if( + zone_timers.begin(), + zone_timers.end(), + [&name](ZoneTimer e) { + return e.name == name; + } + ); + + return e != zone_timers.end() ? e->timer_.GetDuration() : 0; +} + +uint32 Zone::GetTimerRemainingTime(std::string name) +{ + const auto& e = std::find_if( + zone_timers.begin(), + zone_timers.end(), + [&name](ZoneTimer e) { + return e.name == name; + } + ); + + return e != zone_timers.end() ? e->timer_.GetRemainingTime() : 0; +} + +bool Zone::HasTimer(std::string name) +{ + const auto& e = std::find_if( + zone_timers.begin(), + zone_timers.end(), + [&name](ZoneTimer e) { + return e.name == name; + } + ); + + return e != zone_timers.end(); +} + +bool Zone::IsPausedTimer(std::string name) +{ + const auto& e = std::find_if( + paused_zone_timers.begin(), + paused_zone_timers.end(), + [&name](PausedZoneTimer e) { + return e.name == name; + } + ); + + return e != paused_zone_timers.end(); +} + +void Zone::PauseTimer(std::string name) +{ + if (zone_timers.empty()) { + return; + } + + uint32 remaining_time = 0; + + if (!zone_timers.empty()) { + for (auto e = zone_timers.begin(); e != zone_timers.end(); e++) { + if (e->name == name) { + remaining_time = e->timer_.GetRemainingTime(); + zone_timers.erase(e); + break; + } + } + } + + paused_zone_timers.emplace_back( + PausedZoneTimer{ + .name = name, + .remaining_time = remaining_time + } + ); +} + +void Zone::ResumeTimer(std::string name) +{ + if (paused_zone_timers.empty()) { + return; + } + + uint32 remaining_time = 0; + + if (!paused_zone_timers.empty()) { + for (auto e = paused_zone_timers.begin(); e != paused_zone_timers.end(); e++) { + if (e->name == name) { + remaining_time = e->remaining_time; + paused_zone_timers.erase(e); + break; + } + } + } + + if (!remaining_time) { + LogQuests("Paused timer [{}] not found or has expired.", name); + return; + } + + const std::string& export_string = fmt::format( + "{} {}", + name, + remaining_time + ); + + const bool has_resume_event = parse->ZoneHasQuestSub(EVENT_TIMER_RESUME); + + if (!zone_timers.empty()) { + for (auto e : zone_timers) { + if (e.name == name) { + e.timer_.Enable(); + e.timer_.Start(remaining_time, false); + LogQuests( + "Resuming timer [{}] with [{}] ms remaining", + name, + remaining_time + ); + + if (has_resume_event) { + parse->EventZone(EVENT_TIMER_RESUME, this, export_string); + } + } + } + } + + zone_timers.emplace_back(ZoneTimer(name, remaining_time)); + + if (has_resume_event) { + parse->EventZone(EVENT_TIMER_RESUME, this, export_string); + } + + LogQuests( + "Creating a new timer and resuming [{}] with [{}] ms remaining", + name, + remaining_time + ); +} + +void Zone::SetTimer(std::string name, uint32 duration) +{ + zone_timers.emplace_back(ZoneTimer(name, duration)); + + if (parse->ZoneHasQuestSub(EVENT_TIMER_START)) { + parse->EventZone(EVENT_TIMER_START, this, name); + } +} + +void Zone::StopTimer(std::string name) +{ + if (zone_timers.empty()) { + return; + } + + const bool has_stop_event = parse->ZoneHasQuestSub(EVENT_TIMER_STOP); + + for (auto e = zone_timers.begin(); e != zone_timers.end(); e++) { + if (e->name == name) { + if (has_stop_event) { + parse->EventZone(EVENT_TIMER_STOP, this, name); + } + + zone_timers.erase(e); + break; + } + } +} + +void Zone::StopAllTimers() +{ + if (zone_timers.empty()) { + return; + } + + const bool has_stop_event = parse->ZoneHasQuestSub(EVENT_TIMER_STOP); + + for (auto e = zone_timers.begin(); e != zone_timers.end(); e++) { + if (has_stop_event) { + parse->EventZone(EVENT_TIMER_STOP, this, e->name); + } + + e = zone_timers.erase(e); + } +} + #include "zone_loot.cpp" diff --git a/zone/zone.h b/zone/zone.h index 237c7fdb0..693d96359 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -486,6 +486,21 @@ public: static void ClearZoneState(uint32 zone_id, uint32 instance_id); void ReloadMaps(); + struct PausedZoneTimer { + std::string name; + uint32 remaining_time; + }; + + uint32 GetTimerDuration(std::string name); + uint32 GetTimerRemainingTime(std::string name); + bool HasTimer(std::string name); + bool IsPausedTimer(std::string name); + void PauseTimer(std::string name); + void ResumeTimer(std::string name); + void SetTimer(std::string name, uint32 duration); + void StopTimer(std::string name); + void StopAllTimers(); + private: bool allow_mercs; bool can_bind; @@ -552,6 +567,17 @@ private: std::vector m_base_data = { }; uint32_t m_zone_server_id = 0; + + class ZoneTimer { + public: + inline ZoneTimer(std::string _name, uint32 duration) + : name(_name), timer_(duration) { timer_.Start(duration, false); } + std::string name; + Timer timer_; + }; + + std::vector zone_timers; + std::vector paused_zone_timers; }; #endif