[Objects] Add fix_z column to ground spawns (#3992)

* [Objects] Add is_floating column to objects/ground spawns

# Notes
- Allows ground spawns/objects to float without having `FixZ()` called.

* Remove from object.

* Database version

* Fix

* Change to fix_z
This commit is contained in:
Alex King 2024-02-01 05:42:51 -05:00 committed by GitHub
parent 6297c56db2
commit 71f47dbcef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 368 additions and 232 deletions

View File

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

View File

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

View File

@ -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<uint32_t>(strtoul(row[11], nullptr, 10)) : 1;
e.comment = row[12] ? row[12] : "";
e.respawn_timer = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 300;
e.min_expansion = row[14] ? static_cast<int8_t>(atoi(row[14])) : -1;
e.max_expansion = row[15] ? static_cast<int8_t>(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<uint8_t>(strtoul(row[14], nullptr, 10)) : 1;
e.min_expansion = row[15] ? static_cast<int8_t>(atoi(row[15])) : -1;
e.max_expansion = row[16] ? static_cast<int8_t>(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<uint32_t>(strtoul(row[11], nullptr, 10)) : 1;
e.comment = row[12] ? row[12] : "";
e.respawn_timer = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 300;
e.min_expansion = row[14] ? static_cast<int8_t>(atoi(row[14])) : -1;
e.max_expansion = row[15] ? static_cast<int8_t>(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<uint8_t>(strtoul(row[14], nullptr, 10)) : 1;
e.min_expansion = row[15] ? static_cast<int8_t>(atoi(row[15])) : -1;
e.max_expansion = row[16] ? static_cast<int8_t>(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<uint32_t>(strtoul(row[11], nullptr, 10)) : 1;
e.comment = row[12] ? row[12] : "";
e.respawn_timer = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 300;
e.min_expansion = row[14] ? static_cast<int8_t>(atoi(row[14])) : -1;
e.max_expansion = row[15] ? static_cast<int8_t>(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<uint8_t>(strtoul(row[14], nullptr, 10)) : 1;
e.min_expansion = row[15] ? static_cast<int8_t>(atoi(row[15])) : -1;
e.max_expansion = row[16] ? static_cast<int8_t>(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) + "'");

View File

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

View File

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

View File

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

View File

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

View File

@ -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<std::string, std::string> o_EntityVariables;

View File

@ -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<SAYLINK_ITEM_ID){
EQ::ItemInstance* inst = nullptr;
inst = database.CreateItem(groundspawn.spawn[gsindex].item);
gsnumber=groundspawn.spawn[gsindex].max_allowed;
ix=0;
if(inst){
name = groundspawn.spawn[gsindex].name;
for(ix=0;ix<gsnumber;ix++){
memset(&g, 0, sizeof(g));
content_db.LoadGroundSpawns(zoneid, GetInstanceVersion(), &g);
uint32 added = 0;
for (uint16 slot_id = 0; slot_id < 50; slot_id++) {
if (EQ::ValueWithin(g.spawn[slot_id].item_id, 1, (SAYLINK_ITEM_ID - 1))) {
auto inst = database.CreateItem(g.spawn[slot_id].item_id);
const uint32 max_allowed = g.spawn[slot_id].max_allowed;
if (inst) {
for (uint32 i = 0; i < max_allowed; i++) {
auto object = new Object(
inst, name, groundspawn.spawn[gsindex].max_x,
groundspawn.spawn[gsindex].min_x, groundspawn.spawn[gsindex].max_y,
groundspawn.spawn[gsindex].min_y, groundspawn.spawn[gsindex].max_z,
groundspawn.spawn[gsindex].heading,
groundspawn.spawn[gsindex].respawntimer); // new object with id of 10000+
inst,
g.spawn[slot_id].name,
g.spawn[slot_id].max_x,
g.spawn[slot_id].min_x,
g.spawn[slot_id].max_y,
g.spawn[slot_id].min_y,
g.spawn[slot_id].max_z,
g.spawn[slot_id].heading,
g.spawn[slot_id].respawn_timer,
g.spawn[slot_id].fix_z
);
entity_list.AddObject(object, false);
added++;
}
safe_delete(inst);
}
}
}
LogInfo("Loaded [{}] ground spawns", Strings::Commify(added));
LogInfo(
"Loaded [{}] Ground Spawn{}",
Strings::Commify(added),
added != 1 ? "s" : ""
);
return(true);
}