
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.
5532 lines
166 KiB
C++
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, ×tamp, 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;
|
|
}
|