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__