eqemu-server/zone/gm_commands/object_manipulation.cpp
Alex King 71f47dbcef
[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
2024-02-01 04:42:51 -06:00

1283 lines
31 KiB
C++

#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<uint16>(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<std::string> move_x_options_positive;
std::vector<std::string> move_x_options_negative;
std::vector<std::string> move_y_options_positive;
std::vector<std::string> move_y_options_negative;
std::vector<std::string> move_z_options_positive;
std::vector<std::string> move_z_options_negative;
std::vector<std::string> rotate_options_positive;
std::vector<std::string> rotate_options_negative;
std::vector<std::string> size_options_positive;
std::vector<std::string> size_options_negative;
std::vector<std::string> 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<std::string> 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<uint16>(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");
}