diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index fe04be014..dec37a460 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -5241,6 +5241,17 @@ DROP TABLE IF EXISTS item_tick .sql = R"( ALTER TABLE `spawngroup` MODIFY COLUMN `name` varchar(200) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT '' AFTER `id`; +)" + }, + ManifestEntry{ + .version = 9257, + .description = "2024_01_16_ground_spawns_fix_z.sql", + .check = "SHOW COLUMNS FROM `ground_spawns` LIKE `fix_z`", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `ground_spawns` +ADD COLUMN `fix_z` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 AFTER `respawn_timer`; )" } // -- template; copy/paste this when you need to create a new entry diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 6c2cbff31..b2025df82 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -3451,18 +3451,21 @@ struct Make_Pet_Struct { //Simple struct for getting pet info uint32 min_dmg; uint32 max_dmg; }; -struct GroundSpawn{ - float max_x; - float max_y; - float min_x; - float min_y; - float max_z; - float heading; - char name[20]; - uint32 item; - uint32 max_allowed; - uint32 respawntimer; + +struct GroundSpawn { + float max_x = 0.0f; + float max_y = 0.0f; + float min_x = 0.0f; + float min_y = 0.0f; + float max_z = 0.0f; + float heading = 0.0f; + std::string name = std::string(); + uint32 item_id = 0; + uint32 max_allowed = 1; + uint32 respawn_timer = 1; + bool fix_z = true; }; + struct GroundSpawns { struct GroundSpawn spawn[50]; //Assigned max number to allow }; diff --git a/common/repositories/base/base_ground_spawns_repository.h b/common/repositories/base/base_ground_spawns_repository.h index 3505d4f4e..ac8cbc2ec 100644 --- a/common/repositories/base/base_ground_spawns_repository.h +++ b/common/repositories/base/base_ground_spawns_repository.h @@ -33,6 +33,7 @@ public: uint32_t max_allowed; std::string comment; uint32_t respawn_timer; + uint8_t fix_z; int8_t min_expansion; int8_t max_expansion; std::string content_flags; @@ -61,6 +62,7 @@ public: "max_allowed", "comment", "respawn_timer", + "fix_z", "min_expansion", "max_expansion", "content_flags", @@ -85,6 +87,7 @@ public: "max_allowed", "comment", "respawn_timer", + "fix_z", "min_expansion", "max_expansion", "content_flags", @@ -143,6 +146,7 @@ public: e.max_allowed = 1; e.comment = ""; e.respawn_timer = 300; + e.fix_z = 1; e.min_expansion = -1; e.max_expansion = -1; e.content_flags = ""; @@ -197,10 +201,11 @@ public: e.max_allowed = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 1; e.comment = row[12] ? row[12] : ""; e.respawn_timer = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 300; - e.min_expansion = row[14] ? static_cast(atoi(row[14])) : -1; - e.max_expansion = row[15] ? static_cast(atoi(row[15])) : -1; - e.content_flags = row[16] ? row[16] : ""; - e.content_flags_disabled = row[17] ? row[17] : ""; + e.fix_z = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 1; + e.min_expansion = row[15] ? static_cast(atoi(row[15])) : -1; + e.max_expansion = row[16] ? static_cast(atoi(row[16])) : -1; + e.content_flags = row[17] ? row[17] : ""; + e.content_flags_disabled = row[18] ? row[18] : ""; return e; } @@ -247,10 +252,11 @@ public: v.push_back(columns[11] + " = " + std::to_string(e.max_allowed)); v.push_back(columns[12] + " = '" + Strings::Escape(e.comment) + "'"); v.push_back(columns[13] + " = " + std::to_string(e.respawn_timer)); - v.push_back(columns[14] + " = " + std::to_string(e.min_expansion)); - v.push_back(columns[15] + " = " + std::to_string(e.max_expansion)); - v.push_back(columns[16] + " = '" + Strings::Escape(e.content_flags) + "'"); - v.push_back(columns[17] + " = '" + Strings::Escape(e.content_flags_disabled) + "'"); + v.push_back(columns[14] + " = " + std::to_string(e.fix_z)); + v.push_back(columns[15] + " = " + std::to_string(e.min_expansion)); + v.push_back(columns[16] + " = " + std::to_string(e.max_expansion)); + v.push_back(columns[17] + " = '" + Strings::Escape(e.content_flags) + "'"); + v.push_back(columns[18] + " = '" + Strings::Escape(e.content_flags_disabled) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -286,6 +292,7 @@ public: v.push_back(std::to_string(e.max_allowed)); v.push_back("'" + Strings::Escape(e.comment) + "'"); v.push_back(std::to_string(e.respawn_timer)); + v.push_back(std::to_string(e.fix_z)); v.push_back(std::to_string(e.min_expansion)); v.push_back(std::to_string(e.max_expansion)); v.push_back("'" + Strings::Escape(e.content_flags) + "'"); @@ -333,6 +340,7 @@ public: v.push_back(std::to_string(e.max_allowed)); v.push_back("'" + Strings::Escape(e.comment) + "'"); v.push_back(std::to_string(e.respawn_timer)); + v.push_back(std::to_string(e.fix_z)); v.push_back(std::to_string(e.min_expansion)); v.push_back(std::to_string(e.max_expansion)); v.push_back("'" + Strings::Escape(e.content_flags) + "'"); @@ -384,10 +392,11 @@ public: e.max_allowed = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 1; e.comment = row[12] ? row[12] : ""; e.respawn_timer = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 300; - e.min_expansion = row[14] ? static_cast(atoi(row[14])) : -1; - e.max_expansion = row[15] ? static_cast(atoi(row[15])) : -1; - e.content_flags = row[16] ? row[16] : ""; - e.content_flags_disabled = row[17] ? row[17] : ""; + e.fix_z = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 1; + e.min_expansion = row[15] ? static_cast(atoi(row[15])) : -1; + e.max_expansion = row[16] ? static_cast(atoi(row[16])) : -1; + e.content_flags = row[17] ? row[17] : ""; + e.content_flags_disabled = row[18] ? row[18] : ""; all_entries.push_back(e); } @@ -426,10 +435,11 @@ public: e.max_allowed = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 1; e.comment = row[12] ? row[12] : ""; e.respawn_timer = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 300; - e.min_expansion = row[14] ? static_cast(atoi(row[14])) : -1; - e.max_expansion = row[15] ? static_cast(atoi(row[15])) : -1; - e.content_flags = row[16] ? row[16] : ""; - e.content_flags_disabled = row[17] ? row[17] : ""; + e.fix_z = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 1; + e.min_expansion = row[15] ? static_cast(atoi(row[15])) : -1; + e.max_expansion = row[16] ? static_cast(atoi(row[16])) : -1; + e.content_flags = row[17] ? row[17] : ""; + e.content_flags_disabled = row[18] ? row[18] : ""; all_entries.push_back(e); } @@ -518,6 +528,7 @@ public: v.push_back(std::to_string(e.max_allowed)); v.push_back("'" + Strings::Escape(e.comment) + "'"); v.push_back(std::to_string(e.respawn_timer)); + v.push_back(std::to_string(e.fix_z)); v.push_back(std::to_string(e.min_expansion)); v.push_back(std::to_string(e.max_expansion)); v.push_back("'" + Strings::Escape(e.content_flags) + "'"); @@ -558,6 +569,7 @@ public: v.push_back(std::to_string(e.max_allowed)); v.push_back("'" + Strings::Escape(e.comment) + "'"); v.push_back(std::to_string(e.respawn_timer)); + v.push_back(std::to_string(e.fix_z)); v.push_back(std::to_string(e.min_expansion)); v.push_back(std::to_string(e.max_expansion)); v.push_back("'" + Strings::Escape(e.content_flags) + "'"); diff --git a/common/version.h b/common/version.h index b182141d0..28646f3f3 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9256 +#define CURRENT_BINARY_DATABASE_VERSION 9257 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9042 diff --git a/zone/entity.cpp b/zone/entity.cpp index ffeccd563..29a0e80a1 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -4641,36 +4641,44 @@ void EntityList::GroupMessage(uint32 gid, const char *from, const char *message) } } -uint16 EntityList::CreateGroundObject(uint32 itemid, const glm::vec4& position, uint32 decay_time) +uint16 EntityList::CreateGroundObject(uint32 item_id, const glm::vec4& position, uint32 decay_time) { - const EQ::ItemData *is = database.GetItem(itemid); - if (!is) + const auto is = database.GetItem(item_id); + if (!is) { return 0; + } - auto i = new EQ::ItemInstance(is, is->MaxCharges); - if (!i) + auto inst = new EQ::ItemInstance(is, is->MaxCharges); + if (!inst) { return 0; + } + + auto object = new Object(inst, position.x, position.y, position.z, position.w, decay_time); - auto object = new Object(i, position.x, position.y, position.z, position.w, decay_time); entity_list.AddObject(object, true); - safe_delete(i); - if (!object) + safe_delete(inst); + + if (!object) { return 0; + } return object->GetID(); } uint16 EntityList::CreateGroundObjectFromModel(const char *model, const glm::vec4& position, uint8 type, uint32 decay_time) { - if (!model) + if (!model) { return 0; + } auto object = new Object(model, position.x, position.y, position.z, position.w, type); + entity_list.AddObject(object, true); - if (!object) + if (!object) { return 0; + } return object->GetID(); } diff --git a/zone/gm_commands/object_manipulation.cpp b/zone/gm_commands/object_manipulation.cpp index 27214cf27..9edce5f0e 100644 --- a/zone/gm_commands/object_manipulation.cpp +++ b/zone/gm_commands/object_manipulation.cpp @@ -154,13 +154,7 @@ void ObjectManipulation::CommandHandler(Client *c, const Seperator *sep) const uint32 object_id = (ObjectRepository::GetMaxId(content_db) + 1); - Object *o = new Object( - object_id, - od.object_type, - icon, - od, - nullptr - ); + Object* o = new Object(object_id, od.object_type, icon, od); entity_list.AddObject(o, true); @@ -1237,7 +1231,8 @@ void ObjectManipulation::CommandHandler(Client *c, const Seperator *sep) od.object_type = ObjectTypes::StaticLocked; } - o = new Object(object_id, od.object_type, icon, od, nullptr); + o = new Object(object_id, od.object_type, icon, od); + entity_list.AddObject(o, true); c->Message( diff --git a/zone/object.cpp b/zone/object.cpp index 31120e765..48dfc7d0f 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -46,9 +46,12 @@ Object::Object( uint32 id, uint32 type, uint32 icon, - const Object_Struct &object, - const EQ::ItemInstance *inst -) : respawn_timer(0), decay_timer(300000) + const Object_Struct& object, + const EQ::ItemInstance* inst, + bool fix_z +) : +respawn_timer(0), +decay_timer(300000) { user = nullptr; last_user = nullptr; @@ -57,6 +60,7 @@ Object::Object( m_id = id; m_type = type; m_icon = icon; + m_fix_z = fix_z; m_inst = nullptr; m_ground_spawn = false; @@ -77,79 +81,107 @@ Object::Object( m_data.tilt_x = object.tilt_x; m_data.tilt_y = object.tilt_y; - FixZ(); + if (!IsFixZEnabled()) { + FixZ(); + } } //creating a re-ocurring ground spawn. -Object::Object(const EQ::ItemInstance* inst, char* name,float max_x,float min_x,float max_y,float min_y,float z,float heading,uint32 respawntimer) - : respawn_timer(respawntimer * 1000), decay_timer(300000) +Object::Object( + const EQ::ItemInstance* inst, + const std::string& name, + float max_x, + float min_x, + float max_y, + float min_y, + float z, + float heading, + uint32 respawn_timer_, + bool fix_z +) : +respawn_timer(respawn_timer_ * 1000), +decay_timer(300000) { - user = nullptr; - last_user = nullptr; - m_max_x=max_x; - m_max_y=max_y; - m_min_x=min_x; - m_min_y=min_y; - m_id = 0; - m_inst = (inst) ? inst->Clone() : nullptr; - m_type = ObjectTypes::Temporary; - m_icon = 0; + user = nullptr; + last_user = nullptr; + m_max_x = max_x; + m_max_y = max_y; + m_min_x = min_x; + m_min_y = min_y; + m_id = 0; + m_inst = (inst) ? inst->Clone() : nullptr; + m_type = ObjectTypes::Temporary; + m_icon = 0; + m_fix_z = fix_z; m_ground_spawn = true; + decay_timer.Disable(); // Set as much struct data as we can memset(&m_data, 0, sizeof(Object_Struct)); + m_data.heading = heading; - m_data.z = z; + m_data.z = z; m_data.zone_id = zone->GetZoneID(); + respawn_timer.Disable(); - strcpy(m_data.object_name, name); + + strcpy(m_data.object_name, name.c_str()); + RandomSpawn(false); - FixZ(); + if (!IsFixZEnabled()) { + FixZ(); + } // Hardcoded portion for unknown members - m_data.unknown024 = 0x7f001194; - m_data.unknown076 = 0x0000d5fe; - m_data.unknown084 = 0xFFFFFFFF; + m_data.unknown024 = 0x7f001194; + m_data.unknown076 = 0x0000d5fe; + m_data.unknown084 = 0xFFFFFFFF; } // Loading object from client dropping item on ground -Object::Object(Client* client, const EQ::ItemInstance* inst) - : respawn_timer(0), decay_timer(300000) +Object::Object( + Client* client, + const EQ::ItemInstance* inst +) : +respawn_timer(0), +decay_timer(300000) { user = nullptr; last_user = nullptr; // Initialize members - m_id = 0; - m_inst = (inst) ? inst->Clone() : nullptr; - m_type = ObjectTypes::Temporary; - m_icon = 0; + m_id = 0; + m_inst = (inst) ? inst->Clone() : nullptr; + m_type = ObjectTypes::Temporary; + m_icon = 0; m_ground_spawn = false; + m_fix_z = false; + // Set as much struct data as we can memset(&m_data, 0, sizeof(Object_Struct)); + m_data.heading = client->GetHeading(); - m_data.x = client->GetX(); - m_data.y = client->GetY(); - if (client->ClientVersion() >= EQ::versions::ClientVersion::RoF2) - { + m_data.x = client->GetX(); + m_data.y = client->GetY(); + + if (client->ClientVersion() >= EQ::versions::ClientVersion::RoF2) { // RoF2 places items at player's Z, which is 0.625 of their height. m_data.z = client->GetZ() - (client->GetSize() * 0.625f); - } - else - { + } else { m_data.z = client->GetZ(); } + m_data.zone_id = zone->GetZoneID(); decay_timer.Start(); respawn_timer.Disable(); // Hardcoded portion for unknown members - m_data.unknown024 = 0x7f001194; - m_data.unknown076 = 0x0000d5fe; - m_data.unknown084 = 0xFFFFFFFF; + m_data.unknown024 = 0x7f001194; + m_data.unknown076 = 0x0000d5fe; + m_data.unknown084 = 0xFFFFFFFF; // Set object name if (inst) { @@ -157,11 +189,10 @@ Object::Object(Client* client, const EQ::ItemInstance* inst) if (item) { if (item->IDFile[0] == '\0') { strcpy(m_data.object_name, DEFAULT_OBJECT_NAME); - } - else { + } else { // Object name is idfile + _ACTORDEF uint32 len_idfile = strlen(inst->GetItem()->IDFile); - uint32 len_copy = sizeof(m_data.object_name) - len_idfile - 1; + uint32 len_copy = sizeof(m_data.object_name) - len_idfile - 1; if (len_copy > sizeof(DEFAULT_OBJECT_NAME_SUFFIX)) { len_copy = sizeof(DEFAULT_OBJECT_NAME_SUFFIX); } @@ -169,8 +200,7 @@ Object::Object(Client* client, const EQ::ItemInstance* inst) memcpy(&m_data.object_name[0], inst->GetItem()->IDFile, len_idfile); memcpy(&m_data.object_name[len_idfile], DEFAULT_OBJECT_NAME_SUFFIX, len_copy); } - } - else { + } else { strcpy(m_data.object_name, DEFAULT_OBJECT_NAME); } } @@ -178,47 +208,59 @@ Object::Object(Client* client, const EQ::ItemInstance* inst) FixZ(); } -Object::Object(const EQ::ItemInstance *inst, float x, float y, float z, float heading, uint32 decay_time) - : respawn_timer(0), decay_timer(decay_time) +Object::Object( + const EQ::ItemInstance *inst, + float x, + float y, + float z, + float heading, + uint32 decay_time, + bool fix_z +) : +respawn_timer(0), +decay_timer(decay_time) { - user = nullptr; + user = nullptr; last_user = nullptr; // Initialize members - m_id = 0; - m_inst = (inst) ? inst->Clone() : nullptr; - m_type = ObjectTypes::Temporary; - m_icon = 0; + m_id = 0; + m_inst = (inst) ? inst->Clone() : nullptr; + m_type = ObjectTypes::Temporary; + m_icon = 0; m_ground_spawn = false; + m_fix_z = fix_z; + // Set as much struct data as we can memset(&m_data, 0, sizeof(Object_Struct)); + m_data.heading = heading; - m_data.x = x; - m_data.y = y; - m_data.z = z; + m_data.x = x; + m_data.y = y; + m_data.z = z; m_data.zone_id = zone->GetZoneID(); - if (decay_time) + if (decay_time) { decay_timer.Start(); + } respawn_timer.Disable(); // Hardcoded portion for unknown members - m_data.unknown024 = 0x7f001194; - m_data.unknown076 = 0x0000d5fe; - m_data.unknown084 = 0xFFFFFFFF; + m_data.unknown024 = 0x7f001194; + m_data.unknown076 = 0x0000d5fe; + m_data.unknown084 = 0xFFFFFFFF; // Set object name if (inst) { - const EQ::ItemData* item = inst->GetItem(); + const EQ::ItemData *item = inst->GetItem(); if (item) { if (item->IDFile[0] == '\0') { strcpy(m_data.object_name, DEFAULT_OBJECT_NAME); - } - else { + } else { // Object name is idfile + _ACTORDEF uint32 len_idfile = strlen(inst->GetItem()->IDFile); - uint32 len_copy = sizeof(m_data.object_name) - len_idfile - 1; + uint32 len_copy = sizeof(m_data.object_name) - len_idfile - 1; if (len_copy > sizeof(DEFAULT_OBJECT_NAME_SUFFIX)) { len_copy = sizeof(DEFAULT_OBJECT_NAME_SUFFIX); } @@ -226,52 +268,64 @@ Object::Object(const EQ::ItemInstance *inst, float x, float y, float z, float he memcpy(&m_data.object_name[0], inst->GetItem()->IDFile, len_idfile); memcpy(&m_data.object_name[len_idfile], DEFAULT_OBJECT_NAME_SUFFIX, len_copy); } - } - else { + } else { strcpy(m_data.object_name, DEFAULT_OBJECT_NAME); } } - FixZ(); + if (!IsFixZEnabled()) { + FixZ(); + } } -Object::Object(const char *model, float x, float y, float z, float heading, uint8 type, uint32 decay_time) - : respawn_timer(0), decay_timer(decay_time) +Object::Object( + const std::string& model, + float x, + float y, + float z, + float heading, + uint8 type, + uint32 decay_time +) : +respawn_timer(0), +decay_timer(decay_time) { - user = nullptr; + user = nullptr; last_user = nullptr; - EQ::ItemInstance* inst = new EQ::ItemInstance(ItemInstWorldContainer); + + auto* inst = new EQ::ItemInstance(ItemInstWorldContainer); // Initialize members - m_id = 0; - m_inst = (inst) ? inst->Clone() : nullptr; - m_type = type; - m_icon = 0; + m_id = 0; + m_inst = inst->Clone(); + m_type = type; + m_icon = 0; m_ground_spawn = false; + // Set as much struct data as we can memset(&m_data, 0, sizeof(Object_Struct)); m_data.heading = heading; - m_data.x = x; - m_data.y = y; - m_data.z = z; + m_data.x = x; + m_data.y = y; + m_data.z = z; m_data.zone_id = zone->GetZoneID(); - FixZ(); + if (!IsFixZEnabled()) { + FixZ(); + } - if (decay_time) + if (decay_time) { decay_timer.Start(); + } respawn_timer.Disable(); - //Hardcoded portion for unknown members - m_data.unknown024 = 0x7f001194; - m_data.unknown076 = 0x0000d5fe; - m_data.unknown084 = 0xFFFFFFFF; + // Hardcoded portion for unknown members + m_data.unknown024 = 0x7f001194; + m_data.unknown076 = 0x0000d5fe; + m_data.unknown084 = 0xFFFFFFFF; - if(model) - strcpy(m_data.object_name, model); - else - strcpy(m_data.object_name, "IT64_ACTORDEF"); //default object name if model isn't specified for some unknown reason + strcpy(m_data.object_name, !model.empty() ? model.c_str() : "IT64_ACTORDEF"); safe_delete(inst); } @@ -279,7 +333,8 @@ Object::Object(const char *model, float x, float y, float z, float heading, uint Object::~Object() { safe_delete(m_inst); - if(user != nullptr) { + + if (user) { user->SetTradeskillObject(nullptr); } } @@ -298,20 +353,18 @@ void Object::ResetState() { safe_delete(m_inst); - m_id = 0; - m_type = 0; - m_icon = 0; + m_id = 0; + m_type = 0; + m_icon = 0; + memset(&m_data, 0, sizeof(Object_Struct)); } bool Object::Save() { - if (m_id) { - // Update existing + if (m_id) { // Update existing content_db.UpdateObject(m_id, m_type, m_icon, m_data, m_inst); - } - else { - // Doesn't yet exist, add now + } else { // Doesn't yet exist, add now m_id = content_db.AddObject(m_type, m_icon, m_data, m_inst); } @@ -320,14 +373,12 @@ bool Object::Save() uint16 Object::VarSave() { - if (m_id) { - // Update existing + if (m_id) { // Update existing content_db.UpdateObject(m_id, m_type, m_icon, m_data, m_inst); - } - else { - // Doesn't yet exist, add now + } else { // Doesn't yet exist, add now m_id = content_db.AddObject(m_type, m_icon, m_data, m_inst); } + return m_id; } @@ -362,10 +413,10 @@ void Object::PutItem(uint8 index, const EQ::ItemInstance* inst) if (m_inst && m_inst->IsType(EQ::item::ItemClassBag)) { if (inst) { m_inst->PutItem(index, *inst); - } - else { + } else { m_inst->DeleteItem(index); } + database.SaveWorldContainer(zone->GetZoneID(),m_id,m_inst); // This is _highly_ inefficient, but for now it will work: Save entire object to database Save(); @@ -373,19 +424,15 @@ void Object::PutItem(uint8 index, const EQ::ItemInstance* inst) } void Object::Close() { - if(user != nullptr) - { + if (user) { last_user = user; // put any remaining items from the world container back into the player's inventory to avoid item loss // if they close the container without removing all items - EQ::ItemInstance* container = m_inst; - if(container != nullptr) - { - for (uint8 i = EQ::invbag::SLOT_BEGIN; i <= EQ::invbag::SLOT_END; i++) - { - EQ::ItemInstance* inst = container->PopItem(i); - if(inst != nullptr) - { + auto container = m_inst; + if (container) { + for (uint8 i = EQ::invbag::SLOT_BEGIN; i <= EQ::invbag::SLOT_END; i++) { + auto inst = container->PopItem(i); + if (inst) { user->MoveItemToInventory(inst, true); } } @@ -393,6 +440,7 @@ void Object::Close() { user->SetTradeskillObject(nullptr); } + user = nullptr; } @@ -425,26 +473,34 @@ EQ::ItemInstance* Object::PopItem(uint8 index) void Object::CreateSpawnPacket(EQApplicationPacket* app) { app->SetOpcode(OP_GroundSpawn); + safe_delete_array(app->pBuffer); + app->pBuffer = new uchar[sizeof(Object_Struct)]; - app->size = sizeof(Object_Struct); + app->size = sizeof(Object_Struct); + memcpy(app->pBuffer, &m_data, sizeof(Object_Struct)); } void Object::CreateDeSpawnPacket(EQApplicationPacket* app) { app->SetOpcode(OP_ClickObject); + safe_delete_array(app->pBuffer); + app->pBuffer = new uchar[sizeof(ClickObject_Struct)]; - app->size = sizeof(ClickObject_Struct); + app->size = sizeof(ClickObject_Struct); + memset(app->pBuffer, 0, sizeof(ClickObject_Struct)); - ClickObject_Struct* co = (ClickObject_Struct*) app->pBuffer; - co->drop_id = m_data.drop_id; + + auto co = (ClickObject_Struct*) app->pBuffer; + + co->drop_id = m_data.drop_id; co->player_id = 0; } bool Object::Process(){ - if(m_type == ObjectTypes::Temporary && decay_timer.Enabled() && decay_timer.Check()) { + if (m_type == ObjectTypes::Temporary && decay_timer.Enabled() && decay_timer.Check()) { // Send click to all clients (removes entity on client) auto outapp = new EQApplicationPacket(OP_ClickObject, sizeof(ClickObject_Struct)); ClickObject_Struct* click_object = (ClickObject_Struct*)outapp->pBuffer; @@ -457,13 +513,15 @@ bool Object::Process(){ return false; } - if(m_ground_spawn && respawn_timer.Check()){ + if (m_ground_spawn && respawn_timer.Check()){ RandomSpawn(true); } - if (user != nullptr && !entity_list.GetClientByCharID(user->CharacterID())) { + if (user && !entity_list.GetClientByCharID(user->CharacterID())) { last_user = user; + user->SetTradeskillObject(nullptr); + user = nullptr; } @@ -471,18 +529,22 @@ bool Object::Process(){ } void Object::RandomSpawn(bool send_packet) { - if(!m_ground_spawn) + if (!m_ground_spawn) { return; + } m_data.x = zone->random.Real(m_min_x, m_max_x); m_data.y = zone->random.Real(m_min_y, m_max_y); if (m_data.z == BEST_Z_INVALID && zone->HasMap()) { glm::vec3 me; + me.x = m_data.x; me.y = m_data.y; me.z = 0; + glm::vec3 hit; + float best_z = zone->zonemap->FindClosestZ(me, &hit); if (best_z != BEST_Z_INVALID) { m_data.z = best_z + 0.1f; @@ -502,34 +564,44 @@ void Object::RandomSpawn(bool send_packet) { bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) { - if(m_ground_spawn) {//This is a Cool Groundspawn + if (m_ground_spawn) {//This is a Cool Groundspawn respawn_timer.Start(); } + if (m_type == ObjectTypes::Temporary) { - bool cursordelete = false; + bool cursor_delete = false; bool duplicate_lore = false; + if (m_inst && sender) { // if there is a lore conflict, delete the offending item from the server inventory // the client updates itself and takes care of sending "duplicate lore item" messages auto item = m_inst->GetItem(); if (sender->CheckLoreConflict(item)) { duplicate_lore = true; - int16 loreslot = sender->GetInv().HasItem(item->ID, 0, invWhereBank); - if (loreslot != INVALID_INDEX) { // if the duplicate is in the bank, delete it. - sender->DeleteItemInInventory(loreslot); + + const int16 lore_item_slot = sender->GetInv().HasItem(item->ID, 0, invWhereBank); + if (lore_item_slot != INVALID_INDEX) { // if the duplicate is in the bank, delete it. + sender->DeleteItemInInventory(lore_item_slot); + } else { // otherwise, we delete the new one + cursor_delete = true; } - else { - cursordelete = true; - } // otherwise, we delete the new one } if (item->RecastDelay) { if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM) { m_inst->SetRecastTimestamp( - database.GetItemRecastTimestamp(sender->CharacterID(), item->RecastType)); + database.GetItemRecastTimestamp( + sender->CharacterID(), + item->RecastType + ) + ); } else { m_inst->SetRecastTimestamp( - database.GetItemRecastTimestamp(sender->CharacterID(), item->ID)); + database.GetItemRecastTimestamp( + sender->CharacterID(), + item->ID + ) + ); } } @@ -546,13 +618,19 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) if (parse->EventPlayer(EVENT_PLAYER_PICKUP, sender, std::to_string(item->ID), GetID(), &args)) { auto outapp = new EQApplicationPacket(OP_ClickObject, sizeof(ClickObject_Struct)); + memcpy(outapp->pBuffer, click_object, sizeof(ClickObject_Struct)); - auto* co = (ClickObject_Struct*) outapp->pBuffer; + + auto co = (ClickObject_Struct*) outapp->pBuffer; + co->drop_id = 0; + entity_list.QueueClients(nullptr, outapp, false); + safe_delete(outapp); sender->SetTradeskillObject(nullptr); + user = nullptr; return true; @@ -573,28 +651,34 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) sender->DiscoverItem(item->ID); } - if (cursordelete) { // delete the item if it's a duplicate lore. We have to do this because the client expects the item packet + if (cursor_delete) { // delete the item if it's a duplicate lore. We have to do this because the client expects the item packet sender->DeleteItemInInventory(EQ::invslot::slotCursor, 1, true); } sender->DropItemQS(m_inst, true); - if(!m_ground_spawn) + if (!m_ground_spawn) { safe_delete(m_inst); + } // No longer using a tradeskill object sender->SetTradeskillObject(nullptr); + user = nullptr; } // Send click to all clients (removes entity on client) auto outapp = new EQApplicationPacket(OP_ClickObject, sizeof(ClickObject_Struct)); + memcpy(outapp->pBuffer, click_object, sizeof(ClickObject_Struct)); + entity_list.QueueClients(nullptr, outapp, false); + safe_delete(outapp); // Remove object content_db.DeleteObject(m_id); + if (!m_ground_spawn) { entity_list.RemoveEntity(GetID()); } @@ -610,36 +694,38 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) } else { // Tradeskill item auto outapp = new EQApplicationPacket(OP_ClickObjectAction, sizeof(ClickObjectAction_Struct)); - ClickObjectAction_Struct* coa = (ClickObjectAction_Struct*)outapp->pBuffer; + + auto coa = (ClickObjectAction_Struct*) outapp->pBuffer; //TODO: there is prolly a better way to do this. - coa->type = m_type; + coa->type = m_type; coa->unknown16 = 0x0a; - - coa->drop_id = click_object->drop_id; + coa->drop_id = click_object->drop_id; coa->player_id = click_object->player_id; - coa->icon = m_icon; + coa->icon = m_icon; + strn0cpy(coa->object_name, m_display_name, 64); //if this is not the main user, send them a close and a message - if (user == nullptr || user == sender) { + if (!user || user == sender) { coa->open = 0x01; - } - else { + } else { coa->open = 0x00; if (sender->ClientVersion() >= EQ::versions::ClientVersion::RoF) { - coa->drop_id = 0xFFFFFFFF; + coa->drop_id = UINT32_MAX; sender->Message(Chat::White, "Someone else is using that. Try again later."); } } sender->QueuePacket(outapp); + safe_delete(outapp); //if the object allready had a user, we are done - if(user != nullptr) - return(false); + if (user) { + return false; + } // Starting to use this object sender->SetTradeskillObject(this); @@ -649,18 +735,20 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) // Send items inside of container if (m_inst && m_inst->IsType(EQ::item::ItemClassBag)) { - //Clear out no-drop and no-rent items first if different player opens it - if(user != last_user) + if (user != last_user) { m_inst->ClearByFlags(byFlagSet, byFlagSet); + } auto outapp = new EQApplicationPacket(OP_ClientReady, 0); + sender->QueuePacket(outapp); + safe_delete(outapp); + for (uint8 i = EQ::invbag::SLOT_BEGIN; i <= EQ::invbag::SLOT_END; i++) { - const EQ::ItemInstance* inst = m_inst->GetItem(i); + auto inst = m_inst->GetItem(i); if (inst) { - //sender->GetInv().PutItem(i+4000,inst); sender->SendItemPacket(i, inst, ItemPacketWorldContainer); } } @@ -779,17 +867,17 @@ GroundSpawns* ZoneDatabase::LoadGroundSpawns( uint32 slot_id = 0; for (const auto& e : l) { - strcpy(gs->spawn[slot_id].name, e.name.c_str()); - - gs->spawn[slot_id].max_x = e.max_x; - gs->spawn[slot_id].max_y = e.max_y; - gs->spawn[slot_id].max_z = e.max_z; - gs->spawn[slot_id].min_x = e.min_x; - gs->spawn[slot_id].min_y = e.min_y; - gs->spawn[slot_id].heading = e.heading; - gs->spawn[slot_id].item = e.item; - gs->spawn[slot_id].max_allowed = e.max_allowed; - gs->spawn[slot_id].respawntimer = e.respawn_timer; + gs->spawn[slot_id].name = e.name; + gs->spawn[slot_id].max_x = e.max_x; + gs->spawn[slot_id].max_y = e.max_y; + gs->spawn[slot_id].max_z = e.max_z; + gs->spawn[slot_id].min_x = e.min_x; + gs->spawn[slot_id].min_y = e.min_y; + gs->spawn[slot_id].heading = e.heading; + gs->spawn[slot_id].item_id = e.item; + gs->spawn[slot_id].max_allowed = e.max_allowed; + gs->spawn[slot_id].respawn_timer = e.respawn_timer; + gs->spawn[slot_id].fix_z = e.fix_z; slot_id++; } diff --git a/zone/object.h b/zone/object.h index 7fd605626..114efd7e9 100644 --- a/zone/object.h +++ b/zone/object.h @@ -136,14 +136,14 @@ class Object: public Entity { public: // Loading object from database - Object(uint32 id, uint32 type, uint32 icon, const Object_Struct& data, const EQ::ItemInstance* inst); - Object(const EQ::ItemInstance* inst, char* name,float max_x,float min_x,float max_y,float min_y,float z,float heading,uint32 respawntimer); + Object(uint32 id, uint32 type, uint32 icon, const Object_Struct& data, const EQ::ItemInstance* inst = nullptr, bool fix_z = true); + Object(const EQ::ItemInstance* inst, const std::string& name, float max_x, float min_x, float max_y, float min_y, float z, float heading, uint32 respawn_timer, bool fix_z); // Loading object from client dropping item on ground Object(Client* client, const EQ::ItemInstance* inst); - Object(const EQ::ItemInstance *inst, float x, float y, float z, float heading, uint32 decay_time = 300000); - Object(const char *model, float x, float y, float z, float heading, uint8 type, uint32 decay_time = 0); + Object(const EQ::ItemInstance *inst, float x, float y, float z, float heading, uint32 decay_time = 300000, bool fix_z = true); + Object(const std::string& model, float x, float y, float z, float heading, uint8 type, uint32 decay_time = 0); - // Destructor +// Destructor ~Object(); bool Process(); bool IsGroundSpawn() { return m_ground_spawn; } @@ -161,6 +161,10 @@ public: void Depop(); void Repop(); + // Floating + inline bool IsFixZEnabled() const { return m_fix_z; }; + inline void SetFixZ(bool fix_z) { m_fix_z = fix_z; }; + //Decay functions void StartDecay() {decay_timer.Start();} @@ -233,6 +237,8 @@ protected: float m_min_y; bool m_ground_spawn; char m_display_name[64]; + bool m_fix_z; +protected: std::map o_EntityVariables; diff --git a/zone/zone.cpp b/zone/zone.cpp index 146747cdf..48533e7e7 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -297,7 +297,9 @@ bool Zone::LoadZoneObjects() } auto object = new Object(id, type, icon, data, inst); + object->SetDisplayName(e.display_name.c_str()); + entity_list.AddObject(object, false); if (type == ObjectTypes::Temporary && itemid) { @@ -353,39 +355,50 @@ bool Zone::IsSpecialBindLocation(const glm::vec4& location) //this also just loads into entity_list, not really into zone bool Zone::LoadGroundSpawns() { - GroundSpawns groundspawn; + GroundSpawns g; - memset(&groundspawn, 0, sizeof(groundspawn)); - int gsindex=0; - content_db.LoadGroundSpawns(zoneid, GetInstanceVersion(), &groundspawn); - uint32 ix=0; - char* name = nullptr; - uint32 gsnumber=0; - int added = 0; - for(gsindex=0;gsindex<50;gsindex++){ - if(groundspawn.spawn[gsindex].item>0 && groundspawn.spawn[gsindex].item