/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (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.
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"
#include
using namespace std;
#include
#include "../common/misc.h"
#include
#include
#include
#include
#include "Commands/Commands.h"
#include "Zone/pathfinder_interface.h"
#include "NPC_AI.h"
#ifdef WIN32
#include
#include
#include
#pragma comment(lib,"imagehlp.lib")
#else
#include
#include
#ifdef FREEBSD //Timothy Whitman - January 7, 2003
#include
#endif
#include
#include
#include
#include
#include
#include "../common/unix.h"
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
extern int errno;
#endif
#include "../common/servertalk.h"
#include "../common/packet_dump.h"
#include "WorldDatabase.h"
#include "races.h"
#include "classes.h"
#include "../common/seperator.h"
#include "../common/EQStream.h"
#include "../common/EQStreamFactory.h"
#include "../common/opcodemgr.h"
#include "zoneserver.h"
#include "client.h"
#include "LoginServer.h"
#include "World.h"
#include
#include
#include "LuaInterface.h"
#include "Factions.h"
#include "VisualStates.h"
#include "ClientPacketFunctions.h"
#include "SpellProcess.h"
#include "../common/Log.h"
#include "Rules/Rules.h"
#include "Chat/Chat.h"
#include "Tradeskills/Tradeskills.h"
#include "RaceTypes/RaceTypes.h"
#include
#include
#include "Bots/Bot.h"
#ifdef WIN32
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#endif
// int32 numplayers = 0; // never used?
// extern bool GetZoneLongName(char* short_name, char** long_name); // never used?
// extern bool holdzones; // never used?
// extern volatile bool RunLoops; // never used in the zone server?
// extern Classes classes; // never used in the zone server?
#define NO_CATCH 1
extern WorldDatabase database;
extern sint32 numzones;
extern ClientList client_list;
extern LoginServer loginserver;
extern ZoneList zone_list;
extern World world;
extern ConfigReader configReader;
extern Commands commands;
extern LuaInterface* lua_interface;
extern MasterFactionList master_faction_list;
extern VisualStates visual_states;
extern RuleManager rule_manager;
extern Chat chat;
extern MasterRaceTypeList race_types_list;
extern MasterSpellList master_spell_list; // temp - remove later
extern MasterSkillList master_skill_list;
int32 MinInstanceID = 1000;
// JA: Moved most default values to Rules and risky initializers to ZoneServer::Init() - 2012.12.07
ZoneServer::ZoneServer(const char* name) {
incoming_clients = 0;
MIncomingClients.SetName("ZoneServer::MIncomingClients");
depop_zone = false;
repop_zone = false;
respawns_allowed = true;
instanceID = 0;
strcpy(zone_name, name);
zoneID = 0;
rain = 0;
cityzone = false;
always_loaded = false;
locked = false; // JA: implementing /zone lock|unlock commands
pNumPlayers = 0;
LoadingData = true;
zoneShuttingDown = false;
++numzones;
revive_points = 0;
zone_motd = "";
finished_depop = true;
initial_spawn_threads_active = 0;
minimumStatus = 0;
minimumLevel = 0;
maximumLevel = 0;
minimumVersion = 0;
weather_current_severity = 0;
weather_signaled = false;
xp_mod = 0;
isDusk = false;
dusk_hour = 0;
dusk_minute = 0;
dawn_hour = 0;
dawn_minute = 0;
reloading_spellprocess = false;
expansion_flag = 0;
holiday_flag = 0;
can_bind = 1;
can_gate = 1;
MMasterZoneLock = new CriticalSection(MUTEX_ATTRIBUTE_RECURSIVE);
pathing = nullptr;
strcpy(zonesky_file,"");
reloading = true;
watchdogTimestamp = Timer::GetCurrentTime2();
MPendingSpawnRemoval.SetName("ZoneServer::MPendingSpawnRemoval");
lifetime_client_count = 0;
}
typedef map ChangedSpawnMapType;
ZoneServer::~ZoneServer() {
zoneShuttingDown = true; //ensure other threads shut down too
//allow other threads to properly shut down
LogWrite(ZONE__INFO, 0, "Zone", "Initiating zone shutdown of '%s'", zone_name);
int32 disp_count = 0;
int32 next_disp_count = 100;
while (spawnthread_active || initial_spawn_threads_active > 0){
bool disp = false;
if ( disp_count == 0 ) {
disp = true;
}
else if(disp_count >= next_disp_count) {
disp_count = 0;
disp = true;
}
disp_count++;
if (spawnthread_active && disp)
LogWrite(ZONE__DEBUG, 7, "Zone", "Zone shutdown waiting on spawn thread");
if (initial_spawn_threads_active > 0 && disp)
LogWrite(ZONE__DEBUG, 7, "Zone", "Zone shutdown waiting on initial spawn thread");
Sleep(10);
}
MChangedSpawns.lock();
changed_spawns.clear();
MChangedSpawns.unlock();
transport_spawns.clear();
safe_delete(tradeskillMgr);
MMasterZoneLock->lock();
MMasterSpawnLock.writelock(__FUNCTION__, __LINE__);
DeleteData(true);
RemoveLocationProximities();
RemoveLocationGrids();
DeleteSpawns(true);
DeleteGlobalSpawns();
DeleteFlightPaths();
MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__);
MMasterZoneLock->unlock();
world.UpdateServerStatistic(STAT_SERVER_NUM_ACTIVE_ZONES, -1);
// If lockout, public, tradeskill, or quest instance delete from db when zone shuts down
if (InstanceType == SOLO_LOCKOUT_INSTANCE || InstanceType == GROUP_LOCKOUT_INSTANCE || InstanceType == RAID_LOCKOUT_INSTANCE ||
InstanceType == PUBLIC_INSTANCE || InstanceType == TRADESKILL_INSTANCE || InstanceType == QUEST_INSTANCE) {
LogWrite(INSTANCE__DEBUG, 0, "Instance", "Non persistent instance shutting down, deleting instance");
database.DeleteInstance(instanceID);
}
if (pathing != nullptr)
delete pathing;
if (movementMgr != nullptr)
delete movementMgr;
// moved to the bottom as we want spawns deleted first, this used to be above Spawn deletion which is a big no no
safe_delete(spellProcess);
MGridMaps.lock();
std::map::iterator grids;
for(grids = grid_maps.begin(); grids != grid_maps.end(); grids++) {
GridMap* gm = grids->second;
safe_delete(gm);
}
grid_maps.clear();
MGridMaps.unlock();
LogWrite(ZONE__INFO, 0, "Zone", "Completed zone shutdown of '%s'", zone_name);
--numzones;
UpdateWindowTitle(0);
zone_list.Remove(this);
zone_list.RemoveClientZoneReference(this);
safe_delete(MMasterZoneLock);
}
void ZoneServer::IncrementIncomingClients() {
MIncomingClients.writelock(__FUNCTION__, __LINE__);
incoming_clients++;
LogWrite(ZONE__INFO, 0, "Zone", "Increment incoming clients of '%s' zoneid %u (instance id: %u). Current incoming client count: %u", zone_name, zoneID, instanceID, incoming_clients);
MIncomingClients.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::DecrementIncomingClients() {
MIncomingClients.writelock(__FUNCTION__, __LINE__);
bool zeroed = false;
if(incoming_clients)
incoming_clients--;
else
zeroed = true;
LogWrite(ZONE__INFO, 0, "Zone", "Decrement incoming clients of '%s' zoneid %u (instance id: %u). Current incoming client count: %u (was client count previously zero: %u)", zone_name, zoneID, instanceID, incoming_clients, zeroed);
MIncomingClients.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::Init()
{
LogWrite(ZONE__INFO, 0, "Zone", "Loading new Zone '%s'", zone_name);
zone_list.Add(this);
spellProcess = new SpellProcess();
tradeskillMgr = new TradeskillMgr();
/* Dynamic Timers */
regenTimer.Start(rule_manager.GetGlobalRule(R_Zone, RegenTimer)->GetInt32());
client_save.Start(rule_manager.GetGlobalRule(R_Zone, ClientSaveTimer)->GetInt32());
shutdownTimer.Disable();
spawn_range.Start(rule_manager.GetGlobalRule(R_Zone, CheckAttackPlayer)->GetInt32());
aggro_timer.Start(rule_manager.GetGlobalRule(R_Zone, CheckAttackNPC)->GetInt32());
/* Weather stuff */
InitWeather();
/* Static Timers */
// JA - haven't decided yet if these should remain hard-coded. Changing them could break EQ2Emu functionality
spawn_check_add.Start(1000);
spawn_check_remove.Start(30000);
spawn_expire_timer.Start(10000);
respawn_timer.Start(10000);
// there was never a starter for these?
widget_timer.Start(5000);
tracking_timer.Start(5000);
movement_timer.Start(100);
location_prox_timer.Start(1000);
location_grid_timer.Start(1000);
charsheet_changes.Start(500);
// Send game time packet every in game hour (180 sec)
sync_game_time_timer.Start(180000);
// Get the dusk and dawn time from the rule manager and store it in the correct variables
sscanf (rule_manager.GetGlobalRule(R_World, DuskTime)->GetString(), "%d:%d", &dusk_hour, &dusk_minute);
sscanf (rule_manager.GetGlobalRule(R_World, DawnTime)->GetString(), "%d:%d", &dawn_hour, &dawn_minute);
spawn_update.Start(rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16());
LogWrite(ZONE__DEBUG, 0, "Zone", "SpawnUpdateTimer: %ims", rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16());
queue_updates.Start(rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16());
LogWrite(ZONE__DEBUG, 0, "Zone", "QueueUpdateTimer(inherits SpawnUpdateTimer): %ims", rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16());
spawn_delete_timer = rule_manager.GetGlobalRule(R_Zone, SpawnDeleteTimer)->GetInt32();
LogWrite(ZONE__DEBUG, 0, "Zone", "SpawnDeleteTimer: %ums", spawn_delete_timer);
LogWrite(ZONE__DEBUG, 0, "Zone", "Loading zone flight paths");
database.LoadZoneFlightPaths(this);
world.UpdateServerStatistic(STAT_SERVER_NUM_ACTIVE_ZONES, 1);
UpdateWindowTitle(0);
string zoneName(GetZoneFile());
world.LoadRegionMaps(zoneName);
world.LoadMaps(zoneName);
pathing = IPathfinder::Load(zoneName);
movementMgr = new MobMovementManager();
MMasterSpawnLock.SetName("ZoneServer::MMasterSpawnLock");
m_npc_faction_list.SetName("ZoneServer::npc_faction_list");
m_enemy_faction_list.SetName("ZoneServer::enemy_faction_list");
m_reverse_enemy_faction_list.SetName("ZoneServer::reverse_enemy_faction_list");
MDeadSpawns.SetName("ZoneServer::dead_spawns");
MTransportSpawns.SetName("ZoneServer::transport_spawns");
MSpawnList.SetName("ZoneServer::spawn_list");
MTransporters.SetName("ZoneServer::m_transportMaps");
MSpawnGroupAssociation.SetName("ZoneServer::spawn_group_associations");
MSpawnGroupLocations.SetName("ZoneServer::spawn_group_locations");
MSpawnLocationGroups.SetName("ZoneServer::spawn_location_groups");
MSpawnGroupChances.SetName("ZoneServer::spawn_group_chances");
MTransportLocations.SetName("ZoneServer::transporter_locations");
MSpawnLocationList.SetName("ZoneServer::spawn_location_list");
MSpawnDeleteList.SetName("ZoneServer::spawn_delete_list");
MSpawnScriptTimers.SetName("ZoneServer::spawn_script_timers");
MRemoveSpawnScriptTimersList.SetName("ZoneServer::remove_spawn_script_timers_list");
MClientList.SetName("ZoneServer::clients");
MWidgetTimers.SetName("ZoneServer::widget_timers");
#ifdef WIN32
_beginthread(ZoneLoop, 0, this);
_beginthread(SpawnLoop, 0, this);
#else
pthread_create(&ZoneThread, NULL, ZoneLoop, this);
pthread_detach(ZoneThread);
pthread_create(&SpawnThread, NULL, SpawnLoop, this);
pthread_detach(SpawnThread);
#endif
}
void ZoneServer::CancelThreads() {
#ifdef WIN32
LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung, however CancelThreads is unsupported for WIN32.", GetZoneName());
#else
pthread_cancel(ZoneThread);
pthread_cancel(SpawnThread);
#endif
}
void ZoneServer::InitWeather()
{
weather_enabled = rule_manager.GetGlobalRule(R_Zone, WeatherEnabled)->GetBool();
if( weather_enabled && isWeatherAllowed())
{
string tmp;
// set up weather system when zone starts up
weather_type = rule_manager.GetGlobalRule(R_Zone, WeatherType)->GetInt8();
switch(weather_type)
{
case 3: tmp = "Chaotic"; break;
case 2: tmp = "Random"; break;
case 1: tmp = "Dynamic"; break;
default: tmp = "Normal"; break;
}
LogWrite(ZONE__DEBUG, 0, "Zone", "%s: Setting up '%s' weather", zone_name, tmp.c_str());
weather_frequency = rule_manager.GetGlobalRule(R_Zone, WeatherChangeFrequency)->GetInt32();
LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Change weather every %u seconds", zone_name, weather_frequency);
weather_change_chance = rule_manager.GetGlobalRule(R_Zone, WeatherChangeChance)->GetInt8();
LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Chance of weather change: %i%%", zone_name, weather_change_chance);
weather_min_severity = rule_manager.GetGlobalRule(R_Zone, MinWeatherSeverity)->GetFloat();
weather_max_severity = rule_manager.GetGlobalRule(R_Zone, MaxWeatherSeverity)->GetFloat();
LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather Severity min/max is %.2f - %.2f", zone_name, weather_min_severity, weather_max_severity);
// Allow a random roll to determine if weather should start out severe or calm
if( MakeRandomInt(1, 100) > 50)
{
weather_pattern = 1; // default weather to increase in severity initially
weather_current_severity = weather_min_severity;
}
else
{
weather_pattern = 0; // default weather to decrease in severity initially
weather_current_severity = weather_max_severity;
}
LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather Severity set to %.2f, pattern: %i", zone_name, weather_current_severity, weather_pattern);
weather_change_amount = rule_manager.GetGlobalRule(R_Zone, WeatherChangePerInterval)->GetFloat();
LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather change by %.2f each interval", zone_name, weather_change_amount);
if( weather_type > 0 )
{
weather_dynamic_offset = rule_manager.GetGlobalRule(R_Zone, WeatherDynamicMaxOffset)->GetFloat();
LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather Max Offset changes no more than %.2f each interval", zone_name, weather_dynamic_offset);
}
else
weather_dynamic_offset = 0;
SetRain(weather_current_severity);
weather_last_changed_time = Timer::GetUnixTimeStamp();
weatherTimer.Start(rule_manager.GetGlobalRule(R_Zone, WeatherTimer)->GetInt32());
}
}
void ZoneServer::DeleteSpellProcess(){
//Just get a lock to make sure we aren't already looping the spawnprocess or clientprocess if this is different than the calling thread
MMasterSpawnLock.writelock(__FUNCTION__, __LINE__);
MMasterZoneLock->lock();
reloading_spellprocess = true;
// Remove spells from NPC's
Spawn* spawn = 0;
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
spawn = itr->second;
if(spawn && spawn->IsNPC())
((NPC*)spawn)->SetSpells(0);
if(spawn->IsEntity())
((Entity*)spawn)->RemoveSpellBonus(nullptr, true);
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
MMasterZoneLock->unlock();
MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__);
DismissAllPets();
spellProcess->RemoveAllSpells(true);
safe_delete(spellProcess);
}
void ZoneServer::LoadSpellProcess(){
spellProcess = new SpellProcess();
reloading_spellprocess = false;
// Reload NPC's spells
Spawn* spawn = 0;
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
spawn = itr->second;
if(spawn && spawn->IsNPC())
((NPC*)spawn)->SetSpells(world.GetNPCSpells(((NPC*)spawn)->GetPrimarySpellList(), ((NPC*)spawn)->GetSecondarySpellList()));
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::LockAllSpells(Player* player) {
if (player && spellProcess) {
Client* client = ((Player*)player)->GetClient();
if (client)
spellProcess->LockAllSpells(client);
}
}
void ZoneServer::UnlockAllSpells(Player* player) {
if (player && spellProcess) {
Client* client = ((Player*)player)->GetClient();
if (client)
spellProcess->UnlockAllSpells(client);
}
}
void ZoneServer::DeleteFactionLists() {
map *>::iterator faction_itr;
map *>::iterator spawn_itr;
m_enemy_faction_list.writelock(__FUNCTION__, __LINE__);
for (faction_itr = enemy_faction_list.begin(); faction_itr != enemy_faction_list.end(); faction_itr++)
safe_delete(faction_itr->second);
enemy_faction_list.clear();
m_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__);
m_reverse_enemy_faction_list.writelock(__FUNCTION__, __LINE__);
for (faction_itr = reverse_enemy_faction_list.begin(); faction_itr != reverse_enemy_faction_list.end(); faction_itr++)
safe_delete(faction_itr->second);
reverse_enemy_faction_list.clear();
m_reverse_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__);
m_npc_faction_list.writelock(__FUNCTION__, __LINE__);
for (spawn_itr = npc_faction_list.begin(); spawn_itr != npc_faction_list.end(); spawn_itr++)
safe_delete(spawn_itr->second);
npc_faction_list.clear();
m_npc_faction_list.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::DeleteData(bool boot_clients){
Spawn* spawn = 0;
vector tmp_player_list; // changed to a vector from a MutexList as this is a local variable and don't need mutex stuff for the list
// Clear spawn groups
spawn_group_map.clear();
// Loop through the spawn list and set the spawn for deletion
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
spawn = itr->second;
if(spawn){
if(!boot_clients && (spawn->IsPlayer() || spawn->IsBot()))
tmp_player_list.push_back(spawn);
else if(spawn->IsPlayer()){
Client* client = ((Player*)spawn)->GetClient();
if(client)
client->Disconnect();
}
else{
RemoveSpawnSupportFunctions(spawn, boot_clients, true);
RemoveSpawnFromGrid(spawn, spawn->GetLocation());
AddPendingDelete(spawn);
}
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
// Quick hack to prevent a deadlock, RemoveSpawnSupportFunctions() will cancel spells and result in zone->GetSpawnByID()
// being called which read locks the spawn list and caused a dead lock as the above mutex's were write locked
MSpawnList.writelock(__FUNCTION__, __LINE__);
// Clear the spawn list, this was in the mutex above, moved it down so the above mutex could be a read lock
spawn_list.clear();
// Moved this up so we only read lock the list once in this list
vector::iterator spawn_iter2;
for (spawn_iter2 = tmp_player_list.begin(); spawn_iter2 != tmp_player_list.end(); spawn_iter2++) {
spawn_list[(*spawn_iter2)->GetID()] = (*spawn_iter2);
}
MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
// Clear player proximities
RemovePlayerProximity(0, true);
spawn_range_map.clear(true);
if(boot_clients) {
// Refactor
vector::iterator itr;
MClientList.writelock(__FUNCTION__, __LINE__);
for (itr = clients.begin(); itr != clients.end(); itr++)
safe_delete(*itr);
clients.clear();
MClientList.releasewritelock(__FUNCTION__, __LINE__);
}
// Clear and delete spawn locations
MSpawnLocationList.writelock(__FUNCTION__, __LINE__);
map::iterator spawn_location_iter;
for (spawn_location_iter = spawn_location_list.begin(); spawn_location_iter != spawn_location_list.end(); spawn_location_iter++)
safe_delete(spawn_location_iter->second);
spawn_location_list.clear();
MSpawnLocationList.releasewritelock(__FUNCTION__, __LINE__);
// If we allow clients to stay in the zone we need to preserve the revive_points, otherwise if the player dies they will crash
if(revive_points && boot_clients){
vector::iterator revive_iter;
for(revive_iter=revive_points->begin(); revive_iter != revive_points->end(); revive_iter++){
safe_delete(*revive_iter);
}
safe_delete(revive_points);
}
MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__);
map*>::iterator assoc_itr;
for (assoc_itr = spawn_group_associations.begin(); assoc_itr != spawn_group_associations.end(); assoc_itr++)
safe_delete(assoc_itr->second);
spawn_group_associations.clear();
MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__);
MSpawnGroupLocations.writelock(__FUNCTION__, __LINE__);
map*>::iterator loc_itr;
for (loc_itr = spawn_group_locations.begin(); loc_itr != spawn_group_locations.end(); loc_itr++)
safe_delete(loc_itr->second);
spawn_group_locations.clear();
MSpawnGroupLocations.releasewritelock(__FUNCTION__, __LINE__);
MSpawnLocationGroups.writelock(__FUNCTION__, __LINE__);
map*>::iterator group_itr;
for (group_itr = spawn_location_groups.begin(); group_itr != spawn_location_groups.end(); group_itr++)
safe_delete(group_itr->second);
spawn_location_groups.clear();
MSpawnLocationGroups.releasewritelock(__FUNCTION__, __LINE__);
// Clear lists that need more then just a Clear()
DeleteFactionLists();
DeleteSpawnScriptTimers(0, true);
DeleteSpawnScriptTimers();
ClearDeadSpawns();
// Clear lists
movement_spawns.clear();
respawn_timers.clear();
transport_spawns.clear();
quick_database_id_lookup.clear();
MWidgetTimers.writelock(__FUNCTION__, __LINE__);
widget_timers.clear();
MWidgetTimers.releasewritelock(__FUNCTION__, __LINE__);
map::iterator struct_itr;
for (struct_itr = versioned_info_structs.begin(); struct_itr != versioned_info_structs.end(); struct_itr++)
safe_delete(struct_itr->second);
versioned_info_structs.clear();
for (struct_itr = versioned_pos_structs.begin(); struct_itr != versioned_pos_structs.end(); struct_itr++)
safe_delete(struct_itr->second);
versioned_pos_structs.clear();
for (struct_itr = versioned_vis_structs.begin(); struct_itr != versioned_vis_structs.end(); struct_itr++)
safe_delete(struct_itr->second);
versioned_vis_structs.clear();
}
void ZoneServer::RemoveLocationProximities() {
MutexList::iterator itr = location_proximities.begin();
while(itr.Next()){
safe_delete(itr->value);
}
location_proximities.clear();
}
RevivePoint* ZoneServer::GetRevivePoint(int32 id){
vector::iterator revive_iter;
for(revive_iter=revive_points->begin(); revive_iter != revive_points->end(); revive_iter++){
if((*revive_iter)->id == id)
return *revive_iter;
}
return 0;
}
vector* ZoneServer::GetRevivePoints(Client* client)
{
vector* points = new vector;
RevivePoint* closest_point = 0;
// we should not check for revive points if this is null
if ( revive_points != NULL )
{
LogWrite(ZONE__DEBUG, 0, "Zone", "Got revive point in %s!", __FUNCTION__);
float closest = 100000;
float test_closest = 0;
RevivePoint* test_point = 0;
vector::iterator revive_iter;
for(revive_iter=revive_points->begin(); revive_iter != revive_points->end(); revive_iter++)
{
test_point = *revive_iter;
if(test_point)
{
test_closest = client->GetPlayer()->GetDistance(test_point->x, test_point->y, test_point->z);
// should this be changed to list all revive points within max distance or just the closest
if(test_closest < closest)
{
LogWrite(ZONE__DEBUG, 0, "Zone", "test_closest: %.2f, closest: %.2f", test_closest, closest);
closest = test_closest;
closest_point = test_point;
}
if(test_point->always_included ) {
points->push_back(test_point);
if(closest_point == test_point) {
closest_point = nullptr;
closest = 100000;
}
}
}
}
if(closest_point) {
points->push_back(closest_point);
}
}
if(closest_point && points->size() == 0 && closest_point->zone_id == GetZoneID())
{
LogWrite(ZONE__WARNING, 0, "Zone", "Nearest Revive Point too far away. Add another!");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "The closest revive point is quite far away, you might want to ask the server admin for a closer one.");
points->push_back(closest_point);
}
else if(points->size() == 0)
{
LogWrite(ZONE__WARNING, 0, "Zone", "No Revive Points set for zoneID %u. Add some!", GetZoneID());
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There are no revive points for this zone, you might want to ask the server admin for one.");
closest_point = new RevivePoint;
closest_point->heading = GetSafeHeading();
closest_point->id = 0xFFFFFFFF;
closest_point->location_name = "Zone Safe Point";
closest_point->zone_id = GetZoneID();
closest_point->x = GetSafeX();
closest_point->y = GetSafeY();
closest_point->z = GetSafeZ();
closest_point->always_included = true;
points->push_back(closest_point);
}
return points;
}
void ZoneServer::TriggerCharSheetTimer(){
charsheet_changes.Trigger();
}
void ZoneServer::RegenUpdate(){
if(damaged_spawns.size(true) == 0)
return;
Spawn* spawn = 0;
MutexList::iterator spawn_iter = damaged_spawns.begin();
while(spawn_iter.Next()){
spawn = GetSpawnByID(spawn_iter->value);
if(spawn && (((spawn->GetHP() < spawn->GetTotalHP()) && spawn->GetHP()>0) || (spawn->GetPower() < spawn->GetTotalPower()))){
if(spawn->IsEntity())
((Entity*)spawn)->DoRegenUpdate();
if(spawn->IsPlayer()){
Client* client = ((Player*)spawn)->GetClient();
if(client && client->IsReadyForUpdates())
client->QueuePacket(client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion()));
}
}
else
RemoveDamagedSpawn(spawn);
//Spawn no longer valid, remove it from the list
if (!spawn)
damaged_spawns.Remove(spawn_iter->value);
}
}
void ZoneServer::ClearDeadSpawns(){
MDeadSpawns.writelock(__FUNCTION__, __LINE__);
dead_spawns.clear();
MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::ProcessDepop(bool respawns_allowed, bool repop) {
vector::iterator client_itr;
Client* client = 0;
Spawn* spawn = 0;
PacketStruct* packet = 0;
int16 packet_version = 0;
spawn_expire_timers.clear();
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client)
continue;
client->GetPlayer()->SetTarget(0);
if(repop)
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone Repop in progress...");
else{
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone Depop in progress...");
if(respawns_allowed)
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawns will respawn according to their respawn timers.");
}
if(!packet || packet_version != client->GetVersion()){
safe_delete(packet);
packet_version = client->GetVersion();
packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version);
}
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
spawn = itr->second;
if(spawn && !spawn->IsPlayer() && !spawn->IsBot()){
bool dispatched = false;
if(spawn->IsPet())
{
Entity* owner = ((Entity*)spawn)->GetOwner();
if(owner)
{
owner->DismissPet((Entity*)spawn);
dispatched = true;
}
}
spawn->SetDeletedSpawn(true);
if(!dispatched)
SendRemoveSpawn(client, spawn, packet);
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
DeleteTransporters();
safe_delete(packet);
if(!repop && respawns_allowed){
spawn_range_map.clear(true);
MutexList tmp_player_list; // Local variable, never be on another thread so probably don't need the extra mutex code that comes with a MutexList
ClearDeadSpawns();
map::iterator itr;
MSpawnList.writelock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
spawn = itr->second;
if (spawn) {
if(spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0)
respawn_timers.Put(spawn->GetSpawnLocationID(), Timer::GetCurrentTime2() + spawn->GetRespawnTime()*1000);
if(spawn->IsPlayer() || spawn->IsBot())
tmp_player_list.Add(spawn);
else {
RemoveSpawnSupportFunctions(spawn, true);
RemoveSpawnFromGrid(spawn, spawn->GetLocation());
AddPendingDelete(spawn);
}
}
}
spawn_list.clear();
//add back just the clients
MutexList::iterator spawn_iter2 = tmp_player_list.begin();
while(spawn_iter2.Next()) {
spawn_list[spawn_iter2->value->GetID()] = spawn_iter2->value;
}
MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
}
else {
DeleteData(false);
}
if(repop)
{
// reload spirit shards for the current zone
database.LoadSpiritShards(this);
LoadingData = true;
}
}
void ZoneServer::Depop(bool respawns, bool repop) {
respawns_allowed = respawns;
repop_zone = repop;
finished_depop = false;
depop_zone = true;
}
bool ZoneServer::AddCloseSpawnsToSpawnGroup(Spawn* spawn, float radius){
if(!spawn)
return false;
Spawn* close_spawn = 0;
bool ret = true;
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
close_spawn = itr->second;
if(close_spawn && close_spawn != spawn && !close_spawn->IsPlayer() && close_spawn->GetDistance(spawn) <= radius){
if((spawn->IsNPC() && close_spawn->IsNPC()) || (spawn->IsGroundSpawn() && close_spawn->IsGroundSpawn()) || (spawn->IsObject() && close_spawn->IsObject()) || (spawn->IsWidget() && close_spawn->IsWidget()) || (spawn->IsSign() && close_spawn->IsSign())){
if(close_spawn->GetSpawnGroupID() == 0){
spawn->AddSpawnToGroup(close_spawn);
close_spawn->AddSpawnToGroup(spawn);
}
else
ret = false;
}
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
void ZoneServer::RepopSpawns(Client* client, Spawn* in_spawn){
vector* spawns = in_spawn->GetSpawnGroup();
PacketStruct* packet = configReader.getStruct("WS_DestroyGhostCmd", client->GetVersion());
if(spawns){
if(!packet)
return;
Spawn* spawn = 0;
vector::iterator itr;
for(itr = spawns->begin(); itr != spawns->end(); itr++){
spawn = *itr;
spawn->SetDeletedSpawn(true);
SendRemoveSpawn(client, spawn, packet);
}
}
safe_delete(spawns);
if(in_spawn)
in_spawn->SetDeletedSpawn(true);
SendRemoveSpawn(client, in_spawn, packet);
spawn_check_add.Trigger();
safe_delete(packet);
}
bool ZoneServer::AggroVictim(NPC* npc, Spawn* victim, Client* client)
{
bool isEntity = victim->IsEntity();
if(isEntity && !npc->AttackAllowed((Entity*)victim))
return false;
if (npc->HasSpawnGroup()) {
vector* groupVec = npc->GetSpawnGroup();
for (int32 i = 0; i < groupVec->size(); i++) {
Spawn* group_member = groupVec->at(i);
if (group_member && !group_member->EngagedInCombat() && group_member->Alive()) {
CallSpawnScript(group_member, SPAWN_SCRIPT_AGGRO, victim);
if (isEntity)
((NPC*)group_member)->AddHate((Entity*)victim, 50);
else
((NPC*)group_member)->InCombat(true);
}
}
safe_delete(groupVec);
}
else
{
if (isEntity)
{
CallSpawnScript(victim, SPAWN_SCRIPT_AGGRO, victim);
npc->AddHate((Entity*)victim, 50);
}
else
npc->InCombat(true);
}
victim->CheckEncounterState((Entity*)npc, true);
return true;
}
bool ZoneServer::CheckNPCAttacks(NPC* npc, Spawn* victim, Client* client){
if(!npc || !victim)
return true;
if (client) {
int8 arrow = 0;
if (client->IsReadyForUpdates() && npc->CanSeeInvis(client->GetPlayer()) && client->GetPlayer()->GetFactions()->ShouldAttack(npc->GetFactionID()) && npc->AttackAllowed((Entity*)victim, false)) {
if (!npc->EngagedInCombat()) {
if(client->GetPlayer()->GetArrowColor(npc->GetLevel()) != ARROW_COLOR_GRAY) {
AggroVictim(npc, victim, client);
}
else if(npc->IsScaredByStrongPlayers() &&
!client->GetPlayer()->IsSpawnInRangeList(npc->GetID())) {
SendSpawnChanges(npc, client, true, true);
client->GetPlayer()->SetSpawnInRangeList(npc->GetID(), true);
}
}
}
}
else{
AggroVictim(npc, victim, client);
}
return true;
}
bool ZoneServer::CheckEnemyList(NPC* npc) {
vector *factions;
vector::iterator faction_itr;
vector *spawns;
vector::iterator spawn_itr;
map attack_spawns;
map reverse_attack_spawns;
map::iterator itr;
int32 faction_id = npc->GetFactionID();
float distance;
if (faction_id == 0)
return true;
m_enemy_faction_list.readlock(__FUNCTION__, __LINE__);
if (enemy_faction_list.count(faction_id) > 0) {
factions = enemy_faction_list[faction_id];
for (faction_itr = factions->begin(); faction_itr != factions->end(); faction_itr++) {
m_npc_faction_list.readlock(__FUNCTION__, __LINE__);
if (npc_faction_list.count(*faction_itr) > 0) {
spawns = npc_faction_list[*faction_itr];
spawn_itr = spawns->begin();
for (spawn_itr = spawns->begin(); spawn_itr != spawns->end(); spawn_itr++) {
Spawn* spawn = GetSpawnByID(*spawn_itr);
if (spawn) {
if ((!npc->IsPrivateSpawn() || npc->AllowedAccess(spawn)) && (distance = spawn->GetDistance(npc)) <= npc->GetAggroRadius() && npc->CheckLoS(spawn))
attack_spawns[distance] = spawn;
}
}
}
m_npc_faction_list.releasereadlock(__FUNCTION__, __LINE__);
}
}
m_enemy_faction_list.releasereadlock(__FUNCTION__, __LINE__);
m_reverse_enemy_faction_list.readlock(__FUNCTION__, __LINE__);
if (reverse_enemy_faction_list.count(faction_id) > 0) {
factions = reverse_enemy_faction_list[faction_id];
for (faction_itr = factions->begin(); faction_itr != factions->end(); faction_itr++) {
m_npc_faction_list.readlock(__FUNCTION__, __LINE__);
if (npc_faction_list.count(*faction_itr) > 0) {
spawns = npc_faction_list[*faction_itr];
spawn_itr = spawns->begin();
for (spawn_itr = spawns->begin(); spawn_itr != spawns->end(); spawn_itr++) {
Spawn* spawn = GetSpawnByID(*spawn_itr);
if (spawn) {
if ((!npc->IsPrivateSpawn() || npc->AllowedAccess(spawn)) && (distance = spawn->GetDistance(npc)) <= npc->GetAggroRadius() && npc->CheckLoS(spawn))
reverse_attack_spawns[distance] = spawn;
}
}
}
m_npc_faction_list.releasereadlock(__FUNCTION__, __LINE__);
}
}
m_reverse_enemy_faction_list.releasereadlock(__FUNCTION__, __LINE__);
if (attack_spawns.size() > 0) {
for (itr = attack_spawns.begin(); itr != attack_spawns.end(); itr++)
CheckNPCAttacks(npc, itr->second);
}
if (reverse_attack_spawns.size() > 0) {
for (itr = reverse_attack_spawns.begin(); itr != reverse_attack_spawns.end(); itr++)
CheckNPCAttacks((NPC*)itr->second, npc);
}
return attack_spawns.size() == 0;
}
void ZoneServer::RemoveDeadEnemyList(Spawn *spawn)
{
int32 faction_id = spawn->GetFactionID();
vector *spawns;
vector::iterator itr;
m_npc_faction_list.writelock(__FUNCTION__, __LINE__);
if (npc_faction_list.count(faction_id) > 0) {
spawns = npc_faction_list[faction_id];
for (itr = spawns->begin(); itr != spawns->end(); itr++) {
if (*itr == spawn->GetID()) {
spawns->erase(itr);
break;
}
}
}
m_npc_faction_list.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::AddEnemyList(NPC* npc){
int32 faction_id = npc->GetFactionID();
vector *hostile_factions;
vector::iterator itr;
if(faction_id <= 9)
return;
if(!rule_manager.GetGlobalRule(R_Faction, AllowFactionBasedCombat)->GetBool()) {
LogWrite(FACTION__WARNING, 0, "Faction", "Faction Combat is DISABLED via R_Faction::AllowFactionBasedCombat rule!");
return;
}
m_npc_faction_list.readlock(__FUNCTION__, __LINE__);
if (npc_faction_list.count(faction_id) == 0) {
if(faction_id > 10) {
if ((hostile_factions = master_faction_list.GetHostileFactions(faction_id)) != NULL) {
itr = hostile_factions->begin();
for (itr = hostile_factions->begin(); itr != hostile_factions->end(); itr++) {
m_enemy_faction_list.writelock(__FUNCTION__, __LINE__);
if (enemy_faction_list.count(faction_id) == 0)
enemy_faction_list[faction_id] = new vector;
enemy_faction_list[faction_id]->push_back(*itr);
m_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__);
m_reverse_enemy_faction_list.writelock(__FUNCTION__, __LINE__);
if(reverse_enemy_faction_list.count(*itr) == 0)
reverse_enemy_faction_list[*itr] = new vector;
reverse_enemy_faction_list[*itr]->push_back(faction_id);
m_reverse_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__);
}
}
}
/*m_enemy_faction_list.writelock(__FUNCTION__, __LINE__);
if(enemy_faction_list.count(1) == 0)
enemy_faction_list[1] = new vector;
enemy_faction_list[1]->push_back(faction_id);
m_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__);*/
}
m_npc_faction_list.releasereadlock(__FUNCTION__, __LINE__);
m_npc_faction_list.writelock(__FUNCTION__, __LINE__);
if(npc_faction_list.count(faction_id) == 0)
npc_faction_list[faction_id] = new vector;
npc_faction_list[faction_id]->push_back(npc->GetID());
m_npc_faction_list.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::CheckSpawnRange(Client* client, Spawn* spawn, bool initial_login){
if(client && spawn && (initial_login || client->IsConnected())) {
if(spawn != client->GetPlayer()) {
if(spawn_range_map.count(client) == 0)
spawn_range_map.Put(client, new MutexMap());
float curDist = spawn->GetDistance(client->GetPlayer());
int32 ghost_spawn_id = client->GetPlayerPOVGhostSpawnID();
Spawn* otherSpawn = GetSpawnByID(ghost_spawn_id);
if (!client->GetPlayer()->WasSentSpawn(spawn->GetID())
&& (!otherSpawn || otherSpawn->GetDistance(spawn) > SEND_SPAWN_DISTANCE) && curDist > SEND_SPAWN_DISTANCE)
{
return;
}
spawn_range_map.Get(client)->Put(spawn->GetID(), curDist);
if(!initial_login && client && spawn->IsNPC() && (!spawn->IsPrivateSpawn() || spawn->AllowedAccess(client->GetPlayer()))
&& curDist <= ((NPC*)spawn)->GetAggroRadius() && !client->GetPlayer()->GetInvulnerable())
CheckNPCAttacks((NPC*)spawn, client->GetPlayer(), client);
}
if(!initial_login)
CheckPlayerProximity(spawn, client);
}
}
void ZoneServer::CheckSpawnRange(Spawn* spawn){
vector::iterator client_itr;
Client* client = 0;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(client && client->IsReadyForSpawns())
CheckSpawnRange(client, spawn);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
bool ZoneServer::PrepareSpawnID(Player* player, Spawn* spawn){
return player->SetSpawnMap(spawn);
}
void ZoneServer::CheckSendSpawnToClient(Client* client, bool initial_login) {
if (!client) {
LogWrite(ZONE__ERROR, 0, "Zone", "CheckSendSpawnToClient called with an invalid client");
return;
}
if (!initial_login && !client->GetInitialSpawnsSent() || (!initial_login && !client->IsReadyForSpawns()))
return;
Spawn* spawn = 0;
map* > closest_spawns;
if (spawn_range_map.count(client) > 0) {
if (initial_login || client->IsConnected()) {
MutexMap::iterator spawn_iter = spawn_range_map.Get(client)->begin();
while (spawn_iter.Next()) {
spawn = GetSpawnByID(spawn_iter->first, true);
if (spawn && spawn->GetPrivateQuestSpawn()) {
if (!spawn->IsPrivateSpawn())
spawn->AddAllowAccessSpawn(spawn);
if (spawn->MeetsSpawnAccessRequirements(client->GetPlayer())) {
if (spawn->IsPrivateSpawn() && !spawn->AllowedAccess(client->GetPlayer()))
spawn->AddAllowAccessSpawn(client->GetPlayer());
}
else if (spawn->AllowedAccess(client->GetPlayer()))
spawn->RemoveSpawnAccess(client->GetPlayer());
}
if (spawn && spawn != client->GetPlayer() && client->GetPlayer()->ShouldSendSpawn(spawn)) {
if ((!initial_login && spawn_iter->second <= SEND_SPAWN_DISTANCE) || (initial_login && (spawn_iter->second <= (SEND_SPAWN_DISTANCE / 2) || spawn->IsWidget()))) {
if(PrepareSpawnID(client->GetPlayer(), spawn)) {
if (closest_spawns.count(spawn_iter->second) == 0)
closest_spawns[spawn_iter->second] = new vector();
closest_spawns[spawn_iter->second]->push_back(spawn);
}
}
}
}
}
vector::iterator spawn_iter2;
map* >::iterator itr;
for (itr = closest_spawns.begin(); itr != closest_spawns.end(); ) {
for (spawn_iter2 = itr->second->begin(); spawn_iter2 != itr->second->end(); spawn_iter2++) {
spawn = *spawn_iter2;
if(!client->IsReloadingZone() || (client->IsReloadingZone() && spawn != client->GetPlayer()))
SendSpawn(spawn, client);
if (client->ShouldTarget() && client->GetCombineSpawn() == spawn)
client->TargetSpawn(spawn);
}
vector* vect = itr->second;
map* >::iterator tmpitr = itr;
itr++;
closest_spawns.erase(tmpitr);
safe_delete(vect);
}
}
if (initial_login)
client->SetInitialSpawnsSent(true);
}
void ZoneServer::CheckSendSpawnToClient(){
vector::iterator itr;
Client* client = 0;
MClientList.readlock(__FUNCTION__, __LINE__);
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = clients.begin(); itr != clients.end(); itr++) {
client = *itr;
if(client->IsReadyForSpawns())
CheckSendSpawnToClient(client);
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::CheckRemoveSpawnFromClient(Spawn* spawn) {
vector::iterator itr;
Client* client = 0;
PacketStruct* packet = 0;
int16 packet_version = 0;
MClientList.readlock(__FUNCTION__, __LINE__);
for (itr = clients.begin(); itr != clients.end(); itr++) {
client = *itr;
if(client){
int32 ghost_spawn_id = client->GetPlayerPOVGhostSpawnID();
Spawn* otherSpawn = GetSpawnByID(ghost_spawn_id);
if(!packet || packet_version != client->GetVersion()){
safe_delete(packet);
packet_version = client->GetVersion();
packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version);
}
if(spawn && spawn != client->GetPlayer() &&
client->GetPlayer()->WasSentSpawn(spawn->GetID()) &&
!client->GetPlayer()->IsRemovingSpawn(spawn->GetID()) &&
client->GetPlayer()->WasSpawnRemoved(spawn) == false &&
(ghost_spawn_id == 0 || (ghost_spawn_id != spawn->GetID() && otherSpawn && otherSpawn->GetDistance(spawn) > REMOVE_SPAWN_DISTANCE)) &&
(spawn_range_map.Get(client)->Get(spawn->GetID()) > REMOVE_SPAWN_DISTANCE &&
!spawn->IsSign() && !spawn->IsObject() && !spawn->IsWidget() && !spawn->IsTransportSpawn())){
SendRemoveSpawn(client, spawn, packet);
spawn_range_map.Get(client)->erase(spawn->GetID());
}
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
safe_delete(packet);
}
bool ZoneServer::CombatProcess(Spawn* spawn) {
bool ret = true;
if (spawn && spawn->IsEntity())
((Entity*)spawn)->ProcessCombat();
if (spawn && !spawn->Alive() && !spawn->IsLootDispensed()) {
LootProcess(spawn);
}
return ret;
}
void ZoneServer::LootProcess(Spawn* spawn) {
if (spawn->GetLootMethod() == GroupLootMethod::METHOD_ROUND_ROBIN) {
spawn->LockLoot();
if (spawn->CheckLootTimer() || spawn->IsLootWindowComplete()) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: Dispensing loot, loot window was completed? %s.", spawn->GetName(), spawn->IsLootWindowComplete() ? "YES" : "NO");
spawn->DisableLootTimer();
spawn->SetLootDispensed();
Spawn* looter = nullptr;
if (spawn->GetLootGroupID() < 1 && spawn->GetLootWindowList()->size() > 0) {
std::map::iterator itr;
for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) {
Spawn* entry = GetSpawnByID(itr->first, true);
if (entry->IsPlayer()) {
looter = entry;
break;
}
}
int32 item_id = 0;
std::vector item_list;
spawn->GetLootItemsList(&item_list);
spawn->UnlockLoot();
std::vector::iterator item_itr;
for (item_itr = item_list.begin(); item_itr != item_list.end(); item_itr++) {
int32 item_id = *item_itr;
Item* tmpItem = master_item_list.GetItem(item_id);
bool skipItem = spawn->IsItemInLootTier(tmpItem);
if (skipItem)
continue;
if (looter) {
if (looter->IsPlayer()) {
Item* item = spawn->LootItem(item_id);
bool success = false;
success = ((Player*)looter)->GetClient()->HandleLootItem(spawn, item, ((Player*)looter));
if (!success)
spawn->AddLootItem(item);
}
else {
Item* item = spawn->LootItem(item_id);
safe_delete(item);
}
}
}
}
else if (spawn->GetLootGroupID() > 0) {
int32 item_id = 0;
std::vector item_list;
spawn->GetLootItemsList(&item_list);
spawn->UnlockLoot();
spawn->DistributeGroupLoot_RoundRobin(&item_list);
}
if (!spawn->HasLoot()) {
if (spawn->IsNPC())
RemoveDeadSpawn(spawn);
}
else {
spawn->LockLoot();
spawn->SetLootMethod(GroupLootMethod::METHOD_FFA, 0, 0);
spawn->SetLooterSpawnID(0);
spawn->UnlockLoot();
}
}
else {
spawn->UnlockLoot();
}
}
else if ((spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || spawn->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED) && spawn->IsLootTimerRunning()) {
spawn->LockLoot();
if (spawn->CheckLootTimer() || spawn->IsLootWindowComplete()) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: Dispensing loot, loot window was completed? %s.", spawn->GetName(), spawn->IsLootWindowComplete() ? "YES" : "NO");
spawn->DisableLootTimer();
spawn->SetLootDispensed();
// identify any clients that still have the loot window open, close it out
CloseSpawnLootWindow(spawn);
// lotto items while we have loot items in the list
int32 item_id = 0;
std::vector item_list;
spawn->GetLootItemsList(&item_list);
spawn->UnlockLoot();
std::vector::iterator item_itr;
for (item_itr = item_list.begin(); item_itr != item_list.end(); item_itr++) {
int32 item_id = *item_itr;
Item* tmpItem = master_item_list.GetItem(item_id);
bool skipItem = spawn->IsItemInLootTier(tmpItem);
if (skipItem)
continue;
std::map out_entries;
std::map::iterator out_itr;
bool itemNeed = true;
switch (spawn->GetLootMethod()) {
case GroupLootMethod::METHOD_LOTTO: {
spawn->GetSpawnLottoEntries(item_id, &out_entries);
break;
}
case GroupLootMethod::METHOD_NEED_BEFORE_GREED: {
spawn->GetSpawnNeedGreedEntries(item_id, true, &out_entries);
if (out_entries.size() < 1) {
spawn->GetSpawnNeedGreedEntries(item_id, false, &out_entries);
itemNeed = false;
}
break;
}
}
if (out_entries.size() < 1) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: No spawns matched for loot attempt of %s (%u), skip item.", spawn->GetName(), tmpItem ? tmpItem->name.c_str() : "Unknown", item_id);
continue;
}
Spawn* looter = nullptr;
int32 curWinValue = 0;
for (out_itr = out_entries.begin(); out_itr != out_entries.end(); out_itr++) {
Spawn* entry = GetSpawnByID(out_itr->first, true);
if ((out_itr->second > curWinValue) || looter == nullptr) {
curWinValue = out_itr->second;
looter = entry;
}
if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) {
world.GetGroupManager()->SendGroupMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %u on %s.", entry->GetName(), out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown");
}
else {
world.GetGroupManager()->SendGroupMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %s (%u) on %s.", entry->GetName(), itemNeed ? "NEED" : "GREED", out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown");
}
}
if (looter) {
if (looter->IsPlayer()) {
Item* item = spawn->LootItem(item_id);
bool success = false;
success = ((Player*)looter)->GetClient()->HandleLootItem(spawn, item, ((Player*)looter));
if (!success)
spawn->AddLootItem(item);
}
else {
Item* item = spawn->LootItem(item_id);
safe_delete(item);
}
}
}
if (!spawn->HasLoot()) {
if (spawn->IsNPC())
RemoveDeadSpawn(spawn);
}
else {
spawn->LockLoot();
spawn->SetLootMethod(GroupLootMethod::METHOD_FFA, 0, 0);
spawn->SetLooterSpawnID(0);
spawn->UnlockLoot();
}
}
else {
spawn->UnlockLoot();
}
}
}
void ZoneServer::CloseSpawnLootWindow(Spawn* spawn) {
if (spawn->GetLootWindowList()->size() > 0) {
std::map::iterator itr;
for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) {
if (itr->second)
continue;
itr->second = true;
Spawn* looter = GetSpawnByID(itr->first, true);
if (looter && looter->IsPlayer() && ((Player*)looter)->GetClient()) {
LogWrite(LOOT__DEBUG, 0, "Loot", "%s: Close loot for player %s.", spawn->GetName(), looter->GetName());
((Player*)looter)->GetClient()->CloseLoot(spawn->GetID());
}
}
}
}
void ZoneServer::AddPendingDelete(Spawn* spawn) {
MSpawnDeleteList.writelock(__FUNCTION__, __LINE__);
spawn->SetDeletedSpawn(true);
if (spawn_delete_list.count(spawn) == 0)
spawn_delete_list.insert(make_pair(spawn, Timer::GetCurrentTime2() + spawn_delete_timer)); //give other threads up to 30 seconds to stop using this spawn reference
MSpawnDeleteList.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::DeleteSpawns(bool delete_all) {
MSpawnDeleteList.writelock(__FUNCTION__, __LINE__);
MPendingSpawnRemoval.readlock(__FUNCTION__, __LINE__);
if(spawn_delete_list.size() > 0){
map::iterator itr;
map::iterator erase_itr;
int32 current_time = Timer::GetCurrentTime2();
Spawn* spawn = 0;
for (itr = spawn_delete_list.begin(); itr != spawn_delete_list.end(); ) {
if (delete_all || current_time >= itr->second){
// we haven't removed it from the spawn list yet..
if(!delete_all && m_pendingSpawnRemove.count(itr->first->GetID()))
continue;
spawn = itr->first;
if(movementMgr != nullptr) {
movementMgr->RemoveMob((Entity*)spawn);
}
// delete brain if it has one
if(spawn->IsNPC()) {
NPC* tmpNPC = (NPC*)spawn;
if(tmpNPC->Brain())
tmpNPC->SetBrain(nullptr);
}
erase_itr = itr++;
spawn_delete_list.erase(erase_itr);
MSpawnList.writelock(__FUNCTION__, __LINE__);
lua_interface->SetLuaUserDataStale(spawn);
std::map::iterator sitr = spawn_list.find(spawn->GetID());
if(sitr != spawn_list.end()) {
spawn_list.erase(sitr);
}
if(spawn->IsCollector()) {
std::map::iterator subitr = subspawn_list[SUBSPAWN_TYPES::COLLECTOR].find(spawn->GetID());
if(subitr != subspawn_list[SUBSPAWN_TYPES::COLLECTOR].end()) {
subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(subitr);
}
}
if(spawn->GetPickupItemID()) {
std::map::iterator subitr = subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].find(spawn->GetPickupItemID());
if(subitr != subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].end() && subitr->second == spawn) {
subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].erase(subitr);
}
housing_spawn_map.erase(spawn->GetID());
}
MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
spellProcess->RemoveCaster(spawn);
safe_delete(spawn);
}
else
itr++;
}
}
MPendingSpawnRemoval.releasereadlock(__FUNCTION__, __LINE__);
MSpawnDeleteList.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::AddDamagedSpawn(Spawn* spawn){
if (spawn)
damaged_spawns.Add(spawn->GetID());
}
void ZoneServer::RemoveDamagedSpawn(Spawn* spawn){
if (spawn)
damaged_spawns.Remove(spawn->GetID());
}
bool ZoneServer::Process()
{
MMasterZoneLock->lock(); //Changing this back to a recursive lock to fix a possible /reload spells crash with multiple zones running - Foof
SetWatchdogTime(Timer::GetCurrentTime2());
#ifndef NO_CATCH
try
{
#endif
while (zoneID == 0) { //this is loaded by world
SetWatchdogTime(Timer::GetCurrentTime2());
Sleep(10);
}
if (LoadingData) {
if (lua_interface) {
string tmpScript("ZoneScripts/");
tmpScript.append(GetZoneName());
tmpScript.append(".lua");
struct stat buffer;
bool fileExists = (stat(tmpScript.c_str(), &buffer) == 0);
if (fileExists)
lua_interface->RunZoneScript(tmpScript.c_str(), "preinit_zone_script", this);
}
if (reloading) {
LogWrite(COMMAND__DEBUG, 0, "Command", "-Loading Entity Commands...");
database.LoadEntityCommands(this);
LogWrite(NPC__INFO, 0, "NPC", "-Loading Spirit Shard data...");
database.LoadSpiritShards(this);
LogWrite(NPC__INFO, 0, "NPC", "-Load Spirit Shard data complete!");
LogWrite(NPC__INFO, 0, "NPC", "-Loading NPC data...");
database.LoadNPCs(this);
LogWrite(NPC__INFO, 0, "NPC", "-Load NPC data complete!");
LogWrite(OBJECT__INFO, 0, "Object", "-Loading Object data...");
database.LoadObjects(this);
LogWrite(OBJECT__INFO, 0, "Object", "-Load Object data complete!");
LogWrite(SIGN__INFO, 0, "Sign", "-Loading Sign data...");
database.LoadSigns(this);
LogWrite(SIGN__INFO, 0, "Sign", "-Load Sign data complete!");
LogWrite(WIDGET__INFO, 0, "Widget", "-Loading Widget data...");
database.LoadWidgets(this);
LogWrite(WIDGET__INFO, 0, "Widget", "-Load Widget data complete!");
LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Loading Groundspawn data...");
database.LoadGroundSpawns(this);
database.LoadGroundSpawnEntries(this);
LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Load Groundspawn data complete!");
LogWrite(PET__INFO, 0, "Pet", "-Loading Pet data...");
database.GetPetNames(this);
LogWrite(PET__INFO, 0, "Pet", "-Load Pet data complete!");
LogWrite(LOOT__INFO, 0, "Loot", "-Loading Spawn loot data...");
database.LoadLoot(this);
LogWrite(LOOT__INFO, 0, "Loot", "-Loading Spawn loot data complete!");
LogWrite(TRANSPORT__INFO, 0, "Transport", "-Loading Transporters...");
database.LoadTransporters(this);
LogWrite(TRANSPORT__INFO, 0, "Transport", "-Loading Transporters complete!");
reloading = false;
world.RemoveReloadingSubSystem("Spawns");
}
MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__);
spawn_group_associations.clear();
MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__);
MSpawnGroupLocations.writelock(__FUNCTION__, __LINE__);
spawn_group_locations.clear();
MSpawnGroupLocations.releasewritelock(__FUNCTION__, __LINE__);
MSpawnLocationGroups.writelock(__FUNCTION__, __LINE__);
spawn_location_groups.clear();
MSpawnLocationGroups.releasewritelock(__FUNCTION__, __LINE__);
MSpawnGroupChances.writelock(__FUNCTION__, __LINE__);
spawn_group_chances.clear();
MSpawnGroupChances.releasewritelock(__FUNCTION__, __LINE__);
Map* zonemap = world.GetMap(std::string(GetZoneName()),0);
while (zonemap != nullptr && zonemap->IsMapLoading())
{
SetWatchdogTime(Timer::GetCurrentTime2());
// Client loop
ClientProcess(true);
Sleep(10);
}
DeleteTransporters();
ReloadTransporters();
database.LoadSpawns(this);
ProcessSpawnLocations();
if (!revive_points)
revive_points = new vector;
else {
while (!revive_points->empty()) {
safe_delete(revive_points->back());
revive_points->pop_back();
}
}
database.LoadRevivePoints(revive_points, GetZoneID());
RemoveLocationGrids();
database.LoadLocationGrids(this);
MMasterZoneLock->unlock();
while(true) {
ProcessPendingSpawns();
Sleep(20);
MPendingSpawnListAdd.readlock(__FUNCTION__, __LINE__);
int32 count = pending_spawn_list_add.size();
MPendingSpawnListAdd.releasereadlock(__FUNCTION__, __LINE__);
if(count < 1)
break;
}
startupDelayTimer.Start(60000); // this is hard coded for 60 seconds after the zone is loaded to allow a client to at least add itself to the list before we start zone shutdown timer
MMasterZoneLock->lock();
LoadingData = false;
const char* zone_script = world.GetZoneScript(this->GetZoneID());
if (lua_interface && zone_script) {
RemoveLocationProximities();
lua_interface->RunZoneScript(zone_script, "init_zone_script", this);
}
spawn_range.Trigger();
spawn_check_add.Trigger();
}
if (reloading_spellprocess){
MMasterZoneLock->unlock();
return !zoneShuttingDown;
}
if(shutdownTimer.Enabled() && shutdownTimer.Check() && connected_clients.size(true) == 0) {
//if(lifetime_client_count)
zoneShuttingDown = true;
/*else { // allow up to 120 seconds then timeout
LogWrite(ZONE__WARNING, 0, "Zone", "No clients have connected to zone '%s' and the shutdown timer has counted down -- will delay shutdown for 120 seconds.", GetZoneName());
shutdownTimer.Start(120000, true);
lifetime_client_count = 1;
}*/
MMasterZoneLock->unlock();
return false;
}
// client loop
if(charsheet_changes.Check())
SendCharSheetChanges();
// Client loop
ClientProcess(startupDelayTimer.Enabled());
if(startupDelayTimer.Check())
startupDelayTimer.Disable();
if(!reloading && spellProcess)
spellProcess->Process();
if (tradeskillMgr)
tradeskillMgr->Process();
// Client loop
if(client_save.Check())
SaveClients();
// Possibility to do a client loop
if(weather_enabled && weatherTimer.Check())
ProcessWeather();
// client related loop, move to main thread?
if(!zoneShuttingDown)
ProcessDrowning();
// client than location_proximities loop, move to main thread
if (location_prox_timer.Check() && !zoneShuttingDown)
CheckLocationProximity();
// client than location_grid loop, move to main thread
if (location_grid_timer.Check() && !zoneShuttingDown)
CheckLocationGrids();
if (sync_game_time_timer.Check() && !zoneShuttingDown)
SendTimeUpdateToAllClients();
world.MWorldTime.readlock(__FUNCTION__, __LINE__);
int hour = world.GetWorldTimeStruct()->hour;
int minute = world.GetWorldTimeStruct()->minute;
world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__);
if (!isDusk && (hour >= 19 || hour < 8)) {//((hour > dusk_hour || hour < dawn_hour) || ((dusk_hour == hour && minute >= dusk_minute) || (hour == dawn_hour && minute < dawn_minute)))) {
isDusk = true;
const char* zone_script = world.GetZoneScript(GetZoneID());
if (lua_interface && zone_script)
lua_interface->RunZoneScript(zone_script, "dusk", this);
ProcessSpawnConditional(SPAWN_CONDITIONAL_NIGHT);
}
else if (isDusk && hour >= 8 && hour < 19) {//((hour > dawn_hour && hour < dusk_hour) || ((hour == dawn_hour && minute >= dawn_minute) || (hour == dusk_hour && minute < dusk_minute)))) {
isDusk = false;
const char* zone_script = world.GetZoneScript(GetZoneID());
if (lua_interface && zone_script)
lua_interface->RunZoneScript(zone_script, "dawn", this);
ProcessSpawnConditional(SPAWN_CONDITIONAL_DAY);
}
// damaged spawns loop, spawn related, move to spawn thread?
if(regenTimer.Check())
RegenUpdate();
// respawn_timers loop
if(respawn_timer.Check() && !zoneShuttingDown)
CheckRespawns();
// spawn_expire_timers loop
if (spawn_expire_timer.Check() && !zoneShuttingDown)
CheckSpawnExpireTimers();
// widget_timers loop
if(widget_timer.Check() && !zoneShuttingDown)
CheckWidgetTimers();
// spawn_script_timers loop
if(!reloading && !zoneShuttingDown)
CheckSpawnScriptTimers();
// Check to see if a dead spawn needs to be removed
CheckDeadSpawnRemoval();
#ifndef NO_CATCH
}
catch(...)
{
LogWrite(ZONE__ERROR, 0, "Zone", "Exception while running '%s'", GetZoneName());
zoneShuttingDown = true;
MMasterZoneLock->unlock();
return false;
}
#endif
MMasterZoneLock->unlock();
return (zoneShuttingDown == false);
}
bool ZoneServer::SpawnProcess(){
if(depop_zone) {
depop_zone = false;
ProcessDepop(respawns_allowed, repop_zone);
finished_depop = true;
}
MMasterSpawnLock.writelock(__FUNCTION__, __LINE__);
// If the zone is loading data or shutting down don't do anything
if(!LoadingData && !zoneShuttingDown && !reloading_spellprocess) {
// Set some bool's for timers
bool movement = movement_timer.Check();
bool spawnRange = spawn_range.Check();
bool checkRemove = spawn_check_remove.Check();
bool aggroCheck = aggro_timer.Check();
vector pending_spawn_list_remove;
// Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them all
ProcessSpawnRemovals();
map::iterator itr;
if (spawnRange || checkRemove)
{
// Loop through the spawn list
MSpawnList.readlock(__FUNCTION__, __LINE__);
// Loop throught the list to set up spawns to be sent to clients
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
// if zone is shutting down kill the loop
if (zoneShuttingDown)
break;
Spawn* spawn = itr->second;
if (spawn) {
// Checks the range to all clients in the zone
if (spawnRange)
CheckSpawnRange(spawn);
// Checks to see if the spawn needs to be removed from a client
if (checkRemove)
CheckRemoveSpawnFromClient(spawn);
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
}
// Broke the spawn loop into 2 so spawns are sent to the client faster, send the spawns to clients now then resume the spawn loop
// client loop, move to main thread?
// moved this back to the spawn thread as on the main thread during a depop, done on the spawn thread, spawns would start to pop again
// might be an issue with other functions moved from the spawn thread to the main thread?
if(spawn_check_add.Check() && !zoneShuttingDown)
CheckSendSpawnToClient();
// send spawn changes, changed_spawns loop
if (spawn_update.Check() && !zoneShuttingDown) { //check for changed spawns every {Rule:SpawnUpdateTimer} milliseconds (default: 200ms)
SendSpawnChanges();
}
if (movement || aggroCheck)
{
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
// Break the loop if the zone is shutting down
if (zoneShuttingDown)
break;
Spawn* spawn = itr->second;
if (spawn) {
// Process spawn movement
if (movement) {
spawn->ProcessMovement(true);
// update last_movement_update for all spawns (used for time_step)
spawn->last_movement_update = Timer::GetCurrentTime2();
if (!aggroCheck)
CombatProcess(spawn);
}
// Makes NPC's KOS to other NPC's or players
if (aggroCheck)
{
ProcessAggroChecks(spawn);
CombatProcess(spawn);
}
}
else {
// unable to get a valid spawn, lets add the id to another list to remove from the spawn list after this loop is finished
pending_spawn_list_remove.push_back(itr->first);
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
}
// Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them all
if (pending_spawn_list_remove.size() > 0) {
MSpawnList.writelock(__FUNCTION__, __LINE__);
vector::iterator itr2;
for (itr2 = pending_spawn_list_remove.begin(); itr2 != pending_spawn_list_remove.end(); itr2++) {
spawn_list.erase(*itr2);
subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(*itr2);
std::map::iterator hsmitr = housing_spawn_map.find(*itr2);
if(hsmitr != housing_spawn_map.end()) {
subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].erase(hsmitr->second);
housing_spawn_map.erase(hsmitr);
}
}
pending_spawn_list_remove.clear();
MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
}
// Double Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them before we replace with pending spawns
// and also potentially further down when we delete the Spawn* in DeleteSpawns(false)
ProcessSpawnRemovals();
// Check to see if there are spawns waiting to be added to the spawn list, if so add them all
if (pending_spawn_list_add.size() > 0) {
ProcessPendingSpawns();
}
MSpawnList.readlock(__FUNCTION__, __LINE__);
if (movementMgr != nullptr)
movementMgr->Process();
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
if(queue_updates.Check())
ProcessQueuedStateCommands();
// Do other loops for spawns
// tracking, client loop with spawn loop for each client that is tracking, change to a spawn_range_map loop instead of using the main spawn list?
//if (tracking_timer.Check())
//ProcessTracking(); // probably doesn't work as spawn loop iterator is never set
// Delete unused spawns, do this last
if(!zoneShuttingDown)
DeleteSpawns(false);
// Nothing should come after this
//LogWrite(PLAYER__ERROR, 0, "Debug", "Spawn loop time %u", Timer::GetCurrentTime2() - time);
}
MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__);
return (zoneShuttingDown == false);
}
void ZoneServer::CheckDeadSpawnRemoval() {
MDeadSpawns.writelock(__FUNCTION__, __LINE__);
if(dead_spawns.size() > 0){
vector tmp_dead_list;
int32 current_time = Timer::GetCurrentTime2();
Spawn* spawn = 0;
map::iterator itr = dead_spawns.begin();
map::iterator itr_delete;
while (itr != dead_spawns.end()) {
spawn = GetSpawnByID(itr->first);
if (spawn) {
if(current_time >= itr->second)
tmp_dead_list.push_back(spawn);
itr++;
}
else {
itr_delete = itr++;
dead_spawns.erase(itr_delete);
}
}
for(int i=tmp_dead_list.size()-1;i>=0;i--){
spawn = tmp_dead_list[i];
if (!spawn->IsPlayer())
{
dead_spawns.erase(spawn->GetID());
MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__);
RemoveSpawn(spawn, true, true, true, true, true);
MDeadSpawns.writelock(__FUNCTION__, __LINE__);
}
}
}
MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::CheckRespawns(){
vector tmp_respawn_list;
MutexMap::iterator itr = respawn_timers.begin();
while(itr.Next()){
if(Timer::GetCurrentTime2() >= itr->second)
tmp_respawn_list.push_back(itr->first);
}
for(int i=tmp_respawn_list.size()-1;i>=0;i--){
if ( IsInstanceZone() )
{
if ( database.DeleteInstanceSpawnRemoved(GetInstanceID(),tmp_respawn_list[i]) )
{
}
else
{
}
}
ProcessSpawnLocation(tmp_respawn_list[i], true);
respawn_timers.erase(tmp_respawn_list[i]);
}
}
void ZoneServer::CheckSpawnExpireTimers() {
MutexMap::iterator itr = spawn_expire_timers.begin();
while (itr.Next()) {
Spawn* spawn = GetSpawnByID(itr->first);
if (spawn) {
if (Timer::GetCurrentTime2() >= itr.second) {
spawn_expire_timers.erase(itr.first);
Despawn(spawn, spawn->GetRespawnTime());
}
}
else
spawn_expire_timers.erase(itr->first);
}
}
void ZoneServer::AddSpawnExpireTimer(Spawn* spawn, int32 expire_time, int32 expire_offset) {
if (spawn) {
int32 actual_expire_time = expire_time;
if (expire_offset > 0) {
int32 low = expire_time;
int32 high = expire_time + expire_offset;
if (expire_offset < expire_time)
low = expire_time - expire_offset;
int32 range = (high - low) + 1;
actual_expire_time = (low + (int32)((range * rand()) / (RAND_MAX + 1.0)));
}
actual_expire_time *= 1000;
spawn_expire_timers.Put(spawn->GetID(), Timer::GetCurrentTime2() + actual_expire_time);
}
}
void ZoneServer::SaveClient(Client* client){
client->Save();
}
void ZoneServer::SaveClients(){
vector::iterator itr;
Client* client = 0;
MClientList.readlock(__FUNCTION__, __LINE__);
for (itr = clients.begin(); itr != clients.end(); itr++) {
client = *itr;
if(client->IsConnected() && client->IsReadyForUpdates()){
SaveClient(client);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendSpawnVisualState(Spawn* spawn, int16 type){
if(!spawn)
return;
vector::iterator itr;
spawn->SetTempVisualState(type);
Client* client = 0;
MClientList.readlock(__FUNCTION__, __LINE__);
for (itr = clients.begin(); itr != clients.end(); itr++) {
client = *itr;
if(client && client->GetPlayer() != spawn)
AddChangedSpawn(spawn);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendSpawnChangesByDBID(int32 db_id, Client* client, bool override_changes, bool override_vis_changes){
Spawn* spawn = GetSpawnByDatabaseID(db_id);
if(spawn && (spawn->changed || override_changes || override_vis_changes))
SendSpawnChanges(spawn, client, override_changes, override_vis_changes);
}
void ZoneServer::SendSpawnChanges(Spawn* spawn, Client* client, bool override_changes, bool override_vis_changes){
if(client && client->IsConnected() && client->IsReadyForUpdates() && client->GetPlayer()->WasSentSpawn(spawn->GetID()) && (spawn->IsTransportSpawn() || client->GetPlayer()->GetDistance(spawn) < SEND_SPAWN_DISTANCE)){
EQ2Packet* outapp = spawn->spawn_update_packet(client->GetPlayer(), client->GetVersion(), override_changes, override_vis_changes);
if(outapp)
client->QueuePacket(outapp);
}
}
void ZoneServer::SendSpawnChanges(Spawn* spawn){
MClientList.readlock(__FUNCTION__, __LINE__);
MSpawnList.readlock();
if(spawn && spawn->changed){
if(!spawn->IsPlayer() || (spawn->IsPlayer() && (spawn->info_changed || spawn->vis_changed))){
vector::iterator itr;
Client* client = 0;
// MClientList locked at a higher level
for (itr = clients.begin(); itr != clients.end(); itr++) {
client = *itr;
SendSpawnChanges(spawn, client);
}
}
spawn->changed = false;
spawn->info_changed = false;
if(spawn->IsPlayer() == false)
spawn->position_changed = false;
spawn->vis_changed = false;
}
MSpawnList.releasereadlock();
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
Spawn* ZoneServer::FindSpawn(Player* searcher, const char* name){
if(!searcher || !name)
return 0;
Spawn* spawn = 0;
vector find_spawn_list;
vector::iterator fspawn_iter;
int8 name_size = strlen(name);
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
spawn = itr->second;
if(spawn && !strncasecmp(spawn->GetName(), name, name_size))
find_spawn_list.push_back(spawn);
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
Spawn* closest = 0;
float distance = 0;
float test_distance = 0;
for(fspawn_iter=find_spawn_list.begin(); fspawn_iter!=find_spawn_list.end(); fspawn_iter++){
spawn = *fspawn_iter;
if(spawn && ((((test_distance = searcher->GetDistance(spawn)) < distance)) || !closest)){
distance = test_distance;
closest = spawn;
}
}
return closest;
}
void ZoneServer::AddChangedSpawn(Spawn* spawn) {
if (!spawn || (spawn->IsPlayer() && !spawn->info_changed && !spawn->vis_changed) || (spawn->IsPlayer() && ((Player*)spawn)->GetClient() && ((Player*)spawn)->GetClient()->IsReadyForUpdates() == false))
return;
MChangedSpawns.lock_shared();
ChangedSpawnMapType::iterator it = changed_spawns.find(spawn->GetID());
if (it != changed_spawns.end()) {
it->second = true;
MChangedSpawns.unlock_shared();
}
else {
MChangedSpawns.unlock_shared();
MChangedSpawns.lock();
changed_spawns.insert(make_pair(spawn->GetID(),true));
MChangedSpawns.unlock();
}
}
void ZoneServer::RemoveChangedSpawn(Spawn* spawn){
if(!spawn)
return;
MChangedSpawns.lock();
ChangedSpawnMapType::iterator it = changed_spawns.find(spawn->GetID());
if (it != changed_spawns.end()) {
it->second = false;
}
MChangedSpawns.unlock();
}
void ZoneServer::AddDrowningVictim(Player* player){
Client* client = ((Player*)player)->GetClient();
if(client && drowning_victims.count(client) == 0)
drowning_victims.Put(client, Timer::GetCurrentTime2());
}
void ZoneServer::RemoveDrowningVictim(Player* player){
Client* client = ((Player*)player)->GetClient();
if(client)
drowning_victims.erase(client);
}
Client* ZoneServer::GetDrowningVictim(Player* player){
Client* client = ((Player*)player)->GetClient();
if(client && drowning_victims.count(client) > 0)
return(client);
return 0;
}
void ZoneServer::ProcessDrowning(){
vector dead_list;
if(drowning_victims.size(true) > 0){
sint32 damage = 0;
int32 current_time = Timer::GetCurrentTime2();
MutexMap::iterator itr = drowning_victims.begin();
while(itr.Next()){
if(current_time >= itr->second) {
Client* client = itr->first;
Player* player = client->GetPlayer();
drowning_victims.Get(client) = Timer::GetCurrentTime2() + 2000;
damage = player->GetTotalHP()/20 + player->GetInfoStruct()->get_hp_regen();
player->TakeDamage(damage);
if(!player->Alive())
dead_list.push_back(client);
player->SetCharSheetChanged(true);
SendCharSheetChanges(client);
SendDamagePacket(0, player, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, DAMAGE_PACKET_RESULT_SUCCESSFUL, DAMAGE_PACKET_DAMAGE_TYPE_DROWN, damage, 0);
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are drowning!");
}
}
}
if(dead_list.size() > 0){
vector::iterator itr;
for(itr = dead_list.begin(); itr != dead_list.end(); itr++){
RemoveDrowningVictim((*itr)->GetPlayer());
KillSpawn(false, (*itr)->GetPlayer(), nullptr, true, 0, 0, 10); // kill blow type 10 means death by WATER! (glug glug!)
}
}
}
void ZoneServer::SendSpawnChanges(){
std::shared_lock lock(MChangedSpawns);
if (changed_spawns.size() < 1)
return;
set spawns_to_send;
Spawn* spawn = 0;
int count = 0;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for( ChangedSpawnMapType::iterator it = changed_spawns.begin(); it != changed_spawns.end(); ++it ) {
if(!it->second)
continue;
spawn = GetSpawnByID(it->first);
if(spawn){
spawns_to_send.insert(spawn);
count++;
}
}
vector::iterator client_itr;
Client* client = 0;
MClientList.readlock(__FUNCTION__, __LINE__);
if(clients.size())
{
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(client)
client->SendSpawnChanges(spawns_to_send);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
for (const auto& spawn : spawns_to_send) {
spawn->changed = false;
spawn->position_changed = false;
spawn->vis_changed = false;
spawn->info_changed = false;
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendPlayerPositionChanges(Player* player){
if(player){
player->position_changed = false;
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(player != client->GetPlayer() && client->GetPlayer()->WasSentSpawn(player->GetID())){
EQ2Packet* outapp = player->player_position_update_packet(client->GetPlayer(), client->GetVersion());
if(outapp)
client->QueuePacket(outapp);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
}
void ZoneServer::SendCharSheetChanges(){
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++)
SendCharSheetChanges(*client_itr);
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendCharSheetChanges(Client* client){
if(client && client->IsConnected() && client->GetPlayer()->GetCharSheetChanged()){
client->GetPlayer()->SetCharSheetChanged(false);
ClientPacketFunctions::SendCharacterSheet(client);
}
}
int32 ZoneServer::CalculateSpawnGroup(SpawnLocation* spawnlocation, bool respawn)
{
int32 group = 0;
list* groups_at_location = GetSpawnGroupsByLocation(spawnlocation->placement_id);
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
if(groups_at_location){
list::iterator group_location_itr;
float chance = 0;
float total_chance = 0;
map tmp_chances;
set* associated_groups = 0;
for (group_location_itr = groups_at_location->begin(); group_location_itr != groups_at_location->end(); group_location_itr++) {
if(tmp_chances.count(*group_location_itr) > 0)
continue;
associated_groups = GetAssociatedGroups(*group_location_itr);
if(associated_groups){
set::iterator group_itr;
for (group_itr = associated_groups->begin(); group_itr != associated_groups->end(); group_itr++) {
chance = GetSpawnGroupChance(*group_itr);
if(chance > 0){
total_chance += chance;
tmp_chances[*group_itr] = chance;
}
else
tmp_chances[*group_itr] = 0;
}
}
else{ //single group, no associations
chance = GetSpawnGroupChance(*group_location_itr);
total_chance += chance;
tmp_chances[*group_location_itr] = chance;
}
}
if(tmp_chances.size() > 1){
//set the default for any chances not set
map::iterator itr2;
for(itr2 = tmp_chances.begin(); itr2 != tmp_chances.end(); itr2++){
if(itr2->second == 0){
total_chance += 100/tmp_chances.size();
tmp_chances[itr2->first] = (float)(100 / tmp_chances.size());
}
}
}
if(tmp_chances.size() > 1){
float roll = (float)(rand()%((int32)total_chance));
map::iterator itr3;
for (itr3 = tmp_chances.begin(); itr3 != tmp_chances.end(); itr3++){
if(itr3->second >= roll){
group = itr3->first;
break;
}
else
roll -= itr3->second;
}
}
else if(tmp_chances.size() == 1)
group = tmp_chances.begin()->first;
}
if(group > 0){
map* locations = GetSpawnLocationsByGroup(group);
if(locations){
map::iterator itr;
Spawn* spawn = 0;
Spawn* leader = 0;
MSpawnLocationList.readlock(__FUNCTION__, __LINE__);
for (itr = locations->begin(); itr != locations->end(); itr++) {
if(spawn_location_list.count(itr->second) > 0){
spawn = ProcessSpawnLocation(spawn_location_list[itr->second], respawn);
if(!leader && spawn)
leader = spawn;
if(leader)
leader->AddSpawnToGroup(spawn);
if(spawn){
//if(spawn_group_map.count(group) == 0)
// spawn_group_map.Put(group, new MutexList());
MutexList* groupList = &spawn_group_map.Get(group);
groupList->Add(spawn->GetID());
spawn->SetSpawnGroupID(group);
}
}
}
MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__);
}
}
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
return group;
}
void ZoneServer::ProcessSpawnLocation(int32 location_id, bool respawn)
{
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
MSpawnLocationList.readlock(__FUNCTION__, __LINE__);
if(spawn_location_list.count(location_id) > 0)
{
if(respawn) //see if there are any spawns still in game associated with this spawn's group, if so, dont spawn this
{
list* groups = GetSpawnGroupsByLocation(spawn_location_list[location_id]->placement_id);
if(groups)
{
set* associated_groups = 0;
bool should_spawn = true;
list::iterator itr;
for (itr = groups->begin(); itr != groups->end(); itr++) {
associated_groups = GetAssociatedGroups(*itr);
if(associated_groups)
{
set::iterator assoc_itr;
for (assoc_itr = associated_groups->begin(); assoc_itr != associated_groups->end(); assoc_itr++) {
if(spawn_group_map.count(*assoc_itr) > 0 && spawn_group_map.Get(*assoc_itr).size() > 0)
should_spawn = false;
}
}
}
if(should_spawn)
CalculateSpawnGroup(spawn_location_list[location_id]);
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
// need to unlock the list before we exit the function
MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__);
return;
}
}
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
ProcessSpawnLocation(spawn_location_list[location_id], respawn);
}
MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__);
}
Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respawn)
{
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
if(!spawnlocation)
return 0;
Spawn* spawn = 0;
float rand_number = MakeRandomFloat(0, spawnlocation->total_percentage);
for(int32 i=0;ientities.size();i++)
{
if(spawnlocation->entities[i]->spawn_percentage == 0)
continue;
if (spawnlocation->conditional > 0) {
if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_DAY && isDusk)
continue;
if ((spawnlocation->conditional & SPAWN_CONDITIONAL_NIGHT) == SPAWN_CONDITIONAL_NIGHT && !isDusk)
continue;
if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_NOT_RAINING && rain >= 0.75f)
continue;
if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_RAINING && rain < 0.75f)
continue;
}
if (spawnlocation->entities[i]->spawn_percentage >= rand_number) {
if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC)
spawn = AddNPCSpawn(spawnlocation, spawnlocation->entities[i]);
else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN)
spawn = AddGroundSpawn(spawnlocation, spawnlocation->entities[i]);
else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT)
spawn = AddObjectSpawn(spawnlocation, spawnlocation->entities[i]);
else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET)
spawn = AddWidgetSpawn(spawnlocation, spawnlocation->entities[i]);
else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN)
spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]);
if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
database.GetHouseSpawnInstanceData(this, spawn);
if(spawn && spawn->IsOmittedByDBFlag())
{
LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) in spawn location id %u was skipped due to a missing expansion / holiday flag being met (ZoneServer::ProcessSpawnLocation)", spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_location_id);
safe_delete(spawn);
spawn = 0;
continue;
}
else if (!spawn)
{
LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by spawn location to zone %s with location id %u, spawn id %u, spawn type %u.", GetZoneName(), spawnlocation->entities[i]->spawn_location_id, spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_type);
continue;
}
if (spawn)
{
if(respawn)
CallSpawnScript(spawn, SPAWN_SCRIPT_RESPAWN);
else
CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN);
}
break;
}
else
rand_number -= spawnlocation->entities[i]->spawn_percentage;
}
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
return spawn;
}
Spawn* ZoneServer::ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn)
{
if(!spawnlocation)
return 0;
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
Spawn* spawn = 0;
float rand_number = MakeRandomFloat(0, spawnlocation->total_percentage);
for(int32 i=0;ientities.size();i++)
{
if(spawnlocation->entities[i]->spawn_percentage == 0)
continue;
int32 spawnTime = 0;
if(spawnlocation->entities[i]->spawn_percentage >= rand_number)
{
if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC &&
(spawnTime = database.CheckSpawnRemoveInfo(instNPCs,spawnlocation->entities[i]->spawn_location_id)) > 0)
spawn = AddNPCSpawn(spawnlocation, spawnlocation->entities[i]);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN &&
(spawnTime = database.CheckSpawnRemoveInfo(instGroundSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
spawn = AddGroundSpawn(spawnlocation, spawnlocation->entities[i]);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT &&
(spawnTime = database.CheckSpawnRemoveInfo(instObjSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
spawn = AddObjectSpawn(spawnlocation, spawnlocation->entities[i]);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET &&
(spawnTime = database.CheckSpawnRemoveInfo(instWidgetSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
spawn = AddWidgetSpawn(spawnlocation, spawnlocation->entities[i]);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN &&
(spawnTime = database.CheckSpawnRemoveInfo(instSignSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]);
if(spawn && spawn->IsOmittedByDBFlag())
{
LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) in spawn location id %u was skipped due to a missing expansion / holiday flag being met (ZoneServer::ProcessInstanceSpawnLocation)", spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_location_id);
safe_delete(spawn);
spawn = 0;
continue;
}
if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
database.GetHouseSpawnInstanceData(this, spawn);
const char* script = 0;
for(int x=0;x<3;x++)
{
switch(x)
{
case 0:
script = world.GetSpawnEntryScript(spawnlocation->entities[i]->spawn_entry_id);
break;
case 1:
script = world.GetSpawnLocationScript(spawnlocation->entities[i]->spawn_location_id);
break;
case 2:
script = world.GetSpawnScript(spawnlocation->entities[i]->spawn_id);
break;
}
if(spawn && script && lua_interface->GetSpawnScript(script) != 0)
{
spawn->SetSpawnScript(string(script));
break;
}
}
if(spawn)
{
if (respawn)
CallSpawnScript(spawn, SPAWN_SCRIPT_RESPAWN);
else
CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN);
if ( spawnTime > 1 )
{
spawn->SetRespawnTime(spawnTime);
}
}
break;
}
else
rand_number -= spawnlocation->entities[i]->spawn_percentage;
}
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
return spawn;
}
void ZoneServer::ProcessSpawnLocations()
{
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
map* instNPCs = NULL;
map* instGroundSpawns = NULL;
map* instObjSpawns = NULL;
map* instWidgetSpawns = NULL;
map* instSignSpawns = NULL;
if ( this->IsInstanceZone() )
{
LogWrite(SPAWN__DEBUG, 0, "Spawn", "Processing Instance Removed Spawns...");
instNPCs = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_NPC );
instGroundSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_GROUNDSPAWN );
instObjSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_OBJECT );
instWidgetSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_WIDGET );
instSignSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_SIGN );
}
map processed_spawn_locations;
map::iterator itr;
MSpawnLocationList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_location_list.begin(); itr != spawn_location_list.end(); itr++) {
LogWrite(SPAWN__TRACE, 0, "Spawn", "while spawn_location_list itr (#%u)", spawn_location_list.size());
if(itr->second && processed_spawn_locations.count(itr->second->placement_id) > 0) //if we processed one spawn in a spawn group, we processed them all for that group
continue;
if(itr->second && spawn_location_groups.count(itr->second->placement_id) > 0)
{
int32 group_id = CalculateSpawnGroup(itr->second);
if(group_id)
{
LogWrite(SPAWN__TRACE, 0, "Spawn", "is group_id");
set* associated_groups = GetAssociatedGroups(group_id);
if(associated_groups)
{
LogWrite(SPAWN__TRACE, 0, "Spawn", "is associated_groups");
vector* associated_locations = GetAssociatedLocations(associated_groups);
if(associated_locations)
{
LogWrite(SPAWN__TRACE, 0, "Spawn", "is associated_locations");
for(int32 i=0;isize();i++)
{
LogWrite(SPAWN__DEBUG, 5, "Spawn", "Loading processed_spawn_locations...");
processed_spawn_locations[associated_locations->at(i)] = true;
}
safe_delete(associated_locations);
}
}
}
}
else
{
if ( this->IsInstanceZone() )
{
//LogWrite(SPAWN__DEBUG, 5, "Spawn", "ProcessInstanceSpawnLocation (%u)...", itr->second->placement_id);
ProcessInstanceSpawnLocation(itr->second,instNPCs,instGroundSpawns,instObjSpawns,instWidgetSpawns,instSignSpawns);
}
else
{
//LogWrite(SPAWN__DEBUG, 5, "Spawn", "ProcessSpawnLocation (%u)...", itr->second->placement_id);
ProcessSpawnLocation(itr->second);
}
}
}
MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__);
safe_delete(instNPCs);
safe_delete(instGroundSpawns);
safe_delete(instObjSpawns);
safe_delete(instWidgetSpawns);
safe_delete(instSignSpawns);
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
}
void ZoneServer::AddLoot(NPC* npc, Spawn* killer, GroupLootMethod loot_method, int8 item_rarity, int32 group_id){
// this function is ran twice, first on spawn of mob, then at death of mob (gray mob check and no_drop_quest_completed_id check)
// first we see if the skipping of gray mobs loot is enabled, then we move all non body drops
if(killer)
{
npc->SetLootMethod(loot_method, item_rarity, group_id);
int8 skip_loot_gray_mob_flag = rule_manager.GetGlobalRule(R_Loot, SkipLootGrayMob)->GetInt8();
if(skip_loot_gray_mob_flag) {
Player* player = 0;
if(killer->IsPlayer())
player = (Player*)killer;
else if(killer->IsPet()) {
Spawn* owner = ((Entity*)killer)->GetOwner();
if(owner->IsPlayer())
player = (Player*)owner;
}
if(player) {
int8 difficulty = player->GetArrowColor(npc->GetLevel());
if(difficulty == ARROW_COLOR_GRAY) {
npc->ClearNonBodyLoot();
}
}
}
}
// check for starting loot of Spawn and death of Spawn loot (no_drop_quest_completed_id)
vector loot_tables = GetSpawnLootList(npc->GetDatabaseID(), GetZoneID(), npc->GetLevel(), race_types_list.GetRaceType(npc->GetModelType()), npc);
if(loot_tables.size() > 0){
vector* loot_drops = 0;
vector::iterator loot_drop_itr;
LootTable* table = 0;
vector::iterator loot_list_itr;
float chancecoin = 0;
float chancetable = 0;
float chancedrop = 0;
float chancetally = 0;
float droptally = 0;
// the following loop,loops through each table
for(loot_list_itr = loot_tables.begin(); loot_list_itr != loot_tables.end(); loot_list_itr++){
table = GetLootTable(*loot_list_itr);
// if killer is assigned this is on-death, we already assigned coin
if(!killer && table && table->maxcoin > 0){
chancecoin = rand()%100;
if(table->coin_probability >= chancecoin){
if(table->maxcoin > table->mincoin)
npc->AddLootCoins(table->mincoin + rand()%(table->maxcoin - table->mincoin));
}
}
int numberchances = 1;
//if (table->lootdrop_probability == 100){ }
//else
//chancetally += table->lootdrop_probability;
int maxchance = 0;
if (table) {
maxchance = table->maxlootitems;
for (numberchances; numberchances <= maxchance; numberchances++) {
chancetable = static_cast (rand()) / (static_cast (RAND_MAX / 100));
//LogWrite(PLAYER__DEBUG, 0, "Player", "Table Chance: '%f'", chancetable);
float droppercenttotal = 0;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
if (table->lootdrop_probability == 100 || table->lootdrop_probability >= chancetable) {
//LogWrite(PLAYER__DEBUG, 0, "Player", "Probability:%f Table Chance: '%f'", table->lootdrop_probability, chancetable);
loot_drops = GetLootDrops(*loot_list_itr);
if (loot_drops && loot_drops->size() > 0) {
LootDrop* drop = 0;
int16 count = 0;
std::shuffle(loot_drops->begin(), loot_drops->end(), std::default_random_engine(Timer::GetCurrentTime2()));
int16 IC = 0;
for (loot_drop_itr = loot_drops->begin(); loot_drop_itr != loot_drops->end(); loot_drop_itr++) {
drop = *loot_drop_itr;
droppercenttotal += drop->probability;
}
int droplistsize = loot_drops->size();
float chancedroptally = 0;
bool breakIterMaxLoot = false;
chancedrop = static_cast (rand()) / (static_cast (RAND_MAX / 100));
for (loot_drop_itr = loot_drops->begin(); loot_drop_itr != loot_drops->end(); loot_drop_itr++) {
drop = *loot_drop_itr;
// if no killer is provided, then we are instantiating the spawn loot, quest related loot should be added on death of the spawn to check against spawn/group members
if(drop->no_drop_quest_completed_id && killer == nullptr)
continue;
else if(!drop->no_drop_quest_completed_id && killer) // skip since this doesn't have a quest id attached and we are doing after-math of death loot additions
continue;
else if(killer && drop->no_drop_quest_completed_id) // check if the player already completed quest related to item
{
Player* player = nullptr;
if(killer->IsPlayer())
{
player = (Player*)killer;
// player has already completed the quest
if(player->HasQuestBeenCompleted(drop->no_drop_quest_completed_id) && !player->GetGroupMemberInfo())
{
LogWrite(PLAYER__DEBUG, 0, "Player", "%s: Player has completed quest %u, skipping loot item %u", npc->GetName(), drop->no_drop_quest_completed_id, drop->item_id);
continue;
}
else if(player->GetGroupMemberInfo() && world.GetGroupManager()->HasGroupCompletedQuest(player->GetGroupMemberInfo()->group_id, drop->no_drop_quest_completed_id))
{
LogWrite(PLAYER__DEBUG, 0, "Player", "%s: Group %u has completed quest %u, skipping loot item %u", npc->GetName(), player->GetGroupMemberInfo()->group_id, drop->no_drop_quest_completed_id, drop->item_id);
continue;
}
}
else
{
LogWrite(PLAYER__DEBUG, 0, "Player", "%s: Killer is not a player, skipping loot item %u", npc->GetName(), drop->item_id);
continue;
}
}
if (npc->HasLootItemID(drop->item_id))
continue;
if (droppercenttotal >= 100)
droppercenttotal = 100;
chancedroptally += 100 / droppercenttotal * drop->probability;
//chancedrop = static_cast (rand()) / (static_cast (RAND_MAX / 100));
//LogWrite(PLAYER__DEBUG, 0, "Player", "Loot drop: '%i' Chance: %f Prob tally: %f min: %f", drop, chancedrop, chancedroptally, chancedroptally - drop->probability);
if ((chancedroptally == 100) || ((chancedroptally >= chancedrop) && (chancedroptally - (100 / droppercenttotal * drop->probability)) <= chancedrop)) {
//LogWrite(PLAYER__DEBUG, 0, "Player", "Loot drop: '%i' Chance: %f Prob: %f We have a loot drop winner", drop, chancedrop, chancedroptally);
count++;
npc->AddLootItem(drop->item_id, drop->item_charges);
//LogWrite(PLAYER__DEBUG, 0, "Player", "loot Count: '%i'",count);
//LogWrite(MISC__TODO, 1, "TODO", "Auto-Equip new looted items\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
//if(drop->equip_item)
}
// so many items on this table break out and cap it!
if (table->maxlootitems > 0 && count >= table->maxlootitems) {
breakIterMaxLoot = true;
break;
}
}
// hit our max item drop for this table already, break out of numberchances
if(breakIterMaxLoot) {
break;
}
}
}
}
}
}
}
}
void ZoneServer::DeterminePosition(SpawnLocation* spawnlocation, Spawn* spawn){
if(!spawn || !spawnlocation)
return;
int offset = 0;
if(spawnlocation->x_offset > 0){
//since rand() deals with integers only, we are going to divide by 1000 later so that we can use fractions of integers
offset = (int)((spawnlocation->x_offset*1000)+1);
spawn->SetX(spawnlocation->x + ((float)(rand()%offset - rand()%offset))/1000);
}
else
spawn->SetX(spawnlocation->x);
if(spawnlocation->y_offset > 0){
//since rand() deals with integers only, we are going to divide by 1000 later so that we can use fractions of integers
offset = (int)((spawnlocation->y_offset*1000)+1);
spawn->SetY(spawnlocation->y + ((float)(rand()%offset - rand()%offset))/1000);
}
else
spawn->SetY(spawnlocation->y, true, true);
if(spawnlocation->z_offset > 0){
//since rand() deals with integers only, we are going to divide by 1000 later so that we can use fractions of integers
offset = (int)((spawnlocation->z_offset*1000)+1);
spawn->SetZ(spawnlocation->z + ((float)(rand()%offset - rand()%offset))/1000);
}
else
spawn->SetZ(spawnlocation->z);
spawn->SetHeading(spawnlocation->heading);
spawn->SetPitch(spawnlocation->pitch);
spawn->SetRoll(spawnlocation->roll);
spawn->SetSpawnOrigX(spawn->GetX());
spawn->SetSpawnOrigY(spawn->GetY());
spawn->SetSpawnOrigZ(spawn->GetZ());
spawn->SetSpawnOrigHeading(spawn->GetHeading());
spawn->SetSpawnOrigPitch(spawnlocation->pitch);
spawn->SetSpawnOrigRoll(spawnlocation->roll);
spawn->SetLocation(spawnlocation->grid_id);
spawn->SetSpawnLocationPlacementID(spawnlocation->placement_id);
}
NPC* ZoneServer::AddNPCSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__);
NPC* npc = GetNewNPC(spawnentry->spawn_id);
if(npc && !npc->IsOmittedByDBFlag()){
InfoStruct* info = npc->GetInfoStruct();
DeterminePosition(spawnlocation, npc);
npc->SetDatabaseID(spawnentry->spawn_id);
npc->SetSpawnLocationID(spawnentry->spawn_location_id);
npc->SetSpawnEntryID(spawnentry->spawn_entry_id);
npc->SetRespawnTime(spawnentry->respawn);
npc->SetExpireTime(spawnentry->expire_time);
//devn00b add overrides for some spawns
if(spawnentry->hp_override > 0){
npc->SetHP(spawnentry->hp_override);
}
if(spawnentry->lvl_override > 0){
npc->SetLevel(spawnentry->lvl_override);
}
if(spawnentry->mp_override > 0){
npc->SetPower(spawnentry->mp_override);
}
if(spawnentry->str_override > 0){
info->set_str_base(spawnentry->str_override);
info->set_str(spawnentry->str_override);
}
if(spawnentry->sta_override > 0){
info->set_sta_base(spawnentry->sta_override);
info->set_sta(spawnentry->sta_override);
}
if(spawnentry->wis_override > 0){
info->set_wis_base(spawnentry->wis_override);
info->set_wis(spawnentry->wis_override);
}
if(spawnentry->int_override > 0){
info->set_intel_base(spawnentry->int_override);
info->set_intel(spawnentry->int_override);
}
if(spawnentry->agi_override > 0){
info->set_agi_base(spawnentry->agi_override);
info->set_agi(spawnentry->agi_override);
}
if(spawnentry->heat_override > 0){
info->set_heat_base(spawnentry->heat_override);
info->set_heat(spawnentry->heat_override);
}
if(spawnentry->cold_override > 0){
info->set_cold_base(spawnentry->cold_override);
info->set_cold(spawnentry->cold_override);
}
if(spawnentry->magic_override > 0){
info->set_magic_base(spawnentry->magic_override);
info->set_magic(spawnentry->magic_override);
}
if(spawnentry->mental_override > 0){
info->set_mental_base(spawnentry->mental_override);
info->set_mental(spawnentry->mental_override);
}
if(spawnentry->divine_override > 0){
info->set_divine_base(spawnentry->divine_override);
info->set_divine(spawnentry->divine_override);
}
if(spawnentry->disease_override > 0){
info->set_disease_base(spawnentry->disease_override);
info->set_disease(spawnentry->disease_override);
}
if(spawnentry->poison_override > 0){
info->set_poison_base(spawnentry->poison_override);
info->set_poison(spawnentry->poison_override);
}
if(spawnentry->difficulty_override > 0){
npc->SetDifficulty(spawnentry->difficulty_override, 1);
}
if (spawnentry->expire_time > 0)
AddSpawnExpireTimer(npc, spawnentry->expire_time, spawnentry->expire_offset);
AddLoot(npc);
SetSpawnScript(spawnentry, npc);
CallSpawnScript(npc, SPAWN_SCRIPT_PRESPAWN);
AddSpawn(npc);
}
LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__);
return npc;
}
vector* ZoneServer::GetAssociatedLocations(set* groups){
vector* ret = 0;
LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__);
if(groups){
int32 group_id = 0;
set::iterator group_itr;
for (group_itr = groups->begin(); group_itr != groups->end(); group_itr++) {
if(!ret)
ret = new vector();
group_id = *group_itr;
MSpawnGroupLocations.readlock(__FUNCTION__, __LINE__);
if(spawn_group_locations.count(group_id) > 0){
map::iterator itr;
for (itr = spawn_group_locations[group_id]->begin(); itr != spawn_group_locations[group_id]->end(); itr++) {
ret->push_back(itr->first);
}
}
MSpawnGroupLocations.releasereadlock(__FUNCTION__, __LINE__);
}
}
LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__);
return ret;
}
set* ZoneServer::GetAssociatedGroups(int32 group_id) {
set* ret = 0;
MSpawnGroupAssociation.readlock(__FUNCTION__, __LINE__);
LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__);
if(spawn_group_associations.count(group_id) > 0)
ret = spawn_group_associations[group_id];
LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__);
MSpawnGroupAssociation.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
map* ZoneServer::GetSpawnLocationsByGroup(int32 group_id) {
map* ret = 0;
MSpawnGroupLocations.readlock(__FUNCTION__, __LINE__);
if(spawn_group_locations.count(group_id) > 0)
ret = spawn_group_locations[group_id];
MSpawnGroupLocations.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
list* ZoneServer::GetSpawnGroupsByLocation(int32 location_id){
list* ret = 0;
MSpawnLocationGroups.readlock(__FUNCTION__, __LINE__);
if(spawn_location_groups.count(location_id) > 0)
ret = spawn_location_groups[location_id];
MSpawnLocationGroups.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
float ZoneServer::GetSpawnGroupChance(int32 group_id){
float ret = -1;
MSpawnGroupChances.readlock(__FUNCTION__, __LINE__);
if(spawn_group_chances.count(group_id) > 0)
ret = spawn_group_chances[group_id];
MSpawnGroupChances.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
void ZoneServer::AddSpawnGroupChance(int32 group_id, float percent){
MSpawnGroupChances.writelock(__FUNCTION__, __LINE__);
spawn_group_chances[group_id] = percent;
MSpawnGroupChances.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::AddSpawnGroupAssociation(int32 group_id1, int32 group_id2) {
MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__);
//Check if we already have containers for these group ids, if not create them
if (spawn_group_associations.count(group_id1) == 0)
spawn_group_associations[group_id1] = new set;
if (spawn_group_associations.count(group_id2) == 0)
spawn_group_associations[group_id2] = new set;
//Associate groups 1 and 2 now
set* group_1 = spawn_group_associations.find(group_id1)->second;
set* group_2 = spawn_group_associations.find(group_id2)->second;
group_1->insert(group_id2);
group_2->insert(group_id1);
//Associate the remaining groups together
set::iterator itr;
for (itr = group_1->begin(); itr != group_1->end(); itr++){
group_2->insert(*itr);
map*>::iterator assoc_itr = spawn_group_associations.find(*itr);
if (assoc_itr != spawn_group_associations.end())
assoc_itr->second->insert(group_id2);
else {
set* new_set = new set;
spawn_group_associations[*itr] = new_set;
new_set->insert(group_id2);
}
}
for (itr = group_2->begin(); itr != group_2->end(); itr++){
group_1->insert(*itr);
map*>::iterator assoc_itr = spawn_group_associations.find(*itr);
if (assoc_itr != spawn_group_associations.end())
assoc_itr->second->insert(group_id1);
else {
set* new_set = new set;
spawn_group_associations[*itr] = new_set;
new_set->insert(group_id1);
}
}
MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::AddSpawnGroupLocation(int32 group_id, int32 location_id, int32 spawn_location_id) {
MSpawnGroupLocations.writelock(__FUNCTION__, __LINE__);
if(spawn_group_locations.count(group_id) == 0)
spawn_group_locations[group_id] = new map();
(*spawn_group_locations[group_id])[location_id] = spawn_location_id;
MSpawnGroupLocations.releasewritelock(__FUNCTION__, __LINE__);
MSpawnLocationGroups.writelock(__FUNCTION__, __LINE__);
if(spawn_location_groups.count(location_id) == 0)
spawn_location_groups[location_id] = new list();
spawn_location_groups[location_id]->push_back(group_id);
MSpawnLocationGroups.releasewritelock(__FUNCTION__, __LINE__);
MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__);
if(spawn_group_associations.count(group_id) == 0)
spawn_group_associations[group_id] = new set();
spawn_group_associations[group_id]->insert(group_id);
MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__);
}
bool ZoneServer::CallSpawnScript(Spawn* npc, int8 type, Spawn* spawn, const char* message, bool is_door_open, sint32 input_value, sint32* return_value){
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
if(!npc)
return false;
const char* script = npc->GetSpawnScript();
if ( script == nullptr || strlen(script) < 1 )
{
if (!npc->IsPet() && npc->GetZone() != nullptr)
{
string tmpScript;
tmpScript.append("SpawnScripts/");
tmpScript.append(npc->GetZone()->GetZoneName());
tmpScript.append("/");
int count = 0;
for (int s = 0; s < strlen(npc->GetName()); s++)
{
if (isalnum((unsigned char)npc->GetName()[s]))
{
tmpScript += npc->GetName()[s];
count++;
}
}
tmpScript.append(".lua");
if (count < 1)
{
LogWrite(SPAWN__TRACE, 0, "Spawn", "Could not form script name %s..", __FUNCTION__);
}
else
{
struct stat buffer;
bool fileExists = (stat(tmpScript.c_str(), &buffer) == 0);
if (fileExists)
{
LogWrite(SPAWN__WARNING, 0, "Spawn", "No script file described in the database, overriding with SpawnScript at %s", (char*)tmpScript.c_str());
npc->SetSpawnScript(tmpScript);
script = npc->GetSpawnScript();
}
}
}
}
bool result = false;
if(lua_interface && script){
result = true; // default to true, if we don't match a switch case, return false in default case
switch(type){
case SPAWN_SCRIPT_SPAWN:{
lua_interface->RunSpawnScript(script, "spawn", npc);
break;
}
case SPAWN_SCRIPT_RESPAWN:{
lua_interface->RunSpawnScript(script, "respawn", npc);
break;
}
case SPAWN_SCRIPT_ATTACKED:{
lua_interface->RunSpawnScript(script, "attacked", npc, spawn);
break;
}
case SPAWN_SCRIPT_TARGETED:{
lua_interface->RunSpawnScript(script, "targeted", npc, spawn);
break;
}
case SPAWN_SCRIPT_HAILED:{
result = lua_interface->RunSpawnScript(script, "hailed", npc, spawn);
break;
}
case SPAWN_SCRIPT_HAILED_BUSY:{
lua_interface->RunSpawnScript(script, "hailed_busy", npc, spawn);
break;
}
case SPAWN_SCRIPT_DEATH:{
lua_interface->RunSpawnScript(script, "death", npc, spawn);
break;
}
case SPAWN_SCRIPT_KILLED:{
lua_interface->RunSpawnScript(script, "killed", npc, spawn);
break;
}
case SPAWN_SCRIPT_AGGRO:{
lua_interface->RunSpawnScript(script, "aggro", npc, spawn);
break;
}
case SPAWN_SCRIPT_HEALTHCHANGED:{
result = lua_interface->RunSpawnScript(script, "healthchanged", npc, spawn, 0, false, input_value, return_value);
break;
}
case SPAWN_SCRIPT_RANDOMCHAT:{
lua_interface->RunSpawnScript(script, "randomchat", npc, 0, message);
break;
}
case SPAWN_SCRIPT_CUSTOM:
case SPAWN_SCRIPT_TIMER:
case SPAWN_SCRIPT_CONVERSATION:{
lua_interface->RunSpawnScript(script, message, npc, spawn);
break;
}
case SPAWN_SCRIPT_CASTED_ON: {
lua_interface->RunSpawnScript(script, "casted_on", npc, spawn, message);
break;
}
case SPAWN_SCRIPT_AUTO_ATTACK_TICK: {
lua_interface->RunSpawnScript(script, "auto_attack_tick", npc, spawn);
break;
}
case SPAWN_SCRIPT_COMBAT_RESET: {
lua_interface->RunSpawnScript(script, "CombatReset", npc);
break;
}
case SPAWN_SCRIPT_GROUP_DEAD: {
lua_interface->RunSpawnScript(script, "group_dead", npc, spawn);
break;
}
case SPAWN_SCRIPT_HEAR_SAY: {
lua_interface->RunSpawnScript(script, "hear_say", npc, spawn, message);
break;
}
case SPAWN_SCRIPT_PRESPAWN: {
lua_interface->RunSpawnScript(script, "prespawn", npc);
break;
}
case SPAWN_SCRIPT_USEDOOR: {
result = lua_interface->RunSpawnScript(script, "usedoor", npc, spawn, "", is_door_open);
break;
}
case SPAWN_SCRIPT_BOARD: {
result = lua_interface->RunSpawnScript(script, "board", npc, spawn);
break;
}
case SPAWN_SCRIPT_DEBOARD: {
result = lua_interface->RunSpawnScript(script, "deboard", npc, spawn);
break;
}
default:
{
result = false;
break;
}
}
}
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
return result;
}
void ZoneServer::DeleteTransporters() {
MTransportLocations.writelock(__FUNCTION__, __LINE__);
transporter_locations.clear(); //world takes care of actually deleting the data
MTransportLocations.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::ReloadTransporters(){
MutexList* locations = GetLocationTransporters(GetZoneID());
if(locations){
MutexList::iterator itr = locations->begin();
while(itr.Next())
AddTransporter(itr->value);
}
}
void ZoneServer::CheckTransporters(Client* client) {
MTransportLocations.readlock(__FUNCTION__, __LINE__);
if(transporter_locations.size() > 0){
LocationTransportDestination* loc = 0;
list::iterator itr;
for (itr = transporter_locations.begin(); itr != transporter_locations.end(); itr++) {
loc = *itr;
if(client->GetPlayer()->GetDistance(loc->trigger_x, loc->trigger_y, loc->trigger_z) <= loc->trigger_radius){
if(loc->destination_zone_id == 0 || loc->destination_zone_id == GetZoneID()){
EQ2Packet* packet = client->GetPlayer()->Move(loc->destination_x, loc->destination_y, loc->destination_z, client->GetVersion());
if(packet)
client->QueuePacket(packet);
}
else{
ZoneServer* new_zone = zone_list.Get(loc->destination_zone_id);
if(new_zone){
client->GetPlayer()->SetX(loc->destination_x);
client->GetPlayer()->SetY(loc->destination_y);
client->GetPlayer()->SetZ(loc->destination_z);
client->GetPlayer()->SetHeading(loc->destination_heading);
client->Zone(new_zone, false);
}
}
break;
}
}
}
MTransportLocations.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::AddTransporter(LocationTransportDestination* loc) {
MTransportLocations.writelock(__FUNCTION__, __LINE__);
transporter_locations.push_back(loc);
MTransportLocations.releasewritelock(__FUNCTION__, __LINE__);
}
Sign* ZoneServer::AddSignSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
Sign* sign = GetNewSign(spawnentry->spawn_id);
if(sign && !sign->IsOmittedByDBFlag()){
DeterminePosition(spawnlocation, sign);
sign->SetDatabaseID(spawnentry->spawn_id);
sign->SetSpawnLocationID(spawnentry->spawn_location_id);
sign->SetSpawnEntryID(spawnentry->spawn_entry_id);
sign->SetRespawnTime(spawnentry->respawn);
sign->SetExpireTime(spawnentry->expire_time);
if (spawnentry->expire_time > 0)
AddSpawnExpireTimer(sign, spawnentry->expire_time, spawnentry->expire_offset);
SetSpawnScript(spawnentry, sign);
CallSpawnScript(sign, SPAWN_SCRIPT_PRESPAWN);
AddSpawn(sign);
}
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
return sign;
}
Widget* ZoneServer::AddWidgetSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
Widget* widget = GetNewWidget(spawnentry->spawn_id);
if(widget && !widget->IsOmittedByDBFlag()){
DeterminePosition(spawnlocation, widget);
widget->SetDatabaseID(spawnentry->spawn_id);
widget->SetSpawnLocationID(spawnentry->spawn_location_id);
widget->SetSpawnEntryID(spawnentry->spawn_entry_id);
if(!widget->GetIncludeLocation()){
widget->SetX(widget->GetWidgetX());
if(widget->GetCloseY() != 0)
widget->SetY(widget->GetCloseY());
widget->SetZ(widget->GetWidgetZ());
}
widget->SetRespawnTime(spawnentry->respawn);
widget->SetExpireTime(spawnentry->expire_time);
widget->SetSpawnOrigHeading(widget->GetHeading());
if (spawnentry->expire_time > 0)
AddSpawnExpireTimer(widget, spawnentry->expire_time, spawnentry->expire_offset);
SetSpawnScript(spawnentry, widget);
CallSpawnScript(widget, SPAWN_SCRIPT_PRESPAWN);
AddSpawn(widget);
}
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
return widget;
}
Object* ZoneServer::AddObjectSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
Object* object = GetNewObject(spawnentry->spawn_id);
if(object && !object->IsOmittedByDBFlag()){
DeterminePosition(spawnlocation, object);
object->SetDatabaseID(spawnentry->spawn_id);
object->SetSpawnLocationID(spawnentry->spawn_location_id);
object->SetSpawnEntryID(spawnentry->spawn_entry_id);
object->SetRespawnTime(spawnentry->respawn);
object->SetExpireTime(spawnentry->expire_time);
if (spawnentry->expire_time > 0)
AddSpawnExpireTimer(object, spawnentry->expire_time, spawnentry->expire_offset);
SetSpawnScript(spawnentry, object);
CallSpawnScript(object, SPAWN_SCRIPT_PRESPAWN);
AddSpawn(object);
}
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
return object;
}
GroundSpawn* ZoneServer::AddGroundSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
GroundSpawn* spawn = GetNewGroundSpawn(spawnentry->spawn_id);
if(spawn && !spawn->IsOmittedByDBFlag()){
DeterminePosition(spawnlocation, spawn);
spawn->SetDatabaseID(spawnentry->spawn_id);
spawn->SetSpawnLocationID(spawnentry->spawn_location_id);
spawn->SetSpawnEntryID(spawnentry->spawn_entry_id);
spawn->SetRespawnTime(spawnentry->respawn);
spawn->SetExpireTime(spawnentry->expire_time);
if(spawn->GetRandomizeHeading()) {
float rand_heading = MakeRandomFloat(0.0f, 360.0f);
spawn->SetHeading(rand_heading);
}
else {
spawn->SetHeading(spawnlocation->heading);
}
if (spawnentry->expire_time > 0)
AddSpawnExpireTimer(spawn, spawnentry->expire_time, spawnentry->expire_offset);
SetSpawnScript(spawnentry, spawn);
CallSpawnScript(spawn, SPAWN_SCRIPT_PRESPAWN);
AddSpawn(spawn);
}
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
return spawn;
}
void ZoneServer::AddSpawn(Spawn* spawn) {
if(!spawn->IsPlayer()) // we already set it on loadCharacter
spawn->SetZone(this);
MIgnoredWidgets.lock_shared();
std::map::iterator itr;
for(itr = ignored_widgets.begin(); itr != ignored_widgets.end(); itr++) {
spawn->AddIgnoredWidget(itr->first);
}
MIgnoredWidgets.unlock_shared();
spawn->position_changed = false;
spawn->info_changed = false;
spawn->vis_changed = false;
spawn->changed = false;
// Write locking the spawn list here will cause deadlocks, so instead add it to a temp list that the
// main spawn thread will put into the spawn_list when ever it has a chance.
MPendingSpawnListAdd.writelock(__FUNCTION__, __LINE__);
pending_spawn_list_add.push_back(spawn);
MPendingSpawnListAdd.releasewritelock(__FUNCTION__, __LINE__);
spawn_range.Trigger();
if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE && spawn->IsObject())
{
spawn->AddSecondaryEntityCommand("Examine", 20, "house_spawn_examine", "", 0, 0);
spawn->AddSecondaryEntityCommand("Move", 20, "move_item", "", 0, 0);
spawn->AddSecondaryEntityCommand("Pack in Moving Crate", 20, "house_spawn_pack_in_moving_crate", "", 0, 0);
spawn->AddSecondaryEntityCommand("Pick Up", 20, "pickup", "", 0, 0);
spawn->SetShowCommandIcon(1);
}
if(spawn->IsNPC())
AddEnemyList((NPC*)spawn);
if(spawn->IsPlayer() && ((Player*)spawn)->GetGroupMemberInfo())
spawn->SendGroupUpdate();
if (spawn->IsPlayer()) {
((Player*)spawn)->GetInfoStruct()->set_rain(rain);
((Player*)spawn)->SetCharSheetChanged(true);
}
if (movementMgr != nullptr && spawn->IsEntity()) {
movementMgr->AddMob((Entity*)spawn);
}
AddSpawnProximities(spawn);
AddSpawnToGrid(spawn, spawn->GetLocation());
spawn->SetAddedToWorldTimestamp(Timer::GetCurrentTime2());
}
void ZoneServer::AddClient(Client* client){
MClientList.writelock(__FUNCTION__, __LINE__);
lifetime_client_count++;
DecrementIncomingClients();
clients.push_back(client);
MClientList.releasewritelock(__FUNCTION__, __LINE__);
connected_clients.Add(client);
}
void ZoneServer::RemoveClient(Client* client)
{
Guild *guild;
bool dismissPets = false;
if(client)
{
if (client->GetPlayer())
client_list.RemovePlayerFromInvisHistory(client->GetPlayer()->GetID());
LogWrite(ZONE__DEBUG, 0, "Zone", "Sending login equipment appearance updates...");
loginserver.SendImmediateEquipmentUpdatesForChar(client->GetPlayer()->GetCharacterID());
if (!client->IsZoning())
{
client->SaveSpells();
client->GetPlayer()->DeleteSpellEffects(true);
if ((guild = client->GetPlayer()->GetGuild()) != NULL)
guild->GuildMemberLogoff(client->GetPlayer());
chat.LeaveAllChannels(client);
}
if(!zoneShuttingDown && !client->IsZoning())
{
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo();
int32 group_id = 0;
if (gmi) {
group_id = gmi->group_id;
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
if (group_id) {
int32 size = world.GetGroupManager()->GetGroupSize(group_id);
if (size > 1) {
bool send_left_message = size > 2;
// removegroupmember can delete the gmi, so make sure we still have a group_id after
world.GetGroupManager()->RemoveGroupMember(group_id, client->GetPlayer());
if (send_left_message)
world.GetGroupManager()->GroupMessage(group_id, "%s has left the group.", client->GetPlayer()->GetName());
}
}
if( (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0)
{
LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to LD/Exit...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID());
}
else
{
LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to Camp/Quit...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID());
}
dismissPets = true;
//}
}
else
{
LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to some client zoning...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID());
}
map::iterator itr;
for (itr = client->GetPlayer()->SpawnedBots.begin(); itr != client->GetPlayer()->SpawnedBots.end(); itr++) {
Spawn* spawn = GetSpawnByID(itr->second);
if (spawn)
((Bot*)spawn)->Camp();
}
if(dismissPets) {
((Entity*)client->GetPlayer())->DismissAllPets();
}
MClientList.writelock(__FUNCTION__, __LINE__);
LogWrite(ZONE__DEBUG, 0, "Zone", "Calling clients.Remove(client)...");
std::vector::iterator itr2 = find(clients.begin(), clients.end(), client);
if (itr2 != clients.end())
clients.erase(itr2);
MClientList.releasewritelock(__FUNCTION__, __LINE__);
LogWrite(ZONE__INFO, 0, "Zone", "Scheduling client '%s' for removal.", client->GetPlayer()->GetName());
database.ToggleCharacterOnline(client, 0);
RemoveSpawn(client->GetPlayer(), false, true, true, true, true);
int32 DisconnectClientTimer = rule_manager.GetGlobalRule(R_World, RemoveDisconnectedClientsTimer)->GetInt32();
connected_clients.Remove(client, true, DisconnectClientTimer); // changed from a hardcoded 30000 (30 sec) to the DisconnectClientTimer rule
}
}
void ZoneServer::RemoveClientImmediately(Client* client) {
Guild *guild;
if(client)
{
if(client->GetPlayer()) {
if((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) {
client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD);
}
if ((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0) {
client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_CAMPING);
}
client->Disconnect();
}
MClientList.writelock(__FUNCTION__, __LINE__);
std::vector::iterator itr = find(clients.begin(), clients.end(), client);
if (itr != clients.end())
clients.erase(itr);
MClientList.releasewritelock(__FUNCTION__, __LINE__);
//clients.Remove(client, true);
}
}
void ZoneServer::ClientProcess(bool ignore_shutdown_timer)
{
if(!ignore_shutdown_timer && connected_clients.size(true) == 0)
{
MIncomingClients.readlock(__FUNCTION__, __LINE__);
bool shutdownDelayCheck = shutdownDelayTimer.Check();
if((!AlwaysLoaded() && !shutdownTimer.Enabled()) || shutdownDelayCheck)
{
if(incoming_clients && !shutdownDelayTimer.Enabled()) {
LogWrite(ZONE__INFO, 0, "Zone", "Incoming clients (%u) expected for %s, delaying shutdown timer...", incoming_clients, GetZoneName());
int32 timerDelay = rule_manager.GetGlobalRule(R_Zone, ShutdownDelayTimer)->GetInt32();
if(timerDelay < 10) {
LogWrite(ZONE__INFO, 0, "Zone", "Overriding %s shutdown delay timer as other clients are incoming, value %u too short, setting to 10...", GetZoneName(), timerDelay);
timerDelay = 10;
}
shutdownDelayTimer.Start(timerDelay, true);
}
else if(!incoming_clients || shutdownDelayCheck) {
if(!shutdownTimer.Enabled()) {
LogWrite(ZONE__INFO, 0, "Zone", "Starting zone shutdown timer for %s...", GetZoneName());
shutdownTimer.Start();
}
else {
LogWrite(ZONE__INFO, 0, "Zone", "zone shutdown timer for %s has %u remaining...", GetZoneName(), shutdownTimer.GetRemainingTime());
}
}
}
MIncomingClients.releasereadlock(__FUNCTION__, __LINE__);
return;
}
shutdownTimer.Disable();
shutdownDelayTimer.Disable();
Client* client = 0;
MutexList::iterator iterator = connected_clients.begin();
while(iterator.Next())
{
client = iterator->value;
#ifndef NO_CATCH
try
{
#endif
if(zoneShuttingDown || !client->Process(true))
{
if(!zoneShuttingDown && !client->IsZoning())
{
// avoid spam of messages while we await linkdead to complete
if(!client->IsLinkdeadTimerEnabled()) {
bool camping = (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING);
LogWrite(ZONE__DEBUG, 0, "Zone", "Client is disconnecting in %s (camping = %s)", __FUNCTION__, camping ? "true" : "false");
if(!camping) {
client->setConnection(nullptr);
}
}
if((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) {
client->StartLinkdeadTimer();
}
else if( (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0 )
{
//only set LD flag if we're disconnecting but not camping/quitting
client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_LINKDEAD);
client->StartLinkdeadTimer();
}
else {
// camp timer completed, remove client
RemoveClient(client);
client->Disconnect();
}
}
else {
// force boot all players or clients zoning
RemoveClient(client);
client->Disconnect();
}
}
#ifndef NO_CATCH
}
catch(...)
{
LogWrite(ZONE__ERROR, 0, "Zone", "Exception caught when in ZoneServer::ClientProcess() for zone '%s'!\n%s, %i", GetZoneName(), __FUNCTION__, __LINE__);
try{
bool isLinkdead = false;
if(!client->IsZoning())
{
if( (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0 )
{
client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_LINKDEAD);
client->StartLinkdeadTimer();
isLinkdead = true;
if(client->GetPlayer()->GetGroupMemberInfo())
world.GetGroupManager()->GroupMessage(client->GetPlayer()->GetGroupMemberInfo()->group_id, "%s has gone Linkdead.", client->GetPlayer()->GetName());
}
}
if(!isLinkdead) {
RemoveClient(client);
client->Disconnect();
}
}
catch(...){
LogWrite(ZONE__ERROR, 0, "Zone", "Exception caught when in ZoneServer::ClientProcess(), second try\n%s, %i", __FUNCTION__, __LINE__);
}
}
#endif
}
}
void ZoneServer::SimpleMessage(int8 type, const char* message, Spawn* from, float distance, bool send_to_sender){
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(from && client && client->IsConnected() && (send_to_sender || from != client->GetPlayer()) && from->GetDistance(client->GetPlayer()) <= distance){
client->SimpleMessage(type, message);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::HandleChatMessage(Client* client, Spawn* from, const char* to, int16 channel, const char* message, float distance, const char* channel_name, bool show_bubble, int32 language) {
if ((!distance || from->GetDistance(client->GetPlayer()) <= distance) && (!from || !client->GetPlayer()->IsIgnored(from->GetName()))) {
PacketStruct* packet = configReader.getStruct("WS_HearChat", client->GetVersion());
if (packet) {
if (from)
packet->setMediumStringByName("from", from->GetName());
if (client->GetPlayer() != from)
packet->setMediumStringByName("to", client->GetPlayer()->GetName());
packet->setDataByName("channel", client->GetMessageChannelColor(channel));
if (from && ((from == client->GetPlayer()) || (client->GetPlayer()->WasSentSpawn(from->GetID()))))
packet->setDataByName("from_spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(from));
else
packet->setDataByName("from_spawn_id", 0xFFFFFFFF);
packet->setDataByName("to_spawn_id", 0xFFFFFFFF);
packet->setMediumStringByName("message", message);
packet->setDataByName("language", language);
bool hasLanguage = client->GetPlayer()->HasLanguage(language);
if (language > 0 && !hasLanguage)
packet->setDataByName("understood", 0);
else
packet->setDataByName("understood", 1);
show_bubble == true ? packet->setDataByName("show_bubble", 1) : packet->setDataByName("show_bubble", 0);
if (channel_name)
packet->setMediumStringByName("channel_name", channel_name);
EQ2Packet* outapp = packet->serialize();
//DumpPacket(outapp);
client->QueuePacket(outapp);
safe_delete(packet);
}
}
}
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;
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);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::HandleBroadcast(const char* message) {
vector::iterator client_itr;
Client* client = 0;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(client && client->IsConnected())
client->SimpleMessage(CHANNEL_BROADCAST, message);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::HandleAnnouncement(const char* message) {
vector::iterator client_itr;
Client* client = 0;
int32 words = ::CountWordsInString(message);
if (words < 5)
words = 5;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(client && client->IsConnected()) {
client->SimpleMessage(CHANNEL_BROADCAST, message);
client->SendPopupMessage(10, message, "ui_harvest_normal", words, 0xFF, 0xFF, 0x00);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendTimeUpdate(Client* client){
if(client){
PacketStruct* packet = world.GetWorldTime(client->GetVersion());
if(packet){
client->QueuePacket(packet->serialize());
safe_delete(packet);
}
}
}
void ZoneServer::SendTimeUpdateToAllClients(){
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(client && client->IsConnected())
SendTimeUpdate(client);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::UpdateVitality(float amount){
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(client && client->GetPlayer()->GetInfoStruct()->get_xp_vitality() < 100){
if((client->GetPlayer()->GetInfoStruct()->get_xp_vitality() + amount) > 100)
client->GetPlayer()->GetInfoStruct()->set_xp_vitality(100);
else
client->GetPlayer()->GetInfoStruct()->add_xp_vitality(amount);
client->GetPlayer()->SetCharSheetChanged(true);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendSpawn(Spawn* spawn, Client* client){
EQ2Packet* outapp = spawn->serialize(client->GetPlayer(), client->GetVersion());
if(!client->GetPlayer()->IsSendingSpawn(spawn->GetID())) {
safe_delete(outapp);
}
else {
LogWrite(ZONE__DEBUG, 7, "Zone", "%s: Processing SendSpawn for spawn index %u (%s)...", client->GetPlayer()->GetName(), client->GetPlayer()->GetIndexForSpawn(spawn), spawn->GetName());
if(outapp)
client->QueuePacket(outapp, true);
client->GetPlayer()->SetSpawnSentState(spawn, SpawnState::SPAWN_STATE_SENT_WAIT);
}
/*
vis flags:
2 = show icon
4 = targetable
16 = show name
32 = show level/border
activity_status:
4 - linkdead
8 - camping
16 - LFG
32 - LFW
2048 - mentoring
4096 - displays shield
8192 - immunity gained
16384 - immunity remaining
attackable_status
1 - no_hp_bar
4 - not attackable
npc_con
-4 = scowls
-3 = threatening
-2 = dubiously
-1 = apprehensively
0 = indifferent
1 = amiably
2 = kindly
3 = warmly
4 = ally
quest_flag
1 = new quest
2 = update and new quest
3 = update
*/
if(spawn->IsEntity() && spawn->HasTrapTriggered())
client->QueueStateCommand(client->GetPlayer()->GetIDWithPlayerSpawn(spawn), spawn->GetTrapState());
}
Client* ZoneServer::GetClientByName(char* name) {
Client* ret = 0;
vector::iterator itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (itr = clients.begin(); itr != clients.end(); itr++) {
if ((*itr)->GetPlayer()) {
if (strncmp((*itr)->GetPlayer()->GetName(), name, strlen(name)) == 0) {
ret = *itr;
break;
}
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
Client* ZoneServer::GetClientByCharID(int32 charid) {
Client* ret = 0;
vector::iterator itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (itr = clients.begin(); itr != clients.end(); itr++) {
if ((*itr)->GetCharacterID() == charid) {
ret = *itr;
break;
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
void ZoneServer::AddMovementNPC(Spawn* spawn){
if (spawn)
movement_spawns.Put(spawn->GetID(), 1);
}
void ZoneServer::RemoveMovementNPC(Spawn* spawn){
if (spawn)
remove_movement_spawns.Add(spawn->GetID());
}
void ZoneServer::PlayFlavor(Client* client, Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language){
if(!client || !spawn)
return;
PacketStruct* packet = configReader.getStruct("WS_PlayFlavor", client->GetVersion());
if(packet){
packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn));
packet->setDataByName("unknown1", 0xFFFFFFFF);
packet->setDataByName("unknown5", 1, 1);
packet->setDataByName("unknown5", 1, 6);
if(mp3){
packet->setMediumStringByName("mp3", mp3);
packet->setDataByName("key", key1);
packet->setDataByName("key", key2, 1);
}
packet->setMediumStringByName("name", spawn->GetName());
if(text)
packet->setMediumStringByName("text", text);
if(emote) {
if(client->GetVersion() > 546) {
packet->setMediumStringByName("emote", emote);
}
else {
HandleEmote(spawn, std::string(emote));
}
}
if (language != 0)
packet->setDataByName("language", language);
//We should probably add Common = language id 0 or 0xFF so admins can customize more..
if (language == 0 || client->GetPlayer()->HasLanguage(language))
packet->setDataByName("understood", 1);
EQ2Packet* app = packet->serialize();
//DumpPacket(app);
client->QueuePacket(app);
safe_delete(packet);
}
}
void ZoneServer::PlayVoice(Client* client, Spawn* spawn, const char* mp3, int32 key1, int32 key2){
if(!client || !spawn)
return;
PacketStruct* packet = configReader.getStruct("WS_PlayVoice", client->GetVersion());
if(packet){
packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn));
packet->setMediumStringByName("mp3", mp3);
packet->setDataByName("key", key1);
packet->setDataByName("key", key2, 1);
client->QueuePacket(packet->serialize());
safe_delete(packet);
}
}
void ZoneServer::PlayFlavor(Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language){
if(!spawn)
return;
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->GetDistance(spawn) > 30)
continue;
PlayFlavor(client, spawn, mp3, text, emote, key1, key2, language);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::PlayFlavorID(Spawn* spawn, int8 type, int32 id, int16 index, int8 language){
if(!spawn)
return;
Client* client = 0;
vector::iterator client_itr;
VoiceOverStruct non_garble, garble;
bool garble_success = false;
bool success = world.FindVoiceOver(type, id, index, &non_garble, &garble_success, &garble);
VoiceOverStruct* resStruct = nullptr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->GetDistance(spawn) > 30)
continue;
client->SendPlayFlavor(spawn, language, &non_garble, &garble, success, garble_success);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::PlayVoice(Spawn* spawn, const char* mp3, int32 key1, int32 key2){
if(!spawn || !mp3)
return;
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID()))
continue;
PlayVoice(client, spawn, mp3, key1, key2);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::PlaySoundFile(Client* client, const char* name, float origin_x, float origin_y, float origin_z){
if(!name)
return;
PacketStruct* packet = 0;
if(client){
packet = configReader.getStruct("WS_Play3DSound", client->GetVersion());
if(packet){
packet->setMediumStringByName("name", name);
packet->setDataByName("x", origin_x);
packet->setDataByName("y", origin_y);
packet->setDataByName("z", origin_z);
packet->setDataByName("unknown1", 1);
packet->setDataByName("unknown2", 2.5);
packet->setDataByName("unknown3", 15);
client->QueuePacket(packet->serialize());
safe_delete(packet);
}
}
else{
EQ2Packet* outapp = 0;
int16 packet_version = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(client && (!packet || packet_version != client->GetVersion())){
safe_delete(packet);
safe_delete(outapp);
packet_version = client->GetVersion();
packet = configReader.getStruct("WS_Play3DSound", packet_version);
if(packet){
packet->setMediumStringByName("name", name);
packet->setDataByName("x", origin_x);
packet->setDataByName("y", origin_y);
packet->setDataByName("z", origin_z);
packet->setDataByName("unknown1", 1);
packet->setDataByName("unknown2", 2.5);
packet->setDataByName("unknown3", 15);
outapp = packet->serialize();
}
}
if(outapp && client && client->IsReadyForUpdates())
client->QueuePacket(outapp->Copy());
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
safe_delete(packet);
safe_delete(outapp);
}
}
bool ZoneServer::HasWidgetTimer(Spawn* widget){
bool ret = false;
if (widget) {
int32 id = widget->GetID();
map::iterator itr;
MWidgetTimers.readlock(__FUNCTION__, __LINE__);
for (itr = widget_timers.begin(); itr != widget_timers.end(); itr++) {
if(itr->first == id){
ret = true;
break;
}
}
MWidgetTimers.releasereadlock(__FUNCTION__, __LINE__);
}
return ret;
}
void ZoneServer::CheckWidgetTimers(){
vector remove_list;
map::iterator itr;
MWidgetTimers.readlock(__FUNCTION__, __LINE__);
for (itr = widget_timers.begin(); itr != widget_timers.end(); itr++) {
if(Timer::GetCurrentTime2() >= itr->second){
/*Spawn* widget = GetSpawnByID(itr->first);
if (widget && widget->IsWidget())
((Widget*)widget)->HandleTimerUpdate();*/
remove_list.push_back(itr->first);
}
}
MWidgetTimers.releasereadlock(__FUNCTION__, __LINE__);
for (int32 i = 0; i < remove_list.size(); i++) {
Spawn* widget = GetSpawnByID(remove_list[i]);
if (widget && widget->IsWidget())
((Widget*)widget)->HandleTimerUpdate();
}
MWidgetTimers.writelock(__FUNCTION__, __LINE__);
for(int32 i=0;iIsWidget()) {
MWidgetTimers.writelock(__FUNCTION__, __LINE__);
widget_timers[widget->GetID()] = ((int32)(time * 1000)) + Timer::GetCurrentTime2();
MWidgetTimers.releasewritelock(__FUNCTION__, __LINE__);
}
}
Spawn* ZoneServer::GetSpawnGroup(int32 id){
Spawn* ret = 0;
Spawn* spawn = 0;
if(id < 1)
return 0;
bool lookup = false;
if(quick_group_id_lookup.count(id) > 0) {
ret = GetSpawnByID(quick_group_id_lookup.Get(id));
lookup = true;
}
if(ret == NULL) {
if(lookup)
quick_group_id_lookup.erase(id);
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
spawn = itr->second;
if(spawn){
if(spawn->GetSpawnGroupID() == id){
ret = spawn;
quick_group_id_lookup.Put(id, spawn->GetID());
break;
}
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
}
return ret;
}
Spawn* ZoneServer::GetSpawnByLocationID(int32 location_id) {
Spawn* ret = 0;
Spawn* current_spawn = 0;
if(location_id < 1)
return 0;
bool lookup = false;
if(quick_location_id_lookup.count(location_id) > 0) {
ret = GetSpawnByID(quick_location_id_lookup.Get(location_id));
lookup = true;
}
if(ret == NULL) {
if(lookup)
quick_location_id_lookup.erase(location_id);
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
current_spawn = itr->second;
if (current_spawn && current_spawn->GetSpawnLocationID() == location_id) {
ret = current_spawn;
quick_location_id_lookup.Put(location_id, ret->GetID());
break;
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
}
return ret;
}
Spawn* ZoneServer::GetSpawnByDatabaseID(int32 id){
Spawn* ret = 0;
if(id < 1)
return 0;
bool lookup = false;
if(quick_database_id_lookup.count(id) > 0) {
ret = GetSpawnByID(quick_database_id_lookup.Get(id));
lookup = true;
}
if(ret == NULL){
if(lookup)
quick_database_id_lookup.erase(id);
Spawn* spawn = 0;
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++){
spawn = itr->second;
if(spawn){
if(spawn->GetDatabaseID() == id){
quick_database_id_lookup.Put(id, spawn->GetID());
ret = spawn;
break;
}
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
}
return ret;
}
Spawn* ZoneServer::GetSpawnByID(int32 id, bool spawnListLocked) {
Spawn* ret = 0;
if (!spawnListLocked )
MSpawnList.readlock(__FUNCTION__, __LINE__);
if (spawn_list.count(id) > 0)
ret = spawn_list[id];
if (!spawnListLocked)
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
bool ZoneServer::SendRemoveSpawn(Client* client, Spawn* spawn, PacketStruct* packet, bool delete_spawn)
{
if(!client || !spawn || (client && client->GetPlayer() == spawn))
return false;
if(client->GetPlayerPOVGhostSpawnID() == spawn->GetID()) {
client->SetPlayerPOVGhost(nullptr);
}
spawn->RemoveSpawnFromPlayer(client->GetPlayer());
// LogWrite(ZONE__DEBUG, 7, "Zone", "%s: Processing SendRemoveSpawn for spawn index %u (%s)...cur_id: %i, wasremoved:: %i", client->GetPlayer()->GetName(), index, spawn->GetName(), cur_id, wasRemoved);
/* if(packet && index > 0 && !wasRemoved)
{
packet->ResetData();
packet->setDataByName("spawn_index", index);
spawn->RemoveSpawnFromPlayer(client->GetPlayer());
if(delete_spawn)
packet->setDataByName("delete", 1);
client->QueuePacket(packet->serialize());
return true;
}
return false;*/
return true;
}
void ZoneServer::SetSpawnCommand(Spawn* spawn, int8 type, char* value, Client* client){
//commands
LogWrite(MISC__TODO, 1, "TODO", "%s does nothing!\n%s, %i", __FUNCTION__, __FILE__, __LINE__);
}
void ZoneServer::SetSpawnCommand(int32 spawn_id, int8 type, char* value, Client* client){
LogWrite(MISC__TODO, 1, "TODO", "%s does nothing!\n%s, %i", __FUNCTION__, __FILE__, __LINE__);
}
void ZoneServer::ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value){
// This will apply the /spawn set command to all the spawns in the zone with the same DB ID, we do not want to set
// location values (x, y, z, heading, grid) for all spawns in the zone with the same DB ID, only the targeted spawn
if(type == SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT || type == SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT || (type >= SPAWN_SET_VALUE_X && type <= SPAWN_SET_VALUE_LOCATION) ||
type == SPAWN_SET_VALUE_PITCH || type == SPAWN_SET_VALUE_ROLL)
return;
Spawn* tmp = 0;
if(target->IsNPC())
tmp = GetNPC(target->GetDatabaseID());
else if(target->IsObject())
tmp = GetObject(target->GetDatabaseID());
else if(target->IsGroundSpawn())
tmp = GetGroundSpawn(target->GetDatabaseID());
else if(target->IsSign())
tmp = GetSign(target->GetDatabaseID());
else if(target->IsWidget())
tmp = GetWidget(target->GetDatabaseID());
if(tmp && type == SPAWN_SET_VALUE_SPAWN_SCRIPT)
tmp->SetSpawnScript(value);
else if(tmp)
commands.SetSpawnCommand(client, tmp, type, value); // set the master spawn
Spawn* spawn = 0;
// this check needs to be here otherwise every spawn with 0 will be set
if ( target->GetDatabaseID ( ) > 0 )
{
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
spawn = itr->second;
if(spawn && spawn->GetDatabaseID() == target->GetDatabaseID()){
if(type == SPAWN_SET_VALUE_SPAWN_SCRIPT)
spawn->SetSpawnScript(value);
else
commands.SetSpawnCommand(client, spawn, type, value);
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
}
}
void ZoneServer::StopSpawnScriptTimer(Spawn* spawn, std::string functionName){
MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__);
MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__);
if(spawn_script_timers.size() > 0){
set::iterator itr;
SpawnScriptTimer* timer = 0;
for (itr = spawn_script_timers.begin(); itr != spawn_script_timers.end(); ) {
timer = *itr;
if(timer->spawn == spawn->GetID() && (functionName == "" || timer->function == functionName) && remove_spawn_script_timers_list.count(timer) == 0) {
itr = spawn_script_timers.erase(itr);
safe_delete(timer);
}
else
itr++;
}
}
MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__);
MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::DeleteSpawnScriptTimers(Spawn* spawn, bool all){
MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__);
MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__);
if(spawn_script_timers.size() > 0){
set::iterator itr;
SpawnScriptTimer* timer = 0;
for (itr = spawn_script_timers.begin(); itr != spawn_script_timers.end(); itr++) {
timer = *itr;
if((all || timer->spawn == spawn->GetID()) && remove_spawn_script_timers_list.count(timer) == 0)
remove_spawn_script_timers_list.insert(timer);
}
if(all)
spawn_script_timers.clear();
}
MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__);
MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::DeleteSpawnScriptTimers() {
MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__);
MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__);
if(remove_spawn_script_timers_list.size() > 0){
set::iterator itr;
SpawnScriptTimer* timer = 0;
for (itr = remove_spawn_script_timers_list.begin(); itr != remove_spawn_script_timers_list.end(); itr++) {
timer = *itr;
set::iterator itr2;
itr2 = spawn_script_timers.find(timer);
if(itr2 != spawn_script_timers.end())
spawn_script_timers.erase(itr2);
safe_delete(timer);
}
remove_spawn_script_timers_list.clear();
}
MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__);
MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__);
}
void ZoneServer::CheckSpawnScriptTimers(){
DeleteSpawnScriptTimers();
SpawnScriptTimer* timer = 0;
vector call_timers;
MSpawnScriptTimers.readlock(__FUNCTION__, __LINE__);
MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__);
if(spawn_script_timers.size() > 0){
int32 current_time = Timer::GetCurrentTime2();
set::iterator itr;
for (itr = spawn_script_timers.begin(); itr != spawn_script_timers.end(); itr++) {
timer = *itr;
if(remove_spawn_script_timers_list.count(timer) == 0 &&
timer->current_count < timer->max_count && current_time >= timer->timer){
timer->current_count++;
SpawnScriptTimer tmpTimer;
tmpTimer.current_count = timer->current_count;
tmpTimer.function = timer->function;
tmpTimer.player = timer->player;
tmpTimer.spawn = timer->spawn;
tmpTimer.max_count = timer->max_count;
call_timers.push_back(tmpTimer);
}
if(timer->current_count >= timer->max_count && remove_spawn_script_timers_list.count(timer) == 0)
remove_spawn_script_timers_list.insert(timer);
}
}
MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__);
MSpawnScriptTimers.releasereadlock(__FUNCTION__, __LINE__);
if(call_timers.size() > 0){
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());
}
}
}
void ZoneServer::KillSpawnByDistance(Spawn* spawn, float max_distance, bool include_players, bool send_packet){
std::shared_lock lock(MGridMaps);
if(!spawn)
return;
Spawn* test_spawn = 0;
std::map::iterator grids = grid_maps.find(spawn->GetLocation());
if(grids != grid_maps.end()) {
grids->second->MSpawns.lock_shared();
typedef map SpawnMapType;
for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) {
test_spawn = it->second;
if(test_spawn && test_spawn->Alive() && test_spawn->GetID() > 0 && test_spawn->GetID() != spawn->GetID() && test_spawn->IsEntity() &&
(!test_spawn->IsPlayer() || include_players)){
if(test_spawn->GetDistance(spawn) < max_distance)
KillSpawn(true, test_spawn, spawn, send_packet);
}
}
grids->second->MSpawns.unlock_shared();
}
}
void ZoneServer::SpawnSetByDistance(Spawn* spawn, float max_distance, string field, string value){
std::shared_lock lock(MGridMaps);
if(!spawn)
return;
Spawn* test_spawn = 0;
int32 type = commands.GetSpawnSetType(field);
if(type == 0xFFFFFFFF)
return;
std::map::iterator grids = grid_maps.find(spawn->GetLocation());
if(grids != grid_maps.end()) {
grids->second->MSpawns.lock_shared();
typedef map SpawnMapType;
for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) {
test_spawn = it->second;
if(test_spawn && test_spawn->GetID() > 0 && test_spawn->GetID() != spawn->GetID() && !test_spawn->IsPlayer()){
if(test_spawn->GetDistance(spawn) < max_distance){
commands.SetSpawnCommand(0, test_spawn, type, value.c_str());
}
}
}
grids->second->MSpawns.unlock_shared();
}
}
void ZoneServer::AddSpawnScriptTimer(SpawnScriptTimer* timer){
MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__);
spawn_script_timers.insert(timer);
MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__);
}
/*
void ZoneServer::RemoveFromRangeMap(Client* client){
spawn_range_map.erase(client);
}
*/
void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool lock, bool erase_from_spawn_list, bool lock_spell_process)
{
LogWrite(ZONE__DEBUG, 3, "Zone", "Processing RemoveSpawn function for %s (%i)...", spawn->GetName(),spawn->GetID());
PacketStruct* packet = 0;
int16 packet_version = 0;
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if (client && (client->GetVersion() > 283 || !client->IsZoning() || client->GetPlayer() != spawn)) { //don't send destroy ghost of 283 client when zoning
if (client->GetPlayer()->HasTarget() && client->GetPlayer()->GetTarget() == spawn)
client->GetPlayer()->SetTarget(0);
if(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());
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
safe_delete(packet);
spawn->RemoveSpawnProximities();
RemoveSpawnProximities(spawn);
if (movementMgr != nullptr && spawn->IsEntity()) {
movementMgr->RemoveMob((Entity*)spawn);
}
RemoveSpawnSupportFunctions(spawn, lock_spell_process);
if (reloading)
RemoveDeadEnemyList(spawn);
if (lock)
MDeadSpawns.writelock(__FUNCTION__, __LINE__);
if (dead_spawns.count(spawn->GetID()) > 0)
dead_spawns.erase(spawn->GetID());
if (lock)
MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__);
if (spawn_expire_timers.count(spawn->GetID()) > 0)
spawn_expire_timers.erase(spawn->GetID());
spawn->SetDeletedSpawn(true);
// we will remove the spawn ptr and entry in the spawn_list later.. it is not safe right now (lua? client process? spawn process? etc? too many factors)
if(erase_from_spawn_list)
AddPendingSpawnRemove(spawn->GetID());
if(respawn && !spawn->IsPlayer() && spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0)
{
LogWrite(ZONE__DEBUG, 3, "Zone", "Handle NPC Respawn for '%s'.", spawn->GetName());
// handle instance spawn db info
// we don't care if a NPC or a client kills the spawn, we could have events that cause NPCs to kill NPCs.
if(spawn->GetZone()->GetInstanceID() > 0 && spawn->GetSpawnLocationID() > 0)
{
LogWrite(ZONE__DEBUG, 3, "Zone", "Handle NPC Respawn in an Instance.");
// use respawn time to either insert/update entry (likely insert in this situation)
if ( spawn->IsNPC() )
{
database.CreateInstanceSpawnRemoved(spawn->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_NPC,
spawn->GetRespawnTime(),spawn->GetZone()->GetInstanceID());
}
else if ( spawn->IsObject ( ) )
{
database.CreateInstanceSpawnRemoved(spawn->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT,
spawn->GetRespawnTime(),spawn->GetZone()->GetInstanceID());
}
}
else
{
int32 spawnLocationID = spawn->GetSpawnLocationID();
int32 spawnRespawnTime = spawn->GetRespawnTime();
respawn_timers.Put(spawnLocationID, Timer::GetCurrentTime2() + spawnRespawnTime * 1000);
}
}
RemoveSpawnFromGrid(spawn, spawn->GetLocation());
// Do we really need the mutex locks and check to dead_spawns as we remove it from dead spawns at the start of this function
if (lock && !respawn)
MDeadSpawns.readlock(__FUNCTION__, __LINE__);
if(delete_spawn && dead_spawns.count(spawn->GetID()) == 0)
AddPendingDelete(spawn);
if (lock && !respawn)
MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__);
LogWrite(ZONE__DEBUG, 3, "Zone", "Done processing RemoveSpawn function...");
}
Spawn* ZoneServer::GetClosestSpawn(Spawn* spawn, int32 spawn_id){
Spawn* closest_spawn = 0;
Spawn* test_spawn = 0;
float closest_distance = 1000000;
float test_distance = 0;
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
test_spawn = itr->second;
if(test_spawn && test_spawn != spawn && test_spawn->GetDatabaseID() == spawn_id){
test_distance = test_spawn->GetDistance(spawn);
if(test_distance < closest_distance){
closest_distance = test_distance;
closest_spawn = test_spawn;
}
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
return closest_spawn;
}
int32 ZoneServer::GetClosestLocation(Spawn* spawn){
Spawn* closest_spawn = 0;
Spawn* test_spawn = 0;
float closest_distance = 1000000;
float test_distance = 0;
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
test_spawn = itr->second;
if(test_spawn){
test_distance = test_spawn->GetDistance(spawn);
if(test_distance < closest_distance){
closest_distance = test_distance;
closest_spawn = test_spawn;
if(closest_distance < 10)
break;
}
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
if(closest_spawn)
return closest_spawn->GetLocation();
return 0;
}
void ZoneServer::SendQuestUpdates(Client* client, Spawn* spawn){
if(!client)
return;
if(spawn){
if(client->GetPlayer()->WasSentSpawn(spawn->GetID()))
SendSpawnChanges(spawn, client, false, true);
}
else{
client->GetCurrentZone()->SendAllSpawnsForVisChange(client);
}
}
void ZoneServer::SendAllSpawnsForLevelChange(Client* client) {
Spawn* spawn = 0;
if (spawn_range_map.count(client) > 0) {
MutexMap::iterator itr = spawn_range_map.Get(client)->begin();
while (itr.Next()) {
spawn = GetSpawnByID(itr->first);
if (spawn && client->GetPlayer()->WasSentSpawn(spawn->GetID())) {
SendSpawnChanges(spawn, client, false, true);
// Attempt to slow down the packet spam sent to the client
// who the bloody fuck put a Sleep here
//Sleep(5);
}
}
}
}
void ZoneServer::SendAllSpawnsForSeeInvisChange(Client* client) {
Spawn* spawn = 0;
if (spawn_range_map.count(client) > 0) {
MutexMap::iterator itr = spawn_range_map.Get(client)->begin();
while (itr.Next()) {
spawn = GetSpawnByID(itr->first);
if (spawn && spawn->IsEntity() && (((Entity*)spawn)->IsInvis() || ((Entity*)spawn)->IsStealthed()) && client->GetPlayer()->WasSentSpawn(spawn->GetID())) {
SendSpawnChanges(spawn, client, true, true);
}
}
}
}
void ZoneServer::SendAllSpawnsForVisChange(Client* client, bool limitToEntities) {
Spawn* spawn = 0;
if (spawn_range_map.count(client) > 0) {
MutexMap::iterator itr = spawn_range_map.Get(client)->begin();
while (itr.Next()) {
spawn = GetSpawnByID(itr->first);
if (spawn && (!limitToEntities || (limitToEntities && spawn->IsEntity())) && client->GetPlayer()->WasSentSpawn(spawn->GetID())) {
SendSpawnChanges(spawn, client, false, true);
}
}
}
}
void ZoneServer::StartZoneSpawnsForLevelThread(Client* client){
if(zoneShuttingDown)
return;
#ifdef WIN32
_beginthread(SendLevelChangedSpawns, 0, client);
#else
pthread_t thread;
pthread_create(&thread, NULL, SendLevelChangedSpawns, client);
pthread_detach(thread);
#endif
}
void ZoneServer::ReloadClientQuests(){
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(client)
client->ReloadQuests();
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendCalculatedXP(Player* player, Spawn* victim){
if (player && victim) {
if (player->GetGroupMemberInfo()) {
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
PlayerGroup* group = world.GetGroupManager()->GetGroup(player->GetGroupMemberInfo()->group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque* members = group->GetMembers();
deque::iterator itr;
bool skipGrayMob = false;
for (itr = members->begin(); itr != members->end(); itr++) {
GroupMemberInfo* gmi = *itr;
if (gmi->client) {
Player* group_member = gmi->client->GetPlayer();
if(group_member && group_member->GetArrowColor(victim->GetLevel()) == ARROW_COLOR_GRAY) {
skipGrayMob = true;
break;
}
}
}
for (itr = members->begin(); !skipGrayMob && itr != members->end(); itr++) {
GroupMemberInfo* gmi = *itr;
if (gmi->client) {
Player* group_member = gmi->client->GetPlayer();
if(group_member) {
float xp = group_member->CalculateXP(victim) / members->size();
if (xp > 0) {
group_member->AddXP((int32)xp);
}
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
}
else {
float xp = player->CalculateXP(victim);
if (xp > 0) {
Client* client = ((Player*)player)->GetClient();
if(!client)
return;
player->AddXP((int32)xp);
}
}
}
}
void ZoneServer::ProcessFaction(Spawn* spawn, Client* client)
{
if(client && !spawn->IsPlayer() && spawn->GetFactionID() > 10)
{
bool update_result = false;
Faction* faction = 0;
vector* factions = 0;
Player* player = client->GetPlayer();
bool hasfaction = database.VerifyFactionID(player->GetCharacterID(), spawn->GetFactionID());
//if 0 they dont have an entry in the db for this faction. if one they do and we can skip it.
if(hasfaction == 0) {
//Find out the default for this faction
sint32 defaultfaction = master_faction_list.GetDefaultFactionValue(spawn->GetFactionID());
//add the default faction for the player.
player->SetFactionValue(spawn->GetFactionID(), defaultfaction);
//save the character so the new default gets written to the db.
client->Save();
}
if(player->GetFactions()->ShouldDecrease(spawn->GetFactionID()))
{
update_result = player->GetFactions()->DecreaseFaction(spawn->GetFactionID());
faction = master_faction_list.GetFaction(spawn->GetFactionID());
if(faction && update_result)
client->Message(CHANNEL_FACTION, "Your faction standing with %s got worse.", faction->name.c_str());
else if(faction)
client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any worse.", faction->name.c_str());
factions = master_faction_list.GetHostileFactions(spawn->GetFactionID());
if(factions)
{
vector::iterator itr;
for(itr = factions->begin(); itr != factions->end(); itr++)
{
if(player->GetFactions()->ShouldIncrease(*itr))
{
update_result = player->GetFactions()->IncreaseFaction(*itr);
faction = master_faction_list.GetFaction(*itr);
if(faction && update_result)
client->Message(CHANNEL_FACTION, "Your faction standing with %s got better.", faction->name.c_str());
else if(faction)
client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any better.", faction->name.c_str());
}
}
}
}
factions = master_faction_list.GetFriendlyFactions(spawn->GetFactionID());
if(factions)
{
vector::iterator itr;
for(itr = factions->begin(); itr != factions->end(); itr++)
{
if(player->GetFactions()->ShouldDecrease(*itr))
{
bool hasfaction = database.VerifyFactionID(player->GetCharacterID(),spawn->GetFactionID());
if(hasfaction == 0) {
//they do not have the faction. Lets get the default value and feed it in.
sint32 defaultfaction = master_faction_list.GetDefaultFactionValue(spawn->GetFactionID());
//add the default faction for the player.
player->SetFactionValue(spawn->GetFactionID(), defaultfaction);
}
update_result = player->GetFactions()->DecreaseFaction(*itr);
faction = master_faction_list.GetFaction(*itr);
if(faction && update_result)
client->Message(CHANNEL_FACTION, "Your faction standing with %s got worse.", faction->name.c_str());
else if(faction)
client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any worse.", faction->name.c_str());
}
}
}
EQ2Packet* outapp = client->GetPlayer()->GetFactions()->FactionUpdate(client->GetVersion());
if(outapp)
client->QueuePacket(outapp);
}
}
void ZoneServer::Despawn(Spawn* spawn, int32 timer){
if (spawn && movementMgr != nullptr) {
movementMgr->RemoveMob((Entity*)spawn);
}
if(!spawn || spawn->IsPlayer())
return;
if(spawn->IsEntity())
((Entity*)spawn)->InCombat(false);
if(timer == 0)
timer = 1;
AddDeadSpawn(spawn, timer);
}
void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet, int8 type, int8 damage_type, int16 kill_blow_type)
{
bool isSpell = (type == DAMAGE_PACKET_TYPE_SIPHON_SPELL || type == DAMAGE_PACKET_TYPE_SIPHON_SPELL2 ||
type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE || type == DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG ||
type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE2 || type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE3);
MDeadSpawns.readlock(__FUNCTION__, __LINE__);
if(!dead || this->dead_spawns.count(dead->GetID()) > 0) {
MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__);
return;
}
MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__);
PacketStruct* packet = 0;
Client* client = 0;
vector* encounter = 0;
int32 encounter_player_bot_count = 1;
bool killer_in_encounter = false;
int8 loot_state = dead->GetLockedNoLoot();
if(dead->IsEntity())
{
// add any special quest related loot (no_drop_quest_completed)
if(dead->IsNPC() && ((NPC*)dead)->Brain()) {
if(!((NPC*)dead)->Brain()->PlayerInEncounter() || (loot_state != ENCOUNTER_STATE_LOCKED && loot_state != ENCOUNTER_STATE_OVERMATCHED)) {
LogWrite(LOOT__DEBUG, 0, "Loot", "NPC %s bypassed loot drop due to no player in encounter, or encounter state not locked.", ((NPC*)dead)->GetName());
}
else {
Entity* hated = ((NPC*)dead)->Brain()->GetMostHated();
if(hated) {
GroupLootMethod loot_method = GroupLootMethod::METHOD_FFA;
int8 item_rarity = 0;
if(hated->GetGroupMemberInfo()) {
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
PlayerGroup* group = world.GetGroupManager()->GetGroup(hated->GetGroupMemberInfo()->group_id);
if (group) {
loot_method = (GroupLootMethod)group->GetGroupOptions()->loot_method;
item_rarity = group->GetGroupOptions()->loot_items_rarity;
LogWrite(LOOT__DEBUG, 0, "Loot", "%s: Loot method set to %u.", dead->GetName(), loot_method);
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
}
AddLoot((NPC*)dead, hated, loot_method, item_rarity, hated->GetGroupMemberInfo() ? hated->GetGroupMemberInfo()->group_id : 0);
}
}
}
((Entity*)dead)->InCombat(false);
dead->SetInitialState(16512, false); // This will make aerial npc's fall after death
dead->SetHP(0);
dead->SetSpawnType(3);
dead->appearance.attackable = 0;
// Remove hate towards dead from all npc's in the zone
ClearHate((Entity*)dead);
// Check kill and death procs
if (killer && dead != killer){
if (dead->IsEntity())
((Entity*)dead)->CheckProcs(PROC_TYPE_DEATH, killer);
if (killer->IsEntity())
((Entity*)killer)->CheckProcs(PROC_TYPE_KILL, dead);
}
//Check if caster is alive after death proc called, incase of deathsave
if (dead->Alive())
return;
RemoveSpellTimersFromSpawn(dead, true, !dead->IsPlayer(), true, !isSpell);
((Entity*)dead)->IsCasting(false);
if(dead->IsPlayer())
{
((Player*)dead)->UpdatePlayerStatistic(STAT_PLAYER_TOTAL_DEATHS, 1);
client = ((Player*)dead)->GetClient();
((Entity*)dead)->HandleDeathExperienceDebt(killer);
if(client) {
if(client->GetPlayer()->DamageEquippedItems(10, client))
client->QueuePacket(client->GetPlayer()->GetEquipmentList()->serialize(client->GetVersion(), client->GetPlayer()));
client->DisplayDeadWindow();
}
}
else if (dead->IsNPC()) {
encounter = ((NPC*)dead)->Brain()->GetEncounter();
encounter_player_bot_count = ((NPC*)dead)->Brain()->CountPlayerBotInEncounter();
if(encounter_player_bot_count < 1)
encounter_player_bot_count = 1;
}
}
dead->SetActionState(0);
dead->SetTempActionState(0);
// Needs npc to have access to the encounter list for who is allowed to loot
NPC* chest = 0;
if (dead->IsNPC() && !((NPC*)dead)->Brain()->PlayerInEncounter()) {
dead->SetLootCoins(0);
dead->ClearLoot();
}
Spawn* groupMemberAlive = nullptr;
// If dead has loot attempt to drop a chest
if (dead->HasLoot()) {
if(!(groupMemberAlive = dead->IsSpawnGroupMembersAlive(dead))) {
chest = ((Entity*)dead)->DropChest();
}
else {
switch(dead->GetLootDropType()) {
case 0:
// default drop all chest type as a group
dead->TransferLoot(groupMemberAlive);
break;
case 1:
// this is a primary mob it drops its own loot
chest = ((Entity*)dead)->DropChest();
break;
}
}
}
// If dead is an npc get the encounter and loop through it giving out the rewards, no rewards for pets
if (dead->IsNPC() && !dead->IsPet() && !dead->IsBot()) {
Spawn* spawn = 0;
int8 size = encounter->size();
for (int8 i = 0; i < encounter->size(); i++) {
spawn = GetSpawnByID(encounter->at(i), spawnListLocked);
// set a flag to let us know if the killer is in the encounter
if (!killer_in_encounter && spawn == killer)
killer_in_encounter = true;
if (spawn && spawn->IsPlayer()) {
// Update players total kill count
((Player*)spawn)->UpdatePlayerStatistic(STAT_PLAYER_TOTAL_NPC_KILLS, 1);
// If this was an epic mob kill send the announcement for this player
if (dead->GetDifficulty() >= 10)
SendEpicMobDeathToGuild((Player*)spawn, dead);
// Clear hostile spells from the players spell queue
spellProcess->RemoveSpellFromQueue((Player*)spawn, true);
// Get the client of the player
client = ((Player*)spawn)->GetClient();
// valid client?
if (client) {
// Check for quest kill updates
if(!dead->IsNPC() || loot_state != ENCOUNTER_STATE_BROKEN) {
client->CheckPlayerQuestsKillUpdate(dead);
}
// If the dead mob is not a player and if it had a faction with an ID greater or equal to 10 the send faction changes
if (!dead->IsPlayer() && dead->GetFactionID() > 10)
ProcessFaction(dead, client);
// Send xp...this is currently wrong fix it
if (spawn != dead && ((Player*)spawn)->GetArrowColor(dead->GetLevel()) >= ARROW_COLOR_GREEN) {
//SendCalculatedXP((Player*)spawn, dead);
float xp = ((Player*)spawn)->CalculateXP(dead) / encounter_player_bot_count;
if (xp > 0) {
((Player*)spawn)->AddXP((int32)xp);
}
}
}
}
// If a chest is being dropped add this spawn to the chest's encounter so they can loot it
if (chest && spawn && spawn->IsEntity())
chest->Brain()->AddToEncounter((Entity*)spawn);
}
}
// If a chest is being dropped add it to the world and set the timer to remove it.
if (chest) {
AddSpawn(chest);
AddDeadSpawn(chest, 0xFFFFFFFF);
LogWrite(LOOT__DEBUG, 0, "Loot", "Adding a chest to the world...");
}
// Reset client pointer
client = 0;
// Killer was not in the encounter, give them the faction hit but no xp
if (!killer_in_encounter) {
// make sure the killer is a player and the dead spawn had a faction and wasn't a player
if (killer && killer->IsPlayer()) {
if (!dead->IsPlayer() && dead->GetFactionID() > 10) {
client = ((Player*)killer)->GetClient();
if (client)
ProcessFaction(dead, client);
}
// Clear hostile spells from the killers spell queue
spellProcess->RemoveSpellFromQueue((Player*)killer, true);
}
}
// Reset client pointer
client = 0;
vector* group = dead->GetSpawnGroup();
if (group && group->size() == 1)
CallSpawnScript(dead, SPAWN_SCRIPT_GROUP_DEAD, killer);
safe_delete(group);
// Remove the support functions for the dead spawn
RemoveSpawnSupportFunctions(dead, !isSpell);
// Erase the expire timer if it has one
if (spawn_expire_timers.count(dead->GetID()) > 0)
spawn_expire_timers.erase(dead->GetID());
// If dead is an npc or object call the spawn scrip and handle instance stuff
if(dead->IsNPC() || dead->IsObject())
{
// handle instance spawn db info
// we don't care if a NPC or a client kills the spawn, we could have events that cause NPCs to kill NPCs.
if(dead->GetZone()->GetInstanceID() > 0 && dead->GetSpawnLocationID() > 0)
{
// use respawn time to either insert/update entry (likely insert in this situation)
if(dead->IsNPC())
database.CreateInstanceSpawnRemoved(dead->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_NPC, dead->GetRespawnTime(),dead->GetZone()->GetInstanceID());
else if ( dead->IsObject ( ) )
database.CreateInstanceSpawnRemoved(dead->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT, dead->GetRespawnTime(),dead->GetZone()->GetInstanceID());
}
// Call the spawn scripts death() function
CallSpawnScript(dead, SPAWN_SCRIPT_DEATH, killer);
const char* zone_script = world.GetZoneScript(this->GetZoneID());
if (zone_script && lua_interface)
lua_interface->RunZoneScript(zone_script, "spawn_killed", this, dead, 0, 0, killer);
}
int32 victim_id = dead->GetID();
int32 attacker_id = 0xFFFFFFFF;
if(killer)
attacker_id = killer->GetID();
if(send_packet)
{
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client->GetPlayer()->WasSentSpawn(victim_id) || (attacker_id != 0xFFFFFFFF && !client->GetPlayer()->WasSentSpawn(attacker_id)) )
continue;
else if(killer && killer->GetDistance(client->GetPlayer()) > HEAR_SPAWN_DISTANCE)
continue;
packet = configReader.getStruct("WS_HearDeath", client->GetVersion());
if(packet)
{
if(killer)
packet->setDataByName("attacker", client->GetPlayer()->GetIDWithPlayerSpawn(killer));
else
packet->setDataByName("attacker", 0xFFFFFFFF);
packet->setDataByName("defender", client->GetPlayer()->GetIDWithPlayerSpawn(dead));
packet->setDataByName("damage_type", damage_type);
packet->setDataByName("blow_type", kill_blow_type);
client->QueuePacket(packet->serialize());
LogWrite(COMBAT__DEBUG, 0, "Combat", "Zone Killing of '%s' by '%s' damage type %u, blow type %u", dead->GetName(), killer ? killer->GetName() : "", damage_type, kill_blow_type);
safe_delete(packet);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
int32 pop_timer = 0xFFFFFFFF;
if(killer && killer->IsNPC())
{
// Call the spawn scripts killed() function
CallSpawnScript(killer, SPAWN_SCRIPT_KILLED, dead);
if(!dead->IsPlayer())
{
LogWrite(MISC__TODO, 1, "TODO", "Whenever pets are added, check for pet kills\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
// Set the time for the corpse to linger to 5 sec
//pop_timer = 5000;
// commented out the timer so in the event the killer is not a player (pet, guard, something else i haven't thought of)
// the corpse doesn't vanish sooner then it should if it had loot for the players. AddDeadSpawn() will set timers based on if
// the corpse has loot or not if the timer value (pop_timer) is 0xFFFFFFFF
}
}
// If the dead spawns was not a player add it to the dead spawn list
if (!dead->IsPlayer() && !dead->IsBot())
AddDeadSpawn(dead, pop_timer);
// if dead was a player clear hostile spells from its spell queue
if (dead->IsPlayer())
spellProcess->RemoveSpellFromQueue((Player*)dead, true);
if (dead->IsNPC())
((NPC*)dead)->Brain()->ClearHate();
safe_delete(encounter);
}
void ZoneServer::SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, int8 type2, int8 damage_type, int16 damage, const char* spell_name) {
//Scat: was set but never being used anywhere. i see references to 0xFFFFFFFF below so could be old code not used anymore
//int32 attacker_id = 0xFFFFFFFF;
//if(attacker)
// attacker_id = attacker->GetID();
PacketStruct* packet = 0;
Client* client = 0;
if (attacker && victim && victim->IsPlayer() && victim->GetTarget() == 0) {
client = ((Player*)victim)->GetClient();
if (client)
client->TargetSpawn(attacker);
}
if(damage_type == DAMAGE_PACKET_DAMAGE_TYPE_FOCUS) {
damage_type = 0;
type2 = DAMAGE_PACKET_RESULT_FOCUS;
}
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if (!client || (client->GetPlayer() != attacker && client->GetPlayer() != victim && ((attacker && client->GetPlayer()->WasSentSpawn(attacker->GetID()) == false) || (victim && client->GetPlayer()->WasSentSpawn(victim->GetID()) == false))))
continue;
if (attacker && attacker->GetDistance(client->GetPlayer()) > 50)
continue;
if (victim && victim->GetDistance(client->GetPlayer()) > 50)
continue;
switch (type1) {
case DAMAGE_PACKET_TYPE_SIPHON_SPELL:
case DAMAGE_PACKET_TYPE_SIPHON_SPELL2:
packet = configReader.getStruct("WS_HearSiphonSpellDamage", client->GetVersion());
break;
case DAMAGE_PACKET_TYPE_MULTIPLE_DAMAGE:
if (client->GetVersion() > 546)
packet = configReader.getStruct("WS_HearMultipleDamage", client->GetVersion());
else
packet = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion());
break;
case DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG:
case DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE:
packet = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion());
break;
case DAMAGE_PACKET_TYPE_SPELL_DAMAGE2:
case DAMAGE_PACKET_TYPE_SPELL_DAMAGE3:
case DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG:
case DAMAGE_PACKET_TYPE_SPELL_DAMAGE:
if (client->GetVersion() > 546)
packet = configReader.getStruct("WS_HearSpellDamage", client->GetVersion());
else
packet = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion());
if (packet)
packet->setSubstructDataByName("header", "unknown", 5);
break;
case DAMAGE_PACKET_TYPE_RANGE_DAMAGE:
packet = configReader.getStruct("WS_HearRangeDamage", client->GetVersion());
break;
case DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG:
case DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG2:
packet = configReader.getStruct("WS_HearRangeDamage", client->GetVersion());
break;
default:
LogWrite(ZONE__ERROR, 0, "Zone", "Unknown Damage Packet type: %i in ZoneServer::SendDamagePacket.", type1);
MClientList.releasereadlock(__FUNCTION__, __LINE__);
return;
}
if (packet) {
if (client->GetVersion() > 546) {
packet->setSubstructDataByName("header", "packet_type", type1);
packet->setSubstructDataByName("header", "result_type", type2);
packet->setDataByName("damage_type", damage_type);
packet->setDataByName("damage", damage);
}
else {
switch (type2) {
case DAMAGE_PACKET_RESULT_MISS:
packet->setSubstructDataByName("header", "result_type", 1);
break;
case DAMAGE_PACKET_RESULT_DODGE:
packet->setSubstructDataByName("header", "result_type", 2);
break;
case DAMAGE_PACKET_RESULT_PARRY:
packet->setSubstructDataByName("header", "result_type", 3);
break;
case DAMAGE_PACKET_RESULT_RIPOSTE:
packet->setSubstructDataByName("header", "result_type", 4);
break;
case DAMAGE_PACKET_RESULT_BLOCK:
packet->setSubstructDataByName("header", "result_type", 5);
break;
case DAMAGE_PACKET_RESULT_INVULNERABLE:
packet->setSubstructDataByName("header", "result_type", 7);
break;
case DAMAGE_PACKET_RESULT_RESIST:
packet->setSubstructDataByName("header", "result_type", 9);
break;
case DAMAGE_PACKET_RESULT_REFLECT:
packet->setSubstructDataByName("header", "result_type", 10);
break;
case DAMAGE_PACKET_RESULT_IMMUNE:
packet->setSubstructDataByName("header", "result_type", 11);
break;
}
packet->setArrayLengthByName("num_dmg", 1);
packet->setSubstructDataByName("header", "defender_proxy", client->GetPlayer()->GetIDWithPlayerSpawn(victim));
packet->setArrayDataByName("damage_type", damage_type);
packet->setArrayDataByName("damage", damage);
}
if (!attacker)
packet->setSubstructDataByName("header", "attacker", 0xFFFFFFFF);
else
packet->setSubstructDataByName("header", "attacker", client->GetPlayer()->GetIDWithPlayerSpawn(attacker));
packet->setSubstructDataByName("header", "defender", client->GetPlayer()->GetIDWithPlayerSpawn(victim));
if (spell_name) {
packet->setDataByName("spell", 1);
packet->setDataByName("spell_name", spell_name);
}
EQ2Packet* app = packet->serialize();
//DumpPacket(app);
client->QueuePacket(app);
safe_delete(packet);
packet = 0;
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendHealPacket(Spawn* caster, Spawn* target, int16 heal_type, int32 heal_amt, const char* spell_name){
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client || (client->GetPlayer() != caster && ((caster && client->GetPlayer()->WasSentSpawn(caster->GetID()) == false) || (target && client->GetPlayer()->WasSentSpawn(target->GetID()) == false))))
continue;
if(caster && caster->GetDistance(client->GetPlayer()) > 50)
continue;
if(target && target->GetDistance(client->GetPlayer()) > 50)
continue;
PacketStruct* packet = configReader.getStruct("WS_HearHeal", client->GetVersion());
if (packet) {
packet->setDataByName("caster", client->GetPlayer()->GetIDWithPlayerSpawn(caster));
packet->setDataByName("target", client->GetPlayer()->GetIDWithPlayerSpawn(target));
packet->setDataByName("heal_amt", heal_amt);
packet->setDataByName("spellname", spell_name);
packet->setDataByName("type", heal_type);
packet->setDataByName("unknown2", 1);
EQ2Packet* app = packet->serialize();
client->QueuePacket(app);
safe_delete(packet);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendThreatPacket(Spawn* caster, Spawn* target, int32 threat_amt, const char* spell_name) {
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client || (client->GetPlayer() != caster && ((caster && client->GetPlayer()->WasSentSpawn(caster->GetID()) == false) || (target && client->GetPlayer()->WasSentSpawn(target->GetID()) == false))))
continue;
if(caster && caster->GetDistance(client->GetPlayer()) > 50)
continue;
if(target && target->GetDistance(client->GetPlayer()) > 50)
continue;
PacketStruct* packet = configReader.getStruct("WS_HearThreatCmd", client->GetVersion());
if (packet) {
packet->setDataByName("spell_name", spell_name);
packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(caster));
packet->setDataByName("target", client->GetPlayer()->GetIDWithPlayerSpawn(target));
packet->setDataByName("threat_amount", threat_amt);
client->QueuePacket(packet->serialize());
}
safe_delete(packet);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendYellPacket(Spawn* yeller, float max_distance) {
Client* client = 0;
string yellMsg = std::string(yeller->GetName()) + " yelled for help!";
vector::iterator client_itr;
PacketStruct* packet = nullptr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client || !client->GetPlayer() || client->GetPlayer()->WasSentSpawn(yeller->GetID()) == false)
continue;
if(client->GetPlayer()->GetDistance(yeller) > max_distance)
continue;
if(packet && packet->GetVersion() == client->GetVersion()) {
client->QueuePacket(packet->serialize());
}
else {
safe_delete(packet);
packet = configReader.getStruct("WS_EncounterBroken", client->GetVersion());
if (packet) {
packet->setDataByName("message", yellMsg.c_str());
/* none of the other data seems necessary, keeping for reference for future disassembly
packet2->setDataByName("unknown2", 0x40);
packet2->setDataByName("unknown3", 0x40);
packet2->setDataByName("unknown4", 0xFF);
packet2->setDataByName("unknown5", 0xFF);
packet2->setDataByName("unknown6", 0xFF);*/
client->QueuePacket(packet->serialize());
}
}
}
safe_delete(packet);
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::SendSpellFailedPacket(Client* client, int16 error){
if(!client)
return;
PacketStruct* packet = configReader.getStruct("WS_DisplaySpellFailed", client->GetVersion());
if(packet){
/* Temp solution, need to modify the error code before this function and while we still have access to the spell/combat art */
error = master_spell_list.GetSpellErrorValue(client->GetVersion(), error);
if(client->GetVersion() <= 546 && error) {
error += 1;
}
packet->setDataByName("error_code", error);
//packet->PrintPacket();
client->QueuePacket(packet->serialize());
safe_delete(packet);
}
}
void ZoneServer::SendInterruptPacket(Spawn* interrupted, LuaSpell* spell, bool fizzle){
if(!interrupted || !spell)
return;
EQ2Packet* outapp = 0;
PacketStruct* packet = 0;
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client || !client->GetPlayer()->WasSentSpawn(interrupted->GetID()))
continue;
packet = configReader.getStruct(fizzle ? "WS_SpellFizzle" : "WS_Interrupt", client->GetVersion());
if(packet){
packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(interrupted));
packet->setArrayLengthByName("num_targets", spell->targets.size());
for (int32 i = 0; i < spell->targets.size(); i++)
packet->setArrayDataByName("target_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()->GetZone()->GetSpawnByID(spell->targets[i])), i);
packet->setDataByName("spell_id", spell->spell->GetSpellID());
outapp = packet->serialize();
client->QueuePacket(outapp);
safe_delete(packet);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
safe_delete(packet);
}
void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spell_visual_override, int16 casttime_override){
EQ2Packet* outapp = 0;
PacketStruct* packet = 0;
Client* client = 0;
if(!caster || !spell || !spell->spell || spell->interrupted)
return;
if(spell->is_damage_spell && (!spell->has_damaged || spell->resisted)) {
// we did not successfully hit target, so we should not send the visual
return;
}
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client)
continue;
packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion());
if(packet){
int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(caster);
if(!caster_id) {
safe_delete(packet);
continue;
}
packet->setDataByName("spawn_id", caster_id);
packet->setArrayLengthByName("num_targets", spell->targets.size());
for (int32 i = 0; i < spell->targets.size(); i++) {
int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(spell->caster->GetZone()->GetSpawnByID(spell->targets[i]));
if(target_id) {
packet->setArrayDataByName("target", target_id, i);
}
else {
packet->setArrayDataByName("target", 0xFFFFFFFF, i);
}
}
int32 visual_to_use = spell_visual_override > 0 ? spell_visual_override : spell->spell->GetSpellData()->spell_visual;
int32 visual = client->GetSpellVisualOverride(visual_to_use);
packet->setDataByName("spell_visual", visual); //result
if(casttime_override != 0xFFFF) {
packet->setDataByName("cast_time", casttime_override*.01f); //delay
}
else {
packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01f); //delay
}
packet->setDataByName("spell_id", spell->spell->GetSpellID());
packet->setDataByName("spell_level", 1);
packet->setDataByName("spell_tier", spell->spell->GetSpellData()->tier);
outapp = packet->serialize();
client->QueuePacket(outapp);
safe_delete(packet);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
safe_delete(packet);
}
void ZoneServer::SendCastSpellPacket(int32 spell_visual, Spawn* target, Spawn* caster) {
if (target) {
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
Client* client = *client_itr;
if (!client)
continue;
PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion());
if (packet) {
int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(target);
if(!target_id) { // client is not aware of spawn
safe_delete(packet);
continue;
}
if (!caster) {
packet->setDataByName("spawn_id", 0xFFFFFFFF);
}
else {
int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(caster);
if(!caster_id) { // client is not aware of spawn
safe_delete(packet);
continue;
}
packet->setDataByName("spawn_id", caster_id);
}
packet->setArrayLengthByName("num_targets", 1);
packet->setArrayDataByName("target", target_id);
int32 visual = client->GetSpellVisualOverride(spell_visual);
packet->setDataByName("spell_visual", visual);
packet->setDataByName("cast_time", 0);
packet->setDataByName("spell_id", 0);
packet->setDataByName("spell_level", 0);
packet->setDataByName("spell_tier", 1);
client->QueuePacket(packet->serialize());
safe_delete(packet);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
}
void ZoneServer::SendCastEntityCommandPacket(EntityCommand* entity_command, int32 spawn_id, int32 target_id) {
if (entity_command) {
Spawn* spawn = GetSpawnByID(spawn_id);
Spawn* target = GetSpawnByID(target_id);
if (!spawn || !target)
return;
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if (!client || !client->GetPlayer()->WasSentSpawn(spawn_id) || !client->GetPlayer()->WasSentSpawn(target_id))
continue;
PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion());
if (packet) {
int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(spawn);
int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(target);
if(!caster_id || !target_id)
continue;
packet->setDataByName("spawn_id", caster_id);
packet->setArrayLengthByName("num_targets", 1);
packet->setArrayDataByName("target", target_id);
packet->setDataByName("num_targets", 1);
packet->setDataByName("spell_visual", entity_command->spell_visual); //result
packet->setDataByName("cast_time", entity_command->cast_time * 0.01); //delay
packet->setDataByName("spell_id", 1);
packet->setDataByName("spell_level", 1);
packet->setDataByName("spell_tier", 1);
EQ2Packet* outapp = packet->serialize();
client->QueuePacket(outapp);
safe_delete(packet);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
}
void ZoneServer::StartZoneInitialSpawnThread(Client* client){
if(zoneShuttingDown)
return;
#ifdef WIN32
_beginthread(SendInitialSpawns, 0, client);
#else
pthread_t thread;
pthread_create(&thread, NULL, SendInitialSpawns, client);
pthread_detach(thread);
#endif
}
void ZoneServer::SendZoneSpawns(Client* client){
int8 count = 0;
while (LoadingData && count <= 6000) { //sleep for max of 60 seconds (60000ms) while the maps are loading
count++;
Sleep(10);
}
count = 0;
int16 size = 0;
//give the spawn thread a tad bit of time to add the pending_spawns to spawn_list (up to 10 seconds)
while (count < 1000) {
MPendingSpawnListAdd.readlock(__FUNCTION__, __LINE__);
size = pending_spawn_list_add.size();
MPendingSpawnListAdd.releasereadlock(__FUNCTION__, __LINE__);
if (size == 0)
break;
Sleep(10);
count++;
}
initial_spawn_threads_active++;
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
Spawn* spawn = itr->second;
if (spawn) {
if(spawn == client->GetPlayer() && (client->IsReloadingZone() || client->GetPlayer()->IsReturningFromLD()))
{
if(!client->GetPlayer()->SetSpawnMap(spawn))
continue;
}
CheckSpawnRange(client, spawn, true);
}
}
CheckSendSpawnToClient(client, true);
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
client->SetConnected(true);
ClientPacketFunctions::SendFinishedEntitiesList(client);
initial_spawn_threads_active--;
}
vector ZoneServer::GetPlayers(){
vector ret;
Client* client = 0;
vector::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
ret.push_back(client->GetPlayer());
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
int16 ZoneServer::SetSpawnTargetable(Spawn* spawn, float distance){
Spawn* test_spawn = 0;
int16 ret_val = 0;
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
test_spawn = itr->second;
if(test_spawn){
if(test_spawn->GetDistance(spawn) <= distance){
test_spawn->SetTargetable(1);
ret_val++;
}
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
return ret_val;
}
int16 ZoneServer::SetSpawnTargetable(int32 spawn_id){
Spawn* spawn = 0;
int16 ret_val = 0;
map::iterator itr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
spawn = itr->second;
if(spawn){
if(spawn->GetDatabaseID() == spawn_id){
spawn->SetTargetable(1);
ret_val++;
}
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
return ret_val;
}
ZoneInfoSlideStruct* ZoneServer::GenerateSlideStruct(float unknown1a, float unknown1b, int32 unknown2a, int32 unknown2b, int32 unknown3, int32 unknown4, const char* slide, const char* voiceover, int32 key1, int32 key2) {
ZoneInfoSlideStructInfo* info = new ZoneInfoSlideStructInfo();
memset(info, 0, sizeof(ZoneInfoSlideStructInfo));
info->unknown1[0] = unknown1a;
info->unknown1[1] = unknown1b;
info->unknown2[0] = unknown2a;
info->unknown2[1] = unknown2b;
info->unknown3 = unknown3;
info->unknown4 = unknown4;
int8 length = strlen(slide);
if (length >= 128)
length = 127;
strncpy(info->slide, slide, length);
length = strlen(voiceover);
if (length >= 128)
length = 127;
strncpy(info->voiceover, voiceover, length);
info->key1 = key1;
info->key2 = key2;
ZoneInfoSlideStruct* ret = new ZoneInfoSlideStruct();
ret->info = info;
return ret;
}
void ZoneServer::AddZoneInfoSlideStructTransitionInfo(ZoneInfoSlideStruct* info, int32 x, int32 y, float zoom, float transition_time) {
ZoneInfoSlideStructTransitionInfo* transition_info = new ZoneInfoSlideStructTransitionInfo();
transition_info->transition_x = x;
transition_info->transition_y = y;
transition_info->transition_zoom = zoom;
transition_info->transition_time = transition_time;
info->slide_transition_info.push_back(transition_info);
}
vector* ZoneServer::GenerateTutorialSlides() {
vector