mirror of
https://github.com/EQEmu/Server.git
synced 2026-04-05 19:32:25 +00:00
- License was intended to be GPLv3 per earlier commit of GPLv3 LICENSE FILE - This is confirmed by the inclusion of libraries that are incompatible with GPLv2 - This is also confirmed by KLS and the agreement of KLS's predecessors - Added GPLv3 license headers to the compilable source files - Removed Folly licensing in strings.h since the string functions do not match the Folly functions and are standard functions - this must have been left over from previous implementations - Removed individual contributor license headers since the project has been under the "developer" mantle for many years - Removed comments on files that were previously automatically generated since they've been manually modified multiple times and there are no automatic scripts referencing them (removed in 2023)
1300 lines
32 KiB
C++
1300 lines
32 KiB
C++
/* EQEmu: EQEmulator
|
|
|
|
Copyright (C) 2001-2026 EQEmu Development Team
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "common/misc_functions.h"
|
|
#include "common/repositories/object_repository.h"
|
|
#include "common/strings.h"
|
|
#include "object_manipulation.h"
|
|
#include "zone/doors.h"
|
|
#include "zone/object.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");
|
|
}
|