1
0
Emagi b937444425 Fix leashing, leading, rubberbanding mobs. Work in progress for size mod.
Fix #18 Leashing, leading, rubberbanding issues with spawns resolved
Issue #17 Work in Progress, size mod stat support in the works, setting temporary_scale in info struct seems to modify size, the pos_size values in the position struct are not for KoS and older clients.
AddSpellBonus was translating values from float to sint32 early, now we take bonus values into player add bonus so that float values will be honored, as well as sint32.  This applies to uncontested parry, block, dodge, riposte and the size mod.
2025-05-30 21:55:10 -04:00

5532 lines
166 KiB
C++

/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
*/
#include "Spawn.h"
#include <stdio.h>
#include "../common/timer.h"
#include <time.h>
#include <math.h>
#include "Entity.h"
#include "Widget.h"
#include "Sign.h"
#include "../common/MiscFunctions.h"
#include "../common/Log.h"
#include "Rules/Rules.h"
#include "World.h"
#include "LuaInterface.h"
#include "Bots/Bot.h"
#include "Zone/raycast_mesh.h"
#include "RaceTypes/RaceTypes.h"
#include "VisualStates.h"
extern ConfigReader configReader;
extern RuleManager rule_manager;
extern World world;
extern ZoneList zone_list;
extern MasterRaceTypeList race_types_list;
extern LuaInterface* lua_interface;
extern VisualStates visual_states;
Spawn::Spawn(){
loot_coins = 0;
trap_triggered = false;
trap_state = 0;
chest_drop_time = 0;
trap_opened_time = 0;
group_id = 0;
size_offset = 0;
merchant_id = 0;
merchant_type = 0;
merchant_min_level = 0;
merchant_max_level = 0;
memset(&appearance, 0, sizeof(AppearanceData));
memset(&basic_info, 0, sizeof(BasicInfoStruct));
appearance.pos.state = 0x4080;
appearance.difficulty = 6;
size = 32;
appearance.pos.collision_radius = 32;
id = Spawn::NextID();
oversized_packet = 0xFF;
zone = 0;
spawn_location_id = 0;
spawn_entry_id = 0;
spawn_location_spawns_id = 0;
respawn = 0;
expire_time = 0;
expire_offset = 0;
x_offset = 0;
y_offset = 0;
z_offset = 0;
database_id = 0;
packet_num = 1;
changed = false;
vis_changed = false;
position_changed = false;
send_spawn_changes = true;
info_changed = false;
size_changed = false;
appearance.pos.Speed1 = 0;
last_attacker = 0;
faction_id = 0;
running_to = 0;
tmp_visual_state = -1;
tmp_action_state = -1;
transporter_id = 0;
invulnerable = false;
spawn_group_list = 0;
MSpawnGroup = 0;
movement_locations = 0;
target = 0;
primary_command_list_id = 0;
secondary_command_list_id = 0;
is_pet = false;
m_followTarget = 0;
following = false;
req_quests_continued_access = false;
req_quests_override = 0;
req_quests_private = false;
m_illusionModel = 0;
Cell_Info.CurrentCell = nullptr;
Cell_Info.CellListIndex = -1;
m_addedToWorldTimestamp = 0;
m_spawnAnim = 0;
m_spawnAnimLeeway = 0;
m_Update.SetName("Spawn::m_Update");
m_requiredHistory.SetName("Spawn::m_requiredHistory");
m_requiredQuests.SetName("Spawn::m_requiredQuests");
last_heading_angle = 0.0;
last_grid_update = 0;
last_location_update = 0.0;
last_movement_update = Timer::GetCurrentTime2();
forceMapCheck = false;
m_followDistance = 0;
MCommandMutex.SetName("Entity::MCommandMutex");
has_spawn_proximities = false;
pickup_item_id = 0;
pickup_unique_item_id = 0;
disable_sounds = false;
has_quests_required = false;
has_history_required = false;
is_flying_creature = false;
is_water_creature = false;
region_map = nullptr;
current_map = nullptr;
RegionMutex.SetName("Spawn::RegionMutex");
pause_timer.Disable();
m_SpawnMutex.SetName("Spawn::SpawnMutex");
appearance_equipment_list.SetAppearanceType(1);
is_transport_spawn = false;
rail_id = 0;
is_omitted_by_db_flag = false;
loot_tier = 0;
loot_drop_type = 0;
deleted_spawn = false;
is_collector = false;
trigger_widget_id = 0;
scared_by_strong_players = false;
is_alive = true;
SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE);
loot_method = GroupLootMethod::METHOD_FFA;
loot_rarity = 0;
loot_group_id = 0;
looter_spawn_id = 0;
is_loot_complete = false;
is_loot_dispensed = false;
reset_movement = false;
respawn_offset_low = 0;
respawn_offset_high = 0;
duplicated_spawn = true;
ResetKnockedBack();
}
Spawn::~Spawn(){
is_running = false;
vector<Item*>::iterator itr;
for (itr = loot_items.begin(); itr != loot_items.end(); itr++)
safe_delete(*itr);
loot_items.clear();
RemovePrimaryCommands();
for(int32 i=0;i<secondary_command_list.size();i++){
safe_delete(secondary_command_list[i]);
}
secondary_command_list.clear();
RemoveSpawnFromGroup();
MMovementLocations.lock();
if(movement_locations){
while(movement_locations->size()){
safe_delete(movement_locations->front());
movement_locations->pop_front();
}
safe_delete(movement_locations);
}
MMovementLocations.unlock();
MMovementLoop.lock();
for (int32 i = 0; i < movement_loop.size(); i++)
safe_delete(movement_loop.at(i));
movement_loop.clear();
MMovementLoop.unlock();
m_requiredHistory.writelock(__FUNCTION__, __LINE__);
map<int32, LUAHistory*>::iterator lua_itr;
for (lua_itr = required_history.begin(); lua_itr != required_history.end(); lua_itr++) {
safe_delete(lua_itr->second);
}
required_history.clear();
m_requiredHistory.releasewritelock(__FUNCTION__, __LINE__);
m_requiredQuests.writelock(__FUNCTION__, __LINE__);
map<int32, vector<int16>* >::iterator rq_itr;
for (rq_itr = required_quests.begin(); rq_itr != required_quests.end(); rq_itr++){
safe_delete(rq_itr->second);
}
required_quests.clear();
m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__);
// just in case to make sure data is destroyed
RemoveSpawnProximities();
Regions.clear();
}
void Spawn::RemovePrimaryCommands()
{
for (int32 i = 0; i < primary_command_list.size(); i++) {
safe_delete(primary_command_list[i]);
}
primary_command_list.clear();
}
void Spawn::InitializeHeaderPacketData(Player* player, PacketStruct* header, int16 index) {
header->setDataByName("index", index);
if (GetSpawnAnim() > 0 && Timer::GetCurrentTime2() < (GetAddedToWorldTimestamp() + GetSpawnAnimLeeway())) {
int32 spawn_anim = GetSpawnAnim();
if (header->GetVersion() <= 561) { // spell's spell_visual field is based on newer clients, DoF has to convert
Emote* spellVisualEmote = visual_states.FindEmoteByID(spawn_anim, 60085);
if (spellVisualEmote != nullptr && spellVisualEmote->GetMessageString().size() > 0) {
spellVisualEmote = visual_states.FindEmote(spellVisualEmote->GetMessageString(), header->GetVersion());
if (spellVisualEmote) {
spawn_anim = (int32)spellVisualEmote->GetVisualState();
}
}
}
if (header->GetVersion() >= 57080)
header->setDataByName("spawn_anim", spawn_anim);
else
header->setDataByName("spawn_anim", (int16)spawn_anim);
}
else {
if (header->GetVersion() >= 57080)
header->setDataByName("spawn_anim", 0xFFFFFFFF);
else
header->setDataByName("spawn_anim", 0xFFFF);
}
if (primary_command_list.size() > 0){
if (primary_command_list.size() > 1) {
header->setArrayLengthByName("command_list", primary_command_list.size());
for (int32 i = 0; i < primary_command_list.size(); i++) {
header->setArrayDataByName("command_list_name", primary_command_list[i]->name.c_str(), i);
header->setArrayDataByName("command_list_max_distance", primary_command_list[i]->distance, i);
header->setArrayDataByName("command_list_error", primary_command_list[i]->error_text.c_str(), i);
header->setArrayDataByName("command_list_command", primary_command_list[i]->command.c_str(), i);
}
}
if (header->GetVersion() <= 561) {
header->setMediumStringByName("default_command", primary_command_list[0]->name.c_str());
}
else
header->setMediumStringByName("default_command", primary_command_list[0]->command.c_str());
header->setDataByName("max_distance", primary_command_list[0]->distance);
}
if (spawn_group_list && MSpawnGroup){
MSpawnGroup->readlock(__FUNCTION__, __LINE__);
header->setArrayLengthByName("group_size", spawn_group_list->size());
vector<Spawn*>::iterator itr;
int i = 0;
for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++, i++){
int32 idx = 0;
idx = player->GetIDWithPlayerSpawn((*itr));
header->setArrayDataByName("group_spawn_id", idx, i);
}
MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__);
}
header->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(this));
header->setDataByName("crc", 1);
header->setDataByName("time_stamp", Timer::GetCurrentTime2());
}
void Spawn::InitializeVisPacketData(Player* player, PacketStruct* vis_packet) {
int16 version = vis_packet->GetVersion();
//why?
/*if (IsPlayer()) {
appearance.pos.grid_id = 0xFFFFFFFF;
}*/
int8 tag_icon = 0;
int32 tmp_id = 0;
if(faction_id && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_FACTION, faction_id, "", true)) > 0);
else if(IsGroundSpawn() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_GROUNDSPAWN, 1, "", true)) > 0);
else if((this->GetSpawnGroupID() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, 1, "", true)) > 0) ||
(!this->GetSpawnGroupID() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, 0, "", true)) > 0));
else if((this->GetRace() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, GetRace(), "", true)) > 0));
else if(((tmp_id = race_types_list.GetRaceType(GetModelType()) > 0) && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, tmp_id, "", true)) > 0));
else if(((tmp_id = race_types_list.GetRaceBaseType(GetModelType()) > 0) && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, tmp_id, "", true)) > 0));
else if(IsEntity() && (tag_icon = ((Entity*)this)->GetInfoStruct()->get_tag1()) > 0);
vis_packet->setDataByName("tag1", tag_icon);
if (IsPlayer())
vis_packet->setDataByName("player", 1);
if (version <= 561) {
vis_packet->setDataByName("targetable", appearance.targetable);
vis_packet->setDataByName("show_name", appearance.display_name);
vis_packet->setDataByName("attackable", appearance.attackable);
if(appearance.attackable == 1)
vis_packet->setDataByName("attackable_icon", 1);
if (IsPlayer()) {
if (((Player*)this)->IsGroupMember(player))
vis_packet->setDataByName("group_member", 1);
}
}
if (appearance.targetable == 1 || appearance.show_level == 1 || appearance.display_name == 1) {
if (!IsGroundSpawn()) {
int8 arrow_color = ARROW_COLOR_WHITE;
sint8 npc_con = player->GetFactions()->GetCon(faction_id);
if (IsPlayer() && !((Player*)this)->CanSeeInvis(player))
npc_con = 0;
else if (!IsPlayer() && IsEntity() && !((Entity*)this)->CanSeeInvis(player))
npc_con = 0;
if (appearance.attackable == 1)
arrow_color = player->GetArrowColor(GetLevel());
if (version <= 373) {
if (GetMerchantID() > 0)
arrow_color += 7;
else {
if (primary_command_list.size() > 0) {
int16 len = strlen(primary_command_list[0]->command.c_str());
if(len >= 4 && strncmp(primary_command_list[0]->command.c_str(), "bank", 4) == 0)
arrow_color += 14;
else if (len >= 4 && strncmp(primary_command_list[0]->command.c_str(), "hail", 4) == 0)
arrow_color += 21;
else if (len >= 6 && strncmp(primary_command_list[0]->command.c_str(), "attack", 6) == 0) {
if (arrow_color > 5)
arrow_color = 34;
else
arrow_color += 29;
}
}
}
}
if(IsNPC() && (((Entity*)this)->GetLockedNoLoot() == ENCOUNTER_STATE_BROKEN ||
(((Entity*)this)->GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED) ||
((Entity*)this)->GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED && !((NPC*)this)->Brain()->IsEntityInEncounter(player->GetID()))) {
vis_packet->setDataByName("arrow_color", ARROW_COLOR_GRAY);
}
else {
vis_packet->setDataByName("arrow_color", arrow_color);
}
if (appearance.attackable == 0 || IsPlayer() || IsBot() || (IsEntity() && ((Entity*)this)->GetOwner() &&
(((Entity*)this)->GetOwner()->IsPlayer() || ((Entity*)this)->GetOwner()->IsBot()))) {
vis_packet->setDataByName("locked_no_loot", 1);
}
else {
vis_packet->setDataByName("locked_no_loot", appearance.locked_no_loot);
}
if (player->GetArrowColor(GetLevel()) == ARROW_COLOR_GRAY)
if (npc_con == -4)
npc_con = -3;
vis_packet->setDataByName("npc_con", npc_con);
if (appearance.attackable == 1 && IsNPC() && (player->GetFactions()->GetCon(faction_id) <= -4 || ((NPC*)this)->Brain()->GetHate(player) > 1)) {
vis_packet->setDataByName("npc_hate", ((NPC*)this)->Brain()->GetHatePercentage(player));
vis_packet->setDataByName("show_difficulty_arrows", 1);
}
int8 quest_flag = player->CheckQuestFlag(this);
if (version < 1188 && quest_flag >= 16)
quest_flag = 1;
vis_packet->setDataByName("quest_flag", quest_flag);
if (player->HasQuestUpdateRequirement(this)) {
vis_packet->setDataByName("name_quest_icon", 1);
}
}
}
int8 vis_flags = 0;
if (MeetsSpawnAccessRequirements(player)) {
if (appearance.attackable == 1)
vis_flags += 64; //attackable icon
if (appearance.show_level == 1)
vis_flags += 32;
if (appearance.display_name == 1)
vis_flags += 16;
if (IsPlayer() || appearance.targetable == 1)
vis_flags += 4;
if (appearance.show_command_icon == 1)
vis_flags += 2;
if (this == player) {
//if (version <= 283) {
// vis_flags = 1;
//}
//else
vis_flags += 1;
}
}
else if (req_quests_override > 0)
{
//Check to see if there's an override value set
vis_flags = req_quests_override & 0xFF;
}
if (player->HasGMVision())
{
if ((vis_flags & 16) == 0 && appearance.display_name == 0)
vis_flags += 16;
if ((vis_flags & 4) == 0)
vis_flags += 4;
}
if (version < 561 && (vis_flags > 1 || appearance.display_hand_icon > 0) && MeetsSpawnAccessRequirements(player)) //interactable
vis_flags = 1;
else if(version >= 561 && (vis_flags > 1 || appearance.display_hand_icon > 0) && MeetsSpawnAccessRequirements(player))
vis_flags = vis_flags;
else if((req_quests_override & 256) > 0) {
if(vis_flags > 1)
vis_flags = 1;
}
else if(!player->HasGMVision()) {
vis_flags = 0;
}
if (MeetsSpawnAccessRequirements(player)) {
vis_packet->setDataByName("hand_flag", appearance.display_hand_icon);
}
else if ((req_quests_override & 256) > 0) {
vis_packet->setDataByName("hand_flag", 1);
}
vis_packet->setDataByName("vis_flags", vis_flags);
if ((version == 546 || version == 561) && GetMerchantID() > 0) {
vis_packet->setDataByName("guild", "<Merchant>");
}
}
void Spawn::InitializeFooterPacketData(Player* player, PacketStruct* footer) {
if (IsWidget()){
Widget* widget = (Widget*)this;
if (widget->GetMultiFloorLift()) {
footer->setDataByName("widget_x", widget->GetX());
footer->setDataByName("widget_y", widget->GetY());
footer->setDataByName("widget_z", widget->GetZ());
}
else {
footer->setDataByName("widget_x", widget->GetWidgetX());
footer->setDataByName("widget_y", widget->GetWidgetY());
footer->setDataByName("widget_z", widget->GetWidgetZ());
}
footer->setDataByName("widget_id", widget->GetWidgetID());
}
else if (IsSign()){
Sign* sign = (Sign*)this;
footer->setDataByName("widget_id", sign->GetWidgetID());
footer->setDataByName("widget_x", sign->GetWidgetX());
footer->setDataByName("widget_y", sign->GetWidgetY());
footer->setDataByName("widget_z", sign->GetWidgetZ());
int8 showSignText = 1;
if((HasQuestsRequired() || HasHistoryRequired()) && !MeetsSpawnAccessRequirements(player) && (req_quests_override & 512) > 0) {
showSignText = 0;
}
if (sign->GetSignTitle())
footer->setMediumStringByName("title", sign->GetSignTitle());
if (sign->GetSignDescription())
footer->setMediumStringByName("description", sign->GetSignDescription());
footer->setDataByName("sign_distance", sign->GetSignDistance());
footer->setDataByName("show", showSignText);
// in live we see that the language is set when the player does not have it, otherwise its left as 00's.
if(!player->HasLanguage(sign->GetLanguage())) {
footer->setDataByName("language", sign->GetLanguage());
}
}
if ( IsPlayer())
footer->setDataByName("is_player", 1);
if (strlen(appearance.name) < 1)
strncpy(appearance.name,to_string(GetID()).c_str(),128);
if(footer->GetVersion() > 561) {
footer->setMediumStringByName("name", appearance.name);
footer->setMediumStringByName("guild", appearance.sub_title);
footer->setMediumStringByName("prefix", appearance.prefix_title);
footer->setMediumStringByName("suffix", appearance.suffix_title);
footer->setMediumStringByName("last_name", appearance.last_name);
if (appearance.attackable == 0 && GetLevel() > 0)
footer->setDataByName("spawn_type", 1);
else if (appearance.attackable == 0)
footer->setDataByName("spawn_type", 6);
else
footer->setDataByName("spawn_type", 3);
}
else {
footer->setDataByName("guild", appearance.sub_title);
footer->setDataByName("prefix", appearance.prefix_title);
footer->setDataByName("suffix", appearance.suffix_title);
footer->setDataByName("last_name", appearance.last_name);
}
}
EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, int32 value, int16 offset2, int16 offset3, int16 offset4, int32 value2) {
// If spawn is NPC AND is pet && owner is a player && owner is the player passed to this function && player's char sheet pet id is 0
m_Update.writelock(__FUNCTION__, __LINE__);
int16 index = 0;
if ((index = player->GetIndexForSpawn(this)) > 0) {
player->SetSpawnMapIndex(this, index);
}
else {
index = player->SetSpawnMapAndIndex(this);
}
// Jabantiz - [Bug] Client Crash on Revive
if (player->GetIDWithPlayerSpawn(this) == 0) {
player->SetSpawnMap(this);
}
PacketStruct* header = player->GetSpawnHeaderStruct();
if (!header) {
LogWrite(SPAWN__ERROR, 0, "Spawn", "Spawn::spawn_serialize: GetSpawnHeaderStruct returned null!");
player->GetZone()->SetSpawnStructs(player->GetClient());
header = player->GetSpawnHeaderStruct();
if(!header) {
LogWrite(SPAWN__ERROR, 0, "Spawn", "Spawn::spawn_serialize: GetSpawnHeaderStruct still null likely to crash!");
return nullptr;
}
}
header->ResetData();
InitializeHeaderPacketData(player, header, index);
PacketStruct* footer = 0;
if (IsWidget())
footer = player->GetWidgetFooterStruct();
else if (IsSign())
footer = player->GetSignFooterStruct();
else if (version > 561)
footer = player->GetSpawnFooterStruct();
if (footer) {
footer->ResetData();
InitializeFooterPacketData(player, footer);
}
PacketStruct* vis_struct = player->GetSpawnVisStruct();
PacketStruct* info_struct = player->GetSpawnInfoStruct();
PacketStruct* pos_struct = player->GetSpawnPosStruct();
player->info_mutex.writelock(__FUNCTION__, __LINE__);
player->vis_mutex.writelock(__FUNCTION__, __LINE__);
player->pos_mutex.writelock(__FUNCTION__, __LINE__);
info_struct->ResetData();
InitializeInfoPacketData(player, info_struct);
vis_struct->ResetData();
InitializeVisPacketData(player, vis_struct);
pos_struct->ResetData();
InitializePosPacketData(player, pos_struct);
if (version <= 283) {
if (offset == 777) {
info_struct->setDataByName("name", "This is a really long name\n");
info_struct->setDataByName("last_name", "This is a really long LAST name\n");
info_struct->setDataByName("name_suffix", "This is a really long SUFFIX\n");
info_struct->setDataByName("name_prefix", "This is a really long PREFIX\n");
info_struct->setDataByName("unknown", "This is a really long UNKNOWN\n");
info_struct->setDataByName("second_suffix", "This is a really long 2nd SUFFIX\n");
}
//info_struct->setDataByName("unknown2", 3, 0); // level
//info_struct->setDataByName("unknown2", 1, 1); //unknown, two down arrows
//info_struct->setDataByName("unknown2", 1, 2); //unknown
//info_struct->setDataByName("unknown2", 1, 3); //unknown
//info_struct->setDataByName("unknown2", 1, 4); //solo fight
//info_struct->setDataByName("unknown2", 1, 5); //unknown
//info_struct->setDataByName("unknown2", 1, 6); //unknown
//info_struct->setDataByName("unknown2", 1, 7); //merchant
//info_struct->setDataByName("unknown2", 1, 8); //unknown
//info_struct->setDataByName("unknown2", 1, 9); //unknown
//info_struct->setDataByName("unknown2", 1, 10);
//112: 00 00 00 01 02 03 04 05 - 06 07 08 09 0A 00 00 00 | ................ merchant, x4
//112: 00 00 00 01 02 03 04 05 - 00 00 00 00 00 00 00 00 | ................ x4, epic, indifferent
//info_struct->setDataByName("body_size", 42);
//for(int i=0;i<8;i++)
// info_struct->setDataByName("persistent_spell_visuals", 329, i);
//info_struct->setDataByName("persistent_spell_levels", 20);
}
string* vis_data = vis_struct->serializeString();
string* pos_data = pos_struct->serializeString();
string* info_data = info_struct->serializeString();
int16 part2_size = pos_data->length() + vis_data->length() + info_data->length();
uchar* part2 = new uchar[part2_size];
player->AddSpawnPosPacketForXOR(id, (uchar*)pos_data->c_str(), pos_data->length());
player->AddSpawnVisPacketForXOR(id, (uchar*)vis_data->c_str(), vis_data->length());
player->AddSpawnInfoPacketForXOR(id, (uchar*)info_data->c_str(), info_data->length());
int32 vislength = vis_data->length();
int32 poslength = pos_data->length();
int32 infolength = info_data->length();
uchar* ptr = part2;
memcpy(ptr, pos_data->c_str(), pos_data->length());
ptr += pos_data->length();
memcpy(ptr, vis_data->c_str(), vis_data->length());
ptr += vis_data->length();
memcpy(ptr, info_data->c_str(), info_data->length());
player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
player->info_mutex.releasewritelock(__FUNCTION__, __LINE__);
player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
string* part1 = header->serializeString();
string* part3 = 0;
if (footer)
part3 = footer->serializeString();
//uchar blah7[] = {0x01,0x01,0x00,0x00,0x01,0x01,0x00,0x01,0x01,0x00 };
//uchar blah7[] = { 0x03,0x01,0x00,0x01,0x01,0x01,0x00,0x01,0x01,0x00 }; base
//uchar blah7[] = { 0x03,0x00,0x00,0x01,0x01,0x01,0x00,0x01,0x01,0x00 }; //no change
//uchar blah7[] = { 0x03,0x00,0x00,0x00,0x01,0x01,0x00,0x01,0x01,0x00 }; //blue instead of green
//uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x01,0x00 }; //no change
//uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00 }; //not selectable
//uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0x00 }; //no change
//uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00 }; //no longer have the two down arrows
//uchar blah7[] = { 0x01,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00 }; //arrow color green instead of white/gray
//memcpy(part2 + 77, blah7, sizeof(blah7));
//DumpPacket(part2, 885);
if (offset > 0) {
if (offset4 > 0 && offset4 >= offset3) {
uchar* ptr2 = (uchar*)part2;
ptr2 += offset3;
while (offset4 >= offset3) {
int8 jumpsize = 1;
if (value2 > 0xFFFF) {
memcpy(ptr2, (uchar*)&value2, 4);
jumpsize = 4;
}
else if (value2 > 0xFF) {
memcpy(ptr2, (uchar*)&value2, 2);
jumpsize = 2;
}
else {
memcpy(ptr2, (uchar*)&value2, 1);
jumpsize = 1;
}
ptr2 += jumpsize;
offset4 -= jumpsize;
}
}
if (offset2 > 0 && offset2 >= offset) {
uchar* ptr2 = (uchar*)part2;
ptr2 += offset;
while (offset2 >= offset) {
int8 jumpsize = 1;
if (value > 0xFFFF) {
memcpy(ptr2, (uchar*)&value, 4);
jumpsize = 4;
}
else if (value > 0xFF) {
memcpy(ptr2, (uchar*)&value, 2);
jumpsize = 2;
}
else
memcpy(ptr2, (uchar*)&value, 1);
ptr2 += jumpsize;
offset2 -= jumpsize;
}
}
else {
uchar* ptr2 = (uchar*)part2;
ptr2 += offset;
if (value > 0xFFFF)
memcpy(ptr2, (uchar*)&value, 4);
else if (value > 0xFF)
memcpy(ptr2, (uchar*)&value, 2);
else
memcpy(ptr2, (uchar*)&value, 1);
}
cout << "setting offset: " << offset << " to: " << value << endl;
}
//if (offset > 0)
// DumpPacket(part2, part2_size);
uchar tmp[4000];
bool reverse = (version > 373);
part2_size = Pack(tmp, part2, part2_size, 4000, version, reverse);
int32 total_size = part1->length() + part2_size + 3;
if (part3)
total_size += part3->length();
int32 final_packet_size = total_size + 1;
if (version > 373)
final_packet_size += 3;
else {
if (final_packet_size >= 255) {
final_packet_size += 2;
}
}
uchar* final_packet = new uchar[final_packet_size];
ptr = final_packet;
if (version <= 373) {
if ((final_packet_size - total_size) > 1) {
memcpy(ptr, &oversized_packet, sizeof(oversized_packet));
ptr += sizeof(oversized_packet);
memcpy(ptr, &total_size, 2);
ptr += 2;
}
else {
memcpy(ptr, &total_size, 1);
ptr += 1;
}
}
else {
memcpy(ptr, &total_size, sizeof(total_size));
ptr += sizeof(total_size);
}
memcpy(ptr, &oversized_packet, sizeof(oversized_packet));
ptr += sizeof(oversized_packet);
int16 opcode = 0;
if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {
if(IsWidget())
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateWidgetCmd);
else if(IsSign())
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateSignWidgetCmd);
else
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateGhostCmd);
}
memcpy(ptr, &opcode, sizeof(opcode));
ptr += sizeof(opcode);
memcpy(ptr, part1->c_str(), part1->length());
ptr += part1->length();
memcpy(ptr, tmp, part2_size);
ptr += part2_size;
if (part3)
memcpy(ptr, part3->c_str(), part3->length());
delete[] part2;
//printf("SpawnPacket %s (id: %u, index: %u) to %s: p1: %i, p2: %i, p3: %i, ts: %i. poslength: %u, infolength: %u, vislength: %u\n", GetName(), GetID(), index, player->GetName(), part1->length(), part2_size, (part3 != nullptr) ? part3->length() : -1, total_size, poslength, infolength, vislength);
EQ2Packet* ret = new EQ2Packet(OP_ClientCmdMsg, final_packet, final_packet_size);
delete[] final_packet;
m_Update.releasewritelock(__FUNCTION__, __LINE__);
return ret;
}
uchar* Spawn::spawn_info_changes(Player* player, int16 version, int16* info_packet_size){
int16 index = player->GetIndexForSpawn(this);
PacketStruct* packet = player->GetSpawnInfoStruct();
player->info_mutex.writelock(__FUNCTION__, __LINE__);
packet->ResetData();
InitializeInfoPacketData(player, packet);
string* data = packet->serializeString();
int32 size = data->length();
uchar* xor_info_packet = player->GetTempInfoPacketForXOR();
if (!xor_info_packet || size != player->GetTempInfoXorSize())
{
LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateInfoPacket: %i, %i", size, player->GetTempInfoXorSize());
safe_delete(xor_info_packet);
xor_info_packet = player->SetTempInfoPacketForXOR(size);
}
uchar* orig_packet = player->GetSpawnInfoPacketForXOR(id);
if(orig_packet){
memcpy(xor_info_packet, (uchar*)data->c_str(), size);
Encode(xor_info_packet, orig_packet, size);
}
bool changed = false;
for (int i = 0; i < size; ++i) {
if (xor_info_packet[i]) {
changed = true;
break;
}
}
if (!changed) {
player->info_mutex.releasewritelock(__FUNCTION__, __LINE__);
return nullptr;
}
uchar* tmp = new uchar[size + 1000];
size = Pack(tmp, xor_info_packet, size, size+1000, version);
player->info_mutex.releasewritelock(__FUNCTION__, __LINE__);
int32 orig_size = size;
size-=sizeof(int32);
size+=CheckOverLoadSize(index);
*info_packet_size = size + CheckOverLoadSize(size);
uchar* tmp2 = new uchar[*info_packet_size];
uchar* ptr = tmp2;
ptr += DoOverLoad(size, ptr);
ptr += DoOverLoad(index, ptr);
memcpy(ptr, tmp+sizeof(int32), orig_size - sizeof(int32));
delete[] tmp;
return tmp2;
}
uchar* Spawn::spawn_vis_changes(Player* player, int16 version, int16* vis_packet_size){
PacketStruct* vis_struct = player->GetSpawnVisStruct();
int16 index = player->GetIndexForSpawn(this);
player->vis_mutex.writelock(__FUNCTION__, __LINE__);
uchar* orig_packet = player->GetSpawnVisPacketForXOR(id);
vis_struct->ResetData();
InitializeVisPacketData(player, vis_struct);
string* data = vis_struct->serializeString();
int32 size = data->length();
uchar* xor_vis_packet = player->GetTempVisPacketForXOR();
if (!xor_vis_packet || size != player->GetTempVisXorSize())
{
LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateVisPacket: %i, %i", size, player->GetTempVisXorSize());
safe_delete(xor_vis_packet);
xor_vis_packet = player->SetTempVisPacketForXOR(size);
}
if(orig_packet){
memcpy(xor_vis_packet, (uchar*)data->c_str(), size);
Encode(xor_vis_packet, orig_packet, size);
}
bool changed = false;
for (int i = 0; i < size; ++i) {
if (xor_vis_packet[i]) {
changed = true;
break;
}
}
if (!changed) {
player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
return nullptr;
}
uchar* tmp = new uchar[size + 1000];
size = Pack(tmp, xor_vis_packet, size, size+1000, version);
player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
int32 orig_size = size;
size-=sizeof(int32);
size+=CheckOverLoadSize(index);
*vis_packet_size = size + CheckOverLoadSize(size);
uchar* tmp2 = new uchar[*vis_packet_size];
uchar* ptr = tmp2;
ptr += DoOverLoad(size, ptr);
ptr += DoOverLoad(index, ptr);
memcpy(ptr, tmp+sizeof(int32), orig_size - sizeof(int32));
delete[] tmp;
return tmp2;
}
uchar* Spawn::spawn_pos_changes(Player* player, int16 version, int16* pos_packet_size, bool override_) {
int16 index = player->GetIndexForSpawn(this);
PacketStruct* packet = player->GetSpawnPosStruct();
player->pos_mutex.writelock(__FUNCTION__, __LINE__);
uchar* orig_packet = player->GetSpawnPosPacketForXOR(id);
packet->ResetData();
InitializePosPacketData(player, packet);
string* data = packet->serializeString();
int32 size = data->length();
uchar* xor_pos_packet = player->GetTempPosPacketForXOR();
if (!xor_pos_packet || size != player->GetTempPosXorSize())
{
LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiatePosPacket: %i, %i", size, player->GetTempPosXorSize());
safe_delete(xor_pos_packet);
xor_pos_packet = player->SetTempPosPacketForXOR(size);
}
if(orig_packet){
memcpy(xor_pos_packet, (uchar*)data->c_str(), size);
Encode(xor_pos_packet, orig_packet, size);
}
bool changed = false;
for (int i = 0; i < size; ++i) {
if (xor_pos_packet[i]) {
changed = true;
break;
}
}
if (!changed && !override_) {
player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
return nullptr;
}
int16 newSize = size + 1000;
uchar* tmp = new uchar[newSize];
size = Pack(tmp, xor_pos_packet, size, newSize, version);
player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
int32 orig_size = size;
// Needed for CoE+ clients
if (version >= 1188)
size += 1;
if(IsPlayer() && version > 561)
size += 4;
size-=sizeof(int32);
size+=CheckOverLoadSize(index);
*pos_packet_size = size + CheckOverLoadSize(size);
uchar* tmp2 = new uchar[*pos_packet_size];
uchar* ptr = tmp2;
ptr += DoOverLoad(size, ptr);
ptr += DoOverLoad(index, ptr);
// extra byte in coe+ clients, 0 for NPC's 1 for Players
int8 x = 0;
if (IsPlayer() && version > 561) {
if (version >= 1188) {
// set x to 1 and add it to the packet
x = 1;
memcpy(ptr, &x, sizeof(int8));
ptr += sizeof(int8);
}
int32 now = Timer::GetCurrentTime2();
memcpy(ptr, &now, sizeof(int32));
ptr += sizeof(int32);
}
else if (version >= 1188) {
// add x to packet
memcpy(ptr, &x, sizeof(int8));
ptr += sizeof(int8);
}
memcpy(ptr, tmp+sizeof(int32), orig_size - sizeof(int32));
delete[] tmp;
return tmp2;
}
EQ2Packet* Spawn::player_position_update_packet(Player* player, int16 version, bool override_){
if(!player || player->IsPlayer() == false){
LogWrite(SPAWN__ERROR, 0, "Spawn", "Error: Called player_position_update_packet without player!");
return 0;
}
else if(IsPlayer() == false){
LogWrite(SPAWN__ERROR, 0, "Spawn", "Error: Called player_position_update_packet from spawn!");
return 0;
}
static const int8 info_size = 1;
static const int8 vis_size = 1;
int16 pos_packet_size = 0;
m_Update.writelock(__FUNCTION__, __LINE__);
uchar* pos_changes = spawn_pos_changes(player, version, &pos_packet_size, override_);
if (pos_changes == NULL)
{
m_Update.releasewritelock(__FUNCTION__, __LINE__);
return NULL;
}
int32 size = info_size + pos_packet_size + vis_size + 8;
if (version >= 374)
size += 3;
else if (version <= 373 && size >= 255) {//1 byte to 3 for overloaded val
size += 2;
}
static const int8 oversized = 255;
int16 opcode_val = 0;
if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {
opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
}
uchar* tmp = new uchar[size];
memset(tmp, 0, size);
uchar* ptr = tmp;
if (version >= 374) {
size -= 4;
memcpy(ptr, &size, sizeof(int32));
size += 4;
ptr += sizeof(int32);
}
else {
if (size >= 255) {
memcpy(ptr, &oversized, sizeof(int8));
ptr += sizeof(int8);
size -= 3;
memcpy(ptr, &size, sizeof(int16));
size += 3;
ptr += 3;
}
else {
size -= 1;
memcpy(ptr, &size, sizeof(int8));
ptr += sizeof(int8);
size += 1;
ptr += 1;
}
}
memcpy(ptr, &oversized, sizeof(int8));
ptr += sizeof(int8);
memcpy(ptr, &opcode_val, sizeof(int16));
ptr += sizeof(int16);
if (version <= 373) {
int32 timestamp = Timer::GetCurrentTime2();
memcpy(ptr, &timestamp, sizeof(int32));
}
ptr += sizeof(int32);
ptr += info_size;
memcpy(ptr, pos_changes, pos_packet_size);
delete[] pos_changes;
m_Update.releasewritelock(__FUNCTION__, __LINE__);
EQ2Packet* ret_packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size);
// DumpPacket(ret_packet);
delete[] tmp;
return ret_packet;
}
EQ2Packet* Spawn::spawn_update_packet(Player* player, int16 version, bool override_changes, bool override_vis_changes){
if(!player || player->IsPlayer() == false){
LogWrite(SPAWN__ERROR, 0, "Spawn", "Error: Called spawn_update_packet without player!");
return 0;
}
else if((IsPlayer() && info_changed == false && vis_changed == false) || (info_changed == false && vis_changed == false && position_changed == false)){
if(!override_changes && !override_vis_changes)
return 0;
}
uchar* info_changes = 0;
uchar* pos_changes = 0;
uchar* vis_changes = 0;
static const int8 oversized = 255;
int16 opcode_val = 0;
if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {
opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
}
//We need to lock these variables up to make this thread safe
m_Update.writelock(__FUNCTION__, __LINE__);
//These variables are set in the spawn_info_changes, pos and vis changes functions
int16 info_packet_size = 1;
int16 pos_packet_size = 1;
int16 vis_packet_size = 1;
if (info_changed || override_changes)
info_changes = spawn_info_changes(player, version, &info_packet_size);
if ((position_changed || override_changes) && IsPlayer() == false)
pos_changes = spawn_pos_changes(player, version, &pos_packet_size);
if (vis_changed || override_changes || override_vis_changes)
vis_changes = spawn_vis_changes(player, version, &vis_packet_size);
int32 size = info_packet_size + pos_packet_size + vis_packet_size + 8;
if (version >= 374)
size += 3;
else if (version <= 373 && size >= 255) {//1 byte to 3 for overloaded val
size += 2;
}
uchar* tmp = new uchar[size];
memset(tmp, 0, size);
uchar* ptr = tmp;
if (version >= 374) {
size -= 4;
memcpy(ptr, &size, sizeof(int32));
size += 4;
ptr += sizeof(int32);
}
else {
if (size >= 255) {
memcpy(ptr, &oversized, sizeof(int8));
ptr += sizeof(int8);
size -= 3;
memcpy(ptr, &size, sizeof(int16));
size += 3;
ptr += 3;
}
else {
size -= 1;
memcpy(ptr, &size, sizeof(int8));
ptr += sizeof(int8);
size += 1;
}
}
memcpy(ptr, &oversized, sizeof(int8));
ptr += sizeof(int8);
memcpy(ptr, &opcode_val, sizeof(int16));
ptr += sizeof(int16);
if (IsPlayer() == false || version <= 546) { //this isnt sent for player updates, it is sent on position update
//int32 time = Timer::GetCurrentTime2();
packet_num = Timer::GetCurrentTime2();
memcpy(ptr, &packet_num, sizeof(int32));
}
ptr += sizeof(int32);
if(info_changes)
memcpy(ptr, info_changes, info_packet_size);
ptr += info_packet_size;
if(pos_changes)
memcpy(ptr, pos_changes, pos_packet_size);
ptr += pos_packet_size;
if(vis_changes)
memcpy(ptr, vis_changes, vis_packet_size);
EQ2Packet* ret_packet = 0;
if(info_packet_size + pos_packet_size + vis_packet_size > 0)
ret_packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size);
delete[] tmp;
safe_delete_array(info_changes);
safe_delete_array(vis_changes);
safe_delete_array(pos_changes);
m_Update.releasewritelock(__FUNCTION__, __LINE__);
return ret_packet;
}
uchar* Spawn::spawn_info_changes_ex(Player* player, int16 version, int16* info_packet_size) {
int16 index = player->GetIndexForSpawn(this);
PacketStruct* packet = player->GetSpawnInfoStruct();
player->info_mutex.writelock(__FUNCTION__, __LINE__);
packet->ResetData();
InitializeInfoPacketData(player, packet);
string* data = packet->serializeString();
int32 size = data->length();
uchar* xor_info_packet = player->GetTempInfoPacketForXOR();
if (!xor_info_packet || size != player->GetTempInfoXorSize()) {
LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateInfoExPacket: %i, %i", size, player->GetTempInfoXorSize());
safe_delete(xor_info_packet);
xor_info_packet = player->SetTempInfoPacketForXOR(size);
}
uchar* orig_packet = player->GetSpawnInfoPacketForXOR(id);
if (orig_packet) {
//if (!IsPlayer() && this->EngagedInCombat())
//packet->PrintPacket();
memcpy(xor_info_packet, (uchar*)data->c_str(), size);
Encode(xor_info_packet, orig_packet, size);
}
bool changed = false;
for (int i = 0; i < size; ++i) {
if (xor_info_packet[i]) {
changed = true;
break;
}
}
if (!changed) {
player->info_mutex.releasewritelock(__FUNCTION__, __LINE__);
return nullptr;
}
uchar* tmp = new uchar[size + 1000];
size = Pack(tmp, xor_info_packet, size, size+1000, version);
player->info_mutex.releasewritelock(__FUNCTION__, __LINE__);
int32 orig_size = size;
size -= sizeof(int32);
size += CheckOverLoadSize(index);
*info_packet_size = size;
uchar* tmp2 = new uchar[size];
uchar* ptr = tmp2;
ptr += DoOverLoad(index, ptr);
memcpy(ptr, tmp + sizeof(int32), orig_size - sizeof(int32));
delete[] tmp;
return move(tmp2);
}
uchar* Spawn::spawn_vis_changes_ex(Player* player, int16 version, int16* vis_packet_size) {
PacketStruct* vis_struct = player->GetSpawnVisStruct();
int16 index = player->GetIndexForSpawn(this);
player->vis_mutex.writelock(__FUNCTION__, __LINE__);
uchar* orig_packet = player->GetSpawnVisPacketForXOR(id);
vis_struct->ResetData();
InitializeVisPacketData(player, vis_struct);
string* data = vis_struct->serializeString();
int32 size = data->length();
uchar* xor_vis_packet = player->GetTempVisPacketForXOR();
if (!xor_vis_packet || size != player->GetTempVisXorSize()) {
LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateVisExPacket: %i, %i", size, player->GetTempVisXorSize());
safe_delete(xor_vis_packet);
xor_vis_packet = player->SetTempVisPacketForXOR(size);
}
if (orig_packet) {
//if (!IsPlayer() && this->EngagedInCombat())
// vis_struct->PrintPacket();
memcpy(xor_vis_packet, (uchar*)data->c_str(), size);
Encode(xor_vis_packet, orig_packet, size);
}
bool changed = false;
for (int i = 0; i < size; ++i) {
if (xor_vis_packet[i]) {
changed = true;
break;
}
}
if (!changed) {
player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
return nullptr;
}
uchar* tmp = new uchar[size + 1000];
size = Pack(tmp, xor_vis_packet, size, size+1000, version);
player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
int32 orig_size = size;
size -= sizeof(int32);
size += CheckOverLoadSize(index);
*vis_packet_size = size;
uchar* tmp2 = new uchar[size];
uchar* ptr = tmp2;
ptr += DoOverLoad(index, ptr);
memcpy(ptr, tmp + sizeof(int32), orig_size - sizeof(int32));
delete[] tmp;
return move(tmp2);
}
uchar* Spawn::spawn_pos_changes_ex(Player* player, int16 version, int16* pos_packet_size) {
int16 index = player->GetIndexForSpawn(this);
PacketStruct* packet = player->GetSpawnPosStruct();
player->pos_mutex.writelock(__FUNCTION__, __LINE__);
uchar* orig_packet = player->GetSpawnPosPacketForXOR(id);
packet->ResetData();
InitializePosPacketData(player, packet);
string* data = packet->serializeString();
int32 size = data->length();
uchar* xor_pos_packet = player->GetTempPosPacketForXOR();
if (!xor_pos_packet || size != player->GetTempPosXorSize()) {
LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiatePosExPacket: %i, %i", size, player->GetTempPosXorSize());
safe_delete(xor_pos_packet);
xor_pos_packet = player->SetTempPosPacketForXOR(size);
}
if (orig_packet) {
//if (!IsPlayer() && this->EngagedInCombat())
// packet->PrintPacket();
memcpy(xor_pos_packet, (uchar*)data->c_str(), size);
Encode(xor_pos_packet, orig_packet, size);
}
bool changed = false;
for (int i = 0; i < size; ++i) {
if (xor_pos_packet[i]) {
changed = true;
break;
}
}
if (!changed && version > 561) {
player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
return nullptr;
}
int16 newSize = size + 1000;
uchar* tmp = new uchar[newSize];
size = Pack(tmp, xor_pos_packet, size, newSize, version);
player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
int32 orig_size = size;
if (version >= 1188) {
size += 1;
}
if (IsPlayer() && version > 561) {
size += 4;
}
size -= sizeof(int32);
size += CheckOverLoadSize(index);
*pos_packet_size = size;
uchar* tmp2 = new uchar[size];
uchar* ptr = tmp2;
ptr += DoOverLoad(index, ptr);
// extra byte in coe+ clients, 0 for NPC's 1 for Players
int8 x = 0;
if (version > 561) {
if (IsPlayer()) {
if (version >= 1188) {
x = 1;
memcpy(ptr, &x, sizeof(int8));
ptr += sizeof(int8);
}
int32 now = Timer::GetCurrentTime2();
memcpy(ptr, &now, sizeof(int32));
ptr += sizeof(int32);
}
else if (version >= 1188) {
memcpy(ptr, &x, sizeof(int8));
ptr += sizeof(int8);
}
}
memcpy(ptr, tmp + sizeof(int32), orig_size - sizeof(int32));
delete[] tmp;
return move(tmp2);
}
EQ2Packet* Spawn::serialize(Player* player, int16 version){
return 0;
}
Spawn* Spawn::GetTarget(){
Spawn* ret = 0;
if(!GetZone())
return 0;
// only attempt to get a spawn if we had a target stored
if (target != 0)
{
ret = GetZone()->GetSpawnByID(target);
if (!ret)
target = 0;
}
return ret;
}
void Spawn::SetTarget(Spawn* spawn){
SetInfo(&target, spawn ? spawn->GetID() : 0);
}
Spawn* Spawn::GetLastAttacker() {
Spawn* ret = 0;
ret = GetZone()->GetSpawnByID(last_attacker);
if (!ret)
last_attacker = 0;
return ret;
}
void Spawn::SetLastAttacker(Spawn* spawn){
last_attacker = spawn->GetID();
}
void Spawn::SetInvulnerable(bool val){
invulnerable = val;
}
bool Spawn::GetInvulnerable(){
return invulnerable;
}
bool Spawn::TakeDamage(int32 damage){
if(invulnerable)
return false;
if (IsEntity()) {
if (((Entity*)this)->IsMezzed())
((Entity*)this)->RemoveAllMezSpells();
if (damage == 0)
return true;
}
int32 hp = GetHP();
if(damage >= hp) {
SetHP(0);
if (IsPlayer()) {
((Player*)this)->InCombat(false);
((Player*)this)->SetRangeAttack(false);
GetZone()->TriggerCharSheetTimer(); // force char sheet updates now
}
}
else {
SetHP(hp - damage);
// if player flag the char sheet as changed so the ui updates properly
if (IsPlayer())
((Player*)this)->SetCharSheetChanged(true);
}
return true;
}
ZoneServer* Spawn::GetZone(){
return zone;
}
int32 Spawn::GetZoneID(){
return zone ? zone->GetZoneID() : 0;
}
void Spawn::SetZone(ZoneServer* in_zone, int32 version){
zone = in_zone;
if(in_zone)
{
region_map = world.GetRegionMap(std::string(in_zone->GetZoneFile()), version);
current_map = world.GetMap(std::string(in_zone->GetZoneFile()), version);
}
else
{
region_map = nullptr;
current_map = nullptr;
}
}
/*** HIT POINT ***/
void Spawn::SetHP(sint32 new_val, bool setUpdateFlags){
if(new_val < 0)
new_val = 0;
if(new_val == 0){
ClearRunningLocations();
CalculateRunningLocation(true);
if(IsEntity()) {
is_alive = false;
}
}
if(IsNPC() && new_val > 0 && !is_alive) {
is_alive = true;
}
else if(IsPlayer() && new_val > 0 && !is_alive) {
LogWrite(SPAWN__ERROR, 0, "Spawn", "Cannot change player HP > 0 while dead (%s)! Player must revive.", GetName());
return;
}
if(new_val > basic_info.max_hp)
SetInfo(&basic_info.max_hp, new_val, setUpdateFlags);
SetInfo(&basic_info.cur_hp, new_val, setUpdateFlags);
if(/*IsPlayer() &&*/ GetZone() && basic_info.cur_hp > 0 && basic_info.cur_hp < basic_info.max_hp)
GetZone()->AddDamagedSpawn(this);
SendGroupUpdate();
if ( IsPlayer() && new_val == 0 ) // fixes on death not showing hp update for players
((Player*)this)->SetCharSheetChanged(true);
if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() && ((NPC*)this)->GetOwner()->IsPlayer()) {
Player* player = (Player*)((NPC*)this)->GetOwner();
if (player->GetPet() && player->GetCharmedPet()) {
if (this == player->GetPet()) {
player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp);
player->SetCharSheetChanged(true);
}
}
else {
player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp);
player->SetCharSheetChanged(true);
}
}
}
void Spawn::SetTotalHP(sint32 new_val){
if(basic_info.hp_base == 0) {
SetTotalHPBase(new_val);
SetTotalHPBaseInstance(new_val);
}
SetInfo(&basic_info.max_hp, new_val);
if(GetZone() && basic_info.cur_hp > 0 && basic_info.cur_hp < basic_info.max_hp)
GetZone()->AddDamagedSpawn(this);
SendGroupUpdate();
if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() != nullptr && ((NPC*)this)->GetOwner()->IsPlayer()) {
Player* player = (Player*)((NPC*)this)->GetOwner();
if (basic_info.max_hp && player->GetPet() && player->GetCharmedPet()) {
if (this == player->GetPet()) {
player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp);
player->SetCharSheetChanged(true);
}
}
else if(basic_info.max_hp) {
player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp);
player->SetCharSheetChanged(true);
}
}
}
void Spawn::SetTotalHPBase(sint32 new_val)
{
SetInfo(&basic_info.hp_base, new_val);
if(GetZone() && basic_info.cur_hp > 0 && basic_info.cur_hp < basic_info.max_hp)
GetZone()->AddDamagedSpawn(this);
SendGroupUpdate();
}
void Spawn::SetTotalHPBaseInstance(sint32 new_val)
{
SetInfo(&basic_info.hp_base_instance, new_val);
}
sint32 Spawn::GetHP()
{
return basic_info.cur_hp;
}
sint32 Spawn::GetTotalHP()
{
return basic_info.max_hp;
}
sint32 Spawn::GetTotalHPBase()
{
return basic_info.hp_base;
}
sint32 Spawn::GetTotalHPBaseInstance()
{
return basic_info.hp_base_instance;
}
sint32 Spawn::GetTotalPowerBaseInstance()
{
return basic_info.power_base_instance;
}
/*** POWER ***/
void Spawn::SetPower(sint32 power, bool setUpdateFlags){
if(power < 0)
power = 0;
if(power > basic_info.max_power)
SetInfo(&basic_info.max_power, power, setUpdateFlags);
SetInfo(&basic_info.cur_power, power, setUpdateFlags);
if(/*IsPlayer() &&*/ GetZone() && basic_info.cur_power < basic_info.max_power)
GetZone()->AddDamagedSpawn(this);
SendGroupUpdate();
if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() != nullptr && ((NPC*)this)->GetOwner()->IsPlayer()) {
Player* player = (Player*)((NPC*)this)->GetOwner();
if (player->GetPet() && player->GetCharmedPet()) {
if (this == player->GetPet()) {
player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power);
player->SetCharSheetChanged(true);
}
}
else {
player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power);
player->SetCharSheetChanged(true);
}
}
}
void Spawn::SetTotalPower(sint32 new_val)
{
if(basic_info.power_base == 0) {
SetTotalPowerBase(new_val);
SetTotalPowerBaseInstance(new_val);
}
SetInfo(&basic_info.max_power, new_val);
if(GetZone() && basic_info.cur_power < basic_info.max_power)
GetZone()->AddDamagedSpawn(this);
SendGroupUpdate();
if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() != nullptr && ((NPC*)this)->GetOwner()->IsPlayer()) {
Player* player = (Player*)((NPC*)this)->GetOwner();
if (player->GetPet() && player->GetCharmedPet()) {
if (this == player->GetPet()) {
player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power);
player->SetCharSheetChanged(true);
}
}
else {
player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power);
player->SetCharSheetChanged(true);
}
}
}
void Spawn::SetTotalPowerBase(sint32 new_val)
{
SetInfo(&basic_info.power_base, new_val);
if(GetZone() && basic_info.cur_power < basic_info.max_power)
GetZone()->AddDamagedSpawn(this);
SendGroupUpdate();
}
void Spawn::SetTotalPowerBaseInstance(sint32 new_val)
{
SetInfo(&basic_info.power_base_instance, new_val);
}
sint32 Spawn::GetPower()
{
return basic_info.cur_power;
}
sint32 Spawn::GetTotalPower(){
return basic_info.max_power;
}
sint32 Spawn::GetTotalPowerBase()
{
return basic_info.power_base;
}
/*** SAVAGERY ***/
void Spawn::SetSavagery(sint32 savagery, bool setUpdateFlags)
{
/* JA: extremely limited functionality until we better understand Savagery */
if(savagery > basic_info.max_savagery)
SetInfo(&basic_info.max_savagery, savagery, setUpdateFlags);
SetInfo(&basic_info.cur_savagery, savagery, setUpdateFlags);
}
void Spawn::SetTotalSavagery(sint32 new_val)
{
/* JA: extremely limited functionality until we better understand Savagery */
if(basic_info.savagery_base == 0)
SetTotalSavageryBase(new_val);
SetInfo(&basic_info.max_savagery, new_val);
}
void Spawn::SetTotalSavageryBase(sint32 new_val){
SetInfo(&basic_info.savagery_base, new_val);
SendGroupUpdate();
}
sint32 Spawn::GetTotalSavagery()
{
return basic_info.max_savagery;
}
sint32 Spawn::GetSavagery()
{
return basic_info.cur_savagery;
}
/*** DISSONANCE ***/
void Spawn::SetDissonance(sint32 dissonance, bool setUpdateFlags)
{
/* JA: extremely limited functionality until we better understand Dissonance */
if(dissonance > basic_info.max_dissonance)
SetInfo(&basic_info.max_dissonance, dissonance, setUpdateFlags);
SetInfo(&basic_info.cur_dissonance, dissonance, setUpdateFlags);
}
void Spawn::SetTotalDissonance(sint32 new_val)
{
/* JA: extremely limited functionality until we better understand Dissonance */
if(basic_info.dissonance_base == 0)
SetTotalDissonanceBase(new_val);
SetInfo(&basic_info.max_dissonance, new_val);
}
void Spawn::SetTotalDissonanceBase(sint32 new_val)
{
SetInfo(&basic_info.dissonance_base, new_val);
SendGroupUpdate();
}
sint32 Spawn::GetTotalDissonance()
{
return basic_info.max_dissonance;
}
sint32 Spawn::GetDissonance()
{
return basic_info.cur_dissonance;
}
/* --< Alternate Advancement Points >-- */
void Spawn::SetAssignedAA(sint16 new_val)
{
SetInfo(&basic_info.assigned_aa, new_val);
}
void Spawn::SetUnassignedAA(sint16 new_val)
{
SetInfo(&basic_info.unassigned_aa, new_val);
}
void Spawn::SetTradeskillAA(sint16 new_val)
{
SetInfo(&basic_info.tradeskill_aa, new_val);
}
void Spawn::SetUnassignedTradeskillAA(sint16 new_val)
{
SetInfo(&basic_info.unassigned_tradeskill_aa, new_val);
}
void Spawn::SetPrestigeAA(sint16 new_val)
{
SetInfo(&basic_info.prestige_aa, new_val);
}
void Spawn::SetUnassignedPrestigeAA(sint16 new_val)
{
SetInfo(&basic_info.unassigned_prestige_aa, new_val);
}
void Spawn::SetTradeskillPrestigeAA(sint16 new_val)
{
SetInfo(&basic_info.tradeskill_prestige_aa, new_val);
}
void Spawn::SetUnassignedTradeskillPrestigeAA(sint16 new_val)
{
SetInfo(&basic_info.unassigned_tradeskill_prestige_aa, new_val);
}
void Spawn::SetAAXPRewards(int32 value)
{
SetInfo(&basic_info.aaxp_rewards, value, false);
}
sint16 Spawn::GetAssignedAA()
{
return basic_info.assigned_aa;
}
sint16 Spawn::GetUnassignedAA()
{
return basic_info.unassigned_aa;
}
sint16 Spawn::GetTradeskillAA()
{
return basic_info.tradeskill_aa;
}
sint16 Spawn::GetUnassignedTradeskillAA()
{
return basic_info.unassigned_tradeskill_aa;
}
sint16 Spawn::GetPrestigeAA()
{
return basic_info.prestige_aa;
}
sint16 Spawn::GetUnassignedPretigeAA()
{
return basic_info.unassigned_prestige_aa;
}
sint16 Spawn::GetTradeskillPrestigeAA()
{
return basic_info.tradeskill_prestige_aa;
}
sint16 Spawn::GetUnassignedTradeskillPrestigeAA()
{
return basic_info.unassigned_tradeskill_prestige_aa;
}
int32 Spawn::GetAAXPRewards()
{
return basic_info.aaxp_rewards;
}
float Spawn::GetDistance(float x1, float y1, float z1, float x2, float y2, float z2){
x1 = x1 - x2;
y1 = y1 - y2;
z1 = z1 - z2;
return sqrt(x1*x1 + y1*y1 + z1*z1);
}
float Spawn::GetDistance(float x, float y, float z, float radius, bool ignore_y) {
if (ignore_y)
return GetDistance(x, y, z, GetX(), y, GetZ()) - radius;
else
return GetDistance(x, y, z, GetX(), GetY(), GetZ()) - radius;
}
float Spawn::GetDistance(float x, float y, float z, bool ignore_y) {
return GetDistance(x, y, z, 0.0f, ignore_y);
}
float Spawn::GetDistance(Spawn* spawn, bool ignore_y, bool includeRadius){
float ret = 0;
if (spawn)
{
float radius = 0.0f;
if (includeRadius)
radius = CalculateRadius(spawn);
ret = GetDistance(spawn->GetX(), spawn->GetY(), spawn->GetZ(), radius, ignore_y);
}
// maybe distance against ourselves, in that case we want to nullify the radius check
if (ret < 0)
ret = 0.0f;
return ret;
}
float Spawn::GetDistance(Spawn* spawn, float x1, float y1, float z1, bool includeRadius) {
float ret = 0;
if (spawn)
{
float radius = 0.0f;
if (includeRadius)
radius = CalculateRadius(spawn);
ret = GetDistance(x1, y1, z1, spawn->GetX(), spawn->GetY(), spawn->GetZ()) - radius;
}
// maybe distance against ourselves, in that case we want to nullify the radius check
if (ret < 0)
ret = 0.0f;
return ret;
}
float Spawn::CalculateRadius(Spawn* target)
{
float srcRadius = short_to_float(appearance.pos.collision_radius);
if (target)
{
float targRadius = short_to_float(target->appearance.pos.collision_radius);
return (targRadius / 32.0f) + (srcRadius / 32.0f);
}
else
return (srcRadius / 32.0f);
}
int32 Spawn::GetRespawnTime(){
return respawn;
}
void Spawn::SetRespawnTime(int32 time){
respawn = time;
}
sint32 Spawn::GetRespawnOffsetLow(){
return respawn_offset_low;
}
void Spawn::SetRespawnOffsetLow(sint32 time){
respawn_offset_low = time;
}
sint32 Spawn::GetRespawnOffsetHigh(){
return respawn_offset_high;
}
void Spawn::SetRespawnOffsetHigh(sint32 time){
respawn_offset_high = time;
}
int32 Spawn::GetExpireOffsetTime(){
return expire_offset;
}
void Spawn::SetExpireOffsetTime(int32 time){
expire_offset = time;
}
int32 Spawn::GetSpawnLocationID(){
return spawn_location_id;
}
void Spawn::SetSpawnLocationID(int32 id){
spawn_location_id = id;
}
int32 Spawn::GetSpawnEntryID(){
return spawn_entry_id;
}
void Spawn::SetSpawnEntryID(int32 id){
spawn_entry_id = id;
}
int32 Spawn::GetSpawnLocationPlacementID(){
return spawn_location_spawns_id;
}
void Spawn::SetSpawnLocationPlacementID(int32 id){
spawn_location_spawns_id = id;
}
const char* Spawn::GetSpawnScript(){
if(spawn_script.length() > 0)
return spawn_script.c_str();
else
return 0;
}
void Spawn::SetSpawnScript(string name){
spawn_script = name;
}
void Spawn::SetPrimaryCommand(const char* name, const char* command, float distance){
EntityCommand* entity_command = CreateEntityCommand(name, distance, command, "", 0, 0);
if(primary_command_list.size() > 0 && primary_command_list[0]){
safe_delete(primary_command_list[0]);
primary_command_list[0] = entity_command;
}
else
primary_command_list.push_back(entity_command);
}
void Spawn::SetSecondaryCommands(vector<EntityCommand*>* commands){
if(commands && commands->size() > 0){
vector<EntityCommand*>::iterator itr;
if(secondary_command_list.size() > 0){
for(itr = secondary_command_list.begin(); itr != secondary_command_list.end(); itr++){
safe_delete(*itr);
}
secondary_command_list.clear();
}
EntityCommand* command = 0;
for(itr = commands->begin(); itr != commands->end(); itr++){
command = CreateEntityCommand(*itr);
secondary_command_list.push_back(command);
}
}
}
void Spawn::SetPrimaryCommands(vector<EntityCommand*>* commands){
if(commands && commands->size() > 0){
vector<EntityCommand*>::iterator itr;
if(primary_command_list.size() > 0){
for(itr = primary_command_list.begin(); itr != primary_command_list.end(); itr++){
safe_delete(*itr);
}
primary_command_list.clear();
}
EntityCommand* command = 0;
for(itr = commands->begin(); itr != commands->end(); itr++){
command = CreateEntityCommand(*itr);
primary_command_list.push_back(command);
}
}
}
EntityCommand* Spawn::FindEntityCommand(string command, bool primaryOnly) {
EntityCommand* entity_command = 0;
if (primary_command_list.size() > 0) {
vector<EntityCommand*>::iterator itr;
for (itr = primary_command_list.begin(); itr != primary_command_list.end(); itr++) {
if ((*itr)->command.compare(command) == 0) {
entity_command = *itr;
break;
}
}
}
if (primaryOnly)
return entity_command;
if (!entity_command && secondary_command_list.size() > 0) {
vector<EntityCommand*>::iterator itr;
for (itr = secondary_command_list.begin(); itr != secondary_command_list.end(); itr++) {
if ((*itr)->command == command) {
entity_command = *itr;
break;
}
}
}
return entity_command;
}
void Spawn::SetSizeOffset(int8 offset){
size_offset = offset;
}
int8 Spawn::GetSizeOffset(){
return size_offset;
}
void Spawn::SetMerchantID(int32 val){
merchant_id = val;
}
int32 Spawn::GetMerchantID(){
return merchant_id;
}
void Spawn::SetMerchantType(int8 val) {
merchant_type = val;
}
int8 Spawn::GetMerchantType() {
return merchant_type;
}
void Spawn::SetMerchantLevelRange(int32 minLvl, int32 maxLvl) {
merchant_min_level = minLvl;
merchant_max_level = maxLvl;
}
int32 Spawn::GetMerchantMinLevel() {
return merchant_min_level;
}
int32 Spawn::GetMerchantMaxLevel() {
return merchant_max_level;
}
bool Spawn::IsClientInMerchantLevelRange(Client* client, bool sendMessageIfDenied)
{
if (!client)
return false;
if (GetMerchantMinLevel() && client->GetPlayer()->GetLevel() < GetMerchantMinLevel())
{
client->Message(CHANNEL_COLOR_RED, "You are unable to interact with this merchant due to a minimum level %u allowed.", GetMerchantMinLevel());
return false;
}
else if (GetMerchantMaxLevel() && client->GetPlayer()->GetLevel() > GetMerchantMaxLevel())
{
client->Message(CHANNEL_COLOR_RED, "You are unable to interact with this merchant due to a maximum level %u allowed.", GetMerchantMaxLevel());
return false;
}
return true;
}
void Spawn::SetQuestsRequired(Spawn* new_spawn){
m_requiredQuests.writelock(__FUNCTION__, __LINE__);
if(required_quests.size() > 0){
map<int32, vector<int16>* >::iterator itr;
for(itr = required_quests.begin(); itr != required_quests.end(); itr++){
vector<int16>* quest_steps = itr->second;
for (int32 i = 0; i < quest_steps->size(); i++)
new_spawn->SetQuestsRequired(itr->first, quest_steps->at(i));
}
}
m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__);
}
void Spawn::SetQuestsRequired(int32 quest_id, int16 quest_step){
m_requiredQuests.writelock(__FUNCTION__, __LINE__);
if (required_quests.count(quest_id) == 0)
required_quests.insert(make_pair(quest_id, new vector<int16>));
else{
for (int32 i = 0; i < required_quests[quest_id]->size(); i++){
if (required_quests[quest_id]->at(i) == quest_step){
m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__);
return;
}
}
}
has_quests_required = true;
required_quests[quest_id]->push_back(quest_step);
m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__);
}
bool Spawn::HasQuestsRequired(){
return has_quests_required;
}
bool Spawn::HasHistoryRequired(){
return has_history_required;
}
void Spawn::SetRequiredHistory(int32 event_id, int32 value1, int32 value2){
LUAHistory* set_value = new LUAHistory();
set_value->Value = value1;
set_value->Value2 = value2;
set_value->SaveNeeded = false;
m_requiredHistory.writelock(__FUNCTION__, __LINE__);
if (required_history.count(event_id) == 0)
required_history.insert(make_pair(event_id, set_value));
else
{
LUAHistory* tmp_value = required_history[event_id];
required_history[event_id] = set_value;
safe_delete(tmp_value);
}
has_history_required = true;
m_requiredHistory.releasewritelock(__FUNCTION__, __LINE__);
}
void Spawn::SetTransporterID(int32 id){
transporter_id = id;
}
int32 Spawn::GetTransporterID(){
return transporter_id;
}
void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool bSpawnUpdate) {
int16 version = packet->GetVersion();
int32 new_grid_id = 0;
int32 new_widget_id = 0;
float new_y = 0.0f;
float ground_diff = 0.0f;
if(player->GetMap() != nullptr && player->GetMap()->IsMapLoaded())
{
m_GridMutex.writelock(__FUNCTION__, __LINE__);
std::map<int32,TimedGridData>::iterator itr = established_grid_id.find(version);
if ( itr == established_grid_id.end() || itr->second.npc_save || itr->second.timestamp <= (Timer::GetCurrentTime2()))
{
if(itr != established_grid_id.end() && itr->second.x == GetX() && itr->second.z == GetZ() && !itr->second.npc_save) {
itr->second.timestamp = Timer::GetCurrentTime2()+100;
itr->second.npc_save = false;
new_grid_id = itr->second.grid_id;
new_widget_id = itr->second.widget_id;
new_y = itr->second.offset_y;
if(player->GetMap() != GetMap()) {
ground_diff = sqrtf((GetY() - itr->second.zone_ground_y) * (GetY() - itr->second.zone_ground_y));
}
itr->second.timestamp = Timer::GetCurrentTime2()+1000;
}
else {
auto loc = glm::vec3(GetX(), GetZ(), GetY());
new_y = player->FindBestZ(loc, nullptr, &new_grid_id, &new_widget_id);
float zone_ground_y = new_y;
if(player->GetMap() != GetMap()) {
zone_ground_y = FindBestZ(loc, nullptr, nullptr, nullptr);
ground_diff = sqrtf((GetY() - zone_ground_y) * (GetY() - zone_ground_y));
}
TimedGridData data;
data.grid_id = new_grid_id;
data.widget_id = new_widget_id;
data.x = GetX();
data.y = GetY();
data.z = GetZ();
data.offset_y = new_y;
data.zone_ground_y = zone_ground_y;
data.npc_save = false;
data.timestamp = Timer::GetCurrentTime2()+1000;
established_grid_id.insert(make_pair(packet->GetVersion(), data));
}
}
else {
new_grid_id = itr->second.grid_id;
new_widget_id = itr->second.widget_id;
new_y = itr->second.offset_y;
ground_diff = itr->second.zone_ground_y - itr->second.offset_y;
}
m_GridMutex.releasewritelock(__FUNCTION__, __LINE__);
}
if(IsKnockedBack()) {
packet->setDataByName("pos_grid_id", 0);
}
else {
packet->setDataByName("pos_grid_id", new_grid_id != 0 ? new_grid_id : GetLocation());
}
bool include_heading = true;
if (IsWidget() && ((Widget*)this)->GetIncludeHeading() == false)
include_heading = false;
else if (IsSign() && ((Sign*)this)->GetIncludeHeading() == false)
include_heading = false;
if (include_heading){
packet->setDataByName("pos_heading1", appearance.pos.Dir1);
packet->setDataByName("pos_heading2", appearance.pos.Dir2);
}
if (size == 0)
size = 32;
if (version <= 910) {
packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32);
packet->setDataByName("pos_size", size > 0 ? size : 32);
}
else {
packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32);
if(!IsPlayer())
packet->setDataByName("pos_size", size > 0 ? (((float)size) / 32.0f) : 1.0f); // float not an integer
else
packet->setDataByName("pos_size", 1.0f);
// please do not remove! This makes it so NPCs for example do not resize large/small when you are in combat with them!
packet->setDataByName("pos_size_ratio", 1.0f);
}
packet->setDataByName("pos_state", appearance.pos.state);
bool include_location = true;
if (IsWidget() && ((Widget*)this)->GetIncludeLocation() == false)
include_location = false;
else if (IsSign() && ((Sign*)this)->GetIncludeLocation() == false)
include_location = false;
if (include_location){
if (IsWidget() && ((Widget*)this)->GetMultiFloorLift()) {
Widget* widget = (Widget*)this;
float x = appearance.pos.X - widget->GetWidgetX();
float y = appearance.pos.Y - widget->GetWidgetY();
float z = appearance.pos.Z - widget->GetWidgetZ();
packet->setDataByName("pos_x", x);
packet->setDataByName("pos_y", y);
packet->setDataByName("pos_z", z);
}
else {
packet->setDataByName("pos_x", appearance.pos.X);
float result_y = appearance.pos.Y;
if(!IsWidget() && !IsSign() && !IsObject() && !(IsFlyingCreature() || IsWaterCreature() || InWater())) {
result_y = new_y;
}
if(GetMap() != player->GetMap()) {
if(!IsWidget() && !IsSign() && !IsObject())
result_y = new_y;
if(IsFlyingCreature() || IsWaterCreature() || InWater()) {
result_y += ground_diff;
}
}
packet->setDataByName("pos_y", result_y);
packet->setDataByName("pos_z", appearance.pos.Z);
}
if (IsSign())
packet->setDataByName("pos_unknown6", 3, 2);
}
if (IsPlayer()) {
Skill* skill = ((Player*)this)->GetSkillByName("Swimming", false);
sint16 swim_modifier = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMinSpeed)->GetSInt16();
if(skill) {
sint16 max_val = 450;
if(skill->max_val > 0)
max_val = skill->max_val;
sint16 max_swim_mod = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMaxSpeed)->GetSInt16();
float diff = (float)(skill->current_val + ((Player*)this)->GetStat(ITEM_STAT_SWIMMING)) / (float)max_val;
sint16 diff_mod = (sint16)(float)max_swim_mod * diff;
if(diff_mod > max_swim_mod)
swim_modifier = max_swim_mod;
else if(diff_mod > swim_modifier)
swim_modifier = diff_mod;
}
packet->setDataByName("pos_swim_speed_modifier", swim_modifier);
packet->setDataByName("pos_x_velocity", TransformFromFloat(GetSpeedX(), 5));
packet->setDataByName("pos_y_velocity", TransformFromFloat(GetSpeedY(), 5));
packet->setDataByName("pos_z_velocity", TransformFromFloat(GetSpeedZ(), 5));
}
if (appearance.pos.X2 == 0 && appearance.pos.Y2 == 0 && appearance.pos.Z2 && (appearance.pos.X != 0 || appearance.pos.Y != 0 || appearance.pos.Z != 0)) {
appearance.pos.X2 = appearance.pos.X;
appearance.pos.Y2 = appearance.pos.Y;
appearance.pos.Z2 = appearance.pos.Z;
}
if (appearance.pos.X3 == 0 && appearance.pos.Y3 == 0 && appearance.pos.Z3 && (appearance.pos.X != 0 || appearance.pos.Y != 0 || appearance.pos.Z != 0)) {
appearance.pos.X3 = appearance.pos.X;
appearance.pos.Y3 = appearance.pos.Y;
appearance.pos.Z3 = appearance.pos.Z;
}
//Transform To/From Float bits (original client)
//pos_loc_offset[3]: 5
//pos_x_velocity: 5
//pos_y_velocity: 5
//pos_z_velocity: 5
//pos_heading1: 6
//pos_heading2: 6
//pos_speed: 8
//pos_dest_loc_offset[3]: 5
//pos_dest_loc_offset2[3]: 5
//pos_heading_speed: 5
//pos_move_type: 5 (speed_modifier)
//pos_swim_speed_modifier: 5
//pos_side_speed: 8
//pos_vert_speed: 8
//pos_requested_pitch: 6
//pos_requested_pitch_speed: 5
//pos_pitch: 6
//pos_collision_radius: 5
//pos_size: 5
//actor_stop_range: 5
//this is for original box client, destinations used to be offsets
if(version <= 373 || version > 561 || !IsPlayer()) {
if (appearance.pos.X2 != 0 || appearance.pos.Y2 != 0 || appearance.pos.Z2 != 0) {
packet->setDataByName("pos_dest_loc_offset", TransformFromFloat(appearance.pos.X2 - appearance.pos.X, 5));
packet->setDataByName("pos_dest_loc_offset", TransformFromFloat(appearance.pos.Y2 - appearance.pos.Y, 5), 1);
packet->setDataByName("pos_dest_loc_offset", TransformFromFloat(appearance.pos.Z2 - appearance.pos.Z, 5), 2);
}
if (appearance.pos.X3 != 0 || appearance.pos.Y3 != 0 || appearance.pos.Z3 != 0) {
packet->setDataByName("pos_dest_loc_offset2", TransformFromFloat(appearance.pos.X3 - appearance.pos.X, 5));
packet->setDataByName("pos_dest_loc_offset2", TransformFromFloat(appearance.pos.Y3 - appearance.pos.Y, 5), 1);
packet->setDataByName("pos_dest_loc_offset2", TransformFromFloat(appearance.pos.Z3 - appearance.pos.Z, 5), 2);
}
}
bool bSendSpeed = true;
// fixes lifts dropping back to the floor in zones like northfreeport when set as TransportSpawn (Some zones do not support multifloor lifts)
if (IsWidget() && (((Widget*)this)->GetMultiFloorLift() || (IsTransportSpawn()))) {
Widget* widget = (Widget*)this;
float x;
float y;
float z;
bool setCoords = false;
if(!widget->GetMultiFloorLift() && version >= 561) {
packet->setDataByName("pos_next_x", appearance.pos.X2);
packet->setDataByName("pos_next_y", appearance.pos.Y2);
packet->setDataByName("pos_next_z", appearance.pos.Z2);
packet->setDataByName("pos_x3", appearance.pos.X);
packet->setDataByName("pos_y3", appearance.pos.Y);
packet->setDataByName("pos_z3", appearance.pos.Z);
setCoords = true;
}
else if (IsRunning()){
x = appearance.pos.X2 - widget->GetWidgetX();
y = appearance.pos.Y2 - widget->GetWidgetY();
z = appearance.pos.Z2 - widget->GetWidgetZ();
}
else {
x = appearance.pos.X - widget->GetWidgetX();
y = appearance.pos.Y - widget->GetWidgetY();
z = appearance.pos.Z - widget->GetWidgetZ();
}
if(!setCoords) {
packet->setDataByName("pos_next_x", x);
packet->setDataByName("pos_next_y", y);
packet->setDataByName("pos_next_z", z);
packet->setDataByName("pos_x3", x);
packet->setDataByName("pos_y3", y);
packet->setDataByName("pos_z3", z);
}
}
else if(IsFlyingCreature() || IsWaterCreature() || InWater()) {
packet->setDataByName("pos_next_x", appearance.pos.X2);
packet->setDataByName("pos_next_y", (GetMap() != player->GetMap()) ? (ground_diff + new_y) : appearance.pos.Y2);
packet->setDataByName("pos_next_z", appearance.pos.Z2);
packet->setDataByName("pos_x3", appearance.pos.X);
packet->setDataByName("pos_y3", (GetMap() != player->GetMap()) ? (ground_diff + new_y) : appearance.pos.Y);
packet->setDataByName("pos_z3", appearance.pos.Z);
}
//If this is a spawn update or this spawn is currently moving we can send these values, otherwise set speed and next_xyz to 0
//This fixes the bug where spawns with movement scripts face south when initially spawning if they are at their target location.
else if (bSpawnUpdate || memcmp(&appearance.pos.X, &appearance.pos.X2, sizeof(float) * 3) != 0) {
packet->setDataByName("pos_next_x", appearance.pos.X2);
packet->setDataByName("pos_next_y", appearance.pos.Y2);
packet->setDataByName("pos_next_z", appearance.pos.Z2);
packet->setDataByName("pos_x3", appearance.pos.X3);
packet->setDataByName("pos_y3", appearance.pos.Y3);
packet->setDataByName("pos_z3", appearance.pos.Z3);
}
else
{
bSendSpeed = false;
}
//packet->setDataByName("pos_unknown2", 4, 2);
int16 speed_multiplier = rule_manager.GetGlobalRule(R_Spawn, SpeedMultiplier)->GetInt16(); // was 1280, 600 and now 300... investigating why
int8 movement_mode = 0;
if (IsPlayer()) {
Player* player = static_cast<Player*>(this);
sint16 pos_packet_speed = player->GetPosPacketSpeed() * speed_multiplier;
sint16 side_speed = player->GetSideSpeed() * speed_multiplier;
packet->setDataByName("pos_speed", pos_packet_speed);
packet->setDataByName("pos_side_speed", side_speed);
}
else if (bSendSpeed && (!IsNPC() || Alive()) && GetBaseSpeed() != 0.0f) {
sint16 side_speed = GetSpeed() * speed_multiplier;
packet->setDataByName("pos_speed", side_speed);
if(side_speed != 0 && ((IsWidget() && ((Widget*)this)->GetMultiFloorLift()) || IsTransportSpawn())) {
movement_mode = 2;
}
}
else if(((IsWidget() && ((Widget*)this)->GetMultiFloorLift()) || (!IsNPC() && version <= 561 && !IsRunning()))) {
movement_mode = 2;
}
if(IsFlyingCreature() || IsWaterCreature() || InWater()) {
movement_mode = 2;
}
if (IsNPC() || IsPlayer()) {
packet->setDataByName("pos_move_type", 25);
}
else if (IsWidget() || IsSign()) {
packet->setDataByName("pos_move_type", 11);
}
else if(IsGroundSpawn()) {
packet->setDataByName("pos_move_type", 16);
}
if (!IsPlayer()) { // has to be 2 or NPC's warp around when moving
packet->setDataByName("pos_movement_mode", movement_mode);
}
packet->setDataByName("face_actor_id", 0xFFFFFFFF);
packet->setDataByName("pos_pitch1", appearance.pos.Pitch1);
packet->setDataByName("pos_pitch2", appearance.pos.Pitch2);
packet->setDataByName("pos_roll", appearance.pos.Roll);
}
void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
int16 version = packet->GetVersion();
bool spawnHiddenFromClient = false;
int8 classicFlags = 0;
if(version <= 561 && IsPlayer()) {
InitializeFooterPacketData(spawn, packet);
}
// radius of 0 is always seen, -1 is never seen (unless items/spells override), larger than 0 is a defined radius to restrict visibility
sint32 radius = rule_manager.GetZoneRule(GetZoneID(), R_PVP, InvisPlayerDiscoveryRange)->GetSInt32();
if (radius != 0 && (Spawn*)spawn != this && this->IsPlayer() && !spawn->CanSeeInvis((Entity*)this))
spawnHiddenFromClient = true;
if (!spawnHiddenFromClient && (appearance.targetable == 1 || appearance.show_level == 1 || appearance.display_name == 1)) {
if (!IsObject() && !IsGroundSpawn() && !IsWidget() && !IsSign()) {
int8 percent = 0;
if (GetHP() > 0)
percent = (int8)(((float)GetHP() / GetTotalHP()) * 100);
if (version >= 373) {
if (percent < 100) {
packet->setDataByName("hp_remaining", 100 ^ percent);
}
else
packet->setDataByName("hp_remaining", 0);
}
else {
if (percent > 100)
percent = 100;
packet->setDataByName("hp_remaining", percent);
}
if (GetTotalPower() > 0) {
percent = (int8)(((float)GetPower() / GetTotalPower()) * 100);
if (percent > 0)
packet->setDataByName("power_percent", percent);
else
packet->setDataByName("power_percent", 0);
}
}
}
if (version <= 561) {
packet->setDataByName("name", appearance.name);
for (int8 i = 0; i < 8; i++)
packet->setDataByName("unknown1", 0xFF, i);
if (appearance.show_level == 0)
packet->setDataByName("hide_health", 1);
}
if (GetHP() <= 0 && IsEntity()) {
packet->setDataByName("corpse", 1);
if(HasLoot())
packet->setDataByName("loot_icon", 1);
}
if (!IsPlayer())
packet->setDataByName("npc", 1);
if (GetMerchantID() > 0)
packet->setDataByName("merchant", 1);
if(IsEntity() && ((Entity*)this)->GetInfoStruct()->get_size_mod() != 0.0f) {
float mod = ((Entity*)this)->GetInfoStruct()->get_size_mod(); // e.g., -0.25 or +0.25
//packet->setDataByName("temporary_scale", mod); //TODO: Understand what these mod values should be, anything we send makes the client shrink to nothing
}
packet->setDataByName("effective_level", IsEntity() && ((Entity*)this)->GetInfoStruct()->get_effective_level() != 0 ? (int8)((Entity*)this)->GetInfoStruct()->get_effective_level() : (int8)GetLevel());
packet->setDataByName("level", (int8)GetLevel());
packet->setDataByName("unknown4", (int8)GetLevel());
packet->setDataByName("difficulty", GetDifficulty()); //6);
packet->setDataByName("unknown6", 1);
packet->setDataByName("heroic_flag", appearance.heroic_flag);
packet->setDataByName("class", appearance.adventure_class);
int16 model_type = appearance.model_type;
if (GetIllusionModel() != 0) {
if (IsPlayer()) {
if (((Player*)this)->get_character_flag(CF_SHOW_ILLUSION)) {
model_type = GetIllusionModel();
}
}
else
model_type = GetIllusionModel();
}
int16 sogaModelType = appearance.soga_model_type;
if (spawnHiddenFromClient)
{
model_type = 0;
sogaModelType = 0;
}
if(version <= 373 && (model_type == 5864 || model_type == 5865 || model_type == 4015)) {
model_type = 4034;
}
else if(version <= 561 && model_type == 7039) { // goblin
model_type = 145;
}
packet->setDataByName("model_type", model_type);
if (appearance.soga_model_type == 0)
packet->setDataByName("soga_model_type", model_type);
else
packet->setDataByName("soga_model_type", sogaModelType);
int16 action_state = appearance.action_state;
if(IsEntity()) {
std::string actionState = "";
if (GetTempActionState() >= 0) {
action_state = GetTempActionState();
actionState = ((Entity*)this)->GetInfoStruct()->get_combat_action_state();
}
else {
actionState = ((Entity*)this)->GetInfoStruct()->get_action_state();
}
Client* client = spawn->GetClient();
if(IsEntity() && client) {
if(actionState.size() > 0) {
Emote* emote = visual_states.FindEmote(actionState, client->GetVersion());
if(emote != NULL)
action_state = emote->GetVisualState();
}
}
}
packet->setDataByName("action_state", action_state);
bool scaredOfPlayer = false;
if(IsCollector() && spawn->GetCollectionList()->HasCollectionsToHandIn())
packet->setDataByName("visual_state", VISUAL_STATE_COLLECTION_TURN_IN);
else if(!IsRunning() && IsNPC() && IsScaredByStrongPlayers() && spawn->GetArrowColor(GetLevel()) == ARROW_COLOR_GRAY &&
(GetDistance(spawn)) <= ((NPC*)this)->GetAggroRadius() && CheckLoS(spawn)) {
packet->setDataByName("visual_state", VISUAL_STATE_IDLE_AFRAID);
scaredOfPlayer = true;
}
else if (GetTempVisualState() >= 0)
packet->setDataByName("visual_state", GetTempVisualState());
else
packet->setDataByName("visual_state", appearance.visual_state);
if (IsNPC() && !IsPet() && !scaredOfPlayer)
{
if(((Entity*)this)->GetInfoStruct()->get_interaction_flag()) {
if(((Entity*)this)->GetInfoStruct()->get_interaction_flag() == 255) {
packet->setDataByName("interaction_flag", 0);
classicFlags += INFO_CLASSIC_FLAG_NOLOOK;
}
else {
packet->setDataByName("interaction_flag", ((Entity*)this)->GetInfoStruct()->get_interaction_flag()); //this makes NPCs head turn to look at you (12)
}
}
else {
packet->setDataByName("interaction_flag", 12); //turn head since no other value is set
}
}
packet->setDataByName("emote_state", appearance.emote_state);
packet->setDataByName("mood_state", appearance.mood_state);
packet->setDataByName("gender", appearance.gender);
packet->setDataByName("race", appearance.race);
if (IsEntity()) {
Entity* entity = ((Entity*)this);
packet->setDataByName("combat_voice", entity->GetCombatVoice());
packet->setDataByName("emote_voice", entity->GetEmoteVoice());
for (int i = 0; i < 25; i++) {
if (i == 2) { //don't send helm if hidden flag
if (IsPlayer()) {
if (((Player*)this)->get_character_flag(CF_HIDE_HELM)) {
packet->setDataByName("equipment_types", 0, i);
packet->setColorByName("equipment_colors", 0, i);
packet->setColorByName("equipment_highlights", 0, i);
continue;
}
}
if (IsBot()) {
if (!((Bot*)this)->ShowHelm) {
packet->setDataByName("equipment_types", 0, i);
packet->setColorByName("equipment_colors", 0, i);
packet->setColorByName("equipment_highlights", 0, i);
continue;
}
}
}
else if (i == 19) { //don't send cloak if hidden
if (IsPlayer()) {
if (!((Player*)this)->get_character_flag(CF_SHOW_CLOAK)) {
packet->setDataByName("equipment_types", 0, i);
packet->setColorByName("equipment_colors", 0, i);
packet->setColorByName("equipment_highlights", 0, i);
continue;
}
}
if (IsBot()) {
if (!((Bot*)this)->ShowCloak) {
packet->setDataByName("equipment_types", 0, i);
packet->setColorByName("equipment_colors", 0, i);
packet->setColorByName("equipment_highlights", 0, i);
continue;
}
}
}
entity->MEquipment.lock();
packet->setDataByName("equipment_types", entity->equipment.equip_id[i], i);
packet->setColorByName("equipment_colors", entity->equipment.color[i], i);
packet->setColorByName("equipment_highlights", entity->equipment.highlight[i], i);
entity->MEquipment.unlock();
}
packet->setDataByName("mount_type", entity->GetMount());
// find the visual flags
int8 vis_flag = 0;
//Invis + crouch flag check
if (entity->IsStealthed()) {
vis_flag += (INFO_VIS_FLAG_INVIS + INFO_VIS_FLAG_CROUCH);
classicFlags += INFO_VIS_FLAG_INVIS + INFO_VIS_FLAG_CROUCH;
}
//Invis flag check
else if (entity->IsInvis()) {
vis_flag += INFO_VIS_FLAG_INVIS;
classicFlags += INFO_VIS_FLAG_INVIS;
}
//Mount flag check
if (entity->GetMount() > 0) {
vis_flag += INFO_VIS_FLAG_MOUNTED;
if((classicFlags & INFO_CLASSIC_FLAG_NOLOOK) == 0)
classicFlags += INFO_CLASSIC_FLAG_NOLOOK; // serves as dual purpose for NPC's
}
//Hide hood check
bool vis_hide_hood = false;
if (IsPlayer() && ((Player*)this)->get_character_flag(CF_HIDE_HOOD)) {
if(version > 561) {
vis_flag += INFO_VIS_FLAG_HIDE_HOOD;
}
vis_hide_hood = true;
}
else if(IsPlayer()) {
classicFlags += INFO_CLASSIC_FLAG_SHOW_HOOD;
}
if(!vis_hide_hood && appearance.hide_hood && version > 561) {
vis_flag += INFO_VIS_FLAG_HIDE_HOOD;
}
if(spawn == this && IsPlayer() && ((Player*)this)->GetClient() && ((Player*)this)->GetClient()->GetOnAutoMount()) {
classicFlags += INFO_CLASSIC_FLAG_AUTOMOUNT;
}
if(version <= 561) {
packet->setDataByName("flags", classicFlags);
}
packet->setDataByName("visual_flag", vis_flag);
packet->setColorByName("mount_saddle_color", entity->GetMountSaddleColor());
packet->setColorByName("mount_color", entity->GetMountColor());
packet->setDataByName("hair_type_id", entity->features.hair_type);
packet->setDataByName("chest_type_id", entity->features.chest_type);
packet->setDataByName("wing_type_id", entity->features.wing_type);
packet->setDataByName("legs_type_id", entity->features.legs_type);
packet->setDataByName("soga_hair_type_id", entity->features.soga_hair_type);
packet->setDataByName("facial_hair_type_id", entity->features.hair_face_type);
packet->setDataByName("soga_facial_hair_type_id", entity->features.soga_hair_face_type);
for (int i = 0; i < 3; i++) {
packet->setDataByName("eye_type", entity->features.eye_type[i], i);
packet->setDataByName("ear_type", entity->features.ear_type[i], i);
packet->setDataByName("eye_brow_type", entity->features.eye_brow_type[i], i);
packet->setDataByName("cheek_type", entity->features.cheek_type[i], i);
packet->setDataByName("lip_type", entity->features.lip_type[i], i);
packet->setDataByName("chin_type", entity->features.chin_type[i], i);
packet->setDataByName("nose_type", entity->features.nose_type[i], i);
packet->setDataByName("soga_eye_type", entity->features.soga_eye_type[i], i);
packet->setDataByName("soga_ear_type", entity->features.soga_ear_type[i], i);
packet->setDataByName("soga_eye_brow_type", entity->features.soga_eye_brow_type[i], i);
packet->setDataByName("soga_cheek_type", entity->features.soga_cheek_type[i], i);
packet->setDataByName("soga_lip_type", entity->features.soga_lip_type[i], i);
packet->setDataByName("soga_chin_type", entity->features.soga_chin_type[i], i);
packet->setDataByName("soga_nose_type", entity->features.soga_nose_type[i], i);
}
packet->setColorByName("skin_color", entity->features.skin_color);
packet->setColorByName("model_color", entity->features.model_color);
packet->setColorByName("eye_color", entity->features.eye_color);
packet->setColorByName("hair_type_color", entity->features.hair_type_color);
packet->setColorByName("hair_type_highlight_color", entity->features.hair_type_highlight_color);
packet->setColorByName("hair_face_color", entity->features.hair_face_color);
packet->setColorByName("hair_face_highlight_color", entity->features.hair_face_highlight_color);
packet->setColorByName("hair_highlight", entity->features.hair_highlight_color);
packet->setColorByName("wing_color1", entity->features.wing_color1);
packet->setColorByName("wing_color2", entity->features.wing_color2);
packet->setColorByName("hair_color1", entity->features.hair_color1);
packet->setColorByName("hair_color2", entity->features.hair_color2);
packet->setColorByName("soga_skin_color", entity->features.soga_skin_color);
packet->setColorByName("soga_model_color", entity->features.soga_model_color);
packet->setColorByName("soga_eye_color", entity->features.soga_eye_color);
packet->setColorByName("soga_hair_color1", entity->features.soga_hair_color1);
packet->setColorByName("soga_hair_color2", entity->features.soga_hair_color2);
packet->setColorByName("soga_hair_type_color", entity->features.soga_hair_type_color);
packet->setColorByName("soga_hair_type_highlight_color", entity->features.soga_hair_type_highlight_color);
packet->setColorByName("soga_hair_face_color", entity->features.soga_hair_face_color);
packet->setColorByName("soga_hair_face_highlight_color", entity->features.soga_hair_face_highlight_color);
packet->setColorByName("soga_hair_highlight", entity->features.soga_hair_highlight_color);
packet->setDataByName("body_size", entity->features.body_size);
packet->setDataByName("body_age", entity->features.body_age);
packet->setDataByName("soga_body_size", entity->features.soga_body_size);
packet->setDataByName("soga_body_age", entity->features.soga_body_age);
}
else {
EQ2_Color empty;
empty.red = 255;
empty.blue = 255;
empty.green = 255;
packet->setColorByName("skin_color", empty);
packet->setColorByName("model_color", empty);
packet->setColorByName("eye_color", empty);
packet->setColorByName("soga_skin_color", empty);
packet->setColorByName("soga_model_color", empty);
packet->setColorByName("soga_eye_color", empty);
}
if (appearance.icon == 0) {
if (appearance.attackable == 1)
appearance.icon = 0;
else if (GetDifficulty() > 0)
appearance.icon = 4;
else
appearance.icon = 6;
}
// If Coe+ clients modify the values before we send
// if not then just send the value we have.
int8 temp_icon = appearance.icon;
//Check if we need to add the hand icon..
if ((temp_icon & 6) != 6 && appearance.display_hand_icon) {
temp_icon |= 6;
}
//Icon value 28 for boats, set this without modifying the value
if (version >= 1188 && temp_icon != 28) {
if ((temp_icon & 64) > 0) {
temp_icon -= 64; // remove the DoV value;
temp_icon += 128; // add the CoE value
}
if ((temp_icon & 32) > 0) {
temp_icon -= 32; // remove the DoV value;
temp_icon += 64; // add the CoE value
}
if ((temp_icon & 4) > 0) {
temp_icon -= 4; // remove DoV value
temp_icon += 8; // add the CoE icon
}
if ((temp_icon & 6) > 0) {
temp_icon -= 10; // remove DoV value
temp_icon += 12; // add the CoE icon
}
}
packet->setDataByName("icon", temp_icon);//appearance.icon);
if(GetPickupItemID()) {
if(version <= 546) {
packet->setDataByName("house_icon", 1);//appearance.icon);
}
}
int32 temp_activity_status = 0;
if (!Alive() && GetTotalHP() > 0 && !IsObject() && !IsGroundSpawn())
temp_activity_status = 1;
if(version >= 1188 && GetChestDropTime()) {
temp_activity_status = 0;
}
temp_activity_status += (IsNPC() || IsObject() || IsGroundSpawn()) ? 1 << 1 : 0;
if (version > 561) {
// Fix widget or sign having 'Play Legends of Norrath' or 'Tell' options in right click (client hard-coded entity commands)
if(IsWidget() || IsSign())
temp_activity_status = 2;
if (IsGroundSpawn() || GetShowHandIcon())
temp_activity_status += ACTIVITY_STATUS_INTERACTABLE_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_ROLEPLAYING) > 0)
temp_activity_status += ACTIVITY_STATUS_ROLEPLAYING_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_ANONYMOUS) > 0)
temp_activity_status += ACTIVITY_STATUS_ANONYMOUS_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_LINKDEAD) > 0)
temp_activity_status += ACTIVITY_STATUS_LINKDEAD_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_CAMPING) > 0)
temp_activity_status += ACTIVITY_STATUS_CAMPING_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0)
temp_activity_status += ACTIVITY_STATUS_LFG_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_LFW) > 0)
temp_activity_status += ACTIVITY_STATUS_LFW_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0)
temp_activity_status += ACTIVITY_STATUS_SOLID_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_GAINED) > 0)
temp_activity_status += ACTIVITY_STATUS_IMMUNITY_GAINED_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_REMAINING) > 0)
temp_activity_status += ACTIVITY_STATUS_IMMUNITY_REMAINING_1188;
if ((appearance.activity_status & ACTIVITY_STATUS_AFK) > 0)
temp_activity_status += ACTIVITY_STATUS_AFK_1188;
if (EngagedInCombat())
temp_activity_status += ACTIVITY_STATUS_INCOMBAT_1188;
// if this is either a boat or lift let the client be manipulated by the object
// doesn't work for DoF client version 546
if (appearance.icon == 28 || appearance.icon == 12 || IsTransportSpawn())
{
// there is some other flags that setting with a transport breaks their solidity/ability to properly transport
// thus we just consider the following flags for now as all necessary
temp_activity_status = ACTIVITY_STATUS_SOLID_1188;
temp_activity_status += ACTIVITY_STATUS_ISTRANSPORT_1188;
}
}
else if (version == 561) {
// Fix widget or sign having 'Play Legends of Norrath' or 'Tell' options in right click (client hard-coded entity commands)
if(IsWidget() || IsSign())
temp_activity_status = 2;
if (IsGroundSpawn() || GetShowHandIcon())
temp_activity_status += ACTIVITY_STATUS_INTERACTABLE_561;
if ((appearance.activity_status & ACTIVITY_STATUS_ROLEPLAYING) > 0)
temp_activity_status += ACTIVITY_STATUS_ROLEPLAYING_561;
if ((appearance.activity_status & ACTIVITY_STATUS_ANONYMOUS) > 0)
temp_activity_status += ACTIVITY_STATUS_ANONYMOUS_561;
if ((appearance.activity_status & ACTIVITY_STATUS_LINKDEAD) > 0)
temp_activity_status += ACTIVITY_STATUS_LINKDEAD_561;
if ((appearance.activity_status & ACTIVITY_STATUS_CAMPING) > 0)
temp_activity_status += ACTIVITY_STATUS_CAMPING_561;
if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0)
temp_activity_status += ACTIVITY_STATUS_LFG_561;
if ((appearance.activity_status & ACTIVITY_STATUS_LFW) > 0)
temp_activity_status += ACTIVITY_STATUS_LFW_561;
if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0)
temp_activity_status += ACTIVITY_STATUS_SOLID_561;
if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_GAINED) > 0)
temp_activity_status += ACTIVITY_STATUS_IMMUNITY_GAINED_561;
if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_REMAINING) > 0)
temp_activity_status += ACTIVITY_STATUS_IMMUNITY_REMAINING_561;
if ((appearance.activity_status & ACTIVITY_STATUS_AFK) > 0)
temp_activity_status += ACTIVITY_STATUS_AFK_561;
if (EngagedInCombat())
temp_activity_status += ACTIVITY_STATUS_INCOMBAT_561;
// if this is either a boat or lift let the client be manipulated by the object
// doesn't work for DoF client version 546
if (appearance.icon == 28 || appearance.icon == 12 || IsTransportSpawn())
{
// there is some other flags that setting with a transport breaks their solidity/ability to properly transport
// thus we just consider the following flags for now as all necessary
temp_activity_status = ACTIVITY_STATUS_SOLID_561;
temp_activity_status += ACTIVITY_STATUS_ISTRANSPORT_561;
}
}
else
{
temp_activity_status = appearance.activity_status;
if(IsNPC())
temp_activity_status = 0xFF;
// this only partially fixes lifts in classic 283 client if you move just as the lift starts to move
if (appearance.icon == 28 || appearance.icon == 12)
packet->setDataByName("is_transport", 1);
if (MeetsSpawnAccessRequirements(spawn))
packet->setDataByName("hand_icon", appearance.display_hand_icon);
else {
if ((req_quests_override & 256) > 0)
packet->setDataByName("hand_icon", 1);
}
if (IsPlayer()) {
if (((Player*)this)->get_character_flag(CF_AFK))
packet->setDataByName("afk", 1);
if ((appearance.activity_status & ACTIVITY_STATUS_ROLEPLAYING) > 0)
packet->setDataByName("roleplaying", 1);
if ((appearance.activity_status & ACTIVITY_STATUS_ANONYMOUS) > 0)
packet->setDataByName("anonymous", 1);
if ((appearance.activity_status & ACTIVITY_STATUS_LINKDEAD) > 0)
packet->setDataByName("linkdead", 1);
if ((appearance.activity_status & ACTIVITY_STATUS_CAMPING) > 0)
packet->setDataByName("camping", 1);
if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0)
packet->setDataByName("lfg", 1);
}
if (EngagedInCombat()) {
packet->setDataByName("auto_attack", 1);
}
if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0)
packet->setDataByName("solid_object", 1);
}
packet->setDataByName("activity_status", temp_activity_status); //appearance.activity_status);
// If player and player has a follow target
/* Jan 2021 Note!! Setting follow_target 0xFFFFFFFF has the result in causing strange behavior in swimming. Targetting a mob makes you focus down to its swim level, unable to swim above it.
** in the same respect the player will drop like a rock to the bottom of the ocean (seems to be when self set to that flag?)
** for now disabling this, if DoF needs it enabled for whatever reason then we need a version check added.
*/
if (IsPlayer()) {
if (((Player*)this)->GetFollowTarget())
packet->setDataByName("follow_target", version <= 561 ? (((Player*)this)->GetIDWithPlayerSpawn(((Player*)this)->GetFollowTarget())) : ((((Player*)this)->GetIDWithPlayerSpawn(((Player*)this)->GetFollowTarget()) * -1) - 1));
else if(version <= 561) {
packet->setDataByName("follow_target", 0xFFFFFFFF);
}
//else
// packet->setDataByName("follow_target", 0xFFFFFFFF);
}
//else if (!IsPet()) {
// packet->setDataByName("follow_target", 0xFFFFFFFF);
//}
// i think this is used in DoF as a way to make a client say they are in combat with this target and cannot camp, it forces you to stand up if self spawn sends this data
if ((version > 561 || spawn != this) && GetTarget() && GetTarget()->GetTargetable())
packet->setDataByName("target_id", ((spawn->GetIDWithPlayerSpawn(GetTarget()) * -1) - 1));
else
packet->setDataByName("target_id", 0xFFFFFFFF);
//Send spell effects for target window
if(IsEntity()){
InfoStruct* info = ((Entity*)this)->GetInfoStruct();
int8 i = 0;
int16 backdrop = 0;
int16 spell_icon = 0;
int32 spell_id = 0;
LuaSpell* spell = 0;
((Entity*)this)->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__);
while(i < 30){
//Change value of spell id for this packet if spell exists
spell_id = info->spell_effects[i].spell_id;
if(spell_id > 0)
spell_id = 0xFFFFFFFF - spell_id;
else
spell_id = 0;
packet->setSubstructDataByName("spell_effects", "spell_id", spell_id, i);
//Change value of spell icon for this packet if spell exists
spell_icon = info->spell_effects[i].icon;
if(spell_icon > 0){
if(!(spell_icon == 0xFFFF))
spell_icon = 0xFFFF - spell_icon;
}
else
spell_icon = 0;
packet->setSubstructDataByName("spell_effects", "spell_icon", spell_icon, i);
//Change backdrop values to match values in this packet
backdrop = info->spell_effects[i].icon_backdrop;
switch(backdrop){
case 312:
backdrop = 33080;
break;
case 313:
backdrop = 33081;
break;
case 314:
backdrop = 33082;
break;
case 315:
backdrop = 33083;
break;
case 316:
backdrop = 33084;
break;
case 317:
backdrop = 33085;
break;
case (318 || 319):
backdrop = 33086;
break;
default:
break;
}
packet->setSubstructDataByName("spell_effects", "spell_icon_backdrop", backdrop, i);
spell = info->spell_effects[i].spell;
if (spell)
packet->setSubstructDataByName("spell_effects", "spell_triggercount", spell->num_triggers, i);
i++;
}
((Entity*)this)->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__);
}
}
void Spawn::MoveToLocation(Spawn* spawn, float distance, bool immediate, bool mapped){
if(!spawn)
return;
SetRunningTo(spawn);
FaceTarget(spawn, false);
if (!IsPlayer() && distance > 0.0f)
{
if ((IsFlyingCreature() || IsWaterCreature() || InWater()) && CheckLoS(spawn))
{
if (immediate)
ClearRunningLocations();
AddRunningLocation(spawn->GetX(), spawn->GetY(), spawn->GetZ(), GetSpeed(), distance, true, true, "", true);
}
else if (/*!mapped && */GetZone())
{
GetZone()->movementMgr->NavigateTo((Entity*)this, spawn->GetX(), spawn->GetY(), spawn->GetZ());
last_grid_update = Timer::GetCurrentTime2();
}
else
{
if (immediate)
ClearRunningLocations();
AddRunningLocation(spawn->GetX(), spawn->GetY(), spawn->GetZ(), GetSpeed(), distance, true, true, "", mapped);
}
}
}
void Spawn::ProcessMovement(bool isSpawnListLocked){
CheckProximities();
if(IsBot() && ((Bot*)this)->IsCamping()) {
((Bot*)this)->Begin_Camp();
}
if(IsPlayer()){
//Check if player is riding a boat, if so update pos (boat's current location + XYZ offsets)
Player* player = ((Player*)this);
int32 boat_id = player->GetBoatSpawn();
Spawn* boat = 0;
if(boat_id > 0)
boat = GetZone()->GetSpawnByID(boat_id, isSpawnListLocked);
//TODO: MAYBE do this for real boats, not lifts... GetWidgetTypeNameByTypeID
/*if(boat){
SetX(boat->GetX() + player->GetBoatX());
SetY(boat->GetY() + player->GetBoatY());
SetZ(boat->GetZ() + player->GetBoatZ());
}*/
return;
}
if(IsKnockedBack()) {
if(CalculateSpawnProjectilePosition(GetX(), GetY(), GetZ()))
return; // being launched!
}
if(reset_movement) {
ResetMovement();
reset_movement = false;
}
if (forceMapCheck && GetZone() != nullptr && GetMap() != nullptr && GetMap()->IsMapLoaded())
{
FixZ(true);
forceMapCheck = false;
}
if (GetHP() <= 0 && !IsWidget())
return;
if (EngagedInCombat())
{
if(IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted())) {
SetAppearancePosition(GetX(),GetY(),GetZ());
if ( IsEntity() )
((Entity*)this)->SetSpeed(0.0f);
SetSpeed(0.0f);
position_changed = true;
changed = true;
GetZone()->AddChangedSpawn(this);
StopMovement();
return;
}
int locations = 0;
MMovementLocations.lock_shared();
if (movement_locations) {
locations = movement_locations->size();
}
MMovementLocations.unlock_shared();
if (locations < 1 && GetZone() && ((Entity*)this)->IsFeared())
{
CalculateNewFearpoint();
ValidateRunning(true, true);
}
}
Spawn* followTarget = GetZone()->GetSpawnByID(m_followTarget, isSpawnListLocked);
if (!followTarget && m_followTarget > 0)
m_followTarget = 0;
if (following && !IsPauseMovementTimerActive() && followTarget && !((Entity*)this)->IsFeared()) {
// Need to clear m_followTarget before the zoneserver deletes it
if (followTarget->GetHP() <= 0) {
followTarget = 0;
return;
}
if (!IsEntity() || (!((Entity*)this)->IsCasting() && !((Entity*)this)->IsMezzedOrStunned() && !((Entity*)this)->IsRooted())) {
if (GetBaseSpeed() > 0) {
CalculateRunningLocation();
}
else {
float speed = 4.0f;
if (IsEntity())
speed = ((Entity*)this)->GetMaxSpeed();
if (IsEntity())
((Entity*)this)->SetSpeed(speed);
SetSpeed(speed);
}
MovementLocation tmpLoc;
MovementLocation* loc = 0;
MMovementLocations.lock_shared();
if(movement_locations && movement_locations->size() > 0){
loc = movement_locations->front();
if(loc) {
tmpLoc.attackable = loc->attackable;
tmpLoc.gridid = loc->gridid;
tmpLoc.lua_function = string(loc->lua_function);
tmpLoc.mapped = loc->mapped;
tmpLoc.reset_hp_on_runback = loc->reset_hp_on_runback;
tmpLoc.speed = loc->speed;
tmpLoc.stage = loc->stage;
tmpLoc.x = loc->x;
tmpLoc.y = loc->y;
tmpLoc.z = loc->z;
tmpLoc.use_nav_path = loc->use_nav_path;
loc = &tmpLoc;
}
}
MMovementLocations.unlock_shared();
float dist = GetDistance(followTarget, true);
if ((!EngagedInCombat() && m_followDistance > 0 && dist <= m_followDistance) ||
(dist <= rule_manager.GetZoneRule(GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())) {
ClearRunningLocations();
CalculateRunningLocation(true);
}
else if (loc) {
float distance = GetDistance(followTarget, loc->x, loc->y, loc->z);
if ( (!EngagedInCombat() && m_followDistance > 0 && distance > m_followDistance) ||
( EngagedInCombat() && distance > rule_manager.GetZoneRule(GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())) {
float self_distance = GetDistance(this, loc->x, loc->y, loc->z);
if(dist < distance || dist < self_distance) {
ClearRunningLocations();
CalculateRunningLocation(true);
}
else {
MoveToLocation(followTarget, rule_manager.GetZoneRule(GetZoneID(), R_Combat, MaxCombatRange)->GetFloat(), true, loc->mapped);
CalculateRunningLocation();
}
}
}
else {
MoveToLocation(followTarget, rule_manager.GetZoneRule(GetZoneID(), R_Combat, MaxCombatRange)->GetFloat(), false);
CalculateRunningLocation();
}
}
}
bool movementCase = false;
// Movement loop is only for scripted paths
if(!EngagedInCombat() && !IsPauseMovementTimerActive() && !NeedsToResumeMovement() && (!IsNPC() || !((NPC*)this)->m_runningBack)){
MMovementLoop.writelock();
if(movement_loop.size() > 0 && movement_index < movement_loop.size())
{
movementCase = true;
// Get the target location
MovementData* data = movement_loop[movement_index];
// need to resume our movement
if(resume_movement){
MMovementLocations.lock();
if (movement_locations){
while (movement_locations->size()){
safe_delete(movement_locations->front());
movement_locations->pop_front();
}
movement_locations->clear();
}
MMovementLocations.unlock();
data = movement_loop[movement_index];
if(data)
{
if(IsEntity()) {
((Entity*)this)->SetSpeed(data->speed);
}
SetSpeed(data->speed);
if(data->use_movement_location_heading)
SetHeading(data->heading);
else if(!IsWidget())
FaceTarget(data->x, data->z);
// 0 delay at target location, need to set multiple locations
if(data->delay == 0 && movement_loop.size() > 0) {
int16 tmp_index = movement_index+1;
MovementData* data2 = 0;
if(tmp_index < movement_loop.size())
data2 = movement_loop[tmp_index];
else
data2 = movement_loop[0];
AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true, data->use_nav_path);
AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true, data2->use_nav_path);
}
// delay at target location, only need to set 1 location
else
AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, true, "", true, data->use_nav_path);
}
movement_start_time = 0;
resume_movement = false;
}
// If we are not moving or we have arrived at our destination
else if(!IsRunning() || (data && data->x == GetX() && data->y == GetY() && data->z == GetZ())){
// If we were moving remove the last running location (the point we just arrived at)
if(IsRunning()) {
RemoveRunningLocation();
}
// If this waypoint has a delay and we just arrived here (movement_start_time == 0)
if(data && data->delay > 0 && movement_start_time == 0){
// Set the current time
movement_start_time = Timer::GetCurrentTime2();
// If this waypoint had a lua function then call it
if(data->lua_function.length() > 0)
GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str());
int16 nextMove;
if ((int16)(movement_index + 1) < movement_loop.size())
nextMove = movement_index + 1;
else
nextMove = 0;
// Get the next target location
data = movement_loop[nextMove];
//Go ahead and face the next location
if(data) {
FaceTarget(data->x, data->z);
}
}
// If this waypoint has no delay or we have waited the required time (current time >= delay + movement_start_time)
else if(data && data->delay == 0 || (data && data->delay > 0 && Timer::GetCurrentTime2() >= (data->delay+movement_start_time))) {
// if no delay at this waypoint but a lua function for it then call the function
if(data->delay == 0 && data->lua_function.length() > 0)
GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str());
// since we ran a lua function make sure the movement loop is still alive and accurate
if(movement_loop.size() > 0)
{
// Advance the current movement loop index
if((int16)(movement_index+1) < movement_loop.size())
movement_index++;
else
movement_index = 0;
// Get the next target location
data = movement_loop[movement_index];
// set the speed for that location
SetSpeed(data->speed);
if(!IsWidget())
// turn towards the location
FaceTarget(data->x, data->z);
// If 0 delay at location get and set data for the point after it
if(data->delay == 0 && movement_loop.size() > 0){
MMovementLocations.lock();
if(movement_locations)
{
while (movement_locations->size()){
safe_delete(movement_locations->front());
movement_locations->pop_front();
}
// clear current target locations
movement_locations->clear();
}
MMovementLocations.unlock();
// get the data for the location after out new location
int16 tmp_index = movement_index+1;
MovementData* data2 = 0;
if(tmp_index < movement_loop.size())
data2 = movement_loop[tmp_index];
else
data2 = movement_loop[0];
// set the first location (adds it to movement_locations that we just cleared)
AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true, data->use_nav_path);
// set the location after that
AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true, data2->use_nav_path);
}
// there is a delay at the next location so we only need to set it
else {
AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, true, "", true, data->use_nav_path);
}
// reset this timer to 0 now that we are moving again
movement_start_time = 0;
}
}
}
// moving and not at target location yet
else if(GetBaseSpeed() > 0) {
CalculateRunningLocation();
}
// not moving, have a target location but not at it yet
else if (data) {
SetSpeed(data->speed);
AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, true, "", true, data->use_nav_path);
}
}
MMovementLoop.releasewritelock();
}
if (!movementCase && IsRunning() && !IsPauseMovementTimerActive()) {
CalculateRunningLocation();
//last_movement_update = Timer::GetCurrentTime2();
}
else if(movementCase)
{
//last_movement_update = Timer::GetCurrentTime2();
}
/*else if (IsNPC() && !IsRunning() && !EngagedInCombat() && ((NPC*)this)->GetRunbackLocation()) {
// Is an npc that is not moving and not engaged in combat but has a run back location set then clear the runback location
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Clear runback location for %s", GetName());
((NPC*)this)->ClearRunback();
resume_movement = true;
NeedsToResumeMovement(false);
}*/
}
void Spawn::ResetMovement(){
MMovementLoop.writelock();
vector<MovementData*>::iterator itr;
for(itr = movement_loop.begin(); itr != movement_loop.end(); itr++){
safe_delete(*itr);
}
movement_loop.clear();
movement_index = 0;
resume_movement = true;
ClearRunningLocations();
MMovementLoop.releasewritelock();
ValidateRunning(true, true);
}
void Spawn::AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function, float heading, bool include_heading, bool use_nav_path){
LogWrite(LUA__DEBUG, 5, "LUA", "AddMovementLocation: x: %.2f, y: %.2f, z: %.2f, speed: %.2f, delay: %i, lua: %s",
x, y, z, speed, delay, string(lua_function).c_str());
MovementData* data = new MovementData;
data->x = x;
data->y = y;
data->z = z;
data->speed = speed;
data->delay = delay*1000;
if(lua_function)
data->lua_function = string(lua_function);
data->heading = heading;
data->use_movement_location_heading = include_heading;
data->use_nav_path = use_nav_path;
MMovementLoop.lock();
movement_loop.push_back(data);
MMovementLoop.unlock();
}
bool Spawn::ValidateRunning(bool lockMovementLocation, bool lockMovementLoop) {
bool movement = false;
if(lockMovementLocation) {
MMovementLocations.lock_shared();
}
if(movement_locations) {
movement = movement_locations->size() > 0;
}
if(lockMovementLocation) {
MMovementLocations.unlock_shared();
}
if(IsPauseMovementTimerActive() || (IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted()))) {
is_running = false;
return false;
}
if(movement) {
is_running = true;
return true;
}
if(lockMovementLoop) {
MMovementLoop.lock();
}
movement = movement_loop.size() > 0;
if(movement) {
is_running = true;
}
else {
is_running = false;
}
if(lockMovementLoop) {
MMovementLoop.unlock();
}
return movement;
}
bool Spawn::IsRunning(){
return is_running;
}
void Spawn::RunToLocation(float x, float y, float z, float following_x, float following_y, float following_z){
if(IsPauseMovementTimerActive() || (IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted()))) {
is_running = false;
return;
}
if(!IsWidget() && (!EngagedInCombat() || GetDistance(GetTarget()) > rule_manager.GetZoneRule(GetZoneID(), R_Combat, MaxCombatRange)->GetFloat()))
FaceTarget(x, z);
SetPos(&appearance.pos.X2, x, false);
SetPos(&appearance.pos.Z2, z, false);
SetPos(&appearance.pos.Y2, y, false);
if(following_x == 0 && following_y == 0 && following_z == 0){
SetPos(&appearance.pos.X3, x, false);
SetPos(&appearance.pos.Z3, z, false);
SetPos(&appearance.pos.Y3, y, false);
}
else{
SetPos(&appearance.pos.X3, following_x, false);
SetPos(&appearance.pos.Y3, following_y, false);
SetPos(&appearance.pos.Z3, following_z, false);
}
is_running = true;
position_changed = true;
changed = true;
GetZone()->AddChangedSpawn(this);
}
MovementLocation* Spawn::GetCurrentRunningLocation(){
MovementLocation* ret = 0;
MMovementLocations.lock_shared();
if(movement_locations && movement_locations->size() > 0){
ret = movement_locations->front();
}
MMovementLocations.unlock_shared();
return ret;
}
MovementLocation* Spawn::GetLastRunningLocation(){
MovementLocation* ret = 0;
MMovementLocations.lock_shared();
if(movement_locations && movement_locations->size() > 0){
ret = movement_locations->back();
}
MMovementLocations.unlock_shared();
return ret;
}
void Spawn::AddRunningLocation(float x, float y, float z, float speed, float distance_away, bool attackable, bool finished_adding_locations, string lua_function, bool isMapped, bool useNavPath){
if(speed == 0)
return;
if ( IsEntity() )
((Entity*)this)->SetSpeed(speed);
else
this->SetSpeed(speed);
MovementLocation* current_location = 0;
float distance = GetDistance(x, y, z, distance_away != 0);
if(distance_away != 0){
distance -= distance_away;
x = x - (GetX() - x)*distance_away/distance;
z = z - (GetZ() - z)*distance_away/distance;
}
MMovementLocations.lock();
if(!movement_locations){
movement_locations = new deque<MovementLocation*>();
}
MMovementLocations.unlock();
MovementLocation* data = new MovementLocation;
data->mapped = isMapped;
data->x = x;
data->y = y;
data->z = z;
data->speed = speed;
data->attackable = attackable;
data->lua_function = lua_function;
data->gridid = 0; // used for runback defaults
data->reset_hp_on_runback = false;
data->use_nav_path = useNavPath;
MMovementLocations.lock_shared();
if(movement_locations->size() > 0)
current_location = movement_locations->back();
MMovementLocations.unlock_shared();
if(!current_location){
SetSpawnOrigX(GetX());
SetSpawnOrigY(GetY());
SetSpawnOrigZ(GetZ());
SetSpawnOrigHeading(GetHeading());
}
is_running = true;
MMovementLocations.lock();
movement_locations->push_back(data);
MMovementLocations.unlock();
if(!IsPauseMovementTimerActive() && finished_adding_locations){
MMovementLocations.lock();
current_location = movement_locations->front();
SetSpeed(current_location->speed);
if(movement_locations->size() > 1){
data = movement_locations->at(1);
RunToLocation(current_location->x, current_location->y, current_location->z, data->x, data->y, data->z);
}
else
RunToLocation(current_location->x, current_location->y, current_location->z, 0, 0, 0);
MMovementLocations.unlock();
}
}
bool Spawn::RemoveRunningLocation(){
bool ret = false;
MMovementLocations.lock();
if(movement_locations && movement_locations->size() > 0){
delete movement_locations->front();
movement_locations->pop_front();
ret = true;
}
MMovementLocations.unlock();
ValidateRunning(true, false);
return ret;
}
void Spawn::ClearRunningLocations(){
while(RemoveRunningLocation()){}
}
void Spawn::NewWaypointChange(MovementLocation* data){
if(data){
if(NeedsToResumeMovement()){
resume_movement = true;
NeedsToResumeMovement(false);
}
if(!data->attackable)
SetHeading(GetSpawnOrigHeading());
}
if (data && data->lua_function.length() > 0)
GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str());
RemoveRunningLocation();
}
bool Spawn::CalculateChange(){
bool remove_needed = false;
MovementLocation* data = 0;
MovementLocation tmpLoc;
MMovementLocations.lock_shared();
if(movement_locations){
if(movement_locations->size() > 0){
// Target location
data = movement_locations->front();
if(data) {
tmpLoc.attackable = data->attackable;
tmpLoc.gridid = data->gridid;
tmpLoc.lua_function = string(data->lua_function);
tmpLoc.mapped = data->mapped;
tmpLoc.reset_hp_on_runback = data->reset_hp_on_runback;
tmpLoc.speed = data->speed;
tmpLoc.stage = data->stage;
tmpLoc.x = data->x;
tmpLoc.y = data->y;
tmpLoc.z = data->z;
tmpLoc.use_nav_path = data->use_nav_path;
data = &tmpLoc;
}
// If no target or we are at the target location need to remove this point
if(!data)
remove_needed = true;
}
}
MMovementLocations.unlock_shared();
if(remove_needed){
NewWaypointChange(data);
}
else if(data){
// Speed is per second so we need a time_step (amount of time since the last update) to modify movement by
float time_step = (Timer::GetCurrentTime2() - last_movement_update) * 0.001; // * 0.001 is the same as / 1000, float muliplications is suppose to be faster though
// Ensure time_step has a minimum value to avoid excessively small movements
time_step = fmax(time_step, 0.01f); // Minimum time_step of 10ms to ensure meaningful movement
// Get current location
float nx = GetX();
float ny = GetY();
float nz = GetZ();
// Get Forward vecotr
float tar_vx = data->x - nx;
float tar_vy = data->y - ny;
float tar_vz = data->z - nz;
float len = sqrtf(tar_vx * tar_vx + tar_vy * tar_vy + tar_vz * tar_vz);
float lenNoY = sqrtf(tar_vx * tar_vx + tar_vz * tar_vz);
// Scale speed based on distance, ensuring smooth slow down
float base_speed = GetSpeed();
float snap_threshold = fmax(0.4f * base_speed, 0.5f); // Minimum threshold of 0.5 ensures entities can snap even at low speeds
float speed = fmin(base_speed, len / time_step); // Prevent overshooting by capping speed to remaining distance / time_step
// Normalize the forward vector and scale by adjusted speed
if (len > 0.0f) { // Avoid division by zero
tar_vx = (tar_vx / len) * speed * time_step;
tar_vy = (tar_vy / len) * speed * time_step;
tar_vz = (tar_vz / len) * speed * time_step;
}
// Distance less then 0.5 just set the npc to the target location
if (len <= snap_threshold || (!(IsFlyingCreature() || IsWaterCreature() || InWater()) && lenNoY <= snap_threshold)) {
SetX(data->x, false);
SetZ(data->z, false);
SetY(data->y, false, true);
remove_needed = true;
NewWaypointChange(data);
}
else {
SetX(nx + tar_vx, false);
SetZ(nz + tar_vz, false);
if(IsWidget()) {
SetY(ny + tar_vy, false, true);
}
else {
SetY(ny + tar_vy, false);
}
}
int32 newGrid = GetLocation();
if (GetMap() != nullptr) {
m_GridMutex.writelock(__FUNCTION__, __LINE__);
std::map<int32,TimedGridData>::iterator itr = established_grid_id.begin();
if ( itr == established_grid_id.end() || itr->second.timestamp <= (Timer::GetCurrentTime2()))
{
if(itr != established_grid_id.end() && itr->second.x == GetX() && itr->second.z == GetZ()) {
itr->second.timestamp = Timer::GetCurrentTime2()+1000;
itr->second.npc_save = true;
newGrid = itr->second.grid_id;
}
else {
auto loc = glm::vec3(GetX(), GetZ(), GetY());
float new_z = FindBestZ(loc, nullptr, &newGrid);
TimedGridData data;
data.grid_id = newGrid;
data.x = GetX();
data.y = GetY();
data.z = GetZ();
data.npc_save = true;
data.zone_ground_y = new_z;
data.offset_y = new_z;
data.timestamp = Timer::GetCurrentTime2()+1000;
established_grid_id.insert(make_pair(0, data));
}
}
else
newGrid = itr->second.grid_id;
m_GridMutex.releasewritelock(__FUNCTION__, __LINE__);
}
if ((!IsFlyingCreature() || IsTransportSpawn()) && newGrid != 0 && newGrid != GetLocation())
SetLocation(newGrid);
}
return remove_needed;
}
void Spawn::CalculateRunningLocation(bool stop){
bool pauseTimerEnabled = IsPauseMovementTimerActive();
if (!pauseTimerEnabled && !stop && (last_location_update + 10) > Timer::GetCurrentTime2())
return;
else if (!pauseTimerEnabled && !stop)
last_location_update = Timer::GetCurrentTime2();
bool continueElseIf = true;
bool removed = CalculateChange();
if (stop || pauseTimerEnabled) {
//following = false;
SetPos(&appearance.pos.X2, GetX(), false);
SetPos(&appearance.pos.Y2, GetY(), false);
SetPos(&appearance.pos.Z2, GetZ(), false);
SetPos(&appearance.pos.X3, GetX(), false);
SetPos(&appearance.pos.Y3, GetY(), false);
SetPos(&appearance.pos.Z3, GetZ(), false);
continueElseIf = false;
}
else if (removed) {
MMovementLocations.lock_shared();
if(movement_locations) {
if(movement_locations->size() > 0) {
MovementLocation* current_location = movement_locations->at(0);
if (movement_locations->size() > 1) {
MovementLocation* data = movement_locations->at(1);
if(current_location->use_nav_path) {
GetZone()->movementMgr->NavigateTo((Entity*)this, current_location->x, current_location->y, current_location->z);
}
else {
RunToLocation(current_location->x, current_location->y, current_location->z, data->x, data->y, data->z);
}
}
else {
if(current_location->use_nav_path) {
GetZone()->movementMgr->NavigateTo((Entity*)this, current_location->x, current_location->y, current_location->z);
}
else {
RunToLocation(current_location->x, current_location->y, current_location->z, 0, 0, 0);
}
}
continueElseIf = false;
}
}
MMovementLocations.unlock_shared();
}
if (continueElseIf && GetZone() && GetTarget() != NULL && EngagedInCombat())
{
if (GetDistance(GetTarget()) > rule_manager.GetZoneRule(GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())
{
if ((IsFlyingCreature() || IsWaterCreature() || InWater()) && CheckLoS(GetTarget()))
AddRunningLocation(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetSpeed(), 0, false);
else
GetZone()->movementMgr->NavigateTo((Entity*)this, GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ());
}
else
((Entity*)this)->HaltMovement();
}
else if (continueElseIf && !following)
{
position_changed = true;
changed = true;
GetZone()->AddChangedSpawn(this);
}
}
float Spawn::GetFaceTarget(float x, float z) {
float angle;
double diff_x = x - GetX();
double diff_z = z - GetZ();
if (diff_z == 0) {
if (diff_x > 0)
angle = 90;
else
angle = 270;
}
else
angle = ((atan(diff_x / diff_z)) * 180) / 3.14159265358979323846;
if(angle < 0)
angle = angle + 360;
else if(angle > -0.0000001 && angle < 0.0000001)
angle = 0;
else
angle = angle + 180;
if ((diff_x < 0 && diff_z != 0.0) || (diff_x == 0 && diff_z > 0.0))
angle = angle + 180;
if(angle > 360)
angle = angle - 360.0;
return angle;
}
void Spawn::FaceTarget(float x, float z){
float angle;
double diff_x = x - GetX();
double diff_z = z - GetZ();
if (diff_z == 0) {
if (diff_x > 0)
angle = 90;
else
angle = 270;
}
else
angle = ((atan(diff_x / diff_z)) * 180) / 3.14159265358979323846;
if(angle < 0)
angle = angle + 360;
else if(angle > -0.0000001 && angle < 0.0000001)
angle = 0;
else
angle = angle + 180;
if ((diff_x < 0 && diff_z != 0.0) || (diff_x == 0 && diff_z > 0.0))
angle = angle + 180;
if(angle > 360)
angle = angle - 360.0;
SetHeading(angle);
}
void Spawn::FaceTarget(Spawn* target, bool disable_action_state){
if(!target)
return;
FaceTarget(target->GetX(), target->GetZ());
if(GetHP() > 0 && target->IsPlayer() && !EngagedInCombat()){
if(!IsPet() && disable_action_state) {
if(IsNPC()) {
((NPC*)this)->StartRunback();
((NPC*)this)->PauseMovement(30000);
}
SetTempActionState(0);
}
}
}
bool Spawn::MeetsSpawnAccessRequirements(Player* player){
bool ret = false;
Quest* quest = 0;
//Check if we meet all quest requirements first..
m_requiredQuests.readlock(__FUNCTION__, __LINE__);
if (player && required_quests.size() > 0) {
map<int32, vector<int16>* >::iterator itr;
for (itr = required_quests.begin(); itr != required_quests.end(); itr++) {
player->AddQuestRequiredSpawn(this, itr->first);
vector<int16>* quest_steps = itr->second;
for (int32 i = 0; i < quest_steps->size(); i++) {
quest = player->GetQuest(itr->first);
if (req_quests_continued_access) {
if (quest) {
if (quest->GetQuestStepCompleted(quest_steps->at(i))) {
ret = true;
break;
}
}
else if (player->HasQuestBeenCompleted(itr->first)) {
ret = true;
break;
}
}
if (quest && quest->QuestStepIsActive(quest_steps->at(i))) {
ret = true;
break;
}
}
}
}
else
ret = true;
m_requiredQuests.releasereadlock(__FUNCTION__, __LINE__);
if (!ret)
return ret;
//Now check if the player meets all history requirements
m_requiredHistory.readlock(__FUNCTION__, __LINE__);
if (required_history.size() > 0){
map<int32, LUAHistory*>::iterator itr;
for (itr = required_history.begin(); itr != required_history.end(); itr++){
player->AddHistoryRequiredSpawn(this, itr->first);
LUAHistory* player_history = player->GetLUAHistory(itr->first);
if (player_history){
if (player_history->Value != itr->second->Value || player_history->Value2 != itr->second->Value2)
ret = false;
}
else
ret = false;
if (!ret)
break;
}
}
m_requiredHistory.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
vector<Spawn*>* Spawn::GetSpawnGroup(){
vector<Spawn*>* ret_list = 0;
if(spawn_group_list){
ret_list = new vector<Spawn*>();
if(MSpawnGroup)
MSpawnGroup->readlock(__FUNCTION__, __LINE__);
ret_list->insert(ret_list->begin(), spawn_group_list->begin(), spawn_group_list->end());
if(MSpawnGroup)
MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__);
}
return ret_list;
}
bool Spawn::HasSpawnGroup() {
return spawn_group_list && spawn_group_list->size() > 0;
}
bool Spawn::IsInSpawnGroup(Spawn* spawn) {
bool ret = false;
if (HasSpawnGroup() && spawn) {
vector<Spawn*>::iterator itr;
for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) {
if ((*itr) == spawn) {
ret = true;
break;
}
}
}
return ret;
}
Spawn* Spawn::IsSpawnGroupMembersAlive(Spawn* ignore_spawn, bool npc_only) {
Spawn* ret = nullptr;
if (MSpawnGroup && HasSpawnGroup()) {
MSpawnGroup->readlock(__FUNCTION__, __LINE__);
vector<Spawn*>::iterator itr;
for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) {
if ((*itr) != ignore_spawn && (*itr)->Alive() && (!npc_only || (npc_only && (*itr)->IsNPC()))) {
ret = (*itr);
break;
}
}
MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__);
}
return ret;
}
void Spawn::UpdateEncounterState(int8 new_state) {
if (MSpawnGroup && HasSpawnGroup()) {
MSpawnGroup->readlock(__FUNCTION__, __LINE__);
vector<Spawn*>::iterator itr;
for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) {
if ((*itr)->Alive() && (*itr)->IsNPC()) {
NPC* npc = (NPC*)(*itr);
(*itr)->SetLockedNoLoot(new_state);
if(new_state == ENCOUNTER_STATE_BROKEN && npc->Brain()) {
npc->Brain()->ClearEncounter();
}
}
}
MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__);
}
}
void Spawn::CheckEncounterState(Entity* victim, bool test_auto_lock) {
if (!IsEntity() || !victim->IsNPC())
return;
Entity* ent = ((Entity*)this);
if (victim->GetLockedNoLoot() == ENCOUNTER_STATE_AVAILABLE) {
if(IsInSpawnGroup(victim))
return; // can't aggro your own members
Entity* attacker = nullptr;
if (ent->GetOwner())
attacker = ent->GetOwner();
else
attacker = ent;
bool matchedAutoLock = false;
if (attacker->IsEntity() && ((Entity*)attacker)->GetGroupMemberInfo()) {
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
GroupMemberInfo* gmi = ((Entity*)attacker)->GetGroupMemberInfo();
if (gmi && gmi->group_id)
{
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group && ((group->GetGroupOptions()->group_lock_method && group->GetGroupOptions()->group_autolock == 1) || attacker->GetGroupMemberInfo()->leader))
{
matchedAutoLock = true;
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if (!member || member->GetZone() != attacker->GetZone())
continue;
if (member->IsEntity()) {
if (!member->GetInfoStruct()->get_engaged_encounter()) {
member->GetInfoStruct()->set_engaged_encounter(1);
}
if (((NPC*)victim)->Brain()) {
((NPC*)victim)->Brain()->AddHate(member, 0);
((NPC*)victim)->Brain()->AddToEncounter(member);
victim->AddTargetToEncounter(member);
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
}
else if (attacker->GetInfoStruct()->get_group_solo_autolock()|| attacker->GetInfoStruct()->get_group_autolock()) {
matchedAutoLock = true;
if (((NPC*)victim)->Brain()) {
((NPC*)victim)->Brain()->AddHate(attacker, 0);
((NPC*)victim)->Brain()->AddToEncounter(attacker);
victim->AddTargetToEncounter(attacker);
}
}
if (test_auto_lock && !matchedAutoLock) {
return;
}
if (!ent->GetInfoStruct()->get_engaged_encounter()) {
ent->GetInfoStruct()->set_engaged_encounter(1);
}
if (!attacker->GetInfoStruct()->get_engaged_encounter()) {
attacker->GetInfoStruct()->set_engaged_encounter(1);
}
int8 skip_loot_gray_mob_flag = rule_manager.GetZoneRule(GetZoneID(), R_Loot, SkipLootGrayMob)->GetInt8();
int8 difficulty = attacker->GetArrowColor(victim->GetLevel());
int8 new_enc_state = ENCOUNTER_STATE_AVAILABLE;
if (skip_loot_gray_mob_flag && difficulty == ARROW_COLOR_GRAY) {
if (!attacker->IsPlayer() && !attacker->IsBot()) {
new_enc_state = ENCOUNTER_STATE_BROKEN;
}
else {
new_enc_state = ENCOUNTER_STATE_OVERMATCHED;
}
}
else {
if (attacker->IsPlayer() || attacker->IsBot()) {
new_enc_state = ENCOUNTER_STATE_LOCKED;
}
else {
new_enc_state = ENCOUNTER_STATE_BROKEN;
}
}
victim->SetLockedNoLoot(new_enc_state);
victim->UpdateEncounterState(new_enc_state);
}
}
void Spawn::AddTargetToEncounter(Entity* entity) {
if (MSpawnGroup && HasSpawnGroup()) {
MSpawnGroup->readlock(__FUNCTION__, __LINE__);
vector<Spawn*>::iterator itr;
for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) {
if ((*itr) != this && (*itr)->Alive() && (*itr)->IsNPC()) {
((NPC*)(*itr))->Brain()->AddToEncounter(entity);
}
}
MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__);
}
}
void Spawn::AddSpawnToGroup(Spawn* spawn){
if(!spawn)
return;
if(!spawn_group_list){
spawn_group_list = new vector<Spawn*>();
spawn_group_list->push_back(this);
safe_delete(MSpawnGroup);
MSpawnGroup = new Mutex();
MSpawnGroup->SetName("Spawn::MSpawnGroup");
}
vector<Spawn*>::iterator itr;
MSpawnGroup->writelock(__FUNCTION__, __LINE__);
for(itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++){
if((*itr) == spawn){
MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__);
return;
}
}
spawn_group_list->push_back(spawn);
spawn->SetSpawnGroupList(spawn_group_list, MSpawnGroup);
MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__);
}
void Spawn::SetSpawnGroupList(vector<Spawn*>* list, Mutex* mutex){
spawn_group_list = list;
MSpawnGroup = mutex;
}
void Spawn::RemoveSpawnFromGroup(bool erase_all, bool ignore_death){
if(!ignore_death)
SetSpawnGroupID(0);
bool del = false;
if(MSpawnGroup){
MSpawnGroup->writelock(__FUNCTION__, __LINE__);
if(spawn_group_list){
vector<Spawn*>::iterator itr;
Spawn* spawn = 0;
if(spawn_group_list->size() == 1 && !ignore_death)
erase_all = true;
for(itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++){
spawn = *itr;
if (spawn) {
if(!erase_all){
if(spawn == this){
spawn_group_list->erase(itr);
MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__);
spawn_group_list = 0;
MSpawnGroup = 0;
return;
}
}
else{
if (spawn != this)
spawn->SetSpawnGroupList(0, 0);
}
}
}
if (erase_all)
spawn_group_list->clear();
del = (spawn_group_list->size() == 0);
}
MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__);
if (del){
safe_delete(MSpawnGroup);
safe_delete(spawn_group_list);
}
}
}
void Spawn::SetSpawnGroupID(int32 id){
m_SpawnMutex.writelock();
group_id = id;
m_SpawnMutex.releasewritelock();
}
int32 Spawn::GetSpawnGroupID(){
int32 groupid = 0;
m_SpawnMutex.readlock();
groupid = group_id;
m_SpawnMutex.releasereadlock();
return groupid;
}
void Spawn::AddChangedZoneSpawn(){
if(send_spawn_changes && GetZone())
GetZone()->AddChangedSpawn(this);
}
void Spawn::RemoveSpawnAccess(Spawn* spawn) {
if (allowed_access.count(spawn->GetID()) > 0) {
allowed_access.erase(spawn->GetID());
GetZone()->HidePrivateSpawn(this);
}
}
void Spawn::SetFollowTarget(Spawn* spawn, int32 follow_distance) {
if (spawn && spawn != this) {
m_followTarget = spawn->GetID();
m_followDistance = follow_distance;
}
else {
m_followTarget = 0;
if (following)
following = false;
m_followDistance = 0;
}
}
void Spawn::AddTempVariable(string var, string val) {
m_tempVariableTypes[var] = 5;
m_tempVariables[var] = val;
}
void Spawn::AddTempVariable(string var, Spawn* val) {
m_tempVariableTypes[var] = 1;
m_tempVariableSpawn[var] = val->GetID();
}
void Spawn::AddTempVariable(string var, ZoneServer* val) {
m_tempVariableTypes[var] = 2;
m_tempVariableZone[var] = val;
}
void Spawn::AddTempVariable(string var, Item* val) {
m_tempVariableTypes[var] = 3;
m_tempVariableItem[var] = val;
}
void Spawn::AddTempVariable(string var, Quest* val) {
m_tempVariableTypes[var] = 4;
m_tempVariableQuest[var] = val;
}
string Spawn::GetTempVariable(string var) {
string ret = "";
if (m_tempVariables.count(var) > 0)
ret = m_tempVariables[var];
return ret;
}
Spawn* Spawn::GetTempVariableSpawn(string var) {
Spawn* ret = 0;
if (m_tempVariableSpawn.count(var) > 0)
ret = GetZone()->GetSpawnByID(m_tempVariableSpawn[var]);
return ret;
}
ZoneServer* Spawn::GetTempVariableZone(string var) {
ZoneServer* ret = 0;
if (m_tempVariableZone.count(var) > 0)
ret = m_tempVariableZone[var];
return ret;
}
Item* Spawn::GetTempVariableItem(string var) {
Item* ret = 0;
if (m_tempVariableItem.count(var) > 0)
ret = m_tempVariableItem[var];
return ret;
}
Quest* Spawn::GetTempVariableQuest(string var) {
Quest* ret = 0;
if (m_tempVariableQuest.count(var) > 0)
ret = m_tempVariableQuest[var];
return ret;
}
int8 Spawn::GetTempVariableType(string var) {
int8 ret = 0;
if (m_tempVariableTypes.count(var) > 0)
ret = m_tempVariableTypes[var];
return ret;
}
void Spawn::DeleteTempVariable(string var) {
int8 type = GetTempVariableType(var);
switch (type) {
case 1:
m_tempVariableSpawn.erase(var);
break;
case 2:
m_tempVariableZone.erase(var);
break;
case 3:
m_tempVariableItem.erase(var);
break;
case 4:
m_tempVariableQuest.erase(var);
break;
case 5:
m_tempVariables.erase(var);
break;
}
m_tempVariableTypes.erase(var);
}
Spawn* Spawn::GetRunningTo() {
return GetZone()->GetSpawnByID(running_to);
}
Spawn* Spawn::GetFollowTarget() {
return GetZone()->GetSpawnByID(m_followTarget);
}
void Spawn::CopySpawnAppearance(Spawn* spawn){
if (!spawn)
return;
//This function copies the appearace of the provided spawn to this one
if (spawn->IsEntity() && IsEntity()){
memcpy(&((Entity*)this)->features, &((Entity*)spawn)->features, sizeof(CharFeatures));
memcpy(&((Entity*)this)->equipment, &((Entity*)spawn)->equipment, sizeof(EQ2_Equipment));
}
SetSize(spawn->GetSize());
SetModelType(spawn->GetModelType());
}
void Spawn::SetY(float y, bool updateFlags, bool disableYMapCheck)
{
SetPos(&appearance.pos.Y, y, updateFlags);
if (!disableYMapCheck)
FixZ();
}
float Spawn::FindDestGroundZ(glm::vec3 dest, float z_offset)
{
float best_z = BEST_Z_INVALID;
if (GetZone() != nullptr && GetMap() != nullptr)
{
dest.z += z_offset;
best_z = FindBestZ(dest, nullptr);
}
return best_z;
}
float Spawn::FindBestZ(glm::vec3 loc, glm::vec3* result, int32* new_grid_id, int32* new_widget_id) {
std::shared_lock lock(MIgnoredWidgets);
if(!GetMap())
return BEST_Z_INVALID;
float new_z = GetMap()->FindBestZ(loc, nullptr, &ignored_widgets, new_grid_id, new_widget_id);
return new_z;
}
float Spawn::GetFixedZ(const glm::vec3& destination, int32 z_find_offset) {
BenchTimer timer;
timer.reset();
float new_z = destination.z;
if (GetZone() != nullptr && GetMap() != nullptr) {
/* if (flymode == GravityBehavior::Flying)
return new_z;
*/
/* if (zone->HasWaterMap() && zone->watermap->InLiquid(glm::vec3(m_Position)))
return new_z;
*/
/*
* Any more than 5 in the offset makes NPC's hop/snap to ceiling in small corridors
*/
new_z = this->FindDestGroundZ(destination, z_find_offset);
if (new_z != BEST_Z_INVALID) {
if (new_z < -2000) {
new_z = GetY();
}
}
auto duration = timer.elapsed();
LogWrite(MAP__DEBUG, 0, "Map", "Mob::GetFixedZ() ([{%s}]) returned [{%f}] at [{%f}], [{%f}], [{%f}] - Took [{%f}]",
this->GetName(),
new_z,
destination.x,
destination.y,
destination.z,
duration);
}
return new_z;
}
void Spawn::FixZ(bool forceUpdate) {
if (!GetZone()) {
return;
}
/*
if (flymode == GravityBehavior::Flying) {
return;
}*/
/*
if (zone->watermap && zone->watermap->InLiquid(m_Position)) {
return;
}*/
// we do the inwater check here manually to avoid double calling for a Z coordinate
glm::vec3 current_loc(GetX(), GetZ(), GetY());
uint32 GridID = 0;
uint32 WidgetID = 0;
float new_z = GetY();
if(GetMap() != nullptr) {
new_z = FindBestZ(current_loc, nullptr, &GridID, &WidgetID);
if ((IsTransportSpawn() || !IsFlyingCreature()) && GridID != 0 && GridID != GetLocation()) {
LogWrite(PLAYER__DEBUG, 0, "Player", "%s left grid %u and entered grid %u", appearance.name, GetLocation(), GridID);
const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID());
if (zone_script && lua_interface) {
lua_interface->RunZoneScript(zone_script, "leave_location", GetZone(), this, GetLocation());
}
SetLocation(GridID);
if (zone_script && lua_interface) {
lua_interface->RunZoneScript(zone_script, "enter_location", GetZone(), this, GridID);
}
}
trigger_widget_id = WidgetID;
}
// no need to go any further for players, flying creatures or objects, just needed the grid id set
if (IsPlayer() || IsFlyingCreature() || IsObject()) {
return;
}
if ( region_map != nullptr )
{
glm::vec3 targPos(GetX(), GetY(), GetZ());
if(region_map->InWater(targPos, GetLocation()))
return;
}
if (new_z == GetY())
return;
if ((new_z > -2000) && new_z != BEST_Z_INVALID) {
SetY(new_z, forceUpdate, true);
}
else {
LogWrite(MAP__DEBUG, 0, "Map", "[{%s}] is failing to find Z [{%f}]", this->GetName(), std::abs(GetY() - new_z));
}
}
bool Spawn::CheckLoS(Spawn* target)
{
float radiusSrc = 2.0f;
float radiusTarg = 2.0f;
glm::vec3 targpos(target->GetX(), target->GetZ(), target->GetY()+radiusTarg);
glm::vec3 pos(GetX(), GetZ(), GetY()+radiusSrc);
return CheckLoS(pos, targpos);
}
bool Spawn::CheckLoS(glm::vec3 myloc, glm::vec3 oloc)
{
bool res = false;
ZoneServer* zone = GetZone();
if (zone == NULL || GetMap() == NULL || !GetMap()->IsMapLoaded())
return true;
else {
MIgnoredWidgets.lock_shared();
res = GetMap()->CheckLoS(myloc, oloc, &ignored_widgets);
MIgnoredWidgets.unlock_shared();
}
return res;
}
void Spawn::CalculateNewFearpoint()
{
if (GetZone() && GetZone()->pathing) {
auto Node = zone->pathing->GetRandomLocation(glm::vec3(GetX(), GetZ(), GetY()));
if (Node.x != 0.0f || Node.y != 0.0f || Node.z != 0.0f) {
AddRunningLocation(Node.x, Node.y, Node.z, GetSpeed(), 0, true, true, "", true);
}
}
}
Item* Spawn::LootItem(int32 id) {
Item* ret = 0;
vector<Item*>::iterator itr;
MLootItems.lock();
for (itr = loot_items.begin(); itr != loot_items.end(); itr++) {
if ((*itr)->details.item_id == id) {
ret = *itr;
loot_items.erase(itr);
break;
}
}
MLootItems.unlock();
return ret;
}
void Spawn::TransferLoot(Spawn* spawn) {
if(spawn == this || spawn == nullptr)
return; // mmm no
vector<Item*>::iterator itr;
MLootItems.lock();
for (itr = loot_items.begin(); itr != loot_items.end();) {
if (!(*itr)->IsBodyDrop()) {
spawn->AddLootItem(*itr);
itr = loot_items.erase(itr);
}
else {
itr++;
}
}
MLootItems.unlock();
}
int32 Spawn::GetLootItemID() {
int32 ret = 0;
vector<Item*>::iterator itr;
MLootItems.lock();
for (itr = loot_items.begin(); itr != loot_items.end(); itr++) {
ret = (*itr)->details.item_id;
break;
}
MLootItems.unlock();
return ret;
}
void Spawn::GetLootItemsList(std::vector<int32>* out_entries) {
if(!out_entries)
return;
vector<Item*>::iterator itr;
for (itr = loot_items.begin(); itr != loot_items.end(); itr++) {
out_entries->push_back((*itr)->details.item_id);
}
}
bool Spawn::HasLootItemID(int32 id) {
bool ret = false;
vector<Item*>::iterator itr;
MLootItems.readlock(__FUNCTION__, __LINE__);
for (itr = loot_items.begin(); itr != loot_items.end(); itr++) {
if ((*itr)->details.item_id == id) {
ret = true;
break;
}
}
MLootItems.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
void Spawn::CheckProximities()
{
if (!has_spawn_proximities)
return;
if (spawn_proximities.size() > 0)
{
MutexList<SpawnProximity*>::iterator itr = spawn_proximities.begin();
while (itr.Next()) {
SpawnProximity* prox = itr.value;
map<int32, bool>::iterator spawnsItr;
for (spawnsItr = prox->spawns_in_proximity.begin(); spawnsItr != prox->spawns_in_proximity.end(); spawnsItr++) {
Spawn* tmpSpawn = 0;
if (spawnsItr->first &&
((prox->spawn_type == SPAWNPROXIMITY_DATABASE_ID && (tmpSpawn = GetZone()->GetSpawnByDatabaseID(spawnsItr->first)) != 0) ||
(prox->spawn_type == SPAWNPROXIMITY_LOCATION_ID && (tmpSpawn = GetZone()->GetSpawnByLocationID(spawnsItr->first)) != 0)))
{
if (!spawnsItr->second && tmpSpawn->GetDistance(this) <= prox->distance)
{
if (prox->in_range_lua_function.size() > 0)
GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, tmpSpawn, prox->in_range_lua_function.c_str());
spawnsItr->second = true;
}
else if (spawnsItr->second && tmpSpawn->GetDistance(this) > prox->distance)
{
if (prox->leaving_range_lua_function.size() > 0)
GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, tmpSpawn, prox->leaving_range_lua_function.c_str());
spawnsItr->second = false;
}
}
}
}
}
}
void Spawn::AddSpawnToProximity(int32 spawnValue, SpawnProximityType type)
{
if (!has_spawn_proximities)
return;
if (spawn_proximities.size() > 0)
{
MutexList<SpawnProximity*>::iterator itr = spawn_proximities.begin();
while (itr.Next()) {
SpawnProximity* prox = itr->value;
if (prox->spawn_value == spawnValue && prox->spawn_type == type)
prox->spawns_in_proximity.insert(make_pair(spawnValue, false));
}
}
}
void Spawn::RemoveSpawnFromProximity(int32 spawnValue, SpawnProximityType type)
{
if (!has_spawn_proximities)
return;
if (spawn_proximities.size() > 0)
{
MutexList<SpawnProximity*>::iterator itr = spawn_proximities.begin();
while (itr.Next()) {
SpawnProximity* prox = itr->value;
if (prox->spawn_value == spawnValue && prox->spawn_type == type &&
prox->spawns_in_proximity.count(spawnValue) > 0)
prox->spawns_in_proximity.erase(spawnValue);
}
}
}
void Spawn::AddPrimaryEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual, bool defaultDenyList, Player* player) {
EntityCommand* cmd = FindEntityCommand(string(command), true);
bool newCommand = false;
if (!cmd)
{
newCommand = true;
cmd = CreateEntityCommand(name, distance, command, error_text, cast_time, spell_visual, !defaultDenyList);
}
if (defaultDenyList)
SetPermissionToEntityCommand(cmd, player, true);
if (newCommand)
primary_command_list.push_back(cmd);
}
void Spawn::RemovePrimaryEntityCommand(const char* command) {
vector<EntityCommand*>::iterator itr;
string tmpStr(command);
for (itr = primary_command_list.begin(); itr != primary_command_list.end(); itr++) {
EntityCommand* cmd = *itr;
if (cmd->command.compare(tmpStr) == 0)
{
primary_command_list.erase(itr);
delete cmd;
break;
}
}
}
bool Spawn::SetPermissionToEntityCommand(EntityCommand* command, Player* player, bool permissionValue)
{
if(!player)
return false;
return SetPermissionToEntityCommandByCharID(command, player->GetCharacterID(), permissionValue);
}
bool Spawn::SetPermissionToEntityCommandByCharID(EntityCommand* command, int32 charID, bool permissionValue)
{
map<int32, bool>::iterator itr = command->allow_or_deny.find(charID);
if (itr == command->allow_or_deny.end())
command->allow_or_deny.insert(make_pair(charID, permissionValue));
else if (itr->second != permissionValue)
itr->second = permissionValue;
return true;
}
void Spawn::RemoveSpawnFromPlayer(Player* player)
{
m_Update.writelock(__FUNCTION__, __LINE__);
player->RemoveSpawn(this); // sets it as removed
m_Update.releasewritelock(__FUNCTION__, __LINE__);
}
bool Spawn::InWater()
{
bool inWater = false;
if ( region_map != nullptr )
{
glm::vec3 targPos(GetX(), GetY(), GetZ());
if ( IsGroundSpawn() )
targPos.y -= .5f;
if(region_map->InWater(targPos, GetLocation()))
inWater = true;
}
return inWater;
}
bool Spawn::InLava()
{
bool inLava = false;
if ( region_map != nullptr )
{
glm::vec3 targPos(GetX(), GetY(), GetZ());
if ( IsGroundSpawn() )
targPos.y -= .5f;
if(region_map->InLava(targPos, GetLocation()))
inLava = true;
}
return inLava;
}
void Spawn::DeleteRegion(Region_Node* inNode, ZBSP_Node* rootNode)
{
map<map<Region_Node*, ZBSP_Node*>, Region_Status>::iterator testitr;
for (testitr = Regions.begin(); testitr != Regions.end(); testitr++)
{
map<Region_Node*, ZBSP_Node*>::const_iterator actualItr = testitr->first.begin();
Region_Node* node = actualItr->first;
ZBSP_Node* BSP_Root = actualItr->second;
if(inNode == node && rootNode == BSP_Root )
{
testitr = Regions.erase(testitr);
break;
}
}
}
bool Spawn::InRegion(Region_Node* inNode, ZBSP_Node* rootNode)
{
map<map<Region_Node*, ZBSP_Node*>, Region_Status>::iterator testitr;
for (testitr = Regions.begin(); testitr != Regions.end(); testitr++)
{
map<Region_Node*, ZBSP_Node*>::const_iterator actualItr = testitr->first.begin();
Region_Node* node = actualItr->first;
ZBSP_Node* BSP_Root = actualItr->second;
if(inNode == node && rootNode == BSP_Root )
{
return testitr->second.inRegion;
}
}
return false;
}
int32 Spawn::GetRegionType(Region_Node* inNode, ZBSP_Node* rootNode)
{
map<map<Region_Node*, ZBSP_Node*>, Region_Status>::iterator testitr;
for (testitr = Regions.begin(); testitr != Regions.end(); testitr++)
{
map<Region_Node*, ZBSP_Node*>::const_iterator actualItr = testitr->first.begin();
Region_Node* node = actualItr->first;
ZBSP_Node* BSP_Root = actualItr->second;
if(inNode == node && rootNode == BSP_Root )
{
return testitr->second.regionType;
}
}
return false;
}
float Spawn::SpawnAngle(Spawn* target, float selfx, float selfz)
{
if (!target || target == this)
return 0.0f;
float angle, lengthb, vectorx, vectorz, dotp;
float spx = (target->GetX()); // mob xloc (inverse because eq)
float spz = -(target->GetZ()); // mob yloc
float heading = target->GetHeading(); // mob heading
if (heading < 270)
heading += 90;
else
heading -= 270;
heading = heading * 3.1415f / 180.0f; // convert to radians
vectorx = spx + (10.0f * std::cos(heading)); // create a vector based on heading
vectorz = spz + (10.0f * std::sin(heading)); // of spawn length 10
// length of spawn to player vector
lengthb = (float) std::sqrt(((selfx - spx) * (selfx - spx)) + ((-selfz - spz) * (-selfz - spz)));
// calculate dot product to get angle
// Handle acos domain errors due to floating point rounding errors
dotp = ((vectorx - spx) * (selfx - spx) +
(vectorz - spz) * (-selfz - spz)) / (10.0f * lengthb);
if (dotp > 1)
return 0.0f;
else if (dotp < -1)
return 180.0f;
angle = std::acos(dotp);
angle = angle * 180.0f / 3.1415f;
return angle;
}
void Spawn::StopMovement()
{
reset_movement = true;
}
bool Spawn::PauseMovement(int32 period_of_time_ms)
{
if(period_of_time_ms < 1)
period_of_time_ms = 1;
RunToLocation(GetX(),GetY(),GetZ());
pause_timer.Start(period_of_time_ms, true);
return true;
}
bool Spawn::IsPauseMovementTimerActive()
{
if(pause_timer.Check())
pause_timer.Disable();
return pause_timer.Enabled();
}
bool Spawn::IsFlyingCreature()
{
if(!IsEntity())
return false;
return ((Entity*)this)->GetInfoStruct()->get_flying_type();
}
bool Spawn::IsWaterCreature()
{
if(!IsEntity())
return false;
return ((Entity*)this)->GetInfoStruct()->get_water_type();
}
void Spawn::SetFlyingCreature() {
if(!IsEntity() || !rule_manager.GetZoneRule(GetZoneID(), R_Spawn, UseHardCodeFlyingModelType)->GetInt8())
return;
if(((Entity*)this)->GetInfoStruct()->get_flying_type() > 0) // DB spawn npc flag already set
return;
switch (GetModelType())
{
case 260:
case 295:
((Entity*)this)->GetInfoStruct()->set_flying_type(1);
is_flying_creature = true;
break;
default:
((Entity*)this)->GetInfoStruct()->set_flying_type(0);
break;
}
}
void Spawn::SetWaterCreature() {
if(!IsEntity() || !rule_manager.GetZoneRule(GetZoneID(), R_Spawn, UseHardCodeWaterModelType)->GetInt8())
return;
if(((Entity*)this)->GetInfoStruct()->get_water_type() > 0) // DB spawn npc flag already set
return;
switch (GetModelType())
{
case 194:
case 204:
case 210:
case 241:
case 242:
case 254:
case 10668:
case 20828:
((Entity*)this)->GetInfoStruct()->set_water_type(1);
break;
default:
((Entity*)this)->GetInfoStruct()->set_water_type(0);
break;
}
}
void Spawn::AddRailPassenger(int32 char_id)
{
std::lock_guard<std::mutex> lk(m_RailMutex);
rail_passengers.insert(make_pair(char_id,true));
}
void Spawn::RemoveRailPassenger(int32 char_id)
{
std::lock_guard<std::mutex> lk(m_RailMutex);
std::map<int32, bool>::iterator itr = rail_passengers.find(char_id);
if(itr != rail_passengers.end())
rail_passengers.erase(itr);
}
vector<Spawn*> Spawn::GetPassengersOnRail() {
vector<Spawn*> tmp_list;
Spawn* spawn;
m_RailMutex.lock();
std::map<int32, bool>::iterator itr = rail_passengers.begin();
while(itr != rail_passengers.end()){
Client* client = zone_list.GetClientByCharID(itr->first);
if(!client || !client->GetPlayer())
continue;
tmp_list.push_back(client->GetPlayer());
itr++;
}
m_RailMutex.unlock();
return tmp_list;
}
void Spawn::SetAppearancePosition(float x, float y, float z) {
appearance.pos.X = x;
appearance.pos.Y = y;
appearance.pos.Z = z;
appearance.pos.X2 = appearance.pos.X;
appearance.pos.Y2 = appearance.pos.Y;
appearance.pos.Z2 = appearance.pos.Z;
appearance.pos.X3 = appearance.pos.X;
appearance.pos.Y3 = appearance.pos.Y;
appearance.pos.Z3 = appearance.pos.Z;
SetSpeedX(0);
SetSpeedY(0);
SetSpeedZ(0);
if(IsPlayer()) {
((Player*)this)->SetSideSpeed(0);
((Player*)this)->pos_packet_speed = 0;
}
}
int32 Spawn::InsertRegionToSpawn(Region_Node* node, ZBSP_Node* bsp_root, WaterRegionType regionType, bool in_region) {
std::map<Region_Node*, ZBSP_Node*> newMap;
newMap.insert(make_pair(node, bsp_root));
Region_Status status;
status.inRegion = in_region;
status.regionType = regionType;
int32 returnValue = 0;
if(in_region) {
lua_interface->RunRegionScript(node->regionScriptName, "EnterRegion", GetZone(), this, regionType, &returnValue);
}
status.timerTic = returnValue;
status.lastTimerTic = returnValue ? Timer::GetCurrentTime2() : 0;
Regions.insert(make_pair(newMap, status));
return returnValue;
}
bool Spawn::HasRegionTracked(Region_Node* node, ZBSP_Node* bsp_root, bool in_region) {
map<map<Region_Node*, ZBSP_Node*>, Region_Status>::iterator testitr;
for (testitr = Regions.begin(); testitr != Regions.end(); testitr++)
{
map<Region_Node*, ZBSP_Node*>::const_iterator actualItr = testitr->first.begin();
Region_Node *node = actualItr->first;
ZBSP_Node *BSP_Root = actualItr->second;
if(node == actualItr->first && BSP_Root == actualItr->second) {
if(testitr->second.inRegion == in_region)
return true;
else
break;
}
}
return false;
}
void Spawn::SetLocation(int32 id, bool setUpdateFlags)
{
if(GetZone()) {
GetZone()->RemoveSpawnFromGrid(this, GetLocation());
SetPos(&appearance.pos.grid_id, id, setUpdateFlags);
GetZone()->AddSpawnToGrid(this, id);
}
else {
SetPos(&appearance.pos.grid_id, id, setUpdateFlags);
}
}
int8 Spawn::GetArrowColor(int8 spawn_level){
int8 color = 0;
sint16 diff = spawn_level - GetLevel();
if(GetLevel() < 10)
diff *= 3;
else if(GetLevel() <= 20)
diff *= 2;
if(diff >= 9)
color = ARROW_COLOR_RED;
else if(diff >= 5)
color = ARROW_COLOR_ORANGE;
else if(diff >= 1)
color = ARROW_COLOR_YELLOW;
else if(diff == 0)
color = ARROW_COLOR_WHITE;
else if(diff <= -11)
color = ARROW_COLOR_GRAY;
else if(diff <= -6)
color = ARROW_COLOR_GREEN;
else //if(diff < 0)
color = ARROW_COLOR_BLUE;
return color;
}
void Spawn::AddIgnoredWidget(int32 id) {
std::unique_lock lock(MIgnoredWidgets);
if(ignored_widgets.find(id) == ignored_widgets.end()) {
ignored_widgets.insert(make_pair(id,true));
}
}
void Spawn::SendGroupUpdate() {
if (IsEntity() && ((Entity*)this)->GetGroupMemberInfo()) {
((Entity*)this)->UpdateGroupMemberInfo();
world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id);
}
}
bool Spawn::AddNeedGreedItemRequest(int32 item_id, int32 spawn_id, bool need_item) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: AddNeedGreedItemRequest Item ID: %u, Spawn ID: %u, Need Item: %u", GetName(), item_id, spawn_id, need_item);
if (HasSpawnNeedGreedEntry(item_id, spawn_id)) {
return false;
}
need_greed_items.insert(make_pair(item_id, std::make_pair(spawn_id, need_item)));
AddSpawnLootWindowCompleted(spawn_id, false);
return true;
}
bool Spawn::AddLottoItemRequest(int32 item_id, int32 spawn_id) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: AddLottoItemRequest Item ID: %u, Spawn ID: %u", GetName(), item_id, spawn_id);
if (HasSpawnLottoEntry(item_id, spawn_id)) {
return false;
}
lotto_items.insert(make_pair(item_id, spawn_id));
AddSpawnLootWindowCompleted(spawn_id, false);
return true;
}
void Spawn::AddSpawnLootWindowCompleted(int32 spawn_id, bool status_) {
if (loot_complete.find(spawn_id) == loot_complete.end()) {
loot_complete.insert(make_pair(spawn_id, status_));
}
is_loot_complete = HasLootWindowCompleted();
}
bool Spawn::SetSpawnLootWindowCompleted(int32 spawn_id) {
std::map<int32, bool>::iterator itr = loot_complete.find(spawn_id);
if (itr != loot_complete.end()) {
itr->second = true;
is_loot_complete = HasLootWindowCompleted();
return true;
}
return false;
}
bool Spawn::HasSpawnLootWindowCompleted(int32 spawn_id) {
std::map<int32, bool>::iterator itr = loot_complete.find(spawn_id);
if (itr != loot_complete.end() && itr->second) {
return true;
}
return false;
}
bool Spawn::HasSpawnNeedGreedEntry(int32 item_id, int32 spawn_id) {
for (auto [itr, rangeEnd] = need_greed_items.equal_range(item_id); itr != rangeEnd; itr++) {
LogWrite(LOOT__DEBUG, 1, "Loot", "%s: HasSpawnNeedGreedEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second.first);
if (spawn_id == itr->second.first) {
return true;
}
}
return false;
}
bool Spawn::HasSpawnLottoEntry(int32 item_id, int32 spawn_id) {
for (auto [itr, rangeEnd] = lotto_items.equal_range(item_id); itr != rangeEnd; itr++) {
LogWrite(LOOT__DEBUG, 1, "Loot", "%s: HasSpawnLottoEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second);
if (spawn_id == itr->second) {
return true;
}
}
return false;
}
void Spawn::GetSpawnLottoEntries(int32 item_id, std::map<int32, int32>* out_entries) {
if (!out_entries)
return;
std::map<int32, bool> spawn_matches;
for (auto [itr, endrange] = lotto_items.equal_range(item_id); itr != endrange; itr++) {
out_entries->insert(std::make_pair(itr->second, (int32)MakeRandomInt(0, 100)));
spawn_matches[itr->second] = true;
}
// 0xFFFFFFFF represents selecting "All" on the lotto screen
for (auto [itr, endrange] = lotto_items.equal_range(0xFFFFFFFF); itr != endrange; itr++) {
if (spawn_matches.find(itr->second) == spawn_matches.end()) {
out_entries->insert(std::make_pair(itr->second, (int32)MakeRandomInt(0, 100)));
}
}
}
void Spawn::GetSpawnNeedGreedEntries(int32 item_id, bool need_item, std::map<int32, int32>* out_entries) {
if (!out_entries)
return;
for (auto [itr, rangeEnd] = need_greed_items.equal_range(item_id); itr != rangeEnd; itr++) {
out_entries->insert(std::make_pair(itr->second.first, (int32)MakeRandomInt(0, 100)));
}
}
bool Spawn::HasLootWindowCompleted() {
std::map<int32, bool>::iterator itr;
for (itr = loot_complete.begin(); itr != loot_complete.end(); itr++) {
if (!itr->second)
return false;
}
return true;
}
void Spawn::StartLootTimer(Spawn* looter) {
if (!IsLootTimerRunning()) {
int32 loot_timer_time = rule_manager.GetZoneRule(GetZoneID(), R_Loot, LootDistributionTime)->GetInt32() * 1000;
if(rule_manager.GetZoneRule(GetZoneID(), R_Loot, AllowChestUnlockByDropTime)->GetBool() && loot_timer_time > rule_manager.GetZoneRule(GetZoneID(), R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000) {
loot_timer_time = (rule_manager.GetZoneRule(GetZoneID(), R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000) / 2;
}
if(rule_manager.GetZoneRule(GetZoneID(), R_Loot, AllowChestUnlockByTrapTime)->GetBool() && loot_timer_time > rule_manager.GetZoneRule(GetZoneID(), R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000) {
loot_timer_time = (rule_manager.GetZoneRule(GetZoneID(), R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000) / 2;
}
if(loot_timer_time < 1000) {
loot_timer_time = 60000; // hardcode assure they aren't setting some really ridiculous low number
}
loot_timer.Start(loot_timer_time, true);
}
if (looter) {
looter_spawn_id = looter->GetID();
}
}
void Spawn::CloseLoot(Spawn* sender) {
if (sender) {
SetSpawnLootWindowCompleted(sender->GetID());
}
if (sender && looter_spawn_id > 0 && sender->GetID() != looter_spawn_id) {
LogWrite(LOOT__ERROR, 0, "Loot", "%s: CloseLoot Looter Spawn ID: %u does not match sender %u.", GetName(), looter_spawn_id, sender->GetID());
return;
}
if (!IsLootTimerRunning() && GetLootMethod() != GroupLootMethod::METHOD_LOTTO && GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) {
loot_timer.Disable();
}
looter_spawn_id = 0;
}
void Spawn::SetLootMethod(GroupLootMethod method, int8 item_rarity, int32 group_id) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: Set Loot Method : %u, group id : %u", GetName(), (int32)method, group_id);
loot_group_id = group_id;
loot_method = method;
loot_rarity = item_rarity;
if (loot_name.size() < 1) {
loot_name = std::string(GetName());
}
}
bool Spawn::IsItemInLootTier(Item* item) {
if (!item)
return true;
bool skipItem = true;
switch (GetLootRarity()) {
case LootTier::ITEMS_TREASURED_PLUS: {
if (item->details.tier >= ITEM_TAG_TREASURED) {
skipItem = false;
}
break;
}
case LootTier::ITEMS_LEGENDARY_PLUS: {
if (item->details.tier >= ITEM_TAG_LEGENDARY) {
skipItem = false;
}
break;
}
case LootTier::ITEMS_FABLED_PLUS: {
if (item->details.tier >= ITEM_TAG_FABLED) {
skipItem = false;
}
break;
}
default: {
skipItem = false;
break;
}
}
return skipItem;
}
void Spawn::DistributeGroupLoot_RoundRobin(std::vector<int32>* item_list, bool roundRobinTrashLoot) {
std::vector<int32>::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);
Spawn* looter = nullptr;
int8 group_idx = 0;
bool skipItem = IsItemInLootTier(tmpItem);
if ((skipItem && !roundRobinTrashLoot) || (!skipItem && roundRobinTrashLoot))
continue;
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
PlayerGroup* group = world.GetGroupManager()->GetGroup(GetLootGroupID());
if (group) {
std::vector<int32> raidGroups;
world.GetGroupManager()->GetRaidGroups(GetLootGroupID(), &raidGroups);
int8 index = group->GetLastLooterIndex();
int8 actual_index = index;
if(raidGroups.size() > 0) {
group_idx = (index / 6);
if(group_idx < raidGroups.size()) {
group = world.GetGroupManager()->GetGroup(raidGroups.at(group_idx));
if(!group) {
LogWrite(LOOT__ERROR, 0, "Loot", "DistributeGroupLoot_RoundRobin: Failed to find group at index %u with value %u", group_idx, raidGroups.at(group_idx));
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
return;
}
if(index >= 6) {
actual_index = index - (group_idx * 6);
}
else{
actual_index = index;
}
}
else {
LogWrite(LOOT__ERROR, 0, "Loot", "DistributeGroupLoot_RoundRobin: Group at index %u out of range with raidGroups size %u", group_idx, raidGroups.size());
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
return;
}
}
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
int8 size = group->GetMembers()->size();
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
if (actual_index >= size) {
if(raidGroups.size() < 1) {
actual_index = 0;
index = 0;
}
else if(index >= raidGroups.size() * 6) {
group = world.GetGroupManager()->GetGroup(raidGroups.at(0));
if(!group) {
LogWrite(LOOT__ERROR, 0, "Loot", "DistributeGroupLoot_RoundRobin: Failed to find main group");
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
return;
}
actual_index = 0;
index = 0;
group_idx = 0;
}
else {
index = (group_idx + 1) * 6;
actual_index = 0;
group_idx = (index / 6);
if(group_idx < raidGroups.size()) {
group = world.GetGroupManager()->GetGroup(raidGroups.at(group_idx));
if(!group) {
LogWrite(LOOT__ERROR, 0, "Loot", "DistributeGroupLoot_RoundRobin: Failed to find group %u at index %u", raidGroups.at(group_idx), group_idx);
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
return;
}
}
else {
group = world.GetGroupManager()->GetGroup(raidGroups.at(0));
group_idx = 0;
index = 0;
actual_index = 0;
if(!group) {
LogWrite(LOOT__ERROR, 0, "Loot", "DistributeGroupLoot_RoundRobin: Failed to find main group");
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
return;
}
}
}
}
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
bool foundLooter = false;
bool loopAttempted = false;
while (!foundLooter) {
// If we've iterated past the current group's members
if (actual_index >= members->size()) {
if (loopAttempted) {
// If we've looped through all groups and members without finding a player, stop
looter = nullptr;
break;
}
// Move to the next group in the raid
group_idx++;
if (group_idx >= raidGroups.size()) {
// If we've iterated through all raid groups, loop back to the first group
loopAttempted = true;
group_idx = 0;
}
index = (group_idx) * 6;
actual_index = 0;
// Update the group pointer to the next group
group = world.GetGroupManager()->GetGroup(raidGroups.at(group_idx));
if (!group) {
LogWrite(LOOT__ERROR, 0, "Loot", "Failed to find group with ID %u", raidGroups.at(group_idx));
break;
}
// Update members pointer for the new group
members = group->GetMembers();
}
// Get the current member
GroupMemberInfo* gmi = members->at(actual_index);
if (gmi && gmi->member && gmi->member->IsPlayer()) {
looter = gmi->member;
foundLooter = true;
} else {
// Move to the next member
actual_index++;
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
// Update the loot index across all raid groups
index++;
if(raidGroups.size() > 0) {
for (size_t i = 0; i < raidGroups.size(); i++) {
PlayerGroup* tmpGroup = world.GetGroupManager()->GetGroup(raidGroups.at(i));
if (tmpGroup) {
tmpGroup->SetNextLooterIndex(index);
}
}
}
else if(group) {
group->SetNextLooterIndex(index);
}
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
if (looter) {
if (looter->IsPlayer()) {
Item* item = LootItem(item_id);
bool success = false;
success = ((Player*)looter)->GetClient()->HandleLootItem(this, item, ((Player*)looter), roundRobinTrashLoot);
if (!success)
AddLootItem(item);
}
else {
Item* item = LootItem(item_id);
safe_delete(item);
}
}
}
}
const double g = 9.81; // acceleration due to gravity (m/s^2)
void Spawn::CalculateInitialVelocity(float heading, float distanceHorizontal, float distanceVertical, float distanceDepth, float duration) {
float vx = distanceHorizontal / duration;
float vy = (distanceVertical + 0.5 * g * duration * duration) / duration;
float vz = distanceDepth / duration;
// Convert heading angle to radians
knocked_velocity.x = vx * cos(heading);
knocked_velocity.y = vy;
knocked_velocity.z = vz * sin(heading);
}
// Function to calculate the projectile position at a given time
glm::vec3 Spawn::CalculateProjectilePosition(glm::vec3 initialVelocity, float time) {
glm::vec3 position;
position.x = knocked_back_start_x + initialVelocity.x * time;
position.y = knocked_back_start_y + initialVelocity.y * time - 0.5 * g * time * time;
position.z = knocked_back_start_z + initialVelocity.z * time;
auto loc = glm::vec3(position.x, position.z, position.y);
float new_z = FindBestZ(loc, nullptr);
if(new_z > position.y)
position.y = new_z;
return position;
}
bool Spawn::CalculateSpawnProjectilePosition(float x, float y, float z) {
float currentTimeOffset = (Timer::GetCurrentTime2() - last_movement_update) * 0.001; // * 0.001 is the same as / 1000, float muliplications is suppose to be faster though
float stepAheadOne = currentTimeOffset+currentTimeOffset;
knocked_back_time_step += currentTimeOffset;
if(Timer::GetCurrentTime2() >= knocked_back_end_time) {
ResetKnockedBack();
FixZ(true);
return false;
}
glm::vec3 position = CalculateProjectilePosition(knocked_velocity, knocked_back_time_step);
glm::vec3 position_two = position;
if(Timer::GetCurrentTime2() <= knocked_back_end_time+stepAheadOne) {
position_two = CalculateProjectilePosition(knocked_velocity, knocked_back_time_step+stepAheadOne);
}
if(GetMap()) {
glm::vec3 loc(GetX(), GetZ(), GetY() + .5f);
glm::vec3 dest_loc(position_two.x, position_two.z, position_two.y);
MIgnoredWidgets.lock_shared();
glm::vec3 outNorm;
float dist = 0.0f;
bool collide_ = GetMap()->DoCollisionCheck(loc, dest_loc, &ignored_widgets, outNorm, dist);
if(collide_) {
LogWrite(SPAWN__ERROR, 0, "Spawn", "Collision Hit: cur loc x,y,z: %f %f %f. to loc %f %f %f. TimeOffset: %f Total Time: %f Duration: %f", GetX(),GetY(),GetZ(),position_two.x,position_two.y,position_two.z, currentTimeOffset, knocked_back_time_step, knocked_back_duration);
MIgnoredWidgets.unlock_shared();
ResetKnockedBack();
FixZ(true);
return false;
}
MIgnoredWidgets.unlock_shared();
}
LogWrite(SPAWN__ERROR, 0, "Spawn", "x,y,z: %f %f %f. Final %f %f %f. TimeOffset: %f Total Time: %f Duration: %f", GetX(),GetY(),GetZ(),position.x,position.y,position.z, currentTimeOffset, knocked_back_time_step, knocked_back_duration);
SetX(position.x, false);
SetZ(position.z, false);
SetY(position.y, false, true);
SetPos(&appearance.pos.X2, position_two.x, false);
SetPos(&appearance.pos.Z2, position_two.z, false);
SetPos(&appearance.pos.Y2, position_two.y, false);
SetPos(&appearance.pos.X3, position_two.x, false);
SetPos(&appearance.pos.Z3, position_two.z, false);
SetPos(&appearance.pos.Y3, position_two.y, false);
position_changed = true;
changed = true;
GetZone()->AddChangedSpawn(this);
return true;
}
void Spawn::SetKnockback(Spawn* target, int32 duration, float vertical, float horizontal) {
if(knocked_back) {
return; // already being knocked back
}
// Calculate the direction vector from source to destination
glm::vec3 direction = {GetX() - target->GetX(), GetZ() - target->GetZ(), GetY() - target->GetY()};
// Calculate the heading angle in radians
double headingRad = atan2(direction.y, sqrt(direction.x * direction.x + direction.z * direction.z));
knocked_angle = headingRad;
knocked_back_start_x = GetX();
knocked_back_start_y = GetY();
knocked_back_start_z = GetZ();
knocked_back_h_distance = horizontal / 10.0f;
knocked_back_v_distance = vertical / 10.0f;
knocked_back_duration = static_cast<float>(duration) / 1000.0f;
knocked_back_end_time = Timer::GetCurrentTime2() + duration;
CalculateInitialVelocity(knocked_angle, knocked_back_h_distance, knocked_back_h_distance, knocked_back_h_distance, knocked_back_duration);
knocked_back = true;
}
void Spawn::ResetKnockedBack() {
knocked_back = false;
knocked_back_time_step = 0.0f;
knocked_back_h_distance = 0.0f;
knocked_back_v_distance = 0.0f;
knocked_back_duration = 0.0f;
knocked_back_end_time = 0;
knocked_back_start_x = 0.0f;
knocked_back_start_y = 0.0f;
knocked_back_start_z = 0.0f;
knocked_angle = 0.0f;
knocked_velocity.x = 0.0f;
knocked_velocity.y = 0.0f;
knocked_velocity.z = 0.0f;
}