diff --git a/changelog.txt b/changelog.txt index e31f3598a..f8ca4c917 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,21 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 12/29/2015 == +Akkadius: Implemented standardized zone controller scripts (Rule Zone, UseZoneController) Defaulted to true + - When a zone boots, it will spawn an invisible npc by the name of zone_controller + - Lua and Perl scripts can be represented with this npc as zone_controller.pl/lua + - This NPC's ID is ruled be define ZONE_CONTROLLER_NPC_ID 10 + - Two EVENT's uniquely are handled with this NPC/controller (They only work with the zone_controller NPC) + - EVENT_SPAWN_ZONE :: All NPC spawns in the zone trigger the controller and pass the following variables: + $spawned_entity_id + $spawned_npc_id + - EVENT_DEATH_ZONE :: All NPC deaths in the zone trigger the controller event and pass the following variables: + $killer_id + $killer_damage + $killer_spell + $killer_skill + $killed_npc_id + == 12/28/2015 == Kinglykrab: Added GetInstanceTimer() to Perl and Lua. - Added GetInstanceTimerByID(instance_id) to Perl and Lua. diff --git a/common/features.h b/common/features.h index 2ede4b4be..8115efd82 100644 --- a/common/features.h +++ b/common/features.h @@ -233,6 +233,8 @@ enum { //some random constants #define GROUP_EXP_PER_POINT 1000 #define RAID_EXP_PER_POINT 2000 +#define ZONE_CONTROLLER_NPC_ID 10 + //Some hard coded statuses from commands and other places: enum { minStatusToBeGM = 40, diff --git a/common/ruletypes.h b/common/ruletypes.h index 83ef9aff3..41e4e22a4 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -233,6 +233,7 @@ RULE_BOOL(Zone, LevelBasedEXPMods, false) // Allows you to use the level_exp_mod RULE_INT(Zone, WeatherTimer, 600) // Weather timer when no duration is available RULE_BOOL(Zone, EnableLoggedOffReplenishments, true) RULE_INT(Zone, MinOfflineTimeToReplenishments, 21600) // 21600 seconds is 6 Hours +RULE_BOOL(Zone, UseZoneController, true) // Enables the ability to use persistent quest based zone controllers (zone_controller.pl/lua) RULE_CATEGORY_END() RULE_CATEGORY(Map) diff --git a/common/version.h b/common/version.h index b2dd26212..00a999add 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9093 +#define CURRENT_BINARY_DATABASE_VERSION 9094 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9000 #else diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 84ac931a7..86f7ebe7c 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -347,6 +347,7 @@ 9091|2015_12_07_command_settings.sql|SHOW TABLES LIKE 'command_settings'|empty| 9092|2015_12_17_eqtime.sql|SHOW TABLES LIKE 'eqtime'|empty| 9093|2015_12_21_items_updates_evoitem.sql|SHOW COLUMNS FROM `items` LIKE 'evoitem'|empty| +9094|2015_12_29_quest_zone_events.sql|SELECT * FROM perl_event_export_settings WHERE event_description = 'EVENT_SPAWN_ZONE'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2015_12_29_quest_zone_events.sql b/utils/sql/git/required/2015_12_29_quest_zone_events.sql new file mode 100644 index 000000000..783cb477e --- /dev/null +++ b/utils/sql/git/required/2015_12_29_quest_zone_events.sql @@ -0,0 +1,4 @@ +INSERT INTO `perl_event_export_settings` (`event_id`, `event_description`, `export_qglobals`, `export_mob`, `export_zone`, `export_item`, `export_event`) VALUES (81, 'EVENT_SPAWN_ZONE', 0, 0, 0, 0, 1); +INSERT INTO `perl_event_export_settings` (`event_id`, `event_description`, `export_qglobals`, `export_mob`, `export_zone`, `export_item`, `export_event`) VALUES (82, 'EVENT_DEATH_ZONE', 0, 0, 0, 0, 1); +ALTER TABLE `rule_values` +MODIFY COLUMN `notes` text CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL AFTER `rule_value`; \ No newline at end of file diff --git a/zone/attack.cpp b/zone/attack.cpp index 6b2534a43..5f7b19e80 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2009,15 +2009,15 @@ void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, SkillUseTypes attack } } -bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack_skill) { - Log.Out(Logs::Detail, Logs::Combat, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", killerMob->GetName(), damage, spell, attack_skill); +bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, SkillUseTypes attack_skill) { + Log.Out(Logs::Detail, Logs::Combat, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", killer_mob->GetName(), damage, spell, attack_skill); Mob *oos = nullptr; - if(killerMob) { - oos = killerMob->GetOwnerOrSelf(); + if(killer_mob) { + oos = killer_mob->GetOwnerOrSelf(); char buffer[48] = { 0 }; - snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + snprintf(buffer, 47, "%d %d %d %d", killer_mob ? killer_mob->GetID() : 0, damage, spell, static_cast(attack_skill)); if(parse->EventNPC(EVENT_DEATH, this, oos, buffer, 0) != 0) { if(GetHP() < 0) { @@ -2026,15 +2026,15 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack return false; } - if(killerMob && killerMob->IsClient() && (spell != SPELL_UNKNOWN) && damage > 0) { + if(killer_mob && killer_mob->IsClient() && (spell != SPELL_UNKNOWN) && damage > 0) { char val1[20]={0}; entity_list.MessageClose_StringID(this, false, 100, MT_NonMelee, HIT_NON_MELEE, - killerMob->GetCleanName(), GetCleanName(), ConvertArray(damage, val1)); + killer_mob->GetCleanName(), GetCleanName(), ConvertArray(damage, val1)); } } else { char buffer[48] = { 0 }; - snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + snprintf(buffer, 47, "%d %d %d %d", killer_mob ? killer_mob->GetID() : 0, damage, spell, static_cast(attack_skill)); if(parse->EventNPC(EVENT_DEATH, this, nullptr, buffer, 0) != 0) { if(GetHP() < 0) { @@ -2072,21 +2072,21 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack EQApplicationPacket* app= new EQApplicationPacket(OP_Death,sizeof(Death_Struct)); Death_Struct* d = (Death_Struct*)app->pBuffer; d->spawn_id = GetID(); - d->killer_id = killerMob ? killerMob->GetID() : 0; + d->killer_id = killer_mob ? killer_mob->GetID() : 0; d->bindzoneid = 0; d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell; d->attack_skill = SkillDamageTypes[attack_skill]; d->damage = damage; app->priority = 6; - entity_list.QueueClients(killerMob, app, false); + entity_list.QueueClients(killer_mob, app, false); if(respawn2) { respawn2->DeathReset(1); } - if (killerMob) { + if (killer_mob) { if(GetClass() != LDON_TREASURE) - hate_list.AddEntToHateList(killerMob, damage); + hate_list.AddEntToHateList(killer_mob, damage); } safe_delete(app); @@ -2148,8 +2148,8 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack { if(!IsLdonTreasure && MerchantType == 0) { kr->SplitExp((finalxp), this); - if(killerMob && (kr->IsRaidMember(killerMob->GetName()) || kr->IsRaidMember(killerMob->GetUltimateOwner()->GetName()))) - killerMob->TrySpellOnKill(killed_level,spell); + if(killer_mob && (kr->IsRaidMember(killer_mob->GetName()) || kr->IsRaidMember(killer_mob->GetUltimateOwner()->GetName()))) + killer_mob->TrySpellOnKill(killed_level,spell); } /* Send the EVENT_KILLED_MERIT event for all raid members */ for (int i = 0; i < MAX_RAID_MEMBERS; i++) { @@ -2193,8 +2193,8 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack { if(!IsLdonTreasure && MerchantType == 0) { kg->SplitExp((finalxp), this); - if(killerMob && (kg->IsGroupMember(killerMob->GetName()) || kg->IsGroupMember(killerMob->GetUltimateOwner()->GetName()))) - killerMob->TrySpellOnKill(killed_level,spell); + if(killer_mob && (kg->IsGroupMember(killer_mob->GetName()) || kg->IsGroupMember(killer_mob->GetUltimateOwner()->GetName()))) + killer_mob->TrySpellOnKill(killed_level,spell); } /* Send the EVENT_KILLED_MERIT event and update kill tasks * for all group members */ @@ -2244,8 +2244,8 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack if(!GetOwner() || (GetOwner() && !GetOwner()->IsClient())) { give_exp_client->AddEXP((finalxp), conlevel); - if(killerMob && (killerMob->GetID() == give_exp_client->GetID() || killerMob->GetUltimateOwner()->GetID() == give_exp_client->GetID())) - killerMob->TrySpellOnKill(killed_level,spell); + if(killer_mob && (killer_mob->GetID() == give_exp_client->GetID() || killer_mob->GetUltimateOwner()->GetID() == give_exp_client->GetID())) + killer_mob->TrySpellOnKill(killed_level,spell); } } } @@ -2393,20 +2393,30 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack uint16 emoteid = oos->GetEmoteID(); if(emoteid != 0) oos->CastToNPC()->DoNPCEmote(KILLEDNPC, emoteid); - killerMob->TrySpellOnKill(killed_level, spell); + killer_mob->TrySpellOnKill(killed_level, spell); } } WipeHateList(); p_depop = true; - if(killerMob && killerMob->GetTarget() == this) //we can kill things without having them targeted - killerMob->SetTarget(nullptr); //via AE effects and such.. + if(killer_mob && killer_mob->GetTarget() == this) //we can kill things without having them targeted + killer_mob->SetTarget(nullptr); //via AE effects and such.. entity_list.UpdateFindableNPCState(this, true); char buffer[48] = { 0 }; - snprintf(buffer, 47, "%d %d %d %d", killerMob ? killerMob->GetID() : 0, damage, spell, static_cast(attack_skill)); + snprintf(buffer, 47, "%d %d %d %d", killer_mob ? killer_mob->GetID() : 0, damage, spell, static_cast(attack_skill)); parse->EventNPC(EVENT_DEATH_COMPLETE, this, oos, buffer, 0); + + /* Zone controller process EVENT_DEATH_ZONE (Death events) */ + if (RuleB(Zone, UseZoneController)) { + if (entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID) && this->GetNPCTypeID() != ZONE_CONTROLLER_NPC_ID){ + char data_pass[100] = { 0 }; + snprintf(data_pass, 99, "%d %d %d %d %d", killer_mob ? killer_mob->GetID() : 0, damage, spell, static_cast(attack_skill), this->GetNPCTypeID()); + parse->EventNPC(EVENT_DEATH_ZONE, entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID)->CastToNPC(), nullptr, data_pass, 0); + } + } + return true; } diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 617d17d2e..12501b089 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -114,7 +114,9 @@ const char *QuestEventSubroutines[_LargestEventID] = { "EVENT_RESPAWN", "EVENT_DEATH_COMPLETE", "EVENT_UNHANDLED_OPCODE", - "EVENT_TICK" + "EVENT_TICK", + "EVENT_SPAWN_ZONE", + "EVENT_DEATH_ZONE", }; PerlembParser::PerlembParser() : perl(nullptr) { @@ -1424,6 +1426,21 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID ExportVar(package_name.c_str(), "slotid", extradata); break; } + case EVENT_SPAWN_ZONE: { + Seperator sep(data); + ExportVar(package_name.c_str(), "spawned_entity_id", sep.arg[0]); + ExportVar(package_name.c_str(), "spawned_npc_id", sep.arg[1]); + break; + } + case EVENT_DEATH_ZONE: { + Seperator sep(data); + ExportVar(package_name.c_str(), "killer_id", sep.arg[0]); + ExportVar(package_name.c_str(), "killer_damage", sep.arg[1]); + ExportVar(package_name.c_str(), "killer_spell", sep.arg[2]); + ExportVar(package_name.c_str(), "killer_skill", sep.arg[3]); + ExportVar(package_name.c_str(), "killed_npc_id", sep.arg[4]); + break; + } default: { break; diff --git a/zone/entity.cpp b/zone/entity.cpp index 23848f73e..92fefb2ec 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -641,8 +641,18 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) { npc->SetID(GetFreeID()); npc->SetMerchantProbability((uint8) zone->random.Int(0, 99)); + parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0); + /* Zone controller process EVENT_SPAWN_ZONE */ + if (RuleB(Zone, UseZoneController)) { + if (entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID) && npc->GetNPCTypeID() != ZONE_CONTROLLER_NPC_ID){ + char data_pass[100] = { 0 }; + snprintf(data_pass, 99, "%d %d", npc->GetID(), npc->GetNPCTypeID()); + parse->EventNPC(EVENT_SPAWN_ZONE, entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID)->CastToNPC(), nullptr, data_pass, 0); + } + } + uint16 emoteid = npc->GetEmoteID(); if (emoteid != 0) npc->DoNPCEmote(ONSPAWN, emoteid); diff --git a/zone/event_codes.h b/zone/event_codes.h index 7850b0eb3..1b0e18505 100644 --- a/zone/event_codes.h +++ b/zone/event_codes.h @@ -83,7 +83,8 @@ typedef enum { EVENT_DEATH_COMPLETE, EVENT_UNHANDLED_OPCODE, EVENT_TICK, - + EVENT_SPAWN_ZONE, + EVENT_DEATH_ZONE, _LargestEventID } QuestEventID; diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index ff0e357fb..a42efd592 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -1731,7 +1731,9 @@ luabind::scope lua_register_events() { luabind::value("leave_area", static_cast(EVENT_LEAVE_AREA)), luabind::value("death_complete", static_cast(EVENT_DEATH_COMPLETE)), luabind::value("unhandled_opcode", static_cast(EVENT_UNHANDLED_OPCODE)), - luabind::value("tick", static_cast(EVENT_TICK)) + luabind::value("tick", static_cast(EVENT_TICK)), + luabind::value("spawn_zone", static_cast(EVENT_SPAWN_ZONE)), + luabind::value("death_zone", static_cast(EVENT_DEATH_ZONE)) ]; } diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 462752705..aeb1e3228 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -117,7 +117,9 @@ const char *LuaEvents[_LargestEventID] = { "event_respawn", "event_death_complete", "event_unhandled_opcode", - "event_tick" + "event_tick", + "event_spawn_zone", + "event_death_zone" }; extern Zone *zone; diff --git a/zone/npc.cpp b/zone/npc.cpp index 9d39f4ad3..8f09d3f40 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -845,18 +845,22 @@ bool NPC::DatabaseCastAccepted(int spell_id) { bool NPC::SpawnZoneController(){ + if (!RuleB(Zone, UseZoneController)) + return false; + NPCType* npc_type = new NPCType; memset(npc_type, 0, sizeof(NPCType)); strncpy(npc_type->name, "zone_controller", 60); npc_type->cur_hp = 2000000000; npc_type->max_hp = 2000000000; + npc_type->hp_regen = 100000000; npc_type->race = 240; - npc_type->gender = 0; + npc_type->gender = 2; npc_type->class_ = 1; npc_type->deity = 1; npc_type->level = 200; - npc_type->npc_id = 0; + npc_type->npc_id = ZONE_CONTROLLER_NPC_ID; npc_type->loottable_id = 0; npc_type->texture = 3; npc_type->runspeed = 0; @@ -868,6 +872,9 @@ bool NPC::SpawnZoneController(){ npc_type->prim_melee_type = 28; npc_type->sec_melee_type = 28; + npc_type->findable = 0; + npc_type->trackable = 0; + strcpy(npc_type->special_abilities, "12,1^13,1^14,1^15,1^16,1^17,1^19,1^22,1^24,1^25,1^28,1^31,1^35,1^39,1^42,1"); glm::vec4 point; diff --git a/zone/quest_parser_collection.cpp b/zone/quest_parser_collection.cpp index 1f4f99c0e..4d2a2960a 100644 --- a/zone/quest_parser_collection.cpp +++ b/zone/quest_parser_collection.cpp @@ -484,10 +484,17 @@ QuestInterface *QuestParserCollection::GetQIByNPCQuest(uint32 npcid, std::string //second look for /quests/zone/npcname.ext (precedence) const NPCType *npc_type = database.LoadNPCTypesData(npcid); - if(!npc_type) { + if (!npc_type && npcid != ZONE_CONTROLLER_NPC_ID) { return nullptr; } - std::string npc_name = npc_type->name; + + std::string npc_name; + if (npcid == ZONE_CONTROLLER_NPC_ID){ + npc_name = "zone_controller"; + } + else{ + npc_name = npc_type->name; + } int sz = static_cast(npc_name.length()); for(int i = 0; i < sz; ++i) { if(npc_name[i] == '`') {