Work in progress for 0.9.9 so far (making its own branch for now)

- Fixed a repeated rubberband like behavior that Spawn's would run forward toward their target in combat or on their return run to their starting point.
- WS_HearCastSpell for 546-561 version has an extra byte at the end of the packet we were missing (might be in later clients too have to confirm)
- PlayerScripts support added, new function calls, AddTimer support for Players.
- Static Zones / Special Zones will now silently check to startup zones without reporting the log message, log message only on startup or taking on peer leadership.
- Broadcast and Global Announcement are now supported through peering.
- Fix #22 identified a number of loose spawn pointers and changed to int32 spawn id reference.
- Fix #1 support for all known chat codes for various channels, spell casting and damage.
- In conjunction with Fix #1 spell combat messages are fixed, no longer 'YOU cast' when another spawn casts, added last_tell_name to track the chat code %RT server side.
- size_mod added to InfoStruct Float, supports shrinking and growing Non Player's. InfoStruct also has a UINT ignore_size_mod_calc set to 0 by default, when 1 it will let you set the size_mod and items/spells will not override it from stat calculation.
- XP Table is now static (global) in the Player class so we do not constantly call the database each time the player needs to know a level's XP requirement.
- Removal of duplicate spell cast success and effect messages.
- Fix #21 blue xp bar for KoS and DoF displays properly now.
- GetExpRequiredByLevel(level) added to return the EXP required for to reach the level.
- Fix #25 teleporters cleaned up during /reload spawns to avoid crash
- Fix #16 /reload items supported in peering mode.
This commit is contained in:
Emagi 2025-06-08 14:53:52 -04:00
parent 4e43c73f9c
commit 82a5e96000
27 changed files with 964 additions and 234 deletions

View File

@ -3712,6 +3712,12 @@ to zero and treated like placeholders." />
<Data ElementName="damage" Type="int16" />
<Data ElementName="spell_name" Type="EQ2_8Bit_String" Size="1" />
</Struct>
<Struct Name="WS_SetSocialMsg" ClientVersion="1" OpcodeName="OP_SetSocialMsg">
<Data ElementName="num_socials" Type="int8" />
<Data ElementName="social_array" Type="Array" ArraySizeVariable="num_socials">
<Data ElementName="social_name" Type="EQ2_8Bit_String" Size="1" />
</Data>
</Struct>
<Struct Name="WS_HearMultipleDamage" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearCombatCmd">
<Data ElementName="header" Substruct="WS_HearDamage_Header" Size="1" />
<Data ElementName="num_dmg" Type="int8" />
@ -7642,6 +7648,7 @@ to zero and treated like placeholders." />
<Data ElementName="spell_visual" Type="int16" />
<Data ElementName="cast_time" Type="float" />
<Data ElementName="spell_level" Type="int8" />
<Data ElementName="spell_crit" Type="int8" />
</Struct>
<Struct Name="WS_HearCastSpell" ClientVersion="562" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearSpellCastCmd">
<Data ElementName="spawn_id" Type="int32" />
@ -21034,4 +21041,11 @@ to zero and treated like placeholders." />
<Struct Name="WS_CreateBoatTransportMsg" ClientVersion="1" OpcodeName="OP_CreateBoatTransportsMsg">
<Data ElementName="path_id" Type="int8" />
</Struct>
<Struct Name="WS_SetSocialMsg" ClientVersion="1" OpcodeName="OP_SetSocialMsg">
<Data ElementName="num_socials" Type="int8" />
<Data ElementName="social_array" Type="Array" ArraySizeVariable="num_socials">
<Data ElementName="social_name" Type="EQ2_8Bit_String" Size="1" />
<Data ElementName="social_message" Type="EQ2_8Bit_String" Size="1" />
</Data>
</Struct>
</EQ2Emulator>

View File

@ -141,6 +141,10 @@ bool Entity::AttackAllowed(Entity* target, float distance, bool range_attack) {
}
}
if(attacker->IsNPC() && target->IsNPC() && attacker->GetFactionID() > 10 && attacker->GetFactionID() == target->GetFactionID()) {
return false;
}
if (attacker->IsPlayer() && target->IsPlayer())
{
bool pvp_allowed = rule_manager.GetZoneRule(GetZoneID(), R_PVP, AllowPVP)->GetBool();
@ -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 {

View File

@ -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 @@ 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 <http://www.gnu.org/licenses/>.
*/
#include <sys/types.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.hpp>
@ -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;
@ -3138,12 +3153,16 @@ 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();
lua_interface->DestroyRegionScripts();
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);
}
/*

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_COMMANDS__
#define __EQ2_COMMANDS__
#include "../../common/DataBuffer.h"
@ -982,7 +983,7 @@ private:
#define SAVE_AA_PROFILE 758
#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

View File

@ -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,6 +1464,8 @@ void Entity::CalculateBonuses(){
info->set_spell_multi_attack(0);
float previous_size_mod = info->get_size_mod();
if(!info->get_ignore_size_mod_calc())
info->set_size_mod(0.0f);
info->set_dps(0);
info->set_dps_multiplier(0);
@ -1548,6 +1552,7 @@ void Entity::CalculateBonuses(){
info->add_recovery_speed(values->abilityrecoveryspeed);
info->add_spell_reuse_speed(values->spellreusespeed);
info->add_spell_multi_attack(values->spellmultiattackchance);
if(!info->get_ignore_size_mod_calc())
info->add_size_mod(values->size_mod);
info->add_dps(values->dps);
info->add_dps_multiplier(CalculateDPSMultiplier());

View File

@ -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<std::mutex> lk(classMutex); return spell_reuse_speed_; }
float get_spell_multi_attack() { std::lock_guard<std::mutex> lk(classMutex); return spell_multi_attack_; }
float get_size_mod() { std::lock_guard<std::mutex> lk(classMutex); return size_mod_; }
int8 get_ignore_size_mod_calc() { std::lock_guard<std::mutex> lk(classMutex); return ignore_size_mod_calc_; }
float get_dps() { std::lock_guard<std::mutex> lk(classMutex); return dps_; }
float get_dps_multiplier() { std::lock_guard<std::mutex> lk(classMutex); return dps_multiplier_; }
float get_attackspeed() { std::lock_guard<std::mutex> lk(classMutex); return attackspeed_; }
@ -917,6 +924,7 @@ struct InfoStruct{
void set_spell_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_reuse_speed_ = value; }
void set_spell_multi_attack(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_multi_attack_ = value; }
void set_size_mod(float value) { std::lock_guard<std::mutex> lk(classMutex); size_mod_ = value; }
void set_ignore_size_mod_calc(int8 value) { std::lock_guard<std::mutex> lk(classMutex); ignore_size_mod_calc_ = value; }
void set_dps(float value) { std::lock_guard<std::mutex> lk(classMutex); dps_ = value; }
void set_dps_multiplier(float value) { std::lock_guard<std::mutex> lk(classMutex); dps_multiplier_ = value; }
void set_attackspeed(float value) { std::lock_guard<std::mutex> 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_;

View File

@ -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;

View File

@ -14653,3 +14653,13 @@ int EQ2Emu_lua_GetSpellRequiredLevel(lua_State* state) {
return 1;
}
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;
}

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<string, map<lua_State*, bool> >::iterator itr;
map<lua_State*, bool>::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<string, map<lua_State*, bool> >::iterator itr;
map<lua_State*, bool>::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<string, map<lua_State*, bool> >::iterator itr;
map<lua_State*, bool>::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<string, map<lua_State*, bool> >::iterator itr;
map<lua_State*, bool>::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<LuaArg>& 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)

View File

@ -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<LuaArg>& 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<string, map<lua_State*, bool> > item_scripts;
map<string, map<lua_State*, bool> > spawn_scripts;
map<string, map<lua_State*, bool> > zone_scripts;
map<string, map<lua_State*, bool> > player_scripts;
map<string, map<lua_State*, bool> > region_scripts;
map<string, map<lua_State*, LuaSpell*> > spell_scripts;
@ -339,11 +386,13 @@ private:
map<lua_State*, string> item_inverse_scripts;
map<lua_State*, string> spawn_inverse_scripts;
map<lua_State*, string> zone_inverse_scripts;
map<lua_State*, string> player_inverse_scripts;
map<lua_State*, string> region_inverse_scripts;
map<string, Mutex*> item_scripts_mutex;
map<string, Mutex*> spawn_scripts_mutex;
map<string, Mutex*> zone_scripts_mutex;
map<string, Mutex*> player_scripts_mutex;
map<int32, Mutex*> quests_mutex;
map<string, Mutex*> region_scripts_mutex;
map<string, Mutex*> spell_scripts_mutex;
@ -353,6 +402,7 @@ private:
Mutex MSpawnScripts;
Mutex MItemScripts;
Mutex MZoneScripts;
Mutex MPlayerScripts;
Mutex MQuests;
Mutex MLUAMain;
Mutex MSpellDelete;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<int8, int32> Player::m_levelXPReq;
Player::Player(){
tutorial_step = 0;
@ -999,7 +1001,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
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_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();
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){
@ -4533,13 +4558,6 @@ bool Player::AddXP(int32 xp_amount){
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);
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,11 +4585,19 @@ bool Player::AddTSXP(int32 xp_amount){
}
return false;
}
int32 prev_xp_amount = xp_amount;
xp_amount -= GetNeededTSXP() - GetTSXP();
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();
current_xp_percent = ((float)GetTSXP()/(float)GetNeededTSXP())*100;
@ -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<LuaArg> 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) {

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<bool> need_trait_update;
void InitXPTable();
map<int8, int32> m_levelXPReq;
static void InitXPTable();
static map<int8, int32> 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<int8, int32> m_levelXPReq;
//The following variables are for serializing spawn packets
PacketStruct* spawn_pos_struct;

View File

@ -2197,17 +2197,24 @@ 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<float>(size) + (((Entity*)this)->GetInfoStruct()->get_size_mod() * size);
// Round and cast to sint16
new_size = static_cast<sint16>(std::round(scaled));
// Enforce minimum size of 1
new_size = std::max<sint16>(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);
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);
packet->setDataByName("pos_size", new_size > 0 ? (((float)new_size) / 32.0f) : 1.0f); // float not an integer
// 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);
@ -2488,11 +2495,6 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
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());
packet->setDataByName("unknown4", (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)
{

View File

@ -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()) {
@ -3100,3 +3075,192 @@ bool SpellProcess::AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_sp
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();
}
}

View File

@ -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<Entity*,Spell*> spell_que;
MutexList<LuaSpell*> active_spells;

View File

@ -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);

View File

@ -273,6 +273,15 @@ void World::Web_worldhandle_reloadcommand(const http::request<http::string_body>
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<http::string_body>
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<http::string_b
std::string toName(""), fromName(""), msg("");
int32 group_id = 0;
int32 guild_id = 0;
int8 custom_type = 0;
if (auto name = json_tree.get_optional<std::string>("to_name")) {
toName = name.get();
}
@ -744,6 +764,9 @@ void World::Web_worldhandle_sendglobalmessage(const http::request<http::string_b
if (auto guildID = json_tree.get_optional<int32>("guild_id")) {
guild_id = guildID.get();
}
if (auto customType = json_tree.get_optional<int8>("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::request<http::string_b
if (find_client && find_client->GetPlayer()) {
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<http::string_b
}
break;
}
case CHANNEL_BROADCAST: {
switch(custom_type) {
case 0:
zone_list.HandleGlobalBroadcast(msg.c_str());
break;
case 1:
zone_list.HandleGlobalAnnouncement(msg.c_str());
break;
}
break;
}
}
}
@ -827,6 +862,7 @@ void World::Web_worldhandle_sendglobalmessage(const http::request<http::string_b
pt.put("to_name", toName);
pt.put("message", msg);
pt.put("from_language", language);
pt.put("custom_type", custom_type);
pt.put("channel", in_channel);
std::ostringstream oss;
boost::property_tree::write_json(oss, pt);

View File

@ -17,6 +17,12 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/algorithm/string.hpp>
#include <string>
#include <iostream>
#include <filesystem>
#include <vector>
#include <assert.h>
#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 <boost/algorithm/string.hpp>
#include <string>
#include <iostream>
MasterQuestList master_quest_list;
MasterItemList master_item_list;
MasterSpellList master_spell_list;
@ -90,7 +92,6 @@ map<int16, int16> 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<ZoneServer*>::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<ZoneServer*>::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<MerchantItemInfo>* 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());

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<int32, string> spawnentry_scripts;
map<int32, string> spawnlocation_scripts;
map<int32, string> zone_scripts;
map<int32, string> player_scripts;
//vector<PlayerGroup*> player_groups;
//map<GroupMemberInfo*, int32> group_removal_pending;
//map<string, string> pending_groups;

View File

@ -3251,7 +3251,8 @@ string WorldDatabase::GetExpansionIDByVersion(int16 version)
}
void WorldDatabase::LoadSpecialZones(){
void WorldDatabase::LoadSpecialZones(bool silent){
if(!silent)
LogWrite(ZONE__INFO, 0, "Zone", "Starting static zones...");
Query query;
ZoneServer* zone = 0;
@ -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;

View File

@ -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);

View File

@ -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 @@ 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 <http://www.gnu.org/licenses/>.
*/
#include "../common/debug.h"
#include "../common/Log.h"
#include <iostream>
@ -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<LuaArg> 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<LuaArg> 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<BuyBackItem*>::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<Item*>* 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<MerchantItemInfo>* 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<BuyBackItem*>::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<Item*>* 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<LuaArg> 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,6 +9642,7 @@ void Client::DisplayMailMessage(int32 mail_id) {
if (mail->stack || mail->char_item_id)
{
Item* item = master_item_list.GetItem(mail->char_item_id);
if(item) {
item->stack_count = mail->stack > 1 ? mail->stack : 0;
if (version < 860)
packet->setItemByName("item", item, player, 0, version <= 373 ? -2 : -1);
@ -9585,6 +9651,7 @@ void Client::DisplayMailMessage(int32 mail_id) {
else
packet->setItemByName("item", item, player, 0, 2);
}
}
else
{
packet->setDataByName("end_tag2", GetItemPacketType(GetVersion()));
@ -10033,7 +10100,7 @@ void Client::ProcessTeleport(Spawn* spawn, vector<TransportDestination*>* destin
if (transport_id > 0)
has_map = GetCurrentZone()->TransportHasMap(transport_id);
transport_spawn = spawn;
transport_spawn_id = spawn->GetID();
vector<TransportDestination*> transport_list;
vector<TransportDestination*>::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<TransportDestination*>::iterator itr;
for (itr = destinations.begin(); itr != destinations.end(); itr++) {

View File

@ -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<Item*>* search_items;
int32 waypoint_id = 0;
map<string, WaypointInfo> waypoints;
Spawn* transport_spawn;
int32 transport_spawn_id;
Mutex MBuyBack;
deque<BuyBackItem*> buy_back_items;
Spawn* merchant_transaction;
int32 merchant_transaction_id;
Spawn* mail_transaction;
mutable std::shared_mutex MPendingQuestAccept;
vector<int32> pending_quest_accept;
@ -754,7 +761,7 @@ private:
mutable std::shared_mutex MConversation;
map<int32, map<int8, string> > conversation_map;
int32 current_quest_id;
Spawn* banker;
int32 banker_id;
map<int32, int32> sent_spell_details;
map<int32, bool> 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 {

View File

@ -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++;
}

View File

@ -1,8 +1,9 @@
/*
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.
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
@ -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<Client*>::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<SpawnScriptTimer>::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<LuaArg> 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<LuaArg> 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();
}

View File

@ -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__