#include "object_manipulation.h" #include "../doors.h" #include "../object.h" #include "../../common/misc_functions.h" #include "../../common/strings.h" #include "../../common/repositories/object_repository.h" #define MAX_CLIENT_MESSAGE_LENGTH 2000 void ObjectManipulation::CommandHandler(Client *c, const Seperator *sep) { const int arguments = sep->argnum; if (!arguments) { ObjectManipulation::SendSubcommands(c); return; } const bool is_add = !strcasecmp(sep->arg[1], "add"); const bool is_delete = !strcasecmp(sep->arg[1], "delete"); const bool is_edit = !strcasecmp(sep->arg[1], "edit"); const bool is_icon = !strcasecmp(sep->arg[1], "icon"); const bool is_incline = !strcasecmp(sep->arg[1], "incline"); const bool is_model = !strcasecmp(sep->arg[1], "model"); const bool is_move = !strcasecmp(sep->arg[1], "move"); const bool is_rotate = !strcasecmp(sep->arg[1], "rotate"); const bool is_save = !strcasecmp(sep->arg[1], "save"); const bool is_size = !strcasecmp(sep->arg[1], "size"); const bool is_solid_type = !strcasecmp(sep->arg[1], "solid_type"); const bool is_type = !strcasecmp(sep->arg[1], "type"); const bool is_undo = !strcasecmp(sep->arg[1], "undo"); if ( !is_add && !is_delete && !is_edit && !is_icon && !is_incline && !is_model && !is_move && !is_rotate && !is_save && !is_size && !is_solid_type && !is_type && !is_undo ) { ObjectManipulation::SendSubcommands(c); return; } if (is_add) { if (arguments < 2) { c->Message( Chat::White, "Usage: #object add [Type] [Model] [Icon] [Size] [Solid Type] [Incline] | Add an object" ); c->Message( Chat::White, "Note: Model must start with a letter, max length 16. Solid Types | 0 (Solid), 1 (Sometimes Non-Solid)" ); return; } Object_Struct od; memset(&od, 0, sizeof(od)); const uint32 type = Strings::ToUnsignedInt(sep->arg[2]) == ObjectTypes::StaticLocked ? ObjectTypes::StaticUnlocked : Strings::ToUnsignedInt(sep->arg[2]); if (type == ObjectTypes::StaticLocked) { c->Message( Chat::White, "Note: Object Type 0 will act like a tradeskill container and will not reflect " "size, solidtype, or incline values until you commit with '#object save', after " "which it will be unchangeable until you use '#object edit' and zone back in." ); } else if (type == ObjectTypes::Temporary) { c->Message( Chat::White, "Note: Object Type 1 is used for temporarily spawned ground spawns and dropped " "items, which are not supported with #object. See the 'ground_spawns' table in " "the database." ); return; } std::string name = sep->arg[3]; name = Strings::ToUpper(Strings::Replace(name, "'", "")); if (name[0] < 'A' || name[0] > 'Z') { c->Message(Chat::White, "Model name must start with a letter."); return; } const uint32 icon = Strings::ToUnsignedInt(sep->arg[4]); const float size = Strings::ToFloat(sep->arg[5]); const uint16 solid_type = static_cast(Strings::ToUnsignedInt(sep->arg[6])); const uint32 incline = Strings::ToUnsignedInt(sep->arg[7]); const uint32 zone_id = zone->GetZoneID(); const uint16 instance_version = zone->GetInstanceVersion(); od.incline = incline; od.object_type = type; od.size = size; od.solid_type = solid_type; od.x = c->GetX(); od.y = c->GetY(); od.z = c->GetZ(); od.heading = c->GetHeading(); od.zone_id = zone_id; od.zone_instance = instance_version; strn0cpy(od.object_name, name.c_str(), sizeof(od.object_name)); const auto &l = ObjectRepository::GetWhere( content_db, fmt::format( SQL( zoneid = {} AND (version = {} OR version = -1) AND xpos BETWEEN {:.2f} AND {:.2f} AND ypos BETWEEN {:.2f} AND {:.2f} AND zpos BETWEEN {:.2f} AND {:.2f} ), zone_id, instance_version, od.x - 0.2f, od.x + 0.2f, od.y - 0.2f, od.y + 0.2f, od.z - 0.2f, od.z + 0.2f ) ); for (const auto& e : l) { c->Message( Chat::White, fmt::format( "ID: {} Name: {} XYZ: {:.2f}, {:.2f}, {:.2f} Heading: {:.2f}", e.id, e.objectname, e.xpos, e.ypos, e.zpos, e.heading ).c_str() ); } if (!l.empty()) { c->Message(Chat::White, "An object already exists at this location."); return; } const uint32 object_id = (ObjectRepository::GetMaxId(content_db) + 1); Object* o = new Object(object_id, od.object_type, icon, od); entity_list.AddObject(o, true); const float position_offset = 10.0f * sin(c->GetHeading() / 256.0f * 3.14159265f); c->MovePC( c->GetX() - position_offset, c->GetY() - position_offset, c->GetZ(), c->GetHeading() ); const auto &save_saylink = Saylink::Silent( fmt::format( "#object save {}", object_id ), "save" ); c->Message( Chat::White, fmt::format( "Spawned object ID {} at {:.2f}, {:.2f}, {:.2f}, {:.2f}. You can {} it if you'd like.", object_id, od.x, od.y, od.z, od.heading, save_saylink ).c_str() ); c->SetObjectToolEntityId(o->GetID()); } else if (is_delete) { if (!c->GetObjectToolEntityId()) { c->Message(Chat::White, "You do not have a selected object."); return; } Object *o = entity_list.GetObjectByID(c->GetObjectToolEntityId()); if (!o) { c->Message(Chat::White, "You do not have a valid selected object."); return; } const uint32 object_id = o->GetDBID(); auto app = new EQApplicationPacket(); o->CreateDeSpawnPacket(app); entity_list.QueueClients(nullptr, app); entity_list.RemoveObject(o->GetID()); safe_delete(app); const int deleted_object = ObjectRepository::DeleteWhere( content_db, fmt::format( "id = {} AND zoneid = {} AND version = {}", object_id, zone->GetZoneID(), zone->GetInstanceVersion() ) ); if (deleted_object) { c->Message( Chat::White, fmt::format( "Successfully deleted Object ID {}.", object_id ).c_str() ); } else { c->Message( Chat::White, fmt::format( "Failed to delete Object ID {}.", object_id ).c_str() ); } } else if (is_edit) { Object *o = entity_list.GetObjectByID(c->GetObjectToolEntityId()); const uint32 object_id = o->GetDBID(); c->Message( Chat::White, fmt::format( "Object Selected ID [{}] Database ID [{}]", c->GetObjectToolEntityId(), object_id ).c_str() ); std::vector move_x_options_positive; std::vector move_x_options_negative; std::vector move_y_options_positive; std::vector move_y_options_negative; std::vector move_z_options_positive; std::vector move_z_options_negative; std::vector rotate_options_positive; std::vector rotate_options_negative; std::vector size_options_positive; std::vector size_options_negative; std::vector xyz_values = { "0.1", "1", "5", "10", "25", "50", "100" }; for (const auto &v: xyz_values) { const float current_x = o->GetX(); const float current_y = o->GetY(); const float current_z = o->GetZ(); const float current_heading = o->GetHeadingData(); const float new_x = current_x + Strings::ToFloat(v); const float new_y = current_y + Strings::ToFloat(v); const float new_z = current_z + Strings::ToFloat(v); const float new_heading = current_heading + Strings::ToFloat(v); move_x_options_positive.emplace_back( Saylink::Silent( fmt::format( "#object move {:.2f} {:.2f} {:.2f}", new_x, current_y, current_z ), v ) ); move_y_options_positive.emplace_back( Saylink::Silent( fmt::format( "#object move {:.2f} {:.2f} {:.2f}", current_x, new_y, current_z ), v ) ); move_z_options_positive.emplace_back( Saylink::Silent( fmt::format( "#object move {:.2f} {:.2f} {:.2f}", current_x, current_y, new_z ), v ) ); rotate_options_positive.emplace_back( Saylink::Silent( fmt::format( "#object rotate {:.2f}", new_heading ), v ) ); } for (auto v = xyz_values.rbegin(); v != xyz_values.rend(); ++v) { const float current_x = o->GetX(); const float current_y = o->GetY(); const float current_z = o->GetZ(); const float current_heading = o->GetHeadingData(); const float new_x = current_x - Strings::ToFloat(*v); const float new_y = current_y - Strings::ToFloat(*v); const float new_z = current_z - Strings::ToFloat(*v); const float new_heading = current_heading - Strings::ToFloat(*v); move_x_options_negative.emplace_back( Saylink::Silent( fmt::format( "#object move {:.2f} {:.2f} {:.2f}", new_x, current_y, current_z ), *v ) ); move_y_options_negative.emplace_back( Saylink::Silent( fmt::format( "#object move {:.2f} {:.2f} {:.2f}", current_x, new_y, current_z ), *v ) ); move_z_options_negative.emplace_back( Saylink::Silent( fmt::format( "#object move {:.2f} {:.2f} {:.2f}", current_x, current_y, new_z ), *v ) ); rotate_options_negative.emplace_back( Saylink::Silent( fmt::format( "#object rotate {:.2f}", new_heading ), *v ) ); } std::vector size_values = { "1", "5", "10", "25", "50", "100", "1000" }; for (const auto &v: size_values) { const float current_size = o->GetSize(); const float new_size = current_size + Strings::ToFloat(v); size_options_positive.emplace_back( Saylink::Silent(fmt::format("#door size {:.2f}", new_size), v) ); } for (auto v = size_values.rbegin(); v != size_values.rend(); ++v) { const float current_size = o->GetSize(); const float new_size = current_size - Strings::ToFloat(*v); size_options_negative.emplace_back( Saylink::Silent(fmt::format("#door edit {:.2f}", new_size, *v), *v) ); } c->Message( Chat::White, fmt::format( "Name [{}] [{}] [{}] [{}]", o->GetModelName(), Saylink::Silent("#object save", "Save"), Saylink::Silent("#object delete", "Delete"), Saylink::Silent("#object undo", "Undo") ).c_str() ); c->Message( Chat::White, fmt::format( "[{}] - [X] + [{}]", Strings::Implode(" | ", move_x_options_negative), Strings::Implode(" | ", move_x_options_positive) ).c_str() ); c->Message( Chat::White, fmt::format( "[{}] - [Y] + [{}]", Strings::Implode(" | ", move_y_options_negative), Strings::Implode(" | ", move_y_options_positive) ).c_str() ); c->Message( Chat::White, fmt::format( "[{}] - [Z] + [{}]", Strings::Implode(" | ", move_z_options_negative), Strings::Implode(" | ", move_z_options_positive) ).c_str() ); c->Message( Chat::White, fmt::format( "[{}] - [H] + [{}]", Strings::Implode(" | ", rotate_options_negative), Strings::Implode(" | ", rotate_options_positive) ).c_str() ); c->Message( Chat::White, fmt::format( "[{}] - [Size] + [{}]", Strings::Implode(" | ", size_options_negative), Strings::Implode(" | ", size_options_positive) ).c_str() ); return; } else if ( is_icon || is_incline || is_model || is_size || is_solid_type || is_type ) { if (!c->GetObjectToolEntityId()) { c->Message(Chat::White, "You do not have a selected object."); return; } Object *o = entity_list.GetObjectByID(c->GetObjectToolEntityId()); const uint32 object_id = o->GetDBID(); Object_Struct od; if (!o) { auto e = ObjectRepository::FindOne(content_db, object_id); if (!e.id) { c->Message( Chat::White, fmt::format( "Object ID {} does not exist.", object_id ).c_str() ); return; } if (e.zoneid != zone->GetZoneID()) { c->Message( Chat::White, fmt::format( "Object ID {} is not a part of this zone.", object_id ).c_str() ); return; } if (e.version != zone->GetInstanceVersion()) { c->Message( Chat::White, fmt::format( "Object ID {} is not a part of this instance version.", object_id ).c_str() ); return; } memset(&od, 0, sizeof(od)); od.zone_id = e.zoneid; od.zone_instance = e.version; od.object_type = e.type; if (od.object_type == ObjectTypes::StaticLocked) { e.type = ObjectTypes::StaticUnlocked; const int updated = ObjectRepository::UpdateOne(content_db, e); if (updated) { const std::string &reload_saylink = Saylink::Silent( "#reload objects", "reload" ); const auto &save_saylink = Saylink::Silent( fmt::format( "#object save {}", object_id ), "save" ); c->Message( Chat::White, fmt::format( "Object ID {} unlocked, you must {} to make changes then you can {}.", object_id, reload_saylink, save_saylink ).c_str() ); return; } } else if (od.object_type == ObjectTypes::Temporary) { c->Message( Chat::White, "Note: Object Type 1 is used for temporarily spawned ground spawns and dropped " "items, which are not supported with #object. See the 'ground_spawns' table in " "the database." ); return; } } uint32 icon = o->GetIcon(); o->GetObjectData(&od); if (is_icon) { if ( od.object_type <= ObjectTypes::Temporary || od.object_type == ObjectTypes::StaticUnlocked ) { c->Message( Chat::White, fmt::format( "Object ID {} is not a Tradeskill Object and does not support the 'icon' property.", object_id ).c_str() ); return; } if (!Strings::IsNumber(sep->arg[2])) { c->Message(Chat::White, "Invalid icon specified. Please enter a valid icon."); return; } icon = Strings::ToUnsignedInt(sep->arg[2]); o->SetIcon(icon); c->Message( Chat::White, fmt::format( "Object ID {} icon set to {}.", object_id, icon ).c_str() ); } else if (is_incline) { if (od.object_type != ObjectTypes::StaticUnlocked) { c->Message( Chat::White, fmt::format( "Object ID {} is not a static object and does not support the 'incline' property.", object_id ).c_str() ); return; } if (!Strings::IsNumber(sep->arg[2])) { c->Message(Chat::White, "Invalid incline specified. Please enter a valid incline."); return; } od.incline = Strings::ToUnsignedInt(sep->arg[2]); o->SetObjectData(&od); const auto &save_saylink = Saylink::Silent( fmt::format( "#object save {}", object_id ), "save" ); c->Message( Chat::White, fmt::format( "Object ID {} set to {} incline. You will need to {} to retain this change.", object_id, od.incline, save_saylink ).c_str() ); } else if (is_model) { const std::string &object_name = sep->argplus[2]; if (object_name[0] < 'A' || object_name[0] > 'Z') { c->Message(Chat::White, "Model names must begin with a letter."); return; } strn0cpy(od.object_name, object_name.c_str(), sizeof(od.object_name)); c->Message( Chat::White, fmt::format( "Object ID {} is now using model '{}'", object_id, object_name ).c_str() ); } else if (is_size) { if (od.object_type != ObjectTypes::StaticUnlocked) { c->Message( Chat::White, fmt::format( "Object ID {} is not a static object and does not support the 'size' property.", object_id ).c_str() ); return; } if (!Strings::IsNumber(sep->arg[2])) { c->Message(Chat::White, "Invalid size specified. Please provide a valid size."); return; } od.size = Strings::ToFloat(sep->arg[2]); o->SetObjectData(&od); if (od.size == 0.0f) { od.size = 100.0f; } const auto &save_saylink = Saylink::Silent( fmt::format( "#object save {}", object_id ), "save" ); c->Message( Chat::White, fmt::format( "Object ID {} set to {:.2f} size. You will need to {} to retain this change.", object_id, od.size, save_saylink ).c_str() ); } else if (is_solid_type) { if (od.object_type != ObjectTypes::StaticUnlocked) { c->Message( Chat::White, fmt::format( "Object ID {} is not a static object and does not support the 'solid_type' property.", object_id ).c_str() ); return; } if (!Strings::IsNumber(sep->arg[2])) { c->Message(Chat::White, "Invalid solid type specified. Please provide a valid solid type."); return; } od.solid_type = static_cast(Strings::ToUnsignedInt(sep->arg[2])); o->SetObjectData(&od); const auto &save_saylink = Saylink::Silent( fmt::format( "#object save {}", object_id ), "save" ); c->Message( Chat::White, fmt::format( "Object ID {} set to solid type {}. You will need to {} to retain this change.", object_id, od.solid_type, save_saylink ).c_str() ); } else if (is_type) { if (!Strings::IsNumber(sep->arg[2])) { c->Message(Chat::White, "Invalid type specified. Please enter a valid type."); return; } od.object_type = Strings::ToUnsignedInt(sep->arg[2]); if (od.object_type == ObjectTypes::StaticLocked) { const auto &save_saylink = Saylink::Silent( fmt::format( "#object save {}", object_id ), "save" ); c->Message( Chat::White, fmt::format( "Note: Static Objects will not reflect property changes until you {}.", save_saylink ).c_str() ); return; } else if (od.object_type == ObjectTypes::Temporary) { c->Message( Chat::White, "Note: Object Type 1 is used for temporarily spawned ground spawns and dropped " "items, which are not supported with #object. See the 'ground_spawns' table in " "the database." ); return; } o->SetType(od.object_type); c->Message( Chat::White, fmt::format( "Object ID {} set to type {}.", object_id, od.object_type ).c_str() ); } auto app = new EQApplicationPacket(); o->CreateDeSpawnPacket(app); entity_list.QueueClients(0, app); safe_delete(app); app = new EQApplicationPacket(); o->CreateSpawnPacket(app); entity_list.QueueClients(0, app); safe_delete(app); return; } else if (is_move) { if (!c->GetObjectToolEntityId()) { c->Message(Chat::White, "You do not have a selected object."); return; } if (arguments < 2) { c->Message(Chat::White, "Usage: #object move [X] [Y] [Z] | Move the selected object"); return; } Object_Struct od; Object *o = entity_list.GetObjectByID(c->GetObjectToolEntityId()); const uint32 object_id = o->GetDBID(); if (!o) { const auto &e = ObjectRepository::FindOne(content_db, object_id); if (!e.id) { c->Message( Chat::White, fmt::format( "Object ID {} does not exist.", object_id ).c_str() ); return; } od.zone_id = e.zoneid; od.zone_instance = e.version; od.object_type = e.type; if (e.zoneid != zone->GetZoneID()) { c->Message( Chat::White, fmt::format( "Object ID {} is not a part of this zone.", object_id ).c_str() ); return; } if (e.version != zone->GetInstanceVersion()) { c->Message( Chat::White, fmt::format( "Object ID {} is not a part of this instance version.", object_id ).c_str() ); return; } if (od.object_type == ObjectTypes::StaticLocked) { c->Message( Chat::White, fmt::format( "Object ID {} is not unlocked for editing. Use '#object edit' to unlock it.", object_id ).c_str() ); return; } else if (od.object_type == ObjectTypes::Temporary) { c->Message( Chat::White, fmt::format( "Object ID {} is a temporarily spawned ground spawn or dropped item, which is not supported with #object. See the 'ground_spawns' table in the database.", object_id ).c_str() ); return; } else if (od.object_type == ObjectTypes::StaticUnlocked) { const std::string &reload_saylink = Saylink::Silent( "#reload objects", "reload" ); const std::string &save_saylink = Saylink::Silent( fmt::format( "#object save {}", object_id ), "save" ); c->Message( Chat::White, fmt::format( "Object ID {} has been unlocked for editing, you must {} to make changes then you can {}.", object_id, reload_saylink, save_saylink ).c_str() ); } else { c->Message( Chat::White, fmt::format( "Object ID {} could not be located.", object_id ).c_str() ); return; } } const float x = Strings::IsFloat(sep->arg[2]) ? Strings::ToFloat(sep->arg[2]) : 0.0f; const float y = arguments >= 3 ? Strings::ToFloat(sep->arg[3]) : 0.0f; const float z = arguments >= 4 ? Strings::ToFloat(sep->arg[4]) : 0.0f; od.x = x; if (arguments >= 3) { od.y = y; } else { o->GetLocation(nullptr, &od.y, nullptr); } if (arguments >= 4) { od.z = z; } else { o->GetLocation(nullptr, nullptr, &od.z); } o->SetLocation(od.x, od.y, od.z); auto app = new EQApplicationPacket(); o->CreateDeSpawnPacket(app); entity_list.QueueClients(0, app); safe_delete(app); app = new EQApplicationPacket(); o->CreateSpawnPacket(app); entity_list.QueueClients(0, app); safe_delete(app); return; } else if (is_rotate) { if (!c->GetObjectToolEntityId()) { c->Message(Chat::White, "You do not have a selected object."); return; } if (!sep->IsNumber(2)) { c->Message(Chat::White, "Usage: #object rotate [Heading] | Rotate the selected object"); return; } Object *o = entity_list.GetObjectByID(c->GetObjectToolEntityId()); const uint32 object_id = o->GetDBID(); const float heading = Strings::ToFloat(sep->arg[2]); o->SetHeading(heading); auto app = new EQApplicationPacket(); o->CreateDeSpawnPacket(app); entity_list.QueueClients(0, app); safe_delete(app); app = new EQApplicationPacket(); o->CreateSpawnPacket(app); entity_list.QueueClients(0, app); safe_delete(app); return; } else if (is_save) { if (!c->GetObjectToolEntityId()) { c->Message(Chat::White, "You do not have a selected object."); return; } Object_Struct od; Object *o = entity_list.GetObjectByID(c->GetObjectToolEntityId()); uint32 object_id = o->GetDBID(); od.zone_id = 0; od.zone_instance = 0; od.object_type = 0; bool is_new = true; const auto &e = ObjectRepository::FindOne(content_db, object_id); if (e.id) { od.zone_id = e.zoneid; od.zone_instance = e.version; od.object_type = e.type; is_new = false; } if (od.zone_id && od.zone_id != zone->GetZoneID()) { object_id = 0; } if (object_id > 0 && od.zone_instance != zone->GetInstanceVersion()) { object_id = 0; } is_new |= (object_id == 0); o->GetObjectData(&od); od.object_type = o->GetType(); const uint32 icon = o->GetIcon(); if (od.object_type == ObjectTypes::StaticUnlocked) { od.object_type = ObjectTypes::StaticLocked; } int updated = 0; if (!is_new) { auto e = ObjectRepository::FindOne(content_db, object_id); e.xpos = od.x; e.ypos = od.y; e.zpos = od.z; e.heading = od.heading; e.objectname = od.object_name; e.type = od.object_type; e.icon = icon; e.size_percentage = od.size; e.solid_type = od.solid_type; e.incline = od.incline; updated = ObjectRepository::UpdateOne(content_db, e); } else if (!object_id) { auto e = ObjectRepository::NewEntity(); e.xpos = od.x; e.ypos = od.y; e.zpos = od.z; e.heading = od.heading; e.objectname = od.object_name; e.type = od.object_type; e.icon = icon; e.size_percentage = od.size; e.solid_type = od.solid_type; e.incline = od.incline; e.zoneid = zone->GetZoneID(); e.version = zone->GetInstanceVersion(); e = ObjectRepository::InsertOne(content_db, e); updated = e.id ? 2 : 0; object_id = e.id; } else { auto e = ObjectRepository::NewEntity(); e.id = object_id; e.xpos = od.x; e.ypos = od.y; e.zpos = od.z; e.heading = od.heading; e.objectname = od.object_name; e.type = od.object_type; e.icon = icon; e.size_percentage = od.size; e.solid_type = od.solid_type; e.incline = od.incline; e.zoneid = zone->GetZoneID(); e.version = zone->GetInstanceVersion(); updated = ObjectRepository::InsertOne(content_db, e).id ? 1 : 0; } if (!updated) { c->Message( Chat::White, fmt::format( "Object ID {} could not be saved.", object_id ).c_str() ); return; } if (is_new) { if (updated == 1) { c->Message( Chat::White, fmt::format( "Object ID {} has been saved.", object_id ).c_str() ); } else if (updated == 2) { c->Message( Chat::White, fmt::format( "Object ID {} has been created.", object_id ).c_str() ); } return; } c->Message( Chat::White, fmt::format( "Object ID {} has been saved.", object_id ).c_str() ); if (od.object_type == ObjectTypes::StaticLocked) { auto app = new EQApplicationPacket(); o->CreateDeSpawnPacket(app); entity_list.QueueClients(0, app); safe_delete(app); entity_list.RemoveObject(o->GetID()); auto door = DoorsRepository::NewEntity(); door.zone = zone->GetShortName(); door.id = 1000000000 + object_id; // Out of range of normal use for doors.id door.doorid = -1; // Client doesn't care if these are all the same door_id door.pos_x = od.x; door.pos_y = od.y; door.pos_z = od.z; door.heading = od.heading; door.name = Strings::Replace(od.object_name, "_ACTORDEF", ""); door.dest_zone = "NONE"; if ((door.size = od.size) == 0) { door.size = 100; } door.opentype = od.solid_type; switch (door.opentype) { case 0: door.opentype = 31; break; case 1: door.opentype = 9; break; } door.incline = od.incline; // unknown20 = optional incline value door.client_version_mask = 0xFFFFFFFF; Doors *doors = new Doors(door); entity_list.AddDoor(doors); app = new EQApplicationPacket(OP_SpawnDoor, sizeof(Door_Struct)); auto ds = (Door_Struct *) app->pBuffer; memset(ds, 0, sizeof(Door_Struct)); memcpy(ds->name, door.name.c_str(), 32); ds->xPos = door.pos_x; ds->yPos = door.pos_y; ds->zPos = door.pos_z; ds->heading = door.heading; ds->incline = door.incline; ds->size = door.size; ds->doorId = door.doorid; ds->opentype = door.opentype; ds->unknown0052[9] = 1; // *ptr-1 and *ptr-3 from EntityList::MakeDoorSpawnPacket() ds->unknown0052[11] = 1; entity_list.QueueClients(0, app); safe_delete(app); c->Message( Chat::White, fmt::format( "Object ID {} is now a static object and cannot be edited. Use '#object edit' to edit it again.", object_id ).c_str() ); } } else if (is_undo) { if (!c->GetObjectToolEntityId()) { c->Message(Chat::White, "You do not have a selected object."); return; } Object *o = entity_list.GetObjectByID(c->GetObjectToolEntityId()); const uint32 object_id = o->GetDBID(); if (o->GetType() == ObjectTypes::Temporary) { c->Message( Chat::White, "Note: Object Type 1 is used for temporarily spawned ground spawns and dropped " "items, which are not supported with #object. See the 'ground_spawns' table in " "the database." ); return; } auto app = new EQApplicationPacket(); o->CreateDeSpawnPacket(app); entity_list.QueueClients(0, app); entity_list.RemoveObject(o->GetID()); safe_delete(app); const auto &e = ObjectRepository::FindOne(content_db, object_id); if (!e.id) { c->Message( Chat::White, fmt::format( "Object ID {} does not exist.", object_id ).c_str() ); return; } Object_Struct od; memset(&od, 0, sizeof(od)); const uint32 icon = e.icon; od.x = e.xpos; od.y = e.ypos; od.z = e.zpos; od.heading = e.heading; od.object_type = e.type; od.size = e.size_percentage; od.solid_type = e.solid_type; od.incline = e.incline; strn0cpy(od.object_name, e.objectname.c_str(), sizeof(od.object_name)); if (od.object_type == ObjectTypes::StaticUnlocked) { od.object_type = ObjectTypes::StaticLocked; } o = new Object(object_id, od.object_type, icon, od); entity_list.AddObject(o, true); c->Message( Chat::White, fmt::format( "Object ID {} reloaded from database.", object_id ).c_str() ); } } void ObjectManipulation::CommandHeader(Client *c) { c->Message(Chat::White, "------------------------------------------------"); c->Message(Chat::White, "# Object Commands"); c->Message(Chat::White, "------------------------------------------------"); } void ObjectManipulation::SendSubcommands(Client *c) { ObjectManipulation::CommandHeader(c); c->Message( Chat::White, "Usage: #object add [Type] [Model] [Icon] [Size] [Solid Type] [Incline] | Add an object" ); c->Message( Chat::White, "Note: Model must start with a letter, max length 16. Solid Types | 0 (Solid), 1 (Sometimes Non-Solid)" ); c->Message(Chat::White, "Note: Only objects saved in the database can be copied to another instance"); c->Message(Chat::White, "Usage: #object delete | Delete the selected object"); c->Message(Chat::White, "Usage: #object edit | Edit the selected object"); c->Message(Chat::White, "Note: Static Object (Type 0) Properties: model, type, size, solid_type, incline"); c->Message(Chat::White, "Note: Tradeskill Object (Type 2+) Properties: model, type, icon"); c->Message(Chat::White, "Usage: #object icon [Icon] | Edit the selected object's icon"); c->Message(Chat::White, "Usage: #object incline [Incline] | Edit the selected object's incline"); c->Message(Chat::White, "Usage: #object model [Model] | Edit the selected object's model"); c->Message(Chat::White, "Usage: #object move [0|X] [Y] [Z] [H] | Move the selected object"); c->Message(Chat::White, "Note: Using 0 for X moves the object to your position, heading is optional"); c->Message(Chat::White, "Usage: #object rotate [Heading] | Rotate the selected object"); c->Message(Chat::White, "Usage: #object save | Save the selected object"); c->Message(Chat::White, "Usage: #object size [Size] | Edit the selected object's size"); c->Message(Chat::White, "Usage: #object solid_type [Solid Type] | Edit the selected object's solid type"); c->Message(Chat::White, "Usage: #object type [Type] | Edit the selected object's type"); c->Message(Chat::White, "Usage: #object undo | Reload the selected object from the database"); }