From 82a5e9600044c9df0b6f91cd4edc86016744f2cd Mon Sep 17 00:00:00 2001 From: Emagi Date: Sun, 8 Jun 2025 14:53:52 -0400 Subject: [PATCH] Work in progress for 0.9.9 so far (making its own branch for now) - Fixed a repeated rubberband like behavior that Spawn's would run forward toward their target in combat or on their return run to their starting point. - WS_HearCastSpell for 546-561 version has an extra byte at the end of the packet we were missing (might be in later clients too have to confirm) - PlayerScripts support added, new function calls, AddTimer support for Players. - Static Zones / Special Zones will now silently check to startup zones without reporting the log message, log message only on startup or taking on peer leadership. - Broadcast and Global Announcement are now supported through peering. - Fix #22 identified a number of loose spawn pointers and changed to int32 spawn id reference. - Fix #1 support for all known chat codes for various channels, spell casting and damage. - In conjunction with Fix #1 spell combat messages are fixed, no longer 'YOU cast' when another spawn casts, added last_tell_name to track the chat code %RT server side. - size_mod added to InfoStruct Float, supports shrinking and growing Non Player's. InfoStruct also has a UINT ignore_size_mod_calc set to 0 by default, when 1 it will let you set the size_mod and items/spells will not override it from stat calculation. - XP Table is now static (global) in the Player class so we do not constantly call the database each time the player needs to know a level's XP requirement. - Removal of duplicate spell cast success and effect messages. - Fix #21 blue xp bar for KoS and DoF displays properly now. - GetExpRequiredByLevel(level) added to return the EXP required for to reach the level. - Fix #25 teleporters cleaned up during /reload spawns to avoid crash - Fix #16 /reload items supported in peering mode. --- server/WorldStructs.xml | 14 ++ source/WorldServer/Combat.cpp | 36 +--- source/WorldServer/Commands/Commands.cpp | 62 +++--- source/WorldServer/Commands/Commands.h | 7 +- source/WorldServer/Entity.cpp | 9 +- source/WorldServer/Entity.h | 9 + source/WorldServer/Guilds/Guild.cpp | 2 +- source/WorldServer/LuaFunctions.cpp | 12 +- source/WorldServer/LuaFunctions.h | 2 + source/WorldServer/LuaInterface.cpp | 157 ++++++++++++++- source/WorldServer/LuaInterface.h | 50 +++++ source/WorldServer/Player.cpp | 99 +++++++--- source/WorldServer/Player.h | 12 +- source/WorldServer/Spawn.cpp | 29 +-- source/WorldServer/SpellProcess.cpp | 232 +++++++++++++++++++---- source/WorldServer/SpellProcess.h | 2 + source/WorldServer/Web/PeerManager.h | 4 +- source/WorldServer/Web/WorldWeb.cpp | 36 ++++ source/WorldServer/World.cpp | 95 ++++++++-- source/WorldServer/World.h | 13 +- source/WorldServer/WorldDatabase.cpp | 6 +- source/WorldServer/WorldDatabase.h | 2 +- source/WorldServer/client.cpp | 171 ++++++++++++----- source/WorldServer/client.h | 25 ++- source/WorldServer/net.cpp | 5 +- source/WorldServer/zoneserver.cpp | 101 ++++++++-- source/common/version.h | 6 +- 27 files changed, 964 insertions(+), 234 deletions(-) diff --git a/server/WorldStructs.xml b/server/WorldStructs.xml index 2c3c035..932402a 100644 --- a/server/WorldStructs.xml +++ b/server/WorldStructs.xml @@ -3712,6 +3712,12 @@ to zero and treated like placeholders." /> + + + + + + @@ -7642,6 +7648,7 @@ to zero and treated like placeholders." /> + @@ -21034,4 +21041,11 @@ to zero and treated like placeholders." /> + + + + + + + diff --git a/source/WorldServer/Combat.cpp b/source/WorldServer/Combat.cpp index 7e6bc9b..fe7ca00 100644 --- a/source/WorldServer/Combat.cpp +++ b/source/WorldServer/Combat.cpp @@ -140,6 +140,10 @@ bool Entity::AttackAllowed(Entity* target, float distance, bool range_attack) { return false; } } + + if(attacker->IsNPC() && target->IsNPC() && attacker->GetFactionID() > 10 && attacker->GetFactionID() == target->GetFactionID()) { + return false; + } if (attacker->IsPlayer() && target->IsPlayer()) { @@ -495,26 +499,6 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 CheckProcs(PROC_TYPE_OFFENSIVE, victim); CheckProcs(PROC_TYPE_MAGICAL_OFFENSIVE, victim); - - if(spell->GetSpellData()->success_message.length() > 0){ - Client* client = nullptr; - if(IsPlayer()) - client = ((Player*)this)->GetClient(); - if(client){ - string success_message = spell->GetSpellData()->success_message; - if(success_message.find("%t") < 0xFFFFFFFF) - success_message.replace(success_message.find("%t"), 2, victim->GetName()); - client->Message(CHANNEL_YOU_CAST, success_message.c_str()); - //commented out the following line as it was causing a duplicate message EmemJR 5/4/2019 - //GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, hit_result, damage_type, 0, spell->GetName()); - } - } - if(spell->GetSpellData()->effect_message.length() > 0){ - string effect_message = spell->GetSpellData()->effect_message; - if(effect_message.find("%t") < 0xFFFFFFFF) - effect_message.replace(effect_message.find("%t"), 2, victim->GetName()); - GetZone()->SimpleMessage(CHANNEL_SPELLS, effect_message.c_str(), victim, 50); - } } else { successful_hit = false; @@ -603,15 +587,15 @@ bool Entity::ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 if(IsPlayer()) client = ((Player*)this)->GetClient(); if(client) { - if(success_msg.find("%t") < 0xFFFFFFFF) - success_msg.replace(success_msg.find("%t"), 2, victim->GetName()); - client->Message(CHANNEL_YOU_CAST, success_msg.c_str()); + std::string castMsg = std::string(success_msg); + SpellProcess::ReplaceEffectTokens(castMsg, (Spawn*)this, victim); + client->Message(CHANNEL_YOU_CAST, castMsg.c_str()); } } if (effect_msg.length() > 0) { - if(effect_msg.find("%t") < 0xFFFFFFFF) - effect_msg.replace(effect_msg.find("%t"), 2, victim->GetName()); - GetZone()->SimpleMessage(CHANNEL_SPELLS, effect_msg.c_str(), victim, 50); + std::string effectMsg = std::string(effect_msg); + SpellProcess::ReplaceEffectTokens(effectMsg, (Spawn*)this, victim); + GetZone()->SimpleMessage(CHANNEL_SPELLS, effectMsg.c_str(), victim, 50); } } else { diff --git a/source/WorldServer/Commands/Commands.cpp b/source/WorldServer/Commands/Commands.cpp index 0808451..a23d701 100644 --- a/source/WorldServer/Commands/Commands.cpp +++ b/source/WorldServer/Commands/Commands.cpp @@ -1,22 +1,23 @@ /* -EQ2Emulator: Everquest II Server Emulator -Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) -This file is part of EQ2Emulator. + This file is part of EQ2Emulator. -EQ2Emulator 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, either version 3 of the License, or -(at your option) any later version. + EQ2Emulator 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, either version 3 of the License, or + (at your option) any later version. -EQ2Emulator is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. + EQ2Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; 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 EQ2Emulator. If not, see . + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . */ + #include #include #include @@ -1987,6 +1988,18 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } + case COMMAND_RELOAD_PLAYERSCRIPTS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Player Scripts..."); + world.SetReloadingSubsystem("PlayerScripts"); + world.ResetPlayerScripts(); + world.LoadPlayerScripts(); + if (lua_interface) + lua_interface->DestroyPlayerScripts(); + world.RemoveReloadingSubSystem("PlayerScripts"); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } case COMMAND_RELOAD_ENTITYCOMMANDS: { client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Entity Commands..."); world.SetReloadingSubsystem("EntityCommands"); @@ -2679,7 +2692,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie break; } case COMMAND_BANK_DEPOSIT:{ - if(client->GetBanker() && sep && sep->arg[0]){ + Spawn* banker = client->GetCurrentZone()->GetSpawnByID(client->GetBanker()); + if(banker && sep && sep->arg[0]){ int64 amount = 0; string deposit = string(sep->arg[0]); amount = atoi64(deposit.c_str()); @@ -2688,7 +2702,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie break; } case COMMAND_BANK_WITHDRAWAL:{ - if(client->GetBanker() && sep && sep->arg[0] && sep->IsNumber(0)){ + Spawn* banker = client->GetCurrentZone()->GetSpawnByID(client->GetBanker()); + if(banker && sep && sep->arg[0] && sep->IsNumber(0)){ int64 amount = 0; string deposit = string(sep->arg[0]); amount = atoi64(deposit.c_str()); @@ -2891,7 +2906,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie } case COMMAND_CLASS:{ if(sep && sep->arg[ndx][0]){ - client->GetPlayer()->SetPlayerAdventureClass(atoi(sep->arg[ndx])); + client->GetPlayer()->SetPlayerAdventureClass(atoi(sep->arg[ndx]), true); }else client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /class {class_id}"); break; @@ -3137,6 +3152,9 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie world.ResetZoneScripts(); database.LoadZoneScriptData(); + + world.ResetPlayerScripts(); + world.LoadPlayerScripts(); if(lua_interface) { lua_interface->DestroySpawnScripts(); @@ -3144,6 +3162,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie lua_interface->DestroyQuests(); lua_interface->DestroyItemScripts(); lua_interface->DestroyZoneScripts(); + lua_interface->DestroyPlayerScripts(); } int32 quest_count = database.LoadQuests(); @@ -5372,14 +5391,14 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie } case COMMAND_BROADCAST: { if (sep && sep->arg[0]) - zone_list.HandleGlobalBroadcast(sep->argplus[0]); + zone_list.TransmitBroadcast(sep->argplus[0]); else client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /broadcast {message}"); break; } case COMMAND_ANNOUNCE: { if (sep && sep->arg[0]) - zone_list.HandleGlobalAnnouncement(sep->argplus[0]); + zone_list.TransmitGlobalAnnouncement(sep->argplus[0]); else client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /announce {message}"); break; @@ -5401,6 +5420,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie database.LoadMerchantInformation(); // we skip if there is only a reload of single item not all items } + peer_manager.sendPeersMessage("/reloadcommand", command->handler, item_id); + if(item_id > 0) { client->Message(CHANNEL_COLOR_YELLOW, "Reloaded item %u.", item_id); } @@ -6184,11 +6205,6 @@ void Commands::Command_DuelSurrender(Client* client, Seperator* sep) PrintSep(sep, "COMMAND_DUEL_SURRENDER"); LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Surrender Duel Command"); client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); - - // JA, just messin around ;) - char surrender[64]; - sprintf(surrender, "%s surrendered like a cowardly dog!", client->GetPlayer()->GetName()); - zone_list.HandleGlobalAnnouncement(surrender); } /* diff --git a/source/WorldServer/Commands/Commands.h b/source/WorldServer/Commands/Commands.h index 6a3981e..3630616 100644 --- a/source/WorldServer/Commands/Commands.h +++ b/source/WorldServer/Commands/Commands.h @@ -1,6 +1,6 @@ /* EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) This file is part of EQ2Emulator. @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with EQ2Emulator. If not, see . */ + #ifndef __EQ2_COMMANDS__ #define __EQ2_COMMANDS__ #include "../../common/DataBuffer.h" @@ -981,8 +982,8 @@ private: #define CANCEL_AA_PROFILE 757 #define SAVE_AA_PROFILE 758 -#define COMMAND_MOOD 800 - +#define COMMAND_MOOD 800 +#define COMMAND_RELOAD_PLAYERSCRIPTS 801 #define COMMAND_MODIFY 1000 // INSERT INTO `commands`(`id`,`type`,`command`,`subcommand`,`handler`,`required_status`) VALUES ( NULL,'1','modify','','1000','200'); #define COMMAND_MODIFY_CHARACTER 1001 diff --git a/source/WorldServer/Entity.cpp b/source/WorldServer/Entity.cpp index 8267283..1b7427d 100644 --- a/source/WorldServer/Entity.cpp +++ b/source/WorldServer/Entity.cpp @@ -291,6 +291,7 @@ void Entity::MapInfoStruct() get_float_funcs["spell_reuse_speed"] = l::bind(&InfoStruct::get_spell_reuse_speed, &info_struct); get_float_funcs["spell_multi_attack"] = l::bind(&InfoStruct::get_spell_multi_attack, &info_struct); get_float_funcs["size_mod"] = l::bind(&InfoStruct::get_size_mod, &info_struct); + get_int8_funcs["ignore_size_mod_calc"] = l::bind(&InfoStruct::get_ignore_size_mod_calc, &info_struct); get_float_funcs["dps"] = l::bind(&InfoStruct::get_dps, &info_struct); get_float_funcs["dps_multiplier"] = l::bind(&InfoStruct::get_dps_multiplier, &info_struct); get_float_funcs["attackspeed"] = l::bind(&InfoStruct::get_attackspeed, &info_struct); @@ -500,6 +501,7 @@ void Entity::MapInfoStruct() set_float_funcs["spell_reuse_speed"] = l::bind(&InfoStruct::set_spell_reuse_speed, &info_struct, l::_1); set_float_funcs["spell_multi_attack"] = l::bind(&InfoStruct::set_spell_multi_attack, &info_struct, l::_1); set_float_funcs["size_mod"] = l::bind(&InfoStruct::set_size_mod, &info_struct, l::_1); + set_int8_funcs["ignore_size_mod_calc"] = l::bind(&InfoStruct::set_ignore_size_mod_calc, &info_struct, l::_1); set_float_funcs["dps"] = l::bind(&InfoStruct::set_dps, &info_struct, l::_1); set_float_funcs["dps_multiplier"] = l::bind(&InfoStruct::set_dps_multiplier, &info_struct, l::_1); set_float_funcs["attackspeed"] = l::bind(&InfoStruct::set_attackspeed, &info_struct, l::_1); @@ -1462,7 +1464,9 @@ void Entity::CalculateBonuses(){ info->set_spell_multi_attack(0); float previous_size_mod = info->get_size_mod(); - info->set_size_mod(0.0f); + + if(!info->get_ignore_size_mod_calc()) + info->set_size_mod(0.0f); info->set_dps(0); info->set_dps_multiplier(0); info->set_haste(0); @@ -1548,7 +1552,8 @@ void Entity::CalculateBonuses(){ info->add_recovery_speed(values->abilityrecoveryspeed); info->add_spell_reuse_speed(values->spellreusespeed); info->add_spell_multi_attack(values->spellmultiattackchance); - info->add_size_mod(values->size_mod); + if(!info->get_ignore_size_mod_calc()) + info->add_size_mod(values->size_mod); info->add_dps(values->dps); info->add_dps_multiplier(CalculateDPSMultiplier()); info->add_haste(values->attackspeed); diff --git a/source/WorldServer/Entity.h b/source/WorldServer/Entity.h index 980d5de..165f9ea 100644 --- a/source/WorldServer/Entity.h +++ b/source/WorldServer/Entity.h @@ -210,6 +210,8 @@ struct InfoStruct{ recovery_speed_ = 0; spell_reuse_speed_ = 0; spell_multi_attack_ = 0; + size_mod_ = 0.0f; + ignore_size_mod_calc_ = 0; dps_ = 0; dps_multiplier_ = 0; attackspeed_ = 0; @@ -420,6 +422,10 @@ struct InfoStruct{ spell_reuse_speed_ = oldStruct->get_spell_reuse_speed(); spell_multi_attack_ = oldStruct->get_spell_multi_attack(); dps_ = oldStruct->get_dps(); + + size_mod_ = oldStruct->get_size_mod(); + ignore_size_mod_calc_ = oldStruct->get_ignore_size_mod_calc(); + dps_multiplier_ = oldStruct->get_dps_multiplier(); attackspeed_ = oldStruct->get_attackspeed(); haste_ = oldStruct->get_haste(); @@ -637,6 +643,7 @@ struct InfoStruct{ float get_spell_reuse_speed() { std::lock_guard lk(classMutex); return spell_reuse_speed_; } float get_spell_multi_attack() { std::lock_guard lk(classMutex); return spell_multi_attack_; } float get_size_mod() { std::lock_guard lk(classMutex); return size_mod_; } + int8 get_ignore_size_mod_calc() { std::lock_guard lk(classMutex); return ignore_size_mod_calc_; } float get_dps() { std::lock_guard lk(classMutex); return dps_; } float get_dps_multiplier() { std::lock_guard lk(classMutex); return dps_multiplier_; } float get_attackspeed() { std::lock_guard lk(classMutex); return attackspeed_; } @@ -917,6 +924,7 @@ struct InfoStruct{ void set_spell_reuse_speed(float value) { std::lock_guard lk(classMutex); spell_reuse_speed_ = value; } void set_spell_multi_attack(float value) { std::lock_guard lk(classMutex); spell_multi_attack_ = value; } void set_size_mod(float value) { std::lock_guard lk(classMutex); size_mod_ = value; } + void set_ignore_size_mod_calc(int8 value) { std::lock_guard lk(classMutex); ignore_size_mod_calc_ = value; } void set_dps(float value) { std::lock_guard lk(classMutex); dps_ = value; } void set_dps_multiplier(float value) { std::lock_guard lk(classMutex); dps_multiplier_ = value; } void set_attackspeed(float value) { std::lock_guard lk(classMutex); attackspeed_ = value; } @@ -1191,6 +1199,7 @@ private: float spell_reuse_speed_; float spell_multi_attack_; float size_mod_; + int8 ignore_size_mod_calc_; float dps_; float dps_multiplier_; float attackspeed_; diff --git a/source/WorldServer/Guilds/Guild.cpp b/source/WorldServer/Guilds/Guild.cpp index 2fdd2e8..9a80672 100644 --- a/source/WorldServer/Guilds/Guild.cpp +++ b/source/WorldServer/Guilds/Guild.cpp @@ -187,7 +187,7 @@ void Guild::AddEXPCurrent(sint64 exp, bool send_packet) { else strncpy(adjective, "too uber for cheerios", sizeof(adjective) - 1); sprintf(message, "The %s guild <%s> has attained level %u", adjective, name, level); - zone_list.HandleGlobalAnnouncement(message); + zone_list.TransmitGlobalAnnouncement(message); } } save_needed = true; diff --git a/source/WorldServer/LuaFunctions.cpp b/source/WorldServer/LuaFunctions.cpp index b358581..ca288df 100644 --- a/source/WorldServer/LuaFunctions.cpp +++ b/source/WorldServer/LuaFunctions.cpp @@ -14652,4 +14652,14 @@ int EQ2Emu_lua_GetSpellRequiredLevel(lua_State* state) { } return 1; -} \ No newline at end of file +} + +int EQ2Emu_lua_GetExpRequiredByLevel(lua_State* state) { + int8 level = lua_interface->GetInt16Value(state); + lua_interface->ResetFunctionStack(state); + + int32 exp_required = Player::GetNeededXPByLevel(level); + + lua_interface->SetInt32Value(state, exp_required); + return 1; +} diff --git a/source/WorldServer/LuaFunctions.h b/source/WorldServer/LuaFunctions.h index d15461f..f571610 100644 --- a/source/WorldServer/LuaFunctions.h +++ b/source/WorldServer/LuaFunctions.h @@ -680,4 +680,6 @@ int EQ2Emu_lua_GetZonePlayerAvgLevel(lua_State* state); int EQ2Emu_lua_GetZonePlayerFirstLevel(lua_State* state); int EQ2Emu_lua_GetSpellRequiredLevel(lua_State* state); + +int EQ2Emu_lua_GetExpRequiredByLevel(lua_State* state); #endif \ No newline at end of file diff --git a/source/WorldServer/LuaInterface.cpp b/source/WorldServer/LuaInterface.cpp index 47ce46b..b2d7e04 100644 --- a/source/WorldServer/LuaInterface.cpp +++ b/source/WorldServer/LuaInterface.cpp @@ -1,6 +1,6 @@ /* EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) This file is part of EQ2Emulator. @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with EQ2Emulator. If not, see . */ + #include "LuaInterface.h" #include "LuaFunctions.h" #include "WorldDatabase.h" @@ -45,6 +46,7 @@ LuaInterface::LuaInterface() { MSpells.SetName("LuaInterface::MSpells"); MSpawnScripts.SetName("LuaInterface::MSpawnScripts"); MZoneScripts.SetName("LuaInterface::MZoneScripts"); + MPlayerScripts.SetName("LuaInterface::MPlayerScripts"); MQuests.SetName("LuaInterface::MQuests"); MLUAMain.SetName("LuaInterface::MLUAMain"); MItemScripts.SetName("LuaInterface::MItemScripts"); @@ -119,6 +121,7 @@ LuaInterface::~LuaInterface() { DestroyQuests(); DestroyItemScripts(); DestroyZoneScripts(); + DestroyPlayerScripts(); DestroyRegionScripts(); DeleteUserDataPtrs(true); DeletePendingSpells(true); @@ -241,6 +244,25 @@ void LuaInterface::DestroyZoneScripts() { MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); } +void LuaInterface::DestroyPlayerScripts() { + map >::iterator itr; + map::iterator state_itr; + Mutex* mutex = 0; + MPlayerScripts.writelock(__FUNCTION__, __LINE__); + for (itr = player_scripts.begin(); itr != player_scripts.end(); itr++){ + mutex = GetPlayerScriptMutex(itr->first.c_str()); + mutex->writelock(__FUNCTION__, __LINE__); + for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++) + lua_close(state_itr->first); + mutex->releasewritelock(__FUNCTION__, __LINE__); + safe_delete(mutex); + } + player_scripts.clear(); + player_inverse_scripts.clear(); + player_scripts_mutex.clear(); + MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__); +} + void LuaInterface::DestroyRegionScripts() { map >::iterator itr; map::iterator state_itr; @@ -306,6 +328,20 @@ bool LuaInterface::LoadZoneScript(const char* name) { return ret; } +bool LuaInterface::LoadPlayerScript(const char* name) { + bool ret = false; + if (name) { + lua_State* state = LoadLuaFile(name); + if (state) { + MPlayerScripts.writelock(__FUNCTION__, __LINE__); + player_scripts[name][state] = false; + MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__); + ret = true; + } + } + return ret; +} + bool LuaInterface::LoadRegionScript(const char* name) { bool ret = false; if (name) { @@ -469,6 +505,16 @@ const char* LuaInterface::GetScriptName(lua_State* state) } MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); + MPlayerScripts.writelock(__FUNCTION__, __LINE__); + itr = player_inverse_scripts.find(state); + if (itr != player_inverse_scripts.end()) + { + const char* scriptName = itr->second.c_str(); + MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__); + return scriptName; + } + MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__); + MRegionScripts.writelock(__FUNCTION__, __LINE__); itr = region_inverse_scripts.find(state); if (itr != region_inverse_scripts.end()) @@ -500,6 +546,10 @@ bool LuaInterface::LoadZoneScript(string name) { return LoadZoneScript(name.c_str()); } +bool LuaInterface::LoadPlayerScript(string name) { + return LoadPlayerScript(name.c_str()); +} + bool LuaInterface::LoadRegionScript(string name) { return LoadRegionScript(name.c_str()); } @@ -1632,6 +1682,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) { lua_register(state,"GetZonePlayerFirstLevel", EQ2Emu_lua_GetZonePlayerFirstLevel); lua_register(state,"GetSpellRequiredLevel", EQ2Emu_lua_GetSpellRequiredLevel); + lua_register(state,"GetExpRequiredByLevel", EQ2Emu_lua_GetExpRequiredByLevel); } void LuaInterface::LogError(const char* error, ...) { @@ -2172,6 +2223,17 @@ Mutex* LuaInterface::GetZoneScriptMutex(const char* name) { return mutex; } +Mutex* LuaInterface::GetPlayerScriptMutex(const char* name) { + Mutex* mutex = 0; + if(player_scripts_mutex.count(name) > 0) + mutex = player_scripts_mutex[name]; + if(!mutex){ + mutex = new Mutex(); + player_scripts_mutex[name] = mutex; + } + return mutex; +} + Mutex* LuaInterface::GetRegionScriptMutex(const char* name) { Mutex* mutex = 0; if(region_scripts_mutex.count(name) > 0) @@ -2216,6 +2278,14 @@ void LuaInterface::UseZoneScript(const char* name, lua_State* state, bool val) { MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); } +void LuaInterface::UsePlayerScript(const char* name, lua_State* state, bool val) { + + MPlayerScripts.writelock(__FUNCTION__, __LINE__); + player_scripts[name][state] = val; + player_inverse_scripts[state] = name; + MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__); +} + void LuaInterface::UseRegionScript(const char* name, lua_State* state, bool val) { MRegionScripts.writelock(__FUNCTION__, __LINE__); @@ -2328,6 +2398,40 @@ lua_State* LuaInterface::GetZoneScript(const char* name, bool create_new, bool u return ret; } +lua_State* LuaInterface::GetPlayerScript(const char* name, bool create_new, bool use) { + map >::iterator itr; + map::iterator zone_script_itr; + lua_State* ret = 0; + Mutex* mutex = 0; + + itr = player_scripts.find(name); + if(itr != player_scripts.end()) { + mutex = GetPlayerScriptMutex(name); + mutex->readlock(__FUNCTION__, __LINE__); + for(zone_script_itr = itr->second.begin(); zone_script_itr != itr->second.end(); zone_script_itr++){ + if(!zone_script_itr->second){ //not in use + ret = zone_script_itr->first; + + if (use) + { + zone_script_itr->second = true; + break; // don't keep iterating, we already have our result + } + } + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + } + if(!ret && create_new){ + if(name && LoadPlayerScript(name)) + ret = GetPlayerScript(name, create_new, use); + else{ + LogError("Error LUA Zone Script '%s'", name); + return 0; + } + } + return ret; +} + lua_State* LuaInterface::GetRegionScript(const char* name, bool create_new, bool use) { map >::iterator itr; map::iterator region_script_itr; @@ -2699,6 +2803,57 @@ bool LuaInterface::RunZoneScriptWithReturn(string script_name, const char* funct return false; } +bool LuaInterface::RunPlayerScriptWithReturn(const string script_name, const char* function_name, const std::vector& args, sint32* returnValue) { + lua_State* state = GetPlayerScript(script_name.c_str(), true, true); + if (state) { + Mutex* mutex = GetPlayerScriptMutex(script_name.c_str()); + if (mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else { + LogError("Error getting lock for '%s'", script_name.c_str()); + UsePlayerScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))) { + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UsePlayerScript(script_name.c_str(), state, false); + return false; + } + int8 num_params = 0; + for (const LuaArg& arg : args) { + switch (arg.type) { + case LuaArgType::SINT64: SetSInt64Value(state, arg.si); num_params++; break; + case LuaArgType::SINT: SetSInt32Value(state, arg.low_si); num_params++; break; + case LuaArgType::INT64: SetInt64Value(state, arg.i); num_params++; break; + case LuaArgType::INT: SetInt32Value(state, arg.low_i); num_params++; break; + case LuaArgType::FLOAT: SetFloatValue(state, arg.f); num_params++; break; + case LuaArgType::STRING: SetStringValue(state, arg.s.c_str()); num_params++; break; + case LuaArgType::BOOL: SetBooleanValue(state, arg.b); num_params++; break; + case LuaArgType::SPAWN: SetSpawnValue(state, arg.spawn); num_params++; break; + case LuaArgType::ZONE: SetZoneValue(state, arg.zone); num_params++; break; + case LuaArgType::SKILL: SetSkillValue(state, arg.skill); num_params++; break; + case LuaArgType::ITEM: SetItemValue(state, arg.item); num_params++; break; + case LuaArgType::QUEST: SetQuestValue(state, arg.quest); num_params++; break; + case LuaArgType::SPELL: SetSpellValue(state, arg.spell); num_params++; break; + } + } + if (!CallScriptSInt32(state, num_params, returnValue)) { + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UsePlayerScript(script_name.c_str(), state, false); + return false; + } + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UsePlayerScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + bool LuaInterface::RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, sint32 int32_arg1, int32* returnValue) { if (!zone) diff --git a/source/WorldServer/LuaInterface.h b/source/WorldServer/LuaInterface.h index 36f42a5..cecc0bd 100644 --- a/source/WorldServer/LuaInterface.h +++ b/source/WorldServer/LuaInterface.h @@ -108,6 +108,45 @@ struct LuaSpell{ int16 initial_caster_level; }; +enum class LuaArgType { SINT64, INT64, SINT, INT, FLOAT, STRING, BOOL, SPAWN, ZONE, SKILL, ITEM, QUEST, SPELL /* etc */ }; + +struct LuaArg { + LuaArgType type; + union { + sint64 si; + sint64 i; + int32 low_i; + sint32 low_si; + float f; + bool b; + }; + std::string s; + Spawn* spawn = nullptr; + ZoneServer* zone = nullptr; + Skill* skill = nullptr; + Item* item = nullptr; + Quest* quest = nullptr; + LuaSpell* spell = nullptr; + + LuaArg(sint64 val) : type(LuaArgType::SINT64), si(val) {} + LuaArg(sint32 val) : type(LuaArgType::SINT), low_si(val) {} + LuaArg(sint16 val) : type(LuaArgType::SINT), low_si(val) {} + LuaArg(sint8 val) : type(LuaArgType::SINT), low_si(val) {} + LuaArg(int64 val) : type(LuaArgType::INT64), i(val) {} + LuaArg(int32 val) : type(LuaArgType::INT), low_i(val) {} + LuaArg(int16 val) : type(LuaArgType::INT), low_i(val) {} + LuaArg(int8 val) : type(LuaArgType::INT), low_i(val) {} + LuaArg(float val) : type(LuaArgType::FLOAT), f(val) {} + LuaArg(bool val) : type(LuaArgType::BOOL), b(val) {} + LuaArg(const std::string& val) : type(LuaArgType::STRING), s(val) {} + LuaArg(Spawn* val) : type(LuaArgType::SPAWN), spawn(val) {} + LuaArg(ZoneServer* val) : type(LuaArgType::ZONE), zone(val) {} + LuaArg(Skill* val) : type(LuaArgType::SKILL), skill(val) {} + LuaArg(Item* val) : type(LuaArgType::ITEM), item(val) {} + LuaArg(Quest* val) : type(LuaArgType::QUEST), quest(val) {} + LuaArg(LuaSpell* val) : type(LuaArgType::SPELL), spell(val) {} +}; + class LUAUserData{ public: LUAUserData(); @@ -192,6 +231,8 @@ public: bool LoadSpawnScript(const char* name); bool LoadZoneScript(string name); bool LoadZoneScript(const char* name); + bool LoadPlayerScript(string name); + bool LoadPlayerScript(const char* name); bool LoadRegionScript(string name); bool LoadRegionScript(const char* name); LuaSpell* LoadSpellScript(string name); @@ -242,10 +283,12 @@ public: void UseItemScript(const char* name, lua_State* state, bool val); void UseSpawnScript(const char* name, lua_State* state, bool val); void UseZoneScript(const char* name, lua_State* state, bool val); + void UsePlayerScript(const char* name, lua_State* state, bool val); void UseRegionScript(const char* name, lua_State* state, bool val); lua_State* GetItemScript(const char* name, bool create_new = true, bool use = false); lua_State* GetSpawnScript(const char* name, bool create_new = true, bool use = false); lua_State* GetZoneScript(const char* name, bool create_new = true, bool use = false); + lua_State* GetPlayerScript(const char* name, bool create_new = true, bool use = false); lua_State* GetRegionScript(const char* name, bool create_new = true, bool use = false); LuaSpell* GetSpellScript(const char* name, bool create_new = true, bool use = true); LuaSpell* CreateSpellScript(const char* name, lua_State* existState); @@ -263,6 +306,7 @@ public: bool CallSpawnScript(lua_State* state, int8 num_parameters); bool RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, int32 int32_arg1 = 0, const char* str_arg1 = 0, Spawn* spawn_arg1 = 0, int32 int32_arg2 = 0, const char* str_arg2 = 0, Spawn* spawn_arg2 = 0); bool RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue = 0); + bool RunPlayerScriptWithReturn(const string script_name, const char* function_name, const std::vector& args, sint32* returnValue = 0); bool CallScriptInt32(lua_State* state, int8 num_parameters, int32* returnValue = 0); bool CallScriptSInt32(lua_State* state, int8 num_parameters, sint32* returnValue = 0); bool RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, sint32 int32_arg1 = 0, int32* returnValue = 0); @@ -273,6 +317,7 @@ public: void DestroyItemScripts(); void DestroyQuests(bool reload = false); void DestroyZoneScripts(); + void DestroyPlayerScripts(); void DestroyRegionScripts(); void SimpleLogError(const char* error); void LogError(const char* error, ...); @@ -290,6 +335,7 @@ public: Mutex* GetSpawnScriptMutex(const char* name); Mutex* GetItemScriptMutex(const char* name); Mutex* GetZoneScriptMutex(const char* name); + Mutex* GetPlayerScriptMutex(const char* name); Mutex* GetRegionScriptMutex(const char* name); Mutex* GetSpellScriptMutex(const char* name); Mutex* GetQuestMutex(Quest* quest); @@ -330,6 +376,7 @@ private: map > item_scripts; map > spawn_scripts; map > zone_scripts; + map > player_scripts; map > region_scripts; map > spell_scripts; @@ -339,11 +386,13 @@ private: map item_inverse_scripts; map spawn_inverse_scripts; map zone_inverse_scripts; + map player_inverse_scripts; map region_inverse_scripts; map item_scripts_mutex; map spawn_scripts_mutex; map zone_scripts_mutex; + map player_scripts_mutex; map quests_mutex; map region_scripts_mutex; map spell_scripts_mutex; @@ -353,6 +402,7 @@ private: Mutex MSpawnScripts; Mutex MItemScripts; Mutex MZoneScripts; + Mutex MPlayerScripts; Mutex MQuests; Mutex MLUAMain; Mutex MSpellDelete; diff --git a/source/WorldServer/Player.cpp b/source/WorldServer/Player.cpp index feb52b8..d9447ce 100644 --- a/source/WorldServer/Player.cpp +++ b/source/WorldServer/Player.cpp @@ -1,6 +1,6 @@ /* EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) This file is part of EQ2Emulator. @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with EQ2Emulator. If not, see . */ + #include "Player.h" #include "../common/MiscFunctions.h" #include "World.h" @@ -46,6 +47,7 @@ extern MasterItemList master_item_list; extern RuleManager rule_manager; extern MasterTitlesList master_titles_list; extern MasterLanguagesList master_languages_list; +std::map Player::m_levelXPReq; Player::Player(){ tutorial_step = 0; @@ -998,8 +1000,8 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal packet->setDataByName("decrease_falling_dmg", 169); if (version <= 561) { - packet->setDataByName("exp_yellow", info_struct->get_xp_yellow() / 10); - packet->setDataByName("exp_blue", info_struct->get_xp_blue()/100); + packet->setDataByName("exp_yellow", info_struct->get_xp_yellow() / 10); + packet->setDataByName("exp_blue", ((int16)info_struct->get_xp_yellow() % 100) + (info_struct->get_xp_blue() / 100)); } else { packet->setDataByName("exp_yellow", info_struct->get_xp_yellow()); @@ -2136,7 +2138,7 @@ bool Player::AddItemToBank(Item* item) { } EQ2Packet* Player::SendInventoryUpdate(int16 version) { // assure any inventory updates are reflected in sell window - if(GetClient() && GetClient()->GetMerchantTransaction()) + if(GetClient() && GetClient()->GetMerchantTransactionID()) GetClient()->SendSellMerchantList(); return item_list.serialize(this, version); @@ -4419,11 +4421,20 @@ void Player::SetNeededXP(){ //GetInfoStruct()->xp_needed = GetLevel() * 100; // Get xp needed to get to the next level int16 level = GetLevel() + 1; - // If next level is beyond what we have in the map multiply the last value we have by how many levels we are over plus one - if (level > 95) - SetNeededXP(database.GetMysqlExpCurve(95)* ((level - 95) + 1)); + SetNeededXP(GetNeededXPByLevel(level)); +} + +int32 Player::GetNeededXPByLevel(int8 level) { + int32 exp_required = 0; + if (!Player::m_levelXPReq.count(level) && level > 95 && Player::m_levelXPReq.count(95)) { + exp_required = (Player::m_levelXPReq[95] * ((level - 95) + 1)); + } + else if(Player::m_levelXPReq.count(level)) + exp_required = Player::m_levelXPReq[level]; else - SetNeededXP(database.GetMysqlExpCurve(level)); + exp_required = 0; + + return exp_required; } void Player::SetXP(int32 val){ @@ -4519,10 +4530,24 @@ bool Player::AddXP(int32 xp_amount){ SetCharSheetChanged(true); return false; } + int32 prev_xp_amount = xp_amount; xp_amount -= GetNeededXP() - GetXP(); - SetLevel(GetLevel() + 1); + if(GetClient()->ChangeLevel(GetLevel(), GetLevel()+1, prev_xp_amount)) + SetLevel(GetLevel() + 1); + else { + SetXP(GetXP() + prev_xp_amount); + SetCharSheetChanged(true); + return false; + } } + + // set the actual end xp_amount result SetXP(GetXP() + xp_amount); + + if(GetClient()) { + GetClient()->Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp_amount); + } + GetPlayerInfo()->CalculateXPPercentages(); current_xp_percent = ((float)GetXP()/(float)GetNeededXP())*100; if(miniding_min_percent > 0.0f && current_xp_percent >= miniding_min_percent){ @@ -4532,15 +4557,8 @@ bool Player::AddXP(int32 xp_amount){ SetPower(GetTotalPower()); GetZone()->SendCastSpellPacket(332, this, this); //send mini level up spell effect } - - if(GetClient()) { - GetClient()->Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp_amount); - - if (prev_level != GetLevel()) - GetClient()->ChangeLevel(prev_level, GetLevel()); - } - SetCharSheetChanged(true); + SetCharSheetChanged(true); return true; } @@ -4550,7 +4568,16 @@ bool Player::AddTSXP(int32 xp_amount){ MStats.unlock(); float current_xp_percent = ((float)GetTSXP()/(float)GetNeededTSXP())*100; - float miniding_min_percent = ((int)(current_xp_percent/10)+1)*10; + + int32 mini_ding_pct = rule_manager.GetGlobalRule(R_Player, MiniDingPercentage)->GetInt32(); + float miniding_min_percent = 0.0f; + if(mini_ding_pct < 10 || mini_ding_pct > 50) { + mini_ding_pct = 0; + } + else { + miniding_min_percent = ((int)(current_xp_percent/mini_ding_pct)+1)*mini_ding_pct; + } + while((xp_amount + GetTSXP()) >= GetNeededTSXP()){ if (!CheckLevelStatus(GetTSLevel() + 1)) { if(GetClient()) { @@ -4558,10 +4585,18 @@ bool Player::AddTSXP(int32 xp_amount){ } return false; } + int32 prev_xp_amount = xp_amount; xp_amount -= GetNeededTSXP() - GetTSXP(); - SetTSLevel(GetTSLevel() + 1); - SetTSXP(0); - SetNeededTSXP(); + if(GetClient()->ChangeTSLevel(GetLevel(), GetLevel()+1, prev_xp_amount)) { + SetTSLevel(GetTSLevel() + 1); + SetTSXP(0); + SetNeededTSXP(); + } + else { + SetTSXP(GetTSXP() + prev_xp_amount); + SetCharSheetChanged(true); + return false; + } } SetTSXP(GetTSXP() + xp_amount); GetPlayerInfo()->CalculateXPPercentages(); @@ -4577,6 +4612,8 @@ bool Player::AddTSXP(int32 xp_amount){ GetInfoStruct()->set_tradeskill_class2(1); GetInfoStruct()->set_tradeskill_class3(1); } + + SetCharSheetChanged(true); return true; } @@ -6423,7 +6460,8 @@ int32 CharacterInstances::GetInstanceCount() { return instanceList.size(); } -void Player::SetPlayerAdventureClass(int8 new_class){ +void Player::SetPlayerAdventureClass(int8 new_class, bool set_by_gm_command ){ + int8 old_class = GetAdventureClass(); SetAdventureClass(new_class); GetInfoStruct()->set_class1(classes.GetBaseClass(new_class)); GetInfoStruct()->set_class2(classes.GetSecondaryBaseClass(new_class)); @@ -6433,6 +6471,23 @@ void Player::SetPlayerAdventureClass(int8 new_class){ GetZone()->TriggerCharSheetTimer(); if(GetClient()) GetClient()->UpdateTimeStampFlag ( CLASS_UPDATE_FLAG ); + + const char* playerScript = world.GetPlayerScript(0); // 0 = global script + const char* playerZoneScript = world.GetPlayerScript(GetZoneID()); // zone script + if(playerScript || playerZoneScript) { + std::vector args = { + LuaArg(GetZone()), + LuaArg(this), + LuaArg(old_class), + LuaArg(new_class) + }; + if(playerScript) { + lua_interface->RunPlayerScriptWithReturn(playerScript, "on_class_change", args); + } + if(playerZoneScript) { + lua_interface->RunPlayerScriptWithReturn(playerZoneScript, "on_class_change", args); + } + } } void Player::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { diff --git a/source/WorldServer/Player.h b/source/WorldServer/Player.h index cb1a499..d89b02d 100644 --- a/source/WorldServer/Player.h +++ b/source/WorldServer/Player.h @@ -1,6 +1,6 @@ /* EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) This file is part of EQ2Emulator. @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with EQ2Emulator. If not, see . */ + #ifndef __EQ2_PLAYER__ #define __EQ2_PLAYER__ @@ -608,6 +609,7 @@ public: bool TradeskillXPEnabled(); void SetNeededXP(int32 val); void SetNeededXP(); + static int32 GetNeededXPByLevel(int8 level); void SetXP(int32 val); void SetNeededTSXP(int32 val); void SetNeededTSXP(); @@ -789,7 +791,7 @@ public: bool GetIsTracking() const { return is_tracking; } void SetBiography(string new_biography) { biography = new_biography; } string GetBiography() const { return biography; } - void SetPlayerAdventureClass(int8 new_class); + void SetPlayerAdventureClass(int8 new_class, bool set_by_gm_command = false); void SetGuild(Guild* new_guild) { guild = new_guild; } Guild* GetGuild() { return guild; } void AddSkillBonus(int32 spell_id, int32 skill_id, float value); @@ -1112,8 +1114,8 @@ public: mutable std::shared_mutex trait_mutex; std::atomic need_trait_update; - void InitXPTable(); - map m_levelXPReq; + static void InitXPTable(); + static map m_levelXPReq; mutable std::shared_mutex spell_packet_update_mutex; mutable std::shared_mutex raid_update_mutex; @@ -1214,8 +1216,6 @@ private: void AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); void RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); void SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast); -// void InitXPTable(); -// map m_levelXPReq; //The following variables are for serializing spawn packets PacketStruct* spawn_pos_struct; diff --git a/source/WorldServer/Spawn.cpp b/source/WorldServer/Spawn.cpp index 8556681..304a12c 100644 --- a/source/WorldServer/Spawn.cpp +++ b/source/WorldServer/Spawn.cpp @@ -2197,18 +2197,25 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b if (size == 0) size = 32; + float scaled = 0.0f; + + sint16 new_size = size; + if(IsEntity() && !IsPlayer()) { // doesn't work correctly for Player, only non-Player (NPC/Object/etc) + scaled = static_cast(size) + (((Entity*)this)->GetInfoStruct()->get_size_mod() * size); + // Round and cast to sint16 + new_size = static_cast(std::round(scaled)); + // Enforce minimum size of 1 + new_size = std::max(new_size, 1); + } + if (version <= 910) { packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32); - packet->setDataByName("pos_size", size > 0 ? size : 32); + packet->setDataByName("pos_size", new_size > 0 ? new_size : 32); } else { packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32); + packet->setDataByName("pos_size", new_size > 0 ? (((float)new_size) / 32.0f) : 1.0f); // float not an integer - if(!IsPlayer()) - packet->setDataByName("pos_size", size > 0 ? (((float)size) / 32.0f) : 1.0f); // float not an integer - else - packet->setDataByName("pos_size", 1.0f); - // please do not remove! This makes it so NPCs for example do not resize large/small when you are in combat with them! packet->setDataByName("pos_size_ratio", 1.0f); } @@ -2487,11 +2494,6 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) { packet->setDataByName("npc", 1); if (GetMerchantID() > 0) packet->setDataByName("merchant", 1); - - if(IsEntity() && ((Entity*)this)->GetInfoStruct()->get_size_mod() != 0.0f) { - float mod = ((Entity*)this)->GetInfoStruct()->get_size_mod(); // e.g., -0.25 or +0.25 - //packet->setDataByName("temporary_scale", mod); //TODO: Understand what these mod values should be, anything we send makes the client shrink to nothing - } packet->setDataByName("effective_level", IsEntity() && ((Entity*)this)->GetInfoStruct()->get_effective_level() != 0 ? (int8)((Entity*)this)->GetInfoStruct()->get_effective_level() : (int8)GetLevel()); packet->setDataByName("level", (int8)GetLevel()); @@ -3360,8 +3362,9 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){ } if (!movementCase && IsRunning() && !IsPauseMovementTimerActive()) { - CalculateRunningLocation(); - //last_movement_update = Timer::GetCurrentTime2(); + // 6/7/2025: Fixes glitchy movement when spawns are close together and keep warping forward and back to the original position + CalculateRunningLocation(true); + last_movement_update = Timer::GetCurrentTime2(); } else if(movementCase) { diff --git a/source/WorldServer/SpellProcess.cpp b/source/WorldServer/SpellProcess.cpp index 335b825..a608a32 100644 --- a/source/WorldServer/SpellProcess.cpp +++ b/source/WorldServer/SpellProcess.cpp @@ -22,6 +22,7 @@ #include "Tradeskills/Tradeskills.h" #include "ClientPacketFunctions.h" #include "Rules/Rules.h" +#include "races.h" extern MasterSpellList master_spell_list; extern MasterSkillList master_skill_list; @@ -30,6 +31,7 @@ extern LuaInterface* lua_interface; extern Commands commands; extern World world; extern RuleManager rule_manager; +extern Races races; SpellProcess::SpellProcess(){ last_checked_time = 0; @@ -510,32 +512,18 @@ bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removi if(target && target->IsPlayer() && spell->spell->GetSpellData()->fade_message.length() > 0){ Client* client = ((Player*)target)->GetClient(); if(client){ - bool send_to_sender = true; string fade_message = spell->spell->GetSpellData()->fade_message; - if(fade_message.find("%t") != string::npos) - fade_message.replace(fade_message.find("%t"), 2, target->GetName()); + ReplaceEffectTokens(fade_message, spell->caster, target); client->Message(CHANNEL_SPELLS_OTHER, fade_message.c_str()); } } - if (target && target->IsPlayer() && spell->spell->GetSpellData()->fade_message.length() > 0) { + if (target && target->IsPlayer() && spell->spell->GetSpellData()->fade_message_others.length() > 0) { Client* client = ((Player*)target)->GetClient(); if (client) { - bool send_to_sender = true; string fade_message_others = spell->spell->GetSpellData()->fade_message_others; - if (fade_message_others.find("%t") != string::npos) - fade_message_others.replace(fade_message_others.find("%t"), 2, target->GetName()); - if (fade_message_others.find("%c") != string::npos) - fade_message_others.replace(fade_message_others.find("%c"), 2, spell->caster->GetName()); - if (fade_message_others.find("%T") != string::npos) { - fade_message_others.replace(fade_message_others.find("%T"), 2, target->GetName()); - send_to_sender = false; - } - if (fade_message_others.find("%C") != string::npos) { - fade_message_others.replace(fade_message_others.find("%C"), 2, spell->caster->GetName()); - send_to_sender = false; - } + ReplaceEffectTokens(fade_message_others, spell->caster, target); if(spell->caster->GetZone()) { - spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, fade_message_others.c_str(), target, 50, send_to_sender); + spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, fade_message_others.c_str(), target, 50); } } } @@ -1803,29 +1791,16 @@ bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive, bool in_her if(spell->spell->GetSpellData()->success_message.length() > 0){ if(client){ string success_message = spell->spell->GetSpellData()->success_message; - if(success_message.find("%t") != string::npos) - success_message.replace(success_message.find("%t"), 2, spell->caster->GetName()); + ReplaceEffectTokens(success_message, spell->caster, target); client->Message(CHANNEL_SPELLS, success_message.c_str()); } } if(spell->spell->GetSpellData()->effect_message.length() > 0){ string effect_message = spell->spell->GetSpellData()->effect_message; - bool send_to_sender = true; - if(effect_message.find("%t") != string::npos) - effect_message.replace(effect_message.find("%t"), 2, target->GetName()); - if (effect_message.find("%c") != string::npos) - effect_message.replace(effect_message.find("%c"), 2, spell->caster->GetName()); - if (effect_message.find("%T") != string::npos) { - effect_message.replace(effect_message.find("%T"), 2, target->GetName()); - send_to_sender = false; - } - if (effect_message.find("%C") != string::npos) { - effect_message.replace(effect_message.find("%C"), 2, spell->caster->GetName()); - send_to_sender = false; - } + ReplaceEffectTokens(effect_message, spell->caster, target); if(spell->caster && spell->caster->GetZone()) { - spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, effect_message.c_str(), target, 50, send_to_sender); + spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, effect_message.c_str(), target, 50); } } if(target->GetZone()) { @@ -3099,4 +3074,193 @@ bool SpellProcess::AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_sp lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); return ret; +} + +void SpellProcess::ReplaceEffectTokens(std::string& message, Spawn* in_caster, Spawn* in_target) { + std::string caster = in_caster ? std::string(in_caster->GetName()) : "Unknown"; + std::string target = in_target ? std::string(in_target->GetName()) : "Unknown"; + size_t pos; + + if(message.find("%", 0) == std::string::npos) + return; + + // Replace all %C and suppress send + pos = 0; + while ((pos = message.find("%C", pos)) != std::string::npos) { + message.replace(pos, 2, caster); + pos += caster.length(); + } + + // Replace all %c (no suppression) + pos = 0; + while ((pos = message.find("%c", pos)) != std::string::npos) { + message.replace(pos, 2, caster); + pos += caster.length(); + } + + // Replace all %T and suppress send + pos = 0; + while ((pos = message.find("%T", pos)) != std::string::npos) { + message.replace(pos, 2, target); + pos += target.length(); + } + + // Replace all %t (no suppression) + pos = 0; + while ((pos = message.find("%t", pos)) != std::string::npos) { + message.replace(pos, 2, target); + pos += target.length(); + } + + std::string target_assist = in_target && in_target->GetTarget() ? std::string(in_target->GetTarget()->GetName()) : "Unknown"; + + if(target_assist == "Unknown" && target != "Unknown") + target_assist = target; + // Replace all %T and suppress send + pos = 0; + while ((pos = message.find("%A", pos)) != std::string::npos) { + message.replace(pos, 2, target_assist); + pos += target_assist.length(); + } + + // Replace all %t (no suppression) + pos = 0; + while ((pos = message.find("%a", pos)) != std::string::npos) { + message.replace(pos, 2, target_assist); + pos += target_assist.length(); + } + + std::string gender_name = "Unknown"; + std::string gender_oname = "It"; + std::string gender_sname = "It"; + std::string gender_pname = "Its"; + + if(in_target) { + if(in_target->GetGender() == 1) { + gender_name = "Male"; + gender_oname = "Him"; + gender_sname = "He"; + gender_pname = "His"; + } + else if(in_target->GetGender() == 0) { + gender_name = "Female"; + gender_oname = "Her"; + gender_sname = "She"; + gender_pname = "Her"; + } + } + + // Replace all %T and suppress send + pos = 0; + while ((pos = message.find("%G", pos)) != std::string::npos) { + message.replace(pos, 2, gender_name); + pos += gender_name.length(); + } + + // Replace all %t (no suppression) + pos = 0; + while ((pos = message.find("%g", pos)) != std::string::npos) { + message.replace(pos, 2, gender_name); + pos += gender_name.length(); + } + + pos = 0; + while ((pos = message.find("%O", pos)) != std::string::npos) { + message.replace(pos, 2, gender_oname); + pos += gender_oname.length(); + } + + // Replace all %t (no suppression) + pos = 0; + while ((pos = message.find("%o", pos)) != std::string::npos) { + message.replace(pos, 2, gender_oname); + pos += gender_oname.length(); + } + + + pos = 0; + while ((pos = message.find("%S", pos)) != std::string::npos) { + message.replace(pos, 2, gender_sname); + pos += gender_sname.length(); + } + + // Replace all %t (no suppression) + pos = 0; + while ((pos = message.find("%s", pos)) != std::string::npos) { + message.replace(pos, 2, gender_sname); + pos += gender_sname.length(); + } + + + pos = 0; + while ((pos = message.find("%P", pos)) != std::string::npos) { + message.replace(pos, 2, gender_pname); + pos += gender_pname.length(); + } + + // Replace all %t (no suppression) + pos = 0; + while ((pos = message.find("%p", pos)) != std::string::npos) { + message.replace(pos, 2, gender_pname); + pos += gender_pname.length(); + } + + std::string raceName = "Unknown"; + if(in_target) { + const char* raceCaseName = races.GetRaceNameCase(in_target->GetRace()); + if(raceCaseName) + raceName = std::string(raceCaseName); + } + + + std::string tell_name = "Unknown"; + if(in_caster && in_caster->IsPlayer()) { + if(((Player*)in_caster)->GetClient() && ((Player*)in_caster)->GetClient()->GetLastTellName().size() > 0) + tell_name = ((Player*)in_caster)->GetClient()->GetLastTellName(); + } + pos = 0; + while ((pos = message.find("%RT", pos)) != std::string::npos) { + message.replace(pos, 3, tell_name); + pos += tell_name.length(); + } + // Replace all %t (no suppression) + pos = 0; + while ((pos = message.find("%rt", pos)) != std::string::npos) { + message.replace(pos, 3, tell_name); + pos += tell_name.length(); + } + + pos = 0; + while ((pos = message.find("%R", pos)) != std::string::npos) { + message.replace(pos, 2, raceName); + pos += raceName.length(); + } + + // Replace all %t (no suppression) + pos = 0; + while ((pos = message.find("%r", pos)) != std::string::npos) { + message.replace(pos, 2, raceName); + pos += raceName.length(); + } + + + + std::string petName = "Unknown"; + if(in_caster && in_caster->IsEntity() && ((Entity*)in_caster)->GetPet()) { + petName = std::string(((Entity*)in_caster)->GetPet()->GetName()); + } + + + pos = 0; + while ((pos = message.find("%M", pos)) != std::string::npos) { + message.replace(pos, 2, petName); + pos += petName.length(); + } + + // Replace all %t (no suppression) + pos = 0; + while ((pos = message.find("%m", pos)) != std::string::npos) { + message.replace(pos, 2, petName); + pos += petName.length(); + } } \ No newline at end of file diff --git a/source/WorldServer/SpellProcess.h b/source/WorldServer/SpellProcess.h index b421a06..5b6f2ca 100644 --- a/source/WorldServer/SpellProcess.h +++ b/source/WorldServer/SpellProcess.h @@ -401,6 +401,8 @@ public: void DeleteActiveSpell(LuaSpell* spell, bool skipRemoveCurrent = false); static bool AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_spell_targets = true); mutable std::shared_mutex MSpellProcess; + + static void ReplaceEffectTokens(std::string& message, Spawn* in_caster, Spawn* in_target); private: MutexMap spell_que; MutexList active_spells; diff --git a/source/WorldServer/Web/PeerManager.h b/source/WorldServer/Web/PeerManager.h index fa92bb5..8600976 100644 --- a/source/WorldServer/Web/PeerManager.h +++ b/source/WorldServer/Web/PeerManager.h @@ -178,8 +178,8 @@ public: int16 minLevel, int16 maxLevel, int16 minVersion, int32 defaultLockoutTime, int32 defaultReenterTime, int8 instanceType, int32 numPlayers, bool isCityZone, void* zonePtr = nullptr); bool IsClientConnectedPeer(int32 account_id); std::string GetCharacterPeerId(std::string charName); - void SendPeersChannelMessage(int32 group_id, std::string fromName, std::string message, int16 channel, int32 language_id = 0); - void SendPeersGuildChannelMessage(int32 guild_id, std::string fromName, std::string message, int16 channel, int32 language_id = 0); + void SendPeersChannelMessage(int32 group_id, std::string fromName, std::string message, int16 channel, int32 language_id = 0, int8 custom_type = 0); + void SendPeersGuildChannelMessage(int32 guild_id, std::string fromName, std::string message, int16 channel, int32 language_id = 0, int8 custom_type = 0); void sendZonePeerList(Client* client); std::string getZonePeerId(const std::string& inc_zone_name, int32 inc_zone_id, int32 inc_instance_id, ZoneChangeDetails* opt_details = nullptr, bool only_always_loaded = false, int32 matchDuplicatedId = 0); int32 getZoneHighestDuplicateId(const std::string& inc_zone_name, int32 inc_zone_id, bool increment_new_value = true); diff --git a/source/WorldServer/Web/WorldWeb.cpp b/source/WorldServer/Web/WorldWeb.cpp index a9e6588..99a8e12 100644 --- a/source/WorldServer/Web/WorldWeb.cpp +++ b/source/WorldServer/Web/WorldWeb.cpp @@ -273,6 +273,15 @@ void World::Web_worldhandle_reloadcommand(const http::request world.RemoveReloadingSubSystem("ZoneScripts"); break; } + case COMMAND_RELOAD_PLAYERSCRIPTS: { + world.SetReloadingSubsystem("PlayerScripts"); + world.ResetPlayerScripts(); + world.LoadPlayerScripts(); + if (lua_interface) + lua_interface->DestroyPlayerScripts(); + world.RemoveReloadingSubSystem("PlayerScripts"); + break; + } case COMMAND_RELOAD_FACTIONS: { world.SetReloadingSubsystem("Factions"); master_faction_list.Clear(); @@ -368,6 +377,16 @@ void World::Web_worldhandle_reloadcommand(const http::request break; } + case COMMAND_RELOAD_ITEMS: { + LogWrite(COMMAND__INFO, 0, "Command", "Reloading items.."); + + database.ReloadItemList(sub_command); + + if(!item_id) { + database.LoadMerchantInformation(); // we skip if there is only a reload of single item not all items + } + break; + } default: { success = 0; break; @@ -723,6 +742,7 @@ void World::Web_worldhandle_sendglobalmessage(const http::request("to_name")) { toName = name.get(); } @@ -744,6 +764,9 @@ void World::Web_worldhandle_sendglobalmessage(const http::request("guild_id")) { guild_id = guildID.get(); } + if (auto customType = json_tree.get_optional("custom_type")) { + custom_type = customType.get(); + } Client* find_client = zone_list.GetClientByCharName(toName.c_str()); if (find_client && find_client->GetPlayer()->IsIgnored(fromName.c_str())) @@ -754,6 +777,7 @@ void World::Web_worldhandle_sendglobalmessage(const http::requestGetPlayer()) { success = 1; toName = std::string(find_client->GetPlayer()->GetName()); + find_client->SetLastTellName(fromName); find_client->HandleTellMessage(fromName.c_str(), msg.c_str(), toName.c_str(), language); if (find_client->GetPlayer()->get_character_flag(CF_AFK)) { find_client->HandleTellMessage(toName.c_str(), find_client->GetPlayer()->GetAwayMessage().c_str(), fromName.c_str(), find_client->GetPlayer()->GetCurrentLanguage()); @@ -819,6 +843,17 @@ void World::Web_worldhandle_sendglobalmessage(const http::request. */ + +#include +#include +#include +#include +#include #include #include "World.h" #include "Items/Items.h" @@ -44,10 +50,10 @@ #include "Chat/Chat.h" #include "Tradeskills/Tradeskills.h" #include "AltAdvancement/AltAdvancement.h" -#include "LuaInterface.h" #include "HeroicOp/HeroicOp.h" #include "RaceTypes/RaceTypes.h" #include "LuaInterface.h" +#include "SpellProcess.h" #include "../common/version.h" #include "Player.h" @@ -55,10 +61,6 @@ #include "Web/PeerManager.h" #include "Web/HTTPSClientPool.h" -#include -#include -#include - MasterQuestList master_quest_list; MasterItemList master_item_list; MasterSpellList master_spell_list; @@ -90,7 +92,6 @@ map EQOpcodeVersions; WorldDatabase database; GuildList guild_list; Chat chat; -Player player; extern ConfigReader configReader; extern LoginServer loginserver; @@ -103,6 +104,8 @@ extern sint32 numclients; extern PeerManager peer_manager; extern HTTPSClientPool peer_https_pool; +namespace fs = std::filesystem; + World::World() : save_time_timer(300000), time_tick_timer(3000), vitality_timer(3600000), player_stats_timer(60000), server_stats_timer(60000), /*remove_grouped_player(30000),*/ guilds_timer(60000), lotto_players_timer(500), watchdog_timer(10000) { save_time_timer.Start(); time_tick_timer.Start(); @@ -195,7 +198,7 @@ void World::init(std::string web_ipaddr, int16 web_port, std::string cert_file, LoadVoiceOvers(); LogWrite(WORLD__DEBUG, 1, "World", "-Loading EXP Curve From DB..."); - player.InitXPTable(); + Player::InitXPTable(); LogWrite(WORLD__DEBUG, 1, "World", "-Loading EXP Curve From DB Complete!"); LogWrite(WORLD__DEBUG, 1, "World", "-Setting system parameters..."); @@ -537,6 +540,9 @@ bool ZoneList::HandleGlobalChatMessage(Client* from, char* to, int16 channel, co LogWrite(WORLD__ERROR, 0, "World", "HandleGlobalChatMessage() called with an invalid client"); return false; } + + std::string tokenedMsg = std::string(message); + SpellProcess::ReplaceEffectTokens(tokenedMsg, from->GetPlayer(), from->GetPlayer()->GetTarget()); if(channel == CHANNEL_PRIVATE_TELL){ Client* find_client = zone_list.GetClientByCharName(to); if(find_client && find_client->GetPlayer()->IsIgnored(from->GetPlayer()->GetName())) @@ -549,7 +555,7 @@ bool ZoneList::HandleGlobalChatMessage(Client* from, char* to, int16 channel, co boost::property_tree::ptree root; root.put("from_name", from->GetPlayer()->GetName()); root.put("to_name", to); - root.put("message", message); + root.put("message", tokenedMsg.c_str()); root.put("from_language", current_language_id); root.put("channel", channel); std::ostringstream jsonStream; @@ -572,8 +578,9 @@ bool ZoneList::HandleGlobalChatMessage(Client* from, char* to, int16 channel, co else { const char* whoto = find_client->GetPlayer()->GetName(); - find_client->HandleTellMessage(from->GetPlayer()->GetName(), message, whoto, from->GetPlayer()->GetCurrentLanguage()); - from->HandleTellMessage(from->GetPlayer()->GetName(), message, whoto, from->GetPlayer()->GetCurrentLanguage()); + find_client->SetLastTellName(std::string(from->GetPlayer()->GetName())); + find_client->HandleTellMessage(from->GetPlayer()->GetName(), tokenedMsg.c_str(), whoto, from->GetPlayer()->GetCurrentLanguage()); + from->HandleTellMessage(from->GetPlayer()->GetName(), tokenedMsg.c_str(), whoto, from->GetPlayer()->GetCurrentLanguage()); if (find_client->GetPlayer()->get_character_flag(CF_AFK)) { find_client->HandleTellMessage(find_client->GetPlayer()->GetName(), find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); from->HandleTellMessage(find_client->GetPlayer()->GetName(), find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); @@ -641,6 +648,11 @@ void ZoneList::DeleteSpellProcess(){ MZoneList.releasereadlock(__FUNCTION__, __LINE__); } +void ZoneList::TransmitBroadcast(const char* message) { + HandleGlobalBroadcast(message); + peer_manager.SendPeersChannelMessage(0, "", std::string(message), CHANNEL_BROADCAST, 0); +} + void ZoneList::HandleGlobalBroadcast(const char* message) { list::iterator zone_iter; ZoneServer* zone = 0; @@ -653,6 +665,11 @@ void ZoneList::HandleGlobalBroadcast(const char* message) { MZoneList.releasereadlock(__FUNCTION__, __LINE__); } +void ZoneList::TransmitGlobalAnnouncement(const char* message) { + HandleGlobalAnnouncement(message); + peer_manager.SendPeersChannelMessage(0, "", std::string(message), CHANNEL_BROADCAST, 1); +} + void ZoneList::HandleGlobalAnnouncement(const char* message) { list::iterator zone_iter; ZoneServer* zone = 0; @@ -945,12 +962,13 @@ std::string PeerManager::GetCharacterPeerId(std::string charName) { return ""; } -void PeerManager::SendPeersChannelMessage(int32 group_id, std::string fromName, std::string message, int16 channel, int32 language_id) { +void PeerManager::SendPeersChannelMessage(int32 group_id, std::string fromName, std::string message, int16 channel, int32 language_id, int8 custom_type) { boost::property_tree::ptree root; root.put("message", message); root.put("channel", channel); root.put("group_id", group_id); root.put("from_language", language_id); + root.put("custom_type", custom_type); root.put("from_name", fromName); std::ostringstream jsonStream; boost::property_tree::write_json(jsonStream, root); @@ -979,13 +997,14 @@ void PeerManager::SendPeersChannelMessage(int32 group_id, std::string fromName, } } -void PeerManager::SendPeersGuildChannelMessage(int32 guild_id, std::string fromName, std::string message, int16 channel, int32 language_id) { +void PeerManager::SendPeersGuildChannelMessage(int32 guild_id, std::string fromName, std::string message, int16 channel, int32 language_id, int8 custom_type) { boost::property_tree::ptree root; root.put("message", message); root.put("channel", channel); root.put("guild_id", guild_id); root.put("from_language", language_id); root.put("from_name", fromName); + root.put("custom_type", custom_type); std::ostringstream jsonStream; boost::property_tree::write_json(jsonStream, root); std::string jsonPayload = jsonStream.str(); @@ -1794,6 +1813,39 @@ void World::AddZoneScript(int32 id, const char* name) { MZoneScripts.unlock(); } +void World::LoadPlayerScripts() { + const fs::path scriptDir = "PlayerScripts"; + bool hasGlobalLua = false; + + if (!fs::exists(scriptDir) || !fs::is_directory(scriptDir)) { + std::cerr << "Directory does not exist: " << scriptDir << std::endl; + return; + } + + for (const auto& entry : fs::directory_iterator(scriptDir)) { + if (entry.is_regular_file() && entry.path().extension() == ".lua") { + std::string baseName = entry.path().stem().string(); // Strips extension + const std::string filename = entry.path().string(); + int32 zoneID = database.GetZoneID(filename.c_str()); + std::cout << " - Load File " << filename << " with base name: " << baseName << "\n"; + if(zoneID) { + AddPlayerScript(zoneID, filename.c_str()); + } + else if (baseName == "global") { + AddPlayerScript(0, filename.c_str()); + hasGlobalLua = true; + } + } + } +} + +void World::AddPlayerScript(int32 zone_id, const char* zone_name) { + MPlayerScripts.lock(); + if (zone_name) + player_scripts[zone_id] = string(zone_name); + MPlayerScripts.unlock(); +} + const char* World::GetSpawnScript(int32 id){ LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); const char* ret = 0; @@ -1836,6 +1888,15 @@ const char* World::GetZoneScript(int32 id) { return ret; } +const char* World::GetPlayerScript(int32 zone_id) { + const char* ret = 0; + MPlayerScripts.lock(); + if (player_scripts.count(zone_id) > 0) + ret = player_scripts[zone_id].c_str(); + MPlayerScripts.unlock(); + return ret; +} + void World::ResetSpawnScripts(){ MSpawnScripts.lock(); spawn_scripts.clear(); @@ -1850,7 +1911,11 @@ void World::ResetZoneScripts() { MZoneScripts.unlock(); } - +void World::ResetPlayerScripts() { + MPlayerScripts.lock(); + player_scripts.clear(); + MPlayerScripts.unlock(); +} vector* World::GetMerchantItemList(int32 merchant_id, int8 merchant_type, Player* player) { @@ -2595,10 +2660,10 @@ void World::CheckLottoPlayers() { memset(announcement, 0, sizeof(announcement)); sprintf(coin_message, "%s", client->GetCoinMessage(jackpot).c_str()); sprintf(message, "Congratulations! You have won %s!", coin_message); - sprintf(announcement, "%s as won the jackpot containing a total of %s!", client->GetPlayer()->GetName(), coin_message); + sprintf(announcement, "%s has won the jackpot containing a total of %s!", client->GetPlayer()->GetName(), coin_message); client->Message(CHANNEL_COLOR_YELLOW, "You receive %s.", coin_message); client->SendPopupMessage(0, message, "", 2, 0xFF, 0xFF, 0x99); - zone_list.HandleGlobalAnnouncement(announcement); + zone_list.TransmitGlobalAnnouncement(announcement); client->GetPlayer()->AddCoins(jackpot); client->GetPlayer()->GetZone()->SendCastSpellPacket(843, client->GetPlayer()); client->GetPlayer()->GetZone()->SendCastSpellPacket(1413, client->GetPlayer()); diff --git a/source/WorldServer/World.h b/source/WorldServer/World.h index 400431a..44806c1 100644 --- a/source/WorldServer/World.h +++ b/source/WorldServer/World.h @@ -1,6 +1,6 @@ /* EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) This file is part of EQ2Emulator. @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with EQ2Emulator. If not, see . */ + #ifndef EQ2_WORLD_H #define EQ2_WORLD_H @@ -476,6 +477,10 @@ class ZoneList { void CheckFriendZoned(Client* client); // move to Chat/Chat.h? + void TransmitBroadcast(const char* message); + void TransmitGlobalAnnouncement(const char* message); + + // these are handled internally by the transmit and peering bool HandleGlobalChatMessage(Client* from, char* to, int16 channel, const char* message, const char* channel_name = 0, int32 current_language_id = 0); void HandleGlobalBroadcast(const char* message); void HandleGlobalAnnouncement(const char* message); @@ -589,12 +594,16 @@ public: void AddSpawnEntryScript(int32 id, const char* name); void AddSpawnLocationScript(int32 id, const char* name); void AddZoneScript(int32 id, const char* name); + void LoadPlayerScripts(); + void AddPlayerScript(int32 zone_id, const char* zone_name); const char* GetSpawnScript(int32 id); const char* GetSpawnEntryScript(int32 id); const char* GetSpawnLocationScript(int32 id); const char* GetZoneScript(int32 id); + const char* GetPlayerScript(int32 zone_id); void ResetSpawnScripts(); void ResetZoneScripts(); + void ResetPlayerScripts(); int16 GetMerchantItemQuantity(int32 merchant_id, int32 item_id); void DecreaseMerchantQuantity(int32 merchant_id, int32 item_id, int16 amount); int32 GetInventoryID(int32 merchant_id, int32 item_id); @@ -765,6 +774,7 @@ private: Mutex MMerchantList; Mutex MSpawnScripts; Mutex MZoneScripts; + Mutex MPlayerScripts; //Mutex MGroups; mutable std::shared_mutex MNPCSpells; @@ -783,6 +793,7 @@ private: map spawnentry_scripts; map spawnlocation_scripts; map zone_scripts; + map player_scripts; //vector player_groups; //map group_removal_pending; //map pending_groups; diff --git a/source/WorldServer/WorldDatabase.cpp b/source/WorldServer/WorldDatabase.cpp index f119256..3b1cf47 100644 --- a/source/WorldServer/WorldDatabase.cpp +++ b/source/WorldServer/WorldDatabase.cpp @@ -3251,8 +3251,9 @@ string WorldDatabase::GetExpansionIDByVersion(int16 version) } -void WorldDatabase::LoadSpecialZones(){ - LogWrite(ZONE__INFO, 0, "Zone", "Starting static zones..."); +void WorldDatabase::LoadSpecialZones(bool silent){ + if(!silent) + LogWrite(ZONE__INFO, 0, "Zone", "Starting static zones..."); Query query; ZoneServer* zone = 0; MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name, always_loaded, peer_priority FROM zones where always_loaded = 1"); @@ -5589,6 +5590,7 @@ string WorldDatabase::GetMerchantDescription(int32 merchant_id) { void WorldDatabase::LoadTransporters(ZoneServer* zone){ int32 total = 0; + zone->DeleteTransporters(); zone->DeleteGlobalTransporters(); Query query; MYSQL_ROW row; diff --git a/source/WorldServer/WorldDatabase.h b/source/WorldServer/WorldDatabase.h index 847ea0a..40c4742 100644 --- a/source/WorldServer/WorldDatabase.h +++ b/source/WorldServer/WorldDatabase.h @@ -208,7 +208,7 @@ public: int32 SaveCharacter(PacketStruct* create, int32 loginID); int32 LoadNPCAppearanceEquipmentData(ZoneServer* zone); void SaveNPCAppearanceEquipment(int32 spawn_id, int8 slot_id, int16 type, int8 red=0, int8 green=0, int8 blue=0, int8 hred=0, int8 hgreen=0, int8 hblue=0); - void LoadSpecialZones(); + void LoadSpecialZones(bool silent=false); void SaveCharacterSkills(Client* client); void SaveCharacterQuests(Client* client); void SaveCharacterQuestProgress(Client* client, Quest* quest); diff --git a/source/WorldServer/client.cpp b/source/WorldServer/client.cpp index 4453faa..2a7be1d 100644 --- a/source/WorldServer/client.cpp +++ b/source/WorldServer/client.cpp @@ -1,22 +1,23 @@ -/* -EQ2Emulator: Everquest II Server Emulator -Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) -This file is part of EQ2Emulator. + This file is part of EQ2Emulator. -EQ2Emulator 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, either version 3 of the License, or -(at your option) any later version. + EQ2Emulator 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, either version 3 of the License, or + (at your option) any later version. -EQ2Emulator is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. + EQ2Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; 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 EQ2Emulator. If not, see . + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . */ + #include "../common/debug.h" #include "../common/Log.h" #include @@ -136,7 +137,7 @@ Client::Client(EQStream* ieqs) : underworld_cooldown_timer(5000), pos_update(125 eqs = ieqs; ip = eqs->GetrIP(); port = ntohs(eqs->GetrPort()); - merchant_transaction = nullptr; + merchant_transaction_id = 0; mail_window.item = nullptr; // don't want this to be set(loose ptr) when using ResetSendMail to provide rest of the defaults ResetSendMail(false); timestamp_flag = 0; @@ -190,7 +191,7 @@ Client::Client(EQStream* ieqs) : underworld_cooldown_timer(5000), pos_update(125 dead_z = 0.0f; dead_h = 0.0f; lua_debug_timer.Disable(); - transport_spawn = 0; + transport_spawn_id = 0; MBuyBack.SetName("Client::MBuyBack"); MDeletePlayer.SetName("Client::MDeletePlayer"); MQuestPendingUpdates.SetName("Client::MQuestPendingUpdates"); @@ -5428,12 +5429,29 @@ void Client::SendPopupMessage(int8 unknown, const char* text, const char* type, } -void Client::ChangeLevel(int16 old_level, int16 new_level) { +bool Client::ChangeLevel(int16 old_level, int16 new_level, int32 xp_earned) { if (new_level < 1) { SimpleMessage(CHANNEL_COLOR_RED, "You cannot be lower than level 1!"); - return; + return false; + } + const char* playerScript = world.GetPlayerScript(0); // 0 = global script + const char* playerZoneScript = world.GetPlayerScript(GetCurrentZoneID()); // zone script + sint32 returnValue = 0; + if(playerScript || playerZoneScript) { + std::vector args = { + LuaArg(GetPlayer()->GetZone()), + LuaArg(GetPlayer()), + LuaArg(old_level), + LuaArg(new_level), + LuaArg(xp_earned) + }; + if(playerScript && lua_interface->RunPlayerScriptWithReturn(playerScript, "on_level_up", args, &returnValue) && returnValue == -1) { + return false; + } + if(playerZoneScript && lua_interface->RunPlayerScriptWithReturn(playerZoneScript, "on_level_up", args, &returnValue) && returnValue == -1) { + return false; + } } - if (player->GetLevel() != new_level) { player->SetLevel(new_level); if (player->GetGroupMemberInfo()) { @@ -5625,17 +5643,38 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) { if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower()) GetPlayer()->GetZone()->AddDamagedSpawn(GetPlayer()); + + return true; } -void Client::ChangeTSLevel(int16 old_level, int16 new_level) { +bool Client::ChangeTSLevel(int16 old_level, int16 new_level, int32 xp_earned) { if (new_level < 1) { SimpleMessage(CHANNEL_COLOR_RED, "You cannot be lower than level 1!"); - return; + return false; } if ((player->GetTSLevel() >= 9 && player->GetTradeskillClass() == 1) || (player->GetTSLevel() >= 19 && (player->GetTradeskillClass() == 1 || player->GetTradeskillClass() == 2 || player->GetTradeskillClass() == 6 || player->GetTradeskillClass() == 10))) { SimpleMessage(CHANNEL_COLOR_YELLOW, "You can not gain levels until you select your next class!"); - return; + return false; } + const char* playerScript = world.GetPlayerScript(0); // 0 = global script + const char* playerZoneScript = world.GetPlayerScript(GetCurrentZoneID()); // zone script + sint32 returnValue = 0; + if(playerScript || playerZoneScript) { + std::vector args = { + LuaArg(GetPlayer()->GetZone()), + LuaArg(GetPlayer()), + LuaArg(old_level), + LuaArg(new_level), + LuaArg(xp_earned) + }; + if(playerScript && lua_interface->RunPlayerScriptWithReturn(playerScript, "on_tradeskill_level_up", args, &returnValue) && returnValue == -1) { + return false; + } + if(playerZoneScript && lua_interface->RunPlayerScriptWithReturn(playerZoneScript, "on_tradeskill_level_up", args, &returnValue) && returnValue == -1) { + return false; + } + } + if (new_level > old_level) player->UpdatePlayerHistory(HISTORY_TYPE_XP, HISTORY_SUBTYPE_TRADESKILL, new_level, player->GetTradeskillClass()); @@ -5820,6 +5859,7 @@ void Client::ChangeTSLevel(int16 old_level, int16 new_level) { // to when you are actually able to select traits. QueuePacket(GetPlayer()->GetPlayerInfo()->serialize(GetVersion())); QueuePacket(master_trait_list.GetTraitListPacket(this)); + return true; } void Client::CloseLoot(int32 spawn_id) { @@ -6381,19 +6421,18 @@ void Client::CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier, f } } -Spawn* Client::GetBanker() { - return banker; +int32 Client::GetBanker() { + return banker_id; } -void Client::SetBanker(Spawn* in_banker) { - banker = in_banker; - +void Client::SetBanker(int32 in_banker) { + banker_id = in_banker; } void Client::Bank(Spawn* banker, bool cancel) { if (banker && banker->primary_command_list.size() > 0 && banker->primary_command_list[0]->command == "bank") { if (!cancel) - SetBanker(banker); + SetBanker(banker->GetID()); else SetBanker(0); PacketStruct* packet = configReader.getStruct("WS_UpdateBank", GetVersion()); @@ -6550,7 +6589,8 @@ bool Client::BankWithdrawalNoBanker(int64 amount) { void Client::BankWithdrawal(int64 amount) { bool cheater = false; - if (GetBanker() && amount > 0) { + Spawn* banker = GetCurrentZone()->GetSpawnByID(GetBanker()); + if (banker && amount > 0) { string withdrawal = ""; char withdrawal_data[512] = { 0 }; int32 tmp = 0; @@ -6638,7 +6678,8 @@ void Client::BankWithdrawal(int64 amount) { void Client::BankDeposit(int64 amount) { bool cheater = false; - if (GetBanker() && amount > 0) { + Spawn* banker = GetCurrentZone()->GetSpawnByID(GetBanker()); + if (banker && amount > 0) { int32 tmp = 0; char deposit_data[512] = { 0 }; string deposit = ""; @@ -8129,12 +8170,17 @@ void Client::SetLuaDebugClient(bool val) { } void Client::SetMerchantTransaction(Spawn* spawn) { - merchant_transaction = spawn; + if(spawn) { + merchant_transaction_id = spawn->GetID(); + } + else { + merchant_transaction_id = 0; + } } -Spawn* Client::GetMerchantTransaction() { - return merchant_transaction; +int32 Client::GetMerchantTransactionID() { + return merchant_transaction_id; } void Client::SetMailTransaction(Spawn* spawn) { @@ -8199,7 +8245,7 @@ float Client::CalculateSellMultiplier(int32 merchant_id) { } void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); Guild* guild = GetPlayer()->GetGuild(); if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) && spawn->IsClientInMerchantLevelRange(this)) { @@ -8317,7 +8363,7 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { } void Client::BuyBack(int32 item_id, int16 quantity) { - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) && spawn->IsClientInMerchantLevelRange(this)) { deque::iterator itr; @@ -8391,7 +8437,7 @@ void Client::BuyBack(int32 item_id, int16 quantity) { void Client::BuyItem(int32 item_id, int16 quantity) { // Get the merchant we are buying from - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); // Make sure the spawn has a merchant list if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { int32 total_buy_price = 0; @@ -8585,7 +8631,7 @@ void Client::BuyItem(int32 item_id, int16 quantity) { } void Client::RepairItem(int32 item_id) { - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); if (spawn && (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)) { Item* item = player->item_list.GetItemFromID(item_id); if (!item) @@ -8623,7 +8669,7 @@ void Client::RepairItem(int32 item_id) { } void Client::RepairAllItems() { - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); if (spawn && (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)) { vector* repairable_items = GetRepairableItems(); if (repairable_items && repairable_items->size() > 0) { @@ -8771,7 +8817,7 @@ void Client::SendAchievementUpdate(bool first_login) { } void Client::SendBuyMerchantList(bool sell) { - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { vector* items = world.GetMerchantItemList(spawn->GetMerchantID(), spawn->GetMerchantType(), player); if (items) { @@ -8918,7 +8964,7 @@ void Client::SendBuyMerchantList(bool sell) { } void Client::SendSellMerchantList(bool sell) { - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); if (!spawn || (spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY) || (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO)) return; @@ -9054,7 +9100,7 @@ void Client::SendSellMerchantList(bool sell) { void Client::SendBuyBackList(bool sell) { if (GetVersion() <= 561) //this wasn't added until LU37 on July 31st 2007, well after the DoF client return; - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { deque::iterator itr; int i = 0; @@ -9127,7 +9173,7 @@ void Client::SendBuyBackList(bool sell) { } void Client::SendRepairList() { - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); if (spawn) { vector* repairable_items = GetRepairableItems(); PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); @@ -9202,7 +9248,7 @@ void Client::ShowLottoWindow() { SimpleMessage(CHANNEL_COLOR_RED, "This client does not support the gambler UI, only Desert of Flames or later client."); return; } - Spawn* spawn = GetMerchantTransaction(); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); if (spawn) { int32 item_id = rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, GamblingTokenItemID)->GetInt32(); @@ -9554,6 +9600,25 @@ void Client::DisplayMailMessage(int32 mail_id) { safe_delete(update); } if (!mail->already_read) { + const char* playerScript = world.GetPlayerScript(0); // 0 = global script + const char* playerZoneScript = world.GetPlayerScript(GetCurrentZoneID()); // zone script + if(playerScript || playerZoneScript) { + std::vector args = { + LuaArg(GetCurrentZone()), + LuaArg(GetPlayer()), + LuaArg(GetMailTransaction()), + LuaArg(mail->player_from), + LuaArg(mail->subject), + LuaArg(mail->mail_body), + LuaArg(mail->char_item_id) + }; + if(playerScript) { + lua_interface->RunPlayerScriptWithReturn(playerScript, "on_mail_first_read", args); + } + if(playerZoneScript) { + lua_interface->RunPlayerScriptWithReturn(playerZoneScript, "on_mail_first_read", args); + } + } mail->already_read = true; SendMailList(); } @@ -9577,13 +9642,15 @@ void Client::DisplayMailMessage(int32 mail_id) { if (mail->stack || mail->char_item_id) { Item* item = master_item_list.GetItem(mail->char_item_id); - item->stack_count = mail->stack > 1 ? mail->stack : 0; - if (version < 860) - packet->setItemByName("item", item, player, 0, version <= 373 ? -2 : -1); - else if (version < 1193) - packet->setItemByName("item", item, player, 0, 0); - else - packet->setItemByName("item", item, player, 0, 2); + if(item) { + item->stack_count = mail->stack > 1 ? mail->stack : 0; + if (version < 860) + packet->setItemByName("item", item, player, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemByName("item", item, player, 0, 0); + else + packet->setItemByName("item", item, player, 0, 2); + } } else { @@ -10033,7 +10100,7 @@ void Client::ProcessTeleport(Spawn* spawn, vector* destin if (transport_id > 0) has_map = GetCurrentZone()->TransportHasMap(transport_id); - transport_spawn = spawn; + transport_spawn_id = spawn->GetID(); vector transport_list; vector::iterator itr; TransportDestination* destination = 0; @@ -10190,7 +10257,7 @@ void Client::ProcessTeleportLocation(EQApplicationPacket* app) { zone_name = zone_name.substr(0, lastSpacePos); } } - if (this->GetTemporaryTransportID() || (spawn && spawn == transport_spawn && spawn->GetTransporterID())) + if (this->GetTemporaryTransportID() || (spawn && spawn->GetID() == transport_spawn_id && spawn->GetTransporterID())) GetCurrentZone()->GetTransporters(&destinations, this, this->GetTemporaryTransportID() ? this->GetTemporaryTransportID() : spawn->GetTransporterID()); vector::iterator itr; for (itr = destinations.begin(); itr != destinations.end(); itr++) { diff --git a/source/WorldServer/client.h b/source/WorldServer/client.h index 7752663..bb947e1 100644 --- a/source/WorldServer/client.h +++ b/source/WorldServer/client.h @@ -285,8 +285,8 @@ public: void HandleQuickbarUpdateRequest(EQApplicationPacket* app); void SendPopupMessage(int8 unknown, const char* text, const char* type, float size, int8 red, int8 green, int8 blue); void PopulateSkillMap(); - void ChangeLevel(int16 old_level, int16 new_level); - void ChangeTSLevel(int16 old_level, int16 new_level); + bool ChangeLevel(int16 old_level, int16 new_level, int32 xp_earned = 0); + bool ChangeTSLevel(int16 old_level, int16 new_level, int32 xp_earned = 0); bool Summon(const char* search_name); std::string IdentifyInstanceLockout(int32 zoneID, bool displayClient = true); bool IdentifyInstance(ZoneChangeDetails* zone_details, int32 zoneID); @@ -299,8 +299,8 @@ public: bool BankWithdrawalNoBanker(int64 amount); bool BankHasCoin(int64 amount); void BankDeposit(int64 amount); - Spawn* GetBanker(); - void SetBanker(Spawn* in_banker); + int32 GetBanker(); + void SetBanker(int32 in_banker); bool AddItem(int32 item_id, int16 quantity = 0, AddItemType type = AddItemType::NOT_SET); bool AddItem(Item* item, bool* item_deleted = 0, AddItemType type = AddItemType::NOT_SET); bool AddItemToBank(int32 item_id, int16 quantity = 0); @@ -401,13 +401,14 @@ public: void RemoveCombineSpawn(Spawn* spawn); void SaveCombineSpawns(const char* name = 0); Spawn* GetCombineSpawn(); + void SetCombineSpawn(Spawn* spawn) { combine_spawn = spawn; } bool ShouldTarget(); void TargetSpawn(Spawn* spawn); void ReloadQuests(); int32 GetCurrentQuestID() { return current_quest_id; } void SetLuaDebugClient(bool val); void SetMerchantTransaction(Spawn* spawn); - Spawn* GetMerchantTransaction(); + int32 GetMerchantTransactionID(); void SetMailTransaction(Spawn* spawn); Spawn* GetMailTransaction(); void PlaySound(const char* name); @@ -715,6 +716,12 @@ public: void SendReceiveOffer(Client* client_target, int8 type, std::string name, int8 unknown2); bool SendDialogChoice(int32 spawnID, const std::string& windowTextPrompt, const std::string& acceptText, const std::string& acceptCommand, const std::string& declineText, const std::string& declineCommand, int32 time, int8 textBox, int8 textBoxRequired, int32 maxLength); + + void SetTransportSpawnID(int32 id) { transport_spawn_id = id; } + int32 GetTransportSpawnID() { return transport_spawn_id; } + + void SetLastTellName(std::string tellName) { last_tell_name = tellName; } + std::string GetLastTellName() { return last_tell_name; } DialogManager dialog_manager; private: void AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i); @@ -737,10 +744,10 @@ private: vector* search_items; int32 waypoint_id = 0; map waypoints; - Spawn* transport_spawn; + int32 transport_spawn_id; Mutex MBuyBack; deque buy_back_items; - Spawn* merchant_transaction; + int32 merchant_transaction_id; Spawn* mail_transaction; mutable std::shared_mutex MPendingQuestAccept; vector pending_quest_accept; @@ -754,7 +761,7 @@ private: mutable std::shared_mutex MConversation; map > conversation_map; int32 current_quest_id; - Spawn* banker; + int32 banker_id; map sent_spell_details; map sent_item_details; Player* player; @@ -879,6 +886,8 @@ private: uchar* recipe_xor_packet; int recipe_packet_count; int recipe_orig_packet_size; + + std::string last_tell_name; }; class ClientList { diff --git a/source/WorldServer/net.cpp b/source/WorldServer/net.cpp index a069e96..269def4 100644 --- a/source/WorldServer/net.cpp +++ b/source/WorldServer/net.cpp @@ -339,6 +339,9 @@ int main(int argc, char** argv) { LogWrite(LUA__INFO, 0, "LUA", "Loading Zone Scripts..."); database.LoadZoneScriptData(); + LogWrite(LUA__INFO, 0, "LUA", "Loading Player Scripts..."); + world.LoadPlayerScripts(); + LogWrite(WORLD__INFO, 0, "World", "Loading House Zone Data..."); database.LoadHouseZones(); database.LoadPlayerHouses(); @@ -642,7 +645,7 @@ ThreadReturnType StartPeerPoll (void* tmp) std::this_thread::sleep_for(std::chrono::milliseconds(1000)); if(check_zone > 60) { check_zone = 0; - database.LoadSpecialZones(); + database.LoadSpecialZones(true); } check_zone++; } diff --git a/source/WorldServer/zoneserver.cpp b/source/WorldServer/zoneserver.cpp index 50f2604..968dd04 100644 --- a/source/WorldServer/zoneserver.cpp +++ b/source/WorldServer/zoneserver.cpp @@ -1,20 +1,21 @@ /* -EQ2Emulator: Everquest II Server Emulator -Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) -This file is part of EQ2Emulator. -EQ2Emulator 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, either version 3 of the License, or -(at your option) any later version. + This file is part of EQ2Emulator. -EQ2Emulator is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. + EQ2Emulator 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, either version 3 of the License, or + (at your option) any later version. -You should have received a copy of the GNU General Public License -along with EQ2Emulator. If not, see . + EQ2Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; 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 EQ2Emulator. If not, see . */ #include "../common/debug.h" @@ -810,6 +811,7 @@ void ZoneServer::ProcessDepop(bool respawns_allowed, bool repop) { if(!client) continue; client->GetPlayer()->SetTarget(0); + client->SetMailTransaction(0); if(repop) client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone Repop in progress..."); else{ @@ -3910,12 +3912,14 @@ void ZoneServer::HandleChatMessage(Client* client, std::string fromName, const c void ZoneServer::HandleChatMessage(Spawn* from, const char* to, int16 channel, const char* message, float distance, const char* channel_name, bool show_bubble, int32 language){ vector::iterator client_itr; Client* client = 0; + std::string tokenedMsg = std::string(message); + SpellProcess::ReplaceEffectTokens(tokenedMsg, from, from->GetTarget()); MClientList.readlock(__FUNCTION__, __LINE__); for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { client = *client_itr; if(client && client->IsConnected()) - HandleChatMessage(client, from, to, channel, message, distance, channel_name, show_bubble, language); + HandleChatMessage(client, from, to, channel, tokenedMsg.c_str(), distance, channel_name, show_bubble, language); } MClientList.releasereadlock(__FUNCTION__, __LINE__); } @@ -4607,7 +4611,33 @@ void ZoneServer::CheckSpawnScriptTimers(){ vector::iterator itr; for(itr = call_timers.begin(); itr != call_timers.end(); itr++){ SpawnScriptTimer tmpTimer = (SpawnScriptTimer)*itr; - CallSpawnScript(GetSpawnByID(tmpTimer.spawn), SPAWN_SCRIPT_TIMER, tmpTimer.player > 0 ? GetSpawnByID(tmpTimer.player) : 0, tmpTimer.function.c_str()); + Spawn* callSpawn = GetSpawnByID(tmpTimer.spawn); + Spawn* target = nullptr; + if(tmpTimer.player) { + target = GetSpawnByID(tmpTimer.player); + } + if(callSpawn) { + if(!callSpawn->IsPlayer()) { + CallSpawnScript(GetSpawnByID(tmpTimer.spawn), SPAWN_SCRIPT_TIMER, target, tmpTimer.function.c_str()); + } + else { + const char* playerScript = world.GetPlayerScript(0); // 0 = global script + const char* playerZoneScript = world.GetPlayerScript(GetZoneID()); // zone script + if(playerScript || playerZoneScript) { + std::vector args = { + LuaArg(this), + LuaArg(callSpawn), + LuaArg(target) + }; + if(playerScript) { + lua_interface->RunPlayerScriptWithReturn(playerScript, tmpTimer.function.c_str(), args); + } + if(playerZoneScript) { + lua_interface->RunPlayerScriptWithReturn(playerZoneScript, tmpTimer.function.c_str(), args); + } + } + } + } } } } @@ -4714,11 +4744,24 @@ void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { client = *client_itr; - if (client && (client->GetVersion() > 373 || !client->IsZoning() || client->GetPlayer() != spawn)) { //don't send destroy ghost of 283 client when zoning + if (client && (!client->IsZoning() || client->GetPlayer() != spawn)) { if (client->GetPlayer()->HasTarget() && client->GetPlayer()->GetTarget() == spawn) client->GetPlayer()->SetTarget(0); - if(client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->IsSendingSpawn(spawn->GetID())) + if(client->GetMailTransaction() == spawn) + client->SetMailTransaction(0); + if(client->GetBanker() == spawn->GetID()) + client->SetBanker(0); + if(client->GetTransportSpawnID() == spawn->GetID()) + client->SetTransportSpawnID(0); + if(client->GetTempPlacementSpawn() == spawn) + client->SetTempPlacementSpawn(nullptr); + if(client->GetCombineSpawn() == spawn) + client->SetCombineSpawn(nullptr); + + //don't send destroy ghost of 283 client when zoning + if((client->GetVersion() > 373 || !client->IsZoning()) && (client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->IsSendingSpawn(spawn->GetID()))) SendRemoveSpawn(client, spawn, packet, delete_spawn); + if (spawn_range_map.count(client) > 0) spawn_range_map.Get(client)->erase(spawn->GetID()); } @@ -5375,6 +5418,29 @@ void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, boo ((NPC*)dead)->Brain()->ClearHate(); safe_delete(encounter); + + const char* functionToCall = "on_player_death"; + bool isPlayerDead = true; + if(dead->IsPlayer() || (killer && killer->IsPlayer() && (isPlayerDead = false))) { + if(!isPlayerDead) { + functionToCall = "on_player_kill"; + } + const char* playerScript = world.GetPlayerScript(0); // 0 = global script + const char* playerZoneScript = world.GetPlayerScript(GetZoneID()); // zone script + if(playerScript || playerZoneScript) { + std::vector args = { + LuaArg(this), + LuaArg(dead), + LuaArg(killer) + }; + if(playerScript) { + lua_interface->RunPlayerScriptWithReturn(playerScript, functionToCall, args); + } + if(playerZoneScript) { + lua_interface->RunPlayerScriptWithReturn(playerZoneScript, functionToCall, args); + } + } + } } void ZoneServer::SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, int8 type2, int8 damage_type, int16 damage, const char* spell_name) { @@ -8658,6 +8724,7 @@ void ZoneServer::DeleteGlobalSpawns() { ClearEntityCommands(); DeleteGroundSpawnItems(); + DeleteTransporters(); DeleteGlobalTransporters(); DeleteTransporterMaps(); } diff --git a/source/common/version.h b/source/common/version.h index c8843a3..0d6beb4 100644 --- a/source/common/version.h +++ b/source/common/version.h @@ -38,11 +38,11 @@ #endif #if defined(LOGIN) -#define CURRENT_VERSION "0.9.8-thetascorpii-DR1" +#define CURRENT_VERSION "0.9.9-Nebula" #elif defined(WORLD) -#define CURRENT_VERSION "0.9.8-thetascorpii-DR1" +#define CURRENT_VERSION "0.9.9-Nebula" #else -#define CURRENT_VERSION "0.9.8-thetascorpii-DR1" +#define CURRENT_VERSION "0.9.9-Nebula" #endif #define COMPILE_DATE __DATE__