From feeac2e0413a9ae03c675330375f2c381724ed1e Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 8 Apr 2024 15:11:59 -0500 Subject: [PATCH 1/4] feat: refactor slash commands system into more scalable system (#1510) * WIP, but working * Scaffolding * testing and making it compile again * move all commands to functions * renaming to compile * fix failing tests idk how these werent failing before. Seems to have been magic. * move commandss into their namespace make help command useful fix mac error TODO: remove the multiple not founds/ rework the structure to split into help and handling * Just need to fill out the fields, but it's all there templated * Add all aliases, register missing commands * All help text * remove test logs * improvements pass through added code for optimizations and cleanup as well as reduce the amount of scoping for readability and maintainability * Update SlashCommandHandler.cpp * only save command if it is a GM command * simplify if checks * remove broken delimiter * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp --------- Co-authored-by: David Markowitz --- dCommon/dEnums/eGameMessageType.h | 5 +- dGame/dGameMessages/GameMessages.cpp | 12 + dGame/dGameMessages/GameMessages.h | 2 + dGame/dUtilities/SlashCommandHandler.cpp | 2321 ++++++++++++----- dGame/dUtilities/SlashCommandHandler.h | 121 +- dWorldServer/WorldServer.cpp | 4 + .../dEnumsTests/MagicEnumTests.cpp | 2 +- 7 files changed, 1793 insertions(+), 674 deletions(-) diff --git a/dCommon/dEnums/eGameMessageType.h b/dCommon/dEnums/eGameMessageType.h index e3fc22b6..8e6980d6 100644 --- a/dCommon/dEnums/eGameMessageType.h +++ b/dCommon/dEnums/eGameMessageType.h @@ -790,9 +790,10 @@ enum class eGameMessageType : uint16_t { GET_MISSION_TYPE_STATES = 853, GET_TIME_PLAYED = 854, SET_MISSION_VIEWED = 855, - SLASH_COMMAND_TEXT_FEEDBACK = 856, - HANDLE_SLASH_COMMAND_KORE_DEBUGGER = 857, + HKX_VEHICLE_LOADED = 856, + SLASH_COMMAND_TEXT_FEEDBACK = 857, BROADCAST_TEXT_TO_CHATBOX = 858, + HANDLE_SLASH_COMMAND_KORE_DEBUGGER = 859, OPEN_PROPERTY_MANAGEMENT = 860, OPEN_PROPERTY_VENDOR = 861, VOTE_ON_PROPERTY = 862, diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 291dc151..3ef9f3b5 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -6197,3 +6197,15 @@ void GameMessages::HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Ent if (!characterComponent) return; characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY); } + +void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string text) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(eGameMessageType::SLASH_COMMAND_TEXT_FEEDBACK); + bitStream.Write(text.size()); + bitStream.Write(text); + auto sysAddr = entity->GetSystemAddress(); + SEND_PACKET; +} diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 96bbf7c3..b842710e 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -664,6 +664,8 @@ namespace GameMessages { void HandleRemoveDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void HandleConfirmDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); + + void SendSlashCommandFeedbackText(Entity* entity, std::u16string text); }; #endif // GAMEMESSAGES_H diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 0b26e85a..34ae013c 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -1,6 +1,6 @@ /* * Darkflame Universe - * Copyright 2018 + * Copyright 2024 */ #include "SlashCommandHandler.h" @@ -11,15 +11,6 @@ #include #include "dZoneManager.h" -#include /* defines FILENAME_MAX */ -#ifdef _WIN32 -#include -#define GetCurrentDir _getcwd -#else -#include -#define GetCurrentDir getcwd -#endif - #include "Metrics.hpp" #include "User.h" @@ -88,112 +79,967 @@ #include "CDZoneTableTable.h" #include "ePlayerFlag.h" #include "dNavMesh.h" +#include -void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) { - auto commandCopy = command; - // Sanity check that a command was given - if (command.empty() || command.front() != u'/') return; - commandCopy.erase(commandCopy.begin()); +namespace { + std::vector CommandInfos; + std::map RegisteredCommands; +} - // Split the command by spaces - std::string chatCommand; - std::vector args; - auto wideCommand = GeneralUtils::SplitString(commandCopy, u' '); - if (wideCommand.empty()) return; +void SlashCommandHandler::RegisterCommand(Command command) { + if (command.aliases.empty()) { + LOG("Command %s has no aliases! Skipping!", command.help.c_str()); + return; + } - // Convert the command to lowercase - chatCommand = GeneralUtils::UTF16ToWTF8(wideCommand.front()); - std::transform(chatCommand.begin(), chatCommand.end(), chatCommand.begin(), ::tolower); - wideCommand.erase(wideCommand.begin()); - - // Convert the arguements to not u16strings - for (auto wideArg : wideCommand) args.push_back(GeneralUtils::UTF16ToWTF8(wideArg)); - - User* user = UserManager::Instance()->GetUser(sysAddr); - if ((chatCommand == "setgmlevel" || chatCommand == "makegm" || chatCommand == "gmlevel") && user->GetMaxGMLevel() > eGameMasterLevel::CIVILIAN) { - if (args.size() != 1) return; - - const auto level_intermed = GeneralUtils::TryParse(args[0]); - - if (!level_intermed) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid gm level."); - return; + for (const auto& alias : command.aliases) { + LOG_DEBUG("Registering command %s", alias.c_str()); + auto [_, success] = RegisteredCommands.try_emplace(alias, command); + // Don't allow duplicate commands + if (!success) { + LOG_DEBUG("Command alias %s is already registered! Skipping!", alias.c_str()); + continue; } - eGameMasterLevel level = static_cast(level_intermed.value()); + } -#ifndef DEVELOPER_SERVER - if (user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER) { - level = eGameMasterLevel::CIVILIAN; + CommandInfos.push_back(command); +}; + +void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* entity, const SystemAddress& sysAddr) { + auto input = GeneralUtils::UTF16ToWTF8(chat); + if (input.empty() || input.front() != '/') return; + const auto pos = input.find(' '); + std::string command = input.substr(1, pos - 1); + + std::string args; + // make sure the space exists and isn't the last character + if (pos != std::string::npos && pos != input.size()) args = input.substr(input.find(' ') + 1); + LOG_DEBUG("Handling command \"%s\" with args \"%s\"", command.c_str(), args.c_str()); + + const auto commandItr = RegisteredCommands.find(command); + std::string error; + if (commandItr != RegisteredCommands.end()) { + auto& [alias, commandHandle] = *commandItr; + if (entity->GetGMLevel() >= commandHandle.requiredLevel) { + if (commandHandle.requiredLevel > eGameMasterLevel::CIVILIAN) Database::Get()->InsertSlashCommandUsage(entity->GetObjectID(), input); + commandHandle.handle(entity, sysAddr, args); + } else if (entity->GetGMLevel() != eGameMasterLevel::CIVILIAN) { + // We don't need to tell civilians they aren't high enough level + error = "You are not high enough GM level to use \"" + command + "\""; } -#endif + } else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) { + // We don't need to tell civilians commands don't exist + error = "Command " + command + " does not exist!"; + } - if (level > user->GetMaxGMLevel()) { - level = user->GetMaxGMLevel(); - } + if (!error.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(error)); + } +} - if (level == entity->GetGMLevel()) return; - bool success = user->GetMaxGMLevel() >= level; +void SlashCommandHandler::Startup() { - if (success) { + // Register Dev Commands + Command SetGMLevelCommand{ + .help = "Change the GM level of your character", + .info = "Within the authorized range of levels for the current account, changes the character's game master level to the specified value. This is required to use certain commands", + .aliases = { "setgmlevel", "makegm", "gmlevel" }, + .handle = DEVGMCommands::SetGMLevel, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(SetGMLevelCommand); - if (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && level == eGameMasterLevel::CIVILIAN) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); - } else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN && level > eGameMasterLevel::CIVILIAN) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + Command ToggleNameplateCommand{ + .help = "Toggle the visibility of your nameplate. This must be enabled by a server admin to be used.", + .info = "Turns the nameplate above your head that is visible to other players off and on. This must be enabled by a server admin to be used.", + .aliases = { "togglenameplate", "tnp" }, + .handle = DEVGMCommands::ToggleNameplate, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ToggleNameplateCommand); + + Command ToggleSkipCinematicsCommand{ + .help = "Toggle Skipping Cinematics", + .info = "Skips mission and world load related cinematics", + .aliases = { "toggleskipcinematics", "tsc" }, + .handle = DEVGMCommands::ToggleSkipCinematics, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ToggleSkipCinematicsCommand); + + Command KillCommand{ + .help = "Smash a user", + .info = "Smashes the character whom the given user is playing", + .aliases = { "kill" }, + .handle = DEVGMCommands::Kill, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(KillCommand); + + Command MetricsCommand{ + .help = "Display server metrics", + .info = "Prints some information about the server's performance", + .aliases = { "metrics" }, + .handle = DEVGMCommands::Metrics, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(MetricsCommand); + + Command AnnounceCommand{ + .help = " Send and announcement", + .info = "Sends an announcement. `/setanntitle` and `/setannmsg` must be called first to configure the announcement.", + .aliases = { "announce" }, + .handle = DEVGMCommands::Announce, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AnnounceCommand); + + Command SetAnnTitleCommand{ + .help = "Sets the title of an announcement", + .info = "Sets the title of an announcement. Use with `/setannmsg` and `/announce`", + .aliases = { "setanntitle" }, + .handle = DEVGMCommands::SetAnnTitle, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetAnnTitleCommand); + + Command SetAnnMsgCommand{ + .help = "Sets the message of an announcement", + .info = "Sets the message of an announcement. Use with `/setannmtitle` and `/announce`", + .aliases = { "setannmsg" }, + .handle = DEVGMCommands::SetAnnMsg, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetAnnMsgCommand); + + Command ShutdownUniverseCommand{ + .help = "Sends a shutdown message to the master server", + .info = "Sends a shutdown message to the master server. This will send an announcement to all players that the universe will shut down in 10 minutes.", + .aliases = { "shutdownuniverse" }, + .handle = DEVGMCommands::ShutdownUniverse + }; + RegisterCommand(ShutdownUniverseCommand); + + Command SetMinifigCommand{ + .help = "Alters your player's minifig", + .info = "Alters your player's minifig. Body part can be one of \"Eyebrows\", \"Eyes\", \"HairColor\", \"HairStyle\", \"Pants\", \"LeftHand\", \"Mouth\", \"RightHand\", \"Shirt\", or \"Hands\". Changing minifig parts could break the character so this command is limited to GMs.", + .aliases = { "setminifig" }, + .handle = DEVGMCommands::SetMinifig, + .requiredLevel = eGameMasterLevel::FORUM_MODERATOR + }; + RegisterCommand(SetMinifigCommand); + + Command TestMapCommand{ + .help = "Transfers you to the given zone", + .info = "Transfers you to the given zone by id and clone id. Add \"force\" to skip checking if the zone is accessible (this can softlock your character, though, if you e.g. try to teleport to Frostburgh).", + .aliases = { "testmap", "tm" }, + .handle = DEVGMCommands::TestMap, + .requiredLevel = eGameMasterLevel::FORUM_MODERATOR + }; + RegisterCommand(TestMapCommand); + + Command ReportProxPhysCommand{ + .help = "Display proximity sensor info", + .info = "Prints to console the position and radius of proximity sensors.", + .aliases = { "reportproxphys" }, + .handle = DEVGMCommands::ReportProxPhys, + .requiredLevel = eGameMasterLevel::OPERATOR + }; + RegisterCommand(ReportProxPhysCommand); + + Command SpawnPhysicsVertsCommand{ + .help = "Spawns a 1x1 brick at all vertices of phantom physics objects", + .info = "Spawns a 1x1 brick at all vertices of phantom physics objects", + .aliases = { "spawnphysicsverts" }, + .handle = DEVGMCommands::SpawnPhysicsVerts, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpawnPhysicsVertsCommand); + + Command TeleportCommand{ + .help = "Teleports you", + .info = "Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z)", + .aliases = { "teleport", "tele", "tp" }, + .handle = DEVGMCommands::Teleport, + .requiredLevel = eGameMasterLevel::JUNIOR_DEVELOPER + }; + RegisterCommand(TeleportCommand); + + Command ActivateSpawnerCommand{ + .help = "Activates spawner by name", + .info = "Activates spawner by name", + .aliases = { "activatespawner" }, + .handle = DEVGMCommands::ActivateSpawner, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ActivateSpawnerCommand); + + Command AddMissionCommand{ + .help = "Accepts the mission, adding it to your journal.", + .info = "Accepts the mission, adding it to your journal.", + .aliases = { "addmission" }, + .handle = DEVGMCommands::AddMission, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AddMissionCommand); + + Command BoostCommand{ + .help = "Adds boost to a vehicle", + .info = "Adds a passive boost action if you are in a vehicle. If time is given it will end after that amount of time", + .aliases = { "boost" }, + .handle = DEVGMCommands::Boost, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BoostCommand); + + Command UnboostCommand{ + .help = "Removes a passive vehicle boost", + .info = "Removes a passive vehicle boost", + .aliases = { "unboost" }, + .handle = DEVGMCommands::Unboost, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(UnboostCommand); + + Command BuffCommand{ + .help = "Applies a buff", + .info = "Applies a buff with the given id for the given number of seconds", + .aliases = { "buff" }, + .handle = DEVGMCommands::Buff, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BuffCommand); + + Command BuffMeCommand{ + .help = "Sets health, armor, and imagination to 999", + .info = "Sets health, armor, and imagination to 999", + .aliases = { "buffme" }, + .handle = DEVGMCommands::BuffMe, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BuffMeCommand); + + Command BuffMedCommand{ + .help = "Sets health, armor, and imagination to 9", + .info = "Sets health, armor, and imagination to 9", + .aliases = { "buffmed" }, + .handle = DEVGMCommands::BuffMed, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BuffMedCommand); + + Command ClearFlagCommand{ + .help = "Clear a player flag", + .info = "Removes the given health or inventory flag from your player. Equivalent of calling `/setflag off `", + .aliases = { "clearflag" }, + .handle = DEVGMCommands::ClearFlag, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ClearFlagCommand); + + Command CompleteMissionCommand{ + .help = "Completes the mission", + .info = "Completes the mission, removing it from your journal", + .aliases = { "completemission" }, + .handle = DEVGMCommands::CompleteMission, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CompleteMissionCommand); + + Command CreatePrivateCommand{ + .help = "Creates a private zone with password", + .info = "Creates a private zone with password", + .aliases = { "createprivate" }, + .handle = DEVGMCommands::CreatePrivate, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CreatePrivateCommand); + + Command DebugUiCommand{ + .help = "Toggle Debug UI", + .info = "Toggle Debug UI", + .aliases = { "debugui" }, + .handle = DEVGMCommands::DebugUi, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DebugUiCommand); + + Command DismountCommand{ + .help = "Dismounts you from the vehicle or mount", + .info = "Dismounts you from the vehicle or mount", + .aliases = { "dismount" }, + .handle = DEVGMCommands::Dismount, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DismountCommand); + + Command ReloadConfigCommand{ + .help = "Reload Server configs", + .info = "Reloads the server with the new config values.", + .aliases = { "reloadconfig", "reload-config" }, + .handle = DEVGMCommands::ReloadConfig, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ReloadConfigCommand); + + Command ForceSaveCommand{ + .help = "Force save your player", + .info = "While saving to database usually happens on regular intervals and when you disconnect from the server, this command saves your player's data to the database", + .aliases = { "forcesave", "force-save" }, + .handle = DEVGMCommands::ForceSave, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ForceSaveCommand); + + Command FreecamCommand{ + .help = "Toggles freecam mode", + .info = "Toggles freecam mode", + .aliases = { "freecam" }, + .handle = DEVGMCommands::Freecam, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(FreecamCommand); + + Command FreeMoneyCommand{ + .help = "Give yourself coins", + .info = "Give yourself coins", + .aliases = { "freemoney", "givemoney", "money", "givecoins", "coins"}, + .handle = DEVGMCommands::FreeMoney, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(FreeMoneyCommand); + + Command GetNavmeshHeightCommand{ + .help = "Display the navmesh height", + .info = "Display the navmesh height at your current position", + .aliases = { "getnavmeshheight" }, + .handle = DEVGMCommands::GetNavmeshHeight, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GetNavmeshHeightCommand); + + Command GiveUScoreCommand{ + .help = "Gives uscore", + .info = "Gives uscore", + .aliases = { "giveuscore" }, + .handle = DEVGMCommands::GiveUScore, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GiveUScoreCommand); + + Command GmAddItemCommand{ + .help = "Give yourseld an item", + .info = "Adds the given item to your inventory by id", + .aliases = { "gmadditem", "give" }, + .handle = DEVGMCommands::GmAddItem, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GmAddItemCommand); + + Command InspectCommand{ + .help = "Inspect an object", + .info = "Finds the closest entity with the given component or LNV variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. See detailed usage in the DLU docs", + .aliases = { "inspect" }, + .handle = DEVGMCommands::Inspect, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(InspectCommand); + + Command ListSpawnsCommand{ + .help = "List spawn points for players", + .info = "Lists all the character spawn points in the zone. Additionally, this command will display the current scene that plays when the character lands in the next zone, if there is one.", + .aliases = { "list-spawns", "listspawns" }, + .handle = DEVGMCommands::ListSpawns, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ListSpawnsCommand); + + Command LocRowCommand{ + .help = "Prints the your current position and rotation information to the console", + .info = "Prints the your current position and rotation information to the console", + .aliases = { "locrow" }, + .handle = DEVGMCommands::LocRow, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(LocRowCommand); + + Command LookupCommand{ + .help = "Lookup an object", + .info = "Searches through the Objects table in the client SQLite database for items whose display name, name, or description contains the query. Query can be multiple words delimited by spaces.", + .aliases = { "lookup" }, + .handle = DEVGMCommands::Lookup, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(LookupCommand); + + Command PlayAnimationCommand{ + .help = "Play an animation with given ID", + .info = "Play an animation with given ID", + .aliases = { "playanimation", "playanim" }, + .handle = DEVGMCommands::PlayAnimation, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayAnimationCommand); + + Command PlayEffectCommand{ + .help = "Plays an effect", + .info = "Plays an effect", + .aliases = { "playeffect" }, + .handle = DEVGMCommands::PlayEffect, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayEffectCommand); + + Command PlayLvlFxCommand{ + .help = "Plays the level up animation on your character", + .info = "Plays the level up animation on your character", + .aliases = { "playlvlfx" }, + .handle = DEVGMCommands::PlayLvlFx, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayLvlFxCommand); + + Command PlayRebuildFxCommand{ + .help = "Plays the quickbuild animation on your character", + .info = "Plays the quickbuild animation on your character", + .aliases = { "playrebuildfx" }, + .handle = DEVGMCommands::PlayRebuildFx, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayRebuildFxCommand); + + Command PosCommand{ + .help = "Displays your current position in chat and in the console", + .info = "Displays your current position in chat and in the console", + .aliases = { "pos" }, + .handle = DEVGMCommands::Pos, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PosCommand); + + Command RefillStatsCommand{ + .help = "Refills health, armor, and imagination to their maximum level", + .info = "Refills health, armor, and imagination to their maximum level", + .aliases = { "refillstats" }, + .handle = DEVGMCommands::RefillStats, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RefillStatsCommand); + + Command ReforgeCommand{ + .help = "Reforges an item", + .info = "Reforges an item", + .aliases = { "reforge" }, + .handle = DEVGMCommands::Reforge, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ReforgeCommand); + + Command ResetMissionCommand{ + .help = "Sets the state of the mission to accepted but not yet started", + .info = "Sets the state of the mission to accepted but not yet started", + .aliases = { "resetmission" }, + .handle = DEVGMCommands::ResetMission, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ResetMissionCommand); + + Command RotCommand{ + .help = "Displays your current rotation in chat and in the console", + .info = "Displays your current rotation in chat and in the console", + .aliases = { "rot" }, + .handle = DEVGMCommands::Rot, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RotCommand); + + Command RunMacroCommand{ + .help = "Run a macro", + .info = "Runs any command macro found in `./res/macros/`", + .aliases = { "runmacro" }, + .handle = DEVGMCommands::RunMacro, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RunMacroCommand); + + Command SetControlSchemeCommand{ + .help = "Sets the character control scheme to the specified number", + .info = "Sets the character control scheme to the specified number", + .aliases = { "setcontrolscheme" }, + .handle = DEVGMCommands::SetControlScheme, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetControlSchemeCommand); + + Command SetCurrencyCommand{ + .help = "Sets your coins", + .info = "Sets your coins", + .aliases = { "setcurrency", "setcoins" }, + .handle = DEVGMCommands::SetCurrency, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetCurrencyCommand); + + Command SetFlagCommand{ + .help = "Set a player flag", + .info = "Sets the given inventory or health flag to the given value, where value can be one of \"on\" or \"off\". If no value is given, by default this adds the flag to your character (equivalent of calling `/setflag on `)", + .aliases = { "setflag" }, + .handle = DEVGMCommands::SetFlag, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetFlagCommand); + + Command SetInventorySizeCommand{ + .help = "Set your inventory size", + .info = "Sets your inventory size to the given size. If `inventory` is provided, the number or string will be used to set that inventory to the requested size", + .aliases = { "setinventorysize", "setinvsize", "setinvensize" }, + .handle = DEVGMCommands::SetInventorySize, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetInventorySizeCommand); + + Command SetUiStateCommand{ + .help = "Changes UI state", + .info = "Changes UI state", + .aliases = { "setuistate" }, + .handle = DEVGMCommands::SetUiState, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetUiStateCommand); + + Command SpawnCommand{ + .help = "Spawns an object at your location by id", + .info = "Spawns an object at your location by id", + .aliases = { "spawn" }, + .handle = DEVGMCommands::Spawn, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpawnCommand); + + Command SpawnGroupCommand{ + .help = "", + .info = "", + .aliases = { "spawngroup" }, + .handle = DEVGMCommands::SpawnGroup, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpawnGroupCommand); + + Command SpeedBoostCommand{ + .help = "Set the players speed multiplier", + .info = "Sets the speed multiplier to the given amount. `/speedboost 1.5` will set the speed multiplier to 1.5x the normal speed", + .aliases = { "speedboost" }, + .handle = DEVGMCommands::SpeedBoost, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpeedBoostCommand); + + Command StartCelebrationCommand{ + .help = "Starts a celebration effect on your character", + .info = "Starts a celebration effect on your character", + .aliases = { "startcelebration" }, + .handle = DEVGMCommands::StartCelebration, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(StartCelebrationCommand); + + Command StopEffectCommand{ + .help = "Stops the given effect", + .info = "Stops the given effect", + .aliases = { "stopeffect" }, + .handle = DEVGMCommands::StopEffect, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(StopEffectCommand); + + Command ToggleCommand{ + .help = "Toggles UI state", + .info = "Toggles UI state", + .aliases = { "toggle" }, + .handle = DEVGMCommands::Toggle, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ToggleCommand); + + Command TpAllCommand{ + .help = "Teleports all characters to your current position", + .info = "Teleports all characters to your current position", + .aliases = { "tpall" }, + .handle = DEVGMCommands::TpAll, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(TpAllCommand); + + Command TriggerSpawnerCommand{ + .help = "Triggers spawner by name", + .info = "Triggers spawner by name", + .aliases = { "triggerspawner" }, + .handle = DEVGMCommands::TriggerSpawner, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(TriggerSpawnerCommand); + + Command UnlockEmoteCommand{ + .help = "Unlocks for your character the emote of the given id", + .info = "Unlocks for your character the emote of the given id", + .aliases = { "unlock-emote", "unlockemote" }, + .handle = DEVGMCommands::UnlockEmote, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(UnlockEmoteCommand); + + Command SetLevelCommand{ + .help = "Set player level", + .info = "Sets the using entities level to the requested level. Takes an optional parameter of an in-game players username to set the level of", + .aliases = { "setlevel" }, + .handle = DEVGMCommands::SetLevel, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetLevelCommand); + + Command SetSkillSlotCommand{ + .help = "Set an action slot to a specific skill", + .info = "Set an action slot to a specific skill", + .aliases = { "setskillslot" }, + .handle = DEVGMCommands::SetSkillSlot, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetSkillSlotCommand); + + Command SetFactionCommand{ + .help = "Set the players faction", + .info = "Clears the users current factions and sets it", + .aliases = { "setfaction" }, + .handle = DEVGMCommands::SetFaction, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetFactionCommand); + + Command AddFactionCommand{ + .help = "Add the faction to the users list of factions", + .info = "Add the faction to the users list of factions", + .aliases = { "addfaction" }, + .handle = DEVGMCommands::AddFaction, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AddFactionCommand); + + Command GetFactionsCommand{ + .help = "Shows the player's factions", + .info = "Shows the player's factions", + .aliases = { "getfactions" }, + .handle = DEVGMCommands::GetFactions, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GetFactionsCommand); + + Command SetRewardCodeCommand{ + .help = "Set a reward code for your account", + .info = "Sets the rewardcode for the account you are logged into if it's a valid rewardcode, See cdclient table `RewardCodes`", + .aliases = { "setrewardcode" }, + .handle = DEVGMCommands::SetRewardCode, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetRewardCodeCommand); + + Command CrashCommand{ + .help = "Crash the server", + .info = "Crashes the server", + .aliases = { "crash", "pumpkin" }, + .handle = DEVGMCommands::Crash, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CrashCommand); + + Command RollLootCommand{ + .help = "Simulate loot rolls", + .info = "Given a `loot matrix index`, look for `item id` in that matrix `amount` times and print to the chat box statistics of rolling that loot matrix.", + .aliases = { "rollloot", "roll-loot" }, + .handle = DEVGMCommands::RollLoot, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RollLootCommand); + + Command CastSkillCommand{ + .help = "Casts the skill as the player", + .info = "Casts the skill as the player", + .aliases = { "castskill" }, + .handle = DEVGMCommands::CastSkill, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CastSkillCommand); + + Command DeleteInvenCommand{ + .help = "Delete all items from a specified inventory", + .info = "Delete all items from a specified inventory", + .aliases = { "deleteinven" }, + .handle = DEVGMCommands::DeleteInven, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DeleteInvenCommand); + + // Register Greater Than Zero Commands + + Command KickCommand{ + .help = "Kicks the player off the server", + .info = "Kicks the player off the server", + .aliases = { "kick" }, + .handle = GMGreaterThanZeroCommands::Kick, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(KickCommand); + + Command MailItemCommand{ + .help = "Mails an item to the given player", + .info = "Mails an item to the given player. The mailed item has predetermined content. The sender name is set to \"Darkflame Universe\". The title of the message is \"Lost item\". The body of the message is \"This is a replacement item for one you lost\".", + .aliases = { "mailitem" }, + .handle = GMGreaterThanZeroCommands::MailItem, + .requiredLevel = eGameMasterLevel::MODERATOR + }; + RegisterCommand(MailItemCommand); + + Command BanCommand{ + .help = "Bans a user from the server", + .info = "Bans a user from the server", + .aliases = { "ban" }, + .handle = GMGreaterThanZeroCommands::Ban, + .requiredLevel = eGameMasterLevel::SENIOR_MODERATOR + }; + RegisterCommand(BanCommand); + + Command ApprovePropertyCommand{ + .help = "Approves a property", + .info = "Approves the property the player is currently visiting", + .aliases = { "approveproperty" }, + .handle = GMGreaterThanZeroCommands::ApproveProperty, + .requiredLevel = eGameMasterLevel::LEAD_MODERATOR + }; + RegisterCommand(ApprovePropertyCommand); + + Command MuteCommand{ + .help = "Mute a player", + .info = "Mute player for the given amount of time. If no time is given, the mute is indefinite.", + .aliases = { "mute" }, + .handle = GMGreaterThanZeroCommands::Mute, + .requiredLevel = eGameMasterLevel::JUNIOR_DEVELOPER + }; + RegisterCommand(MuteCommand); + + Command FlyCommand{ + .help = "Toggle flying", + .info = "Toggles your flying state with an optional parameter for the speed scale.", + .aliases = { "fly" }, + .handle = GMGreaterThanZeroCommands::Fly, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(FlyCommand); + + Command AttackImmuneCommand{ + .help = "Make yourself immune to attacks", + .info = "Sets the character's immunity to basic attacks state, where value can be one of \"1\", to make yourself immune to basic attack damage, or \"0\" to undo", + .aliases = { "attackimmune" }, + .handle = GMGreaterThanZeroCommands::AttackImmune, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AttackImmuneCommand); + + Command GmImmuneCommand{ + .help = "Sets the character's GMImmune state", + .info = "Sets the character's GMImmune state, where value can be one of \"1\", to make yourself immune to damage, or \"0\" to undo", + .aliases = { "gmimmune" }, + .handle = GMGreaterThanZeroCommands::GmImmune, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GmImmuneCommand); + + Command GmInvisCommand{ + .help = "Toggles invisibility for the character", + .info = "Toggles invisibility for the character, though it's currently a bit buggy. Requires nonzero GM Level for the character, but the account must have a GM level of 8", + .aliases = { "gminvis" }, + .handle = GMGreaterThanZeroCommands::GmInvis, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GmInvisCommand); + + Command SetNameCommand{ + + .help = "Sets a temporary name for your player", + .info = "Sets a temporary name for your player. The name resets when you log out", + .aliases = { "setname" }, + .handle = GMGreaterThanZeroCommands::SetName, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetNameCommand); + + Command TitleCommand{ + .help = "Give your character a title", + .info = "Temporarily appends your player's name with \" - <title>\". This resets when you log out", + .aliases = { "title" }, + .handle = GMGreaterThanZeroCommands::Title, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(TitleCommand); + + + // Register GM Zero Commands + Command HelpCommand{ + .help = "Display command info", + .info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short desctiptions.", + .aliases = { "help", "h"}, + .handle = GMZeroCommands::Help, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(HelpCommand); + + Command CreditsCommand{ + .help = "Displays DLU Credits", + .info = "Displays the names of the people behind Darkflame Universe.", + .aliases = { "credits" }, + .handle = GMZeroCommands::Credits, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(CreditsCommand); + + Command InfoCommand{ + .help = "Displays server info", + .info = "Displays server info to the user, including where to find the server's source code", + .aliases = { "info" }, + .handle = GMZeroCommands::Info, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(InfoCommand); + + Command DieCommand{ + .help = "Smashes the player", + .info = "Smashes the player as if they were killed by something", + .aliases = { "die" }, + .handle = GMZeroCommands::Die, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(DieCommand); + + Command PingCommand{ + .help = "Displays your average ping.", + .info = "Displays your average ping. If the `-l` flag is used, the latest ping is displayed.", + .aliases = { "ping" }, + .handle = GMZeroCommands::Ping, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(PingCommand); + + Command PvpCommand{ + .help = "Toggle your PVP flag", + .info = "Toggle your PVP flag", + .aliases = { "pvp" }, + .handle = GMZeroCommands::Pvp, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(PvpCommand); + + Command RequestMailCountCommand{ + .help = "Gets the players mail count", + .info = "Sends notification with number of unread messages in the player's mailbox", + .aliases = { "requestmailcount" }, + .handle = GMZeroCommands::RequestMailCount, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(RequestMailCountCommand); + + Command WhoCommand{ + .help = "Displays all players on the instance", + .info = "Displays all players on the instance", + .aliases = { "who" }, + .handle = GMZeroCommands::Who, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(WhoCommand); + + Command FixStatsCommand{ + .help = "Resets skills, buffs, and destroyables", + .info = "Resets skills, buffs, and destroyables", + .aliases = { "fix-stats" }, + .handle = GMZeroCommands::FixStats, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(FixStatsCommand); + + Command JoinCommand{ + .help = "Join a private zone", + .info = "Join a private zone with given password", + .aliases = { "join" }, + .handle = GMZeroCommands::Join, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(JoinCommand); + + Command LeaveZoneCommand{ + .help = "Leave an instanced zone", + .info = "If you are in an instanced zone, transfers you to the closest main world. For example, if you are in an instance of Avant Gardens Survival or the Spider Queen Battle, you are sent to Avant Gardens. If you are in the Battle of Nimbus Station, you are sent to Nimbus Station.", + .aliases = { "leave-zone", "leavezone" }, + .handle = GMZeroCommands::LeaveZone, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(LeaveZoneCommand); + + Command ResurrectCommand{ + .help = "Resurrects the player", + .info = "Resurrects the player", + .aliases = { "resurrect" }, + .handle = GMZeroCommands::Resurrect, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ResurrectCommand); + + Command InstanceInfoCommand{ + .help = "Display LWOZoneID info for the current zone", + .info = "Display LWOZoneID info for the current zone", + .aliases = { "instanceinfo" }, + .handle = GMZeroCommands::InstanceInfo, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(InstanceInfoCommand); + +} + +namespace GMZeroCommands { + // The star delimiter is to be used for marking the start and end of a localized string. + void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + std::ostringstream feedback; + if (args.empty()) { + feedback << "----- Commands -----"; + for (const auto& command : CommandInfos) { + // TODO: Limit displaying commands based on GM level they require + if (command.requiredLevel > entity->GetGMLevel()) continue; + LOG("Help command: %s", command.aliases[0].c_str()); + feedback << "\n/" << command.aliases[0] << ": " << command.help; + } + } else { + bool foundCommand = false; + for (const auto& command : CommandInfos) { + if (std::ranges::find(command.aliases, args) == command.aliases.end()) continue; + + if (entity->GetGMLevel() < command.requiredLevel) break; + foundCommand = true; + feedback << "----- " << command.aliases.at(0) << " -----\n"; + // info can be a localizable string + feedback << command.info; + if (command.aliases.size() == 1) break; + + feedback << "\nAliases: "; + for (size_t i = 0; i < command.aliases.size(); i++) { + if (i > 0) feedback << ", "; + feedback << command.aliases[i]; + } } - WorldPackets::SendGMLevelChange(sysAddr, success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); - GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); - entity->SetGMLevel(level); - LOG("User %s (%i) has changed their GM level to %i for charID %llu", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); + // Let GameMasters know if the command doesn't exist + if (!foundCommand && entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) feedback << "Command " << std::quoted(args) << " does not exist!"; } + const auto feedbackStr = feedback.str(); + if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); } -#ifndef DEVELOPER_SERVER - if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER)) { - WorldPackets::SendGMLevelChange(sysAddr, true, user->GetMaxGMLevel(), entity->GetGMLevel(), eGameMasterLevel::CIVILIAN); - GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN); - entity->SetGMLevel(eGameMasterLevel::CIVILIAN); - - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); - - ChatPackets::SendSystemMessage(sysAddr, u"Your game master level has been changed, you may not be able to use all commands."); - } -#endif - - if (chatCommand == "togglenameplate" && (Game::config->GetValue("allow_nameplate_off") == "1" || entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER)) { - auto* character = entity->GetCharacter(); - - if (character && character->GetBillboardVisible()) { - character->SetBillboardVisible(false); - ChatPackets::SendSystemMessage(sysAddr, u"Your nameplate has been turned off and is not visible to players currently in this zone."); - } else { - character->SetBillboardVisible(true); - ChatPackets::SendSystemMessage(sysAddr, u"Your nameplate is now on and visible to all players."); - } - return; - } - - if (chatCommand == "toggleskipcinematics" && (Game::config->GetValue("allow_players_to_skip_cinematics") == "1" || entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER)) { - auto* character = entity->GetCharacter(); - if (!character) return; - bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS); - character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current); - if (!current) { - ChatPackets::SendSystemMessage(sysAddr, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now."); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Cinematics will no longer be skipped."); - } - - return; - } - - - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - //HANDLE ALL NON GM SLASH COMMANDS RIGHT HERE! - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - if (chatCommand == "pvp") { + void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto* character = entity->GetComponent(); if (character == nullptr) { @@ -208,11 +1054,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit message << character->GetName() << " changed their PVP flag to " << std::to_string(character->GetPvpEnabled()) << "!"; ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, GeneralUtils::UTF8ToUTF16(message.str()), true); - - return; } - if (chatCommand == "who") { + void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args) { ChatPackets::SendSystemMessage( sysAddr, u"Players in this instance: (" + GeneralUtils::to_u16string(PlayerManager::GetAllPlayers().size()) + u")" @@ -228,8 +1072,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "ping") { - if (!args.empty() && args[0] == "-l") { + void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (!args.empty() && args.starts_with("-l")) { std::stringstream message; message << "Your latest ping: " << std::to_string(Game::server->GetLatestPing(sysAddr)) << "ms"; @@ -240,10 +1084,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); } - return; } - if (chatCommand == "fix-stats") { + void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { // Reset skill component and buff component auto* skillComponent = entity->GetComponent(); auto* buffComponent = entity->GetComponent(); @@ -264,8 +1107,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit destroyableComponent->FixStats(); } - if (chatCommand == "credits" || chatCommand == "info") { - const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); + void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()); { AMFArrayValue args; @@ -285,11 +1128,32 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); }); - - return; } - if (chatCommand == "leave-zone" || chatCommand == "leavezone") { + void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); + + { + AMFArrayValue args; + + args.Insert("state", "Story"); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", args); + } + + entity->AddCallbackTimer(0.5f, [customText, entity]() { + AMFArrayValue args; + + args.Insert("visible", true); + args.Insert("text", customText); + + LOG("Sending %s", customText.c_str()); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); + }); + } + + void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto currentZone = Game::zoneManager->GetZone()->GetZoneID().GetMapID(); LWOMAPID newZone = 0; @@ -329,9 +1193,12 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit }); } - if (chatCommand == "join" && !args.empty()) { + void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map..."); - const auto& password = args[0]; + const auto& password = splitArgs[0]; ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); @@ -348,52 +1215,121 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit }); } - if (user->GetMaxGMLevel() == eGameMasterLevel::CIVILIAN || entity->GetGMLevel() >= eGameMasterLevel::CIVILIAN) { - if (chatCommand == "die") { - entity->Smash(entity->GetObjectID()); - } - - if (chatCommand == "resurrect") { - ScriptedActivityComponent* scriptedActivityComponent = Game::zoneManager->GetZoneControlObject()->GetComponent(); - - if (scriptedActivityComponent) { // check if user is in activity world and if so, they can't resurrect - ChatPackets::SendSystemMessage(sysAddr, u"You cannot resurrect in an activity world."); - return; - } - - GameMessages::SendResurrect(entity); - } - - if (chatCommand == "requestmailcount") { - Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID()); - } - - if (chatCommand == "instanceinfo") { - const auto zoneId = Game::zoneManager->GetZone()->GetZoneID(); - - ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); - } - - if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) return; + void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->Smash(entity->GetObjectID()); } - if (chatCommand == "resetmission" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto missionId = GeneralUtils::TryParse(args[0]); + void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ScriptedActivityComponent* scriptedActivityComponent = Game::zoneManager->GetZoneControlObject()->GetComponent(); + + if (scriptedActivityComponent) { // check if user is in activity world and if so, they can't resurrect + ChatPackets::SendSystemMessage(sysAddr, u"You cannot resurrect in an activity world."); + return; + } + + GameMessages::SendResurrect(entity); + } + + void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID()); + } + + void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto zoneId = Game::zoneManager->GetZone()->GetZoneID(); + + ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); + } +}; + +namespace DEVGMCommands { + void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + User* user = UserManager::Instance()->GetUser(entity->GetSystemAddress()); + + const auto level_intermed = GeneralUtils::TryParse(args); + if (!level_intermed) { + GameMessages::SendSlashCommandFeedbackText(entity, u"Invalid GM level."); + return; + } + eGameMasterLevel level = static_cast(level_intermed.value()); + +#ifndef DEVELOPER_SERVER + if (user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER) { + level = eGameMasterLevel::CIVILIAN; + } +#endif + + if (level > user->GetMaxGMLevel()) level = user->GetMaxGMLevel(); + + if (level == entity->GetGMLevel()) return; + bool success = user->GetMaxGMLevel() >= level; + + if (success) { + WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); + GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); + entity->SetGMLevel(level); + LOG("User %s (%i) has changed their GM level to %i for charID %llu", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); + } + +#ifndef DEVELOPER_SERVER + if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER)) { + WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), true, user->GetMaxGMLevel(), entity->GetGMLevel(), eGameMasterLevel::CIVILIAN); + GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN); + entity->SetGMLevel(eGameMasterLevel::CIVILIAN); + + GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::SendSlashCommandFeedbackText(entity, u"Your game master level has been changed, you may not be able to use all commands."); + } +#endif + } + + + void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if ((Game::config->GetValue("allow_nameplate_off") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER)) return; + + auto* character = entity->GetCharacter(); + if (character && character->GetBillboardVisible()) { + character->SetBillboardVisible(false); + GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate has been turned off and is not visible to players currently in this zone."); + } else { + character->SetBillboardVisible(true); + GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate is now on and visible to all players."); + } + } + + void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER) return; + auto* character = entity->GetCharacter(); + if (!character) return; + bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS); + character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current); + if (!current) { + GameMessages::SendSlashCommandFeedbackText(entity, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now."); + } else { + GameMessages::SendSlashCommandFeedbackText(entity, u"Cinematics will no longer be skipped."); + } + } + + void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto missionId = GeneralUtils::TryParse(splitArgs[0]); if (!missionId) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission ID."); return; } - + auto* missionComponent = entity->GetComponent(); if (!missionComponent) return; missionComponent->ResetMission(missionId.value()); } - // Log command to database - Database::Get()->InsertSlashCommandUsage(entity->GetObjectID(), chatCommand); + void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; - if (chatCommand == "setminifig" && args.size() == 2 && entity->GetGMLevel() >= eGameMasterLevel::FORUM_MODERATOR) { // could break characters so only allow if GM > 0 - const auto minifigItemIdExists = GeneralUtils::TryParse(args[1]); + const auto minifigItemIdExists = GeneralUtils::TryParse(splitArgs[1]); if (!minifigItemIdExists) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig Item Id ID."); return; @@ -401,7 +1337,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit const int32_t minifigItemId = minifigItemIdExists.value(); Game::entityManager->DestructEntity(entity, sysAddr); auto* charComp = entity->GetComponent(); - std::string lowerName = args[0]; + std::string lowerName = splitArgs[0]; if (lowerName.empty()) return; std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); if (lowerName == "eyebrows") { @@ -437,8 +1373,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); // need to retoggle because it gets reenabled on creation of new character } - if ((chatCommand == "playanimation" || chatCommand == "playanim") && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - std::u16string anim = GeneralUtils::ASCIIToUTF16(args[0], args[0].size()); + void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + std::u16string anim = GeneralUtils::ASCIIToUTF16(splitArgs[0], splitArgs[0].size()); RenderComponent::PlayAnimation(entity, anim); auto* possessorComponent = entity->GetComponent(); if (possessorComponent) { @@ -447,18 +1386,19 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "list-spawns" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args) { for (const auto& pair : Game::entityManager->GetSpawnPointEntities()) { ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(pair.first)); } ChatPackets::SendSystemMessage(sysAddr, u"Current: " + GeneralUtils::ASCIIToUTF16(entity->GetCharacter()->GetTargetScene())); - - return; } - if (chatCommand == "unlock-emote" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto emoteID = GeneralUtils::TryParse(args.at(0)); + void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto emoteID = GeneralUtils::TryParse(splitArgs[0]); if (!emoteID) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid emote ID."); @@ -468,14 +1408,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit entity->GetCharacter()->UnlockEmote(emoteID.value()); } - if (chatCommand == "force-save" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args) { entity->GetCharacter()->SaveXMLToDatabase(); } - if (chatCommand == "kill" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + ChatPackets::SendSystemMessage(sysAddr, u"Brutally murdering that player, if online on this server."); - auto* player = PlayerManager::GetPlayer(args[0]); + auto* player = PlayerManager::GetPlayer(splitArgs[0]); if (player) { player->Smash(entity->GetObjectID()); ChatPackets::SendSystemMessage(sysAddr, u"It has been done, do you feel good about yourself now?"); @@ -486,8 +1429,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - if (chatCommand == "speedboost" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto boostOptional = GeneralUtils::TryParse(args[0]); + void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto boostOptional = GeneralUtils::TryParse(splitArgs[0]); if (!boostOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost."); return; @@ -517,18 +1463,20 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->SerializeEntity(entity); } - if (chatCommand == "freecam" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto state = !entity->GetVar(u"freecam"); entity->SetVar(u"freecam", state); GameMessages::SendSetPlayerControlScheme(entity, static_cast(state ? 9 : 1)); ChatPackets::SendSystemMessage(sysAddr, u"Toggled freecam."); - return; } - if (chatCommand == "setcontrolscheme" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto scheme = GeneralUtils::TryParse(args[0]); + void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto scheme = GeneralUtils::TryParse(splitArgs[0]); if (!scheme) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid control scheme."); @@ -538,44 +1486,39 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendSetPlayerControlScheme(entity, static_cast(scheme.value())); ChatPackets::SendSystemMessage(sysAddr, u"Switched control scheme."); - return; } - if (chatCommand == "approveproperty" && entity->GetGMLevel() >= eGameMasterLevel::LEAD_MODERATOR) { + void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; - if (PropertyManagementComponent::Instance() != nullptr) { - PropertyManagementComponent::Instance()->UpdateApprovedStatus(true); - } - - return; - } - - if (chatCommand == "setuistate" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { AMFArrayValue uiState; - uiState.Insert("state", args.at(0)); + uiState.Insert("state", splitArgs[0]); GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "pushGameState", uiState); ChatPackets::SendSystemMessage(sysAddr, u"Switched UI state."); - - return; } - if (chatCommand == "toggle" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + AMFArrayValue amfArgs; amfArgs.Insert("visible", true); - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, args[0], amfArgs); + GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, splitArgs[0], amfArgs); ChatPackets::SendSystemMessage(sysAddr, u"Toggled UI state."); - - return; } - if ((chatCommand == "setinventorysize" || chatCommand == "setinvsize") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - const auto sizeOptional = GeneralUtils::TryParse(args[0]); + void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto sizeOptional = GeneralUtils::TryParse(splitArgs[0]); if (!sizeOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid size."); return; @@ -585,21 +1528,21 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit eInventoryType selectedInventory = eInventoryType::ITEMS; // a possible inventory was provided if we got more than 1 argument - if (args.size() >= 2) { - selectedInventory = GeneralUtils::TryParse(args.at(1)).value_or(eInventoryType::INVALID); + if (splitArgs.size() >= 2) { + selectedInventory = GeneralUtils::TryParse(splitArgs.at(1)).value_or(eInventoryType::INVALID); if (selectedInventory == eInventoryType::INVALID) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory."); return; } else { // In this case, we treat the input as a string and try to find it in the reflection list - std::transform(args.at(1).begin(), args.at(1).end(), args.at(1).begin(), ::toupper); + std::transform(splitArgs.at(1).begin(), splitArgs.at(1).end(), splitArgs.at(1).begin(), ::toupper); for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { - if (std::string_view(args.at(1)) == std::string_view(InventoryType::InventoryTypeToString(static_cast(index)))) selectedInventory = static_cast(index); + if (std::string_view(splitArgs.at(1)) == std::string_view(InventoryType::InventoryTypeToString(static_cast(index)))) selectedInventory = static_cast(index); } } ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory " + - GeneralUtils::ASCIIToUTF16(args.at(1)) + + GeneralUtils::ASCIIToUTF16(splitArgs.at(1)) + u" to size " + GeneralUtils::to_u16string(size)); } else ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory ITEMS to size " + GeneralUtils::to_u16string(size)); @@ -610,18 +1553,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit inventory->SetSize(size); } - - return; } - if (chatCommand == "runmacro" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() != 1) return; + void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; // Only process if input does not contain separator charaters - if (args[0].find("/") != std::string::npos) return; - if (args[0].find("\\") != std::string::npos) return; + if (splitArgs[0].find("/") != std::string::npos) return; + if (splitArgs[0].find("\\") != std::string::npos) return; - auto infile = Game::assetManager->GetFile(("macros/" + args[0] + ".scm").c_str()); + auto infile = Game::assetManager->GetFile(("macros/" + splitArgs[0] + ".scm").c_str()); if (!infile) { ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); @@ -639,14 +1581,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } else { ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); } - - return; } - if (chatCommand == "addmission" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 0) return; + void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; - const auto missionID = GeneralUtils::TryParse(args.at(0)); + const auto missionID = GeneralUtils::TryParse(splitArgs.at(0)); if (!missionID) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); @@ -655,13 +1596,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit auto comp = static_cast(entity->GetComponent(eReplicaComponentType::MISSION)); if (comp) comp->AcceptMission(missionID.value(), true); - return; } - if (chatCommand == "completemission" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 0) return; + void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; - const auto missionID = GeneralUtils::TryParse(args.at(0)); + const auto missionID = GeneralUtils::TryParse(splitArgs.at(0)); if (!missionID) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); @@ -670,35 +1611,41 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit auto comp = static_cast(entity->GetComponent(eReplicaComponentType::MISSION)); if (comp) comp->CompleteMission(missionID.value(), true); - return; } - if (chatCommand == "setflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto flagId = GeneralUtils::TryParse(args.at(0)); + void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() == 1) { + const auto flagId = GeneralUtils::TryParse(splitArgs.at(0)); - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), true); + } else if (splitArgs.size() >= 2) { + const auto flagId = GeneralUtils::TryParse(splitArgs.at(1)); + std::string onOffFlag = splitArgs.at(0); + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + if (onOffFlag != "off" && onOffFlag != "on") { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on"); } - - entity->GetCharacter()->SetPlayerFlag(flagId.value(), true); } - if (chatCommand == "setflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 2) { - const auto flagId = GeneralUtils::TryParse(args.at(1)); - std::string onOffFlag = args.at(0); - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; - } - if (onOffFlag != "off" && onOffFlag != "on") { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type."); - return; - } - entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on"); - } - if (chatCommand == "clearflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto flagId = GeneralUtils::TryParse(args.at(0)); + void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto flagId = GeneralUtils::TryParse(splitArgs.at(0)); if (!flagId) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); @@ -708,52 +1655,43 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit entity->GetCharacter()->SetPlayerFlag(flagId.value(), false); } - if (chatCommand == "playeffect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { - const auto effectID = GeneralUtils::TryParse(args.at(0)); + void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto effectID = GeneralUtils::TryParse(splitArgs.at(0)); if (!effectID) return; // FIXME: use fallible ASCIIToUTF16 conversion, because non-ascii isn't valid anyway - GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID.value(), GeneralUtils::ASCIIToUTF16(args.at(1)), args.at(2)); + GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID.value(), GeneralUtils::ASCIIToUTF16(splitArgs.at(1)), splitArgs.at(2)); } - if (chatCommand == "stopeffect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - GameMessages::SendStopFXEffect(entity, true, args[0]); + void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + GameMessages::SendStopFXEffect(entity, true, splitArgs[0]); } - if (chatCommand == "setanntitle" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() < 0) return; - - std::stringstream ss; - for (auto string : args) - ss << string << " "; - - entity->GetCharacter()->SetAnnouncementTitle(ss.str()); - return; + void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SetAnnouncementTitle(args); } - if (chatCommand == "setannmsg" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() < 0) return; - - std::stringstream ss; - for (auto string : args) - ss << string << " "; - - entity->GetCharacter()->SetAnnouncementMessage(ss.str()); - return; + void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SetAnnouncementMessage(args); } - if (chatCommand == "announce" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (entity->GetCharacter()->GetAnnouncementTitle().size() == 0 || entity->GetCharacter()->GetAnnouncementMessage().size() == 0) { + void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (entity->GetCharacter()->GetAnnouncementTitle().empty() || entity->GetCharacter()->GetAnnouncementMessage().empty()) { ChatPackets::SendSystemMessage(sysAddr, u"Use /setanntitle & /setannmsg <msg> first!"); return; } - SendAnnouncement(entity->GetCharacter()->GetAnnouncementTitle(), entity->GetCharacter()->GetAnnouncementMessage()); - return; + SlashCommandHandler::SendAnnouncement(entity->GetCharacter()->GetAnnouncementTitle(), entity->GetCharacter()->GetAnnouncementMessage()); } - if (chatCommand == "shutdownuniverse" && entity->GetGMLevel() == eGameMasterLevel::OPERATOR) { + void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args) { //Tell the master server that we're going to be shutting down whole "universe": CBITSTREAM; BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::SHUTDOWN_UNIVERSE); @@ -761,11 +1699,10 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Sent universe shutdown notification to master."); //Tell chat to send an announcement to all servers - SendAnnouncement("Servers Closing Soon!", "DLU servers will close for maintenance in 10 minutes from now."); - return; + SlashCommandHandler::SendAnnouncement("Servers Closing Soon!", "DLU servers will close for maintenance in 10 minutes from now."); } - if (chatCommand == "getnavmeshheight" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto control = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); if (!control) return; @@ -774,9 +1711,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, msg); } - if (chatCommand == "gmadditem" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 1) { - const auto itemLOT = GeneralUtils::TryParse<uint32_t>(args.at(0)); + void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() == 1) { + const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); if (!itemLOT) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); @@ -786,14 +1725,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); inventory->AddItem(itemLOT.value(), 1, eLootSourceType::MODERATION); - } else if (args.size() == 2) { - const auto itemLOT = GeneralUtils::TryParse<uint32_t>(args.at(0)); + } else if (splitArgs.size() == 2) { + const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); if (!itemLOT) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); return; } - const auto count = GeneralUtils::TryParse<uint32_t>(args.at(1)); + const auto count = GeneralUtils::TryParse<uint32_t>(splitArgs.at(1)); if (!count) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid item count."); return; @@ -807,82 +1746,25 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "mailitem" && entity->GetGMLevel() >= eGameMasterLevel::MODERATOR && args.size() >= 2) { - const auto& playerName = args[0]; + void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); - auto playerInfo = Database::Get()->GetCharacterInfo(playerName); - - uint32_t receiverID = 0; - if (!playerInfo) { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); - - return; - } - - receiverID = playerInfo->id; - - const auto lot = GeneralUtils::TryParse<LOT>(args.at(1)); - - if (!lot) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot."); - return; - } - - IMail::MailInfo mailInsert; - mailInsert.senderId = entity->GetObjectID(); - mailInsert.senderUsername = "Darkflame Universe"; - mailInsert.receiverId = receiverID; - mailInsert.recipient = playerName; - mailInsert.subject = "Lost item"; - mailInsert.body = "This is a replacement item for one you lost."; - mailInsert.itemID = LWOOBJID_EMPTY; - mailInsert.itemLOT = lot.value(); - mailInsert.itemSubkey = LWOOBJID_EMPTY; - mailInsert.itemCount = 1; - Database::Get()->InsertNewMail(mailInsert); - - ChatPackets::SendSystemMessage(sysAddr, u"Mail sent"); - - return; - } - - if (chatCommand == "setname" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - std::string name = ""; - - for (const auto& arg : args) { - name += arg + " "; - } - - GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); - } - - if (chatCommand == "title" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - std::string name = entity->GetCharacter()->GetName() + " - "; - - for (const auto& arg : args) { - name += arg + " "; - } - - GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); - } - - if ((chatCommand == "teleport" || chatCommand == "tele") && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_MODERATOR) { NiPoint3 pos{}; - if (args.size() == 3) { + if (splitArgs.size() == 3) { - const auto x = GeneralUtils::TryParse<float>(args.at(0)); + const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); if (!x) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); return; } - const auto y = GeneralUtils::TryParse<float>(args.at(1)); + const auto y = GeneralUtils::TryParse<float>(splitArgs.at(1)); if (!y) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid y."); return; } - const auto z = GeneralUtils::TryParse<float>(args.at(2)); + const auto z = GeneralUtils::TryParse<float>(splitArgs.at(2)); if (!z) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); return; @@ -894,15 +1776,15 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z); GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); - } else if (args.size() == 2) { + } else if (splitArgs.size() == 2) { - const auto x = GeneralUtils::TryParse<float>(args.at(0)); + const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); if (!x) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); return; } - const auto z = GeneralUtils::TryParse<float>(args.at(1)); + const auto z = GeneralUtils::TryParse<float>(splitArgs.at(1)); if (!z) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); return; @@ -918,7 +1800,6 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport <x> (<y>) <z> - if no Y given, will teleport to the height of the terrain (or any physics object)."); } - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); if (possessorComponent) { auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); @@ -933,7 +1814,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "tpall" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto pos = entity->GetPosition(); const auto characters = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CHARACTER); @@ -941,11 +1822,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit for (auto* character : characters) { GameMessages::SendTeleport(character->GetObjectID(), pos, NiQuaternion(), character->GetSystemAddress()); } - - return; } - if (chatCommand == "dismount" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto* possessorComponent = entity->GetComponent<PossessorComponent>(); if (possessorComponent) { auto possessableId = possessorComponent->GetPossessable(); @@ -956,181 +1835,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "fly" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { - auto* character = entity->GetCharacter(); - - if (character) { - bool isFlying = character->GetIsFlying(); - - if (isFlying) { - GameMessages::SendSetJetPackMode(entity, false); - - character->SetIsFlying(false); - } else { - float speedScale = 1.0f; - - if (args.size() >= 1) { - const auto tempScaleStore = GeneralUtils::TryParse<float>(args.at(0)); - - if (tempScaleStore) { - speedScale = tempScaleStore.value(); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse speed scale argument."); - } - } - - float airSpeed = 20 * speedScale; - float maxAirSpeed = 30 * speedScale; - float verticalVelocity = 1.5 * speedScale; - - GameMessages::SendSetJetPackMode(entity, true, true, false, 167, airSpeed, maxAirSpeed, verticalVelocity); - - character->SetIsFlying(true); - } - } - } - - //------- GM COMMANDS TO ACTUALLY MODERATE -------- - - if (chatCommand == "mute" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { - if (args.size() >= 1) { - auto* player = PlayerManager::GetPlayer(args[0]); - - uint32_t accountId = 0; - LWOOBJID characterId = 0; - - if (player == nullptr) { - auto characterInfo = Database::Get()->GetCharacterInfo(args[0]); - - if (characterInfo) { - accountId = characterInfo->accountId; - characterId = characterInfo->id; - - GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); - GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); - } - - if (accountId == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); - - return; - } - } else { - auto* character = player->GetCharacter(); - auto* user = character != nullptr ? character->GetParentUser() : nullptr; - if (user) accountId = user->GetAccountID(); - characterId = player->GetObjectID(); - } - - time_t expire = 1; // Default to indefinate mute - - if (args.size() >= 2) { - const auto days = GeneralUtils::TryParse<uint32_t>(args[1]); - if (!days) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid days."); - - return; - } - - std::optional<uint32_t> hours; - if (args.size() >= 3) { - hours = GeneralUtils::TryParse<uint32_t>(args[2]); - if (!hours) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid hours."); - - return; - } - } - - expire = time(NULL); - expire += 24 * 60 * 60 * days.value(); - expire += 60 * 60 * hours.value_or(0); - } - - if (accountId != 0) Database::Get()->UpdateAccountUnmuteTime(accountId, expire); - - char buffer[32] = "brought up for review.\0"; - - if (expire != 1) { - std::tm* ptm = std::localtime(&expire); - // Format: Mo, 15.06.2009 20:20:00 - std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm); - } - - const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer)); - - ChatPackets::SendSystemMessage(sysAddr, u"Muted: " + GeneralUtils::UTF8ToUTF16(args[0]) + u" until " + timeStr); - - //Notify chat about it - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE); - - bitStream.Write(characterId); - bitStream.Write(expire); - - Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /mute <username> <days (optional)> <hours (optional)>"); - } - } - - if (chatCommand == "kick" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_MODERATOR) { - if (args.size() == 1) { - auto* player = PlayerManager::GetPlayer(args[0]); - - std::u16string username = GeneralUtils::UTF8ToUTF16(args[0]); - if (player == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + username); - return; - } - - Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::KICK); - - ChatPackets::SendSystemMessage(sysAddr, u"Kicked: " + username); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /kick <username>"); - } - } - - if (chatCommand == "ban" && entity->GetGMLevel() >= eGameMasterLevel::SENIOR_MODERATOR) { - if (args.size() == 1) { - auto* player = PlayerManager::GetPlayer(args[0]); - - uint32_t accountId = 0; - - if (player == nullptr) { - auto characterInfo = Database::Get()->GetCharacterInfo(args[0]); - - if (characterInfo) { - accountId = characterInfo->accountId; - } - - if (accountId == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); - - return; - } - } else { - auto* character = player->GetCharacter(); - auto* user = character != nullptr ? character->GetParentUser() : nullptr; - if (user) accountId = user->GetAccountID(); - } - - if (accountId != 0) Database::Get()->UpdateAccountBan(accountId, true); - - if (player != nullptr) { - Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Banned: " + GeneralUtils::ASCIIToUTF16(args[0])); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /ban <username>"); - } - } - - //------------------------------------------------- - - if (chatCommand == "buffme" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); if (dest) { dest->SetHealth(999); @@ -1143,8 +1848,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->SerializeEntity(entity); } - if (chatCommand == "startcelebration" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto celebration = GeneralUtils::TryParse<int32_t>(args.at(0)); + void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto celebration = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); if (!celebration) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid celebration."); @@ -1154,7 +1862,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendStartCelebrationEffect(entity, entity->GetSystemAddress(), celebration.value()); } - if (chatCommand == "buffmed" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); if (dest) { dest->SetHealth(9); @@ -1167,8 +1875,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->SerializeEntity(entity); } - if (chatCommand == "refillstats" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - + void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); if (dest) { dest->SetHealth(static_cast<int32_t>(dest->GetMaxHealth())); @@ -1179,17 +1886,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->SerializeEntity(entity); } - if (chatCommand == "lookup" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto query = CDClientDatabase::CreatePreppedStmt( "SELECT `id`, `name` FROM `Objects` WHERE `displayName` LIKE ?1 OR `name` LIKE ?1 OR `description` LIKE ?1 LIMIT 50"); - // Concatenate all of the arguments into a single query so a multi word query can be used properly. - std::string conditional = args[0]; - args.erase(args.begin()); - for (auto& argument : args) { - conditional += ' ' + argument; - } - const std::string query_text = "%" + conditional + "%"; + const std::string query_text = "%" + args + "%"; query.bind(1, query_text.c_str()); auto tables = query.execQuery(); @@ -1201,11 +1902,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "spawn" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + ControllablePhysicsComponent* comp = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); if (!comp) return; - const auto lot = GeneralUtils::TryParse<uint32_t>(args[0]); + const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!lot) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); @@ -1230,17 +1934,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->ConstructEntity(newEntity); } - if (chatCommand == "spawngroup" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { - auto controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>(); - if (!controllablePhysicsComponent) return; + void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; - const auto lot = GeneralUtils::TryParse<LOT>(args[0]); + const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!lot) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); return; } - const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(args[1]); + const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); if (!numberToSpawnOptional && numberToSpawnOptional.value() > 0) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); return; @@ -1248,7 +1952,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit uint32_t numberToSpawn = numberToSpawnOptional.value(); // Must spawn within a radius of at least 0.0f - const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(args[2]); + const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(splitArgs[2]); if (!radiusToSpawnWithinOptional && radiusToSpawnWithinOptional.value() < 0.0f) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); return; @@ -1261,7 +1965,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit info.spawnerID = entity->GetObjectID(); info.spawnerNodeID = 0; - auto playerPosition = controllablePhysicsComponent->GetPosition(); + auto playerPosition = entity->GetPosition(); while (numberToSpawn > 0) { auto randomAngle = GeneralUtils::GenerateRandomNumber<float>(0.0f, 2 * PI); auto randomRadius = GeneralUtils::GenerateRandomNumber<float>(0.0f, radiusToSpawnWithin); @@ -1282,8 +1986,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if ((chatCommand == "giveuscore") && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto uscoreOptional = GeneralUtils::TryParse<int32_t>(args[0]); + void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto uscoreOptional = GeneralUtils::TryParse<int32_t>(splitArgs[0]); if (!uscoreOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid uscore."); return; @@ -1296,19 +2003,22 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit eLootSourceType lootType = eLootSourceType::MODERATION; - if (args.size() >= 2) { - const auto type = GeneralUtils::TryParse<eLootSourceType>(args[1]); + if (splitArgs.size() >= 2) { + const auto type = GeneralUtils::TryParse<eLootSourceType>(splitArgs[1]); lootType = type.value_or(lootType); } GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), uscore, lootType); } - if ((chatCommand == "setlevel") && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + // We may be trying to set a specific players level to a level. If so override the entity with the requested players. std::string requestedPlayerToSetLevelOf = ""; - if (args.size() > 1) { - requestedPlayerToSetLevelOf = args[1]; + if (splitArgs.size() > 1) { + requestedPlayerToSetLevelOf = splitArgs[1]; auto requestedPlayer = PlayerManager::GetPlayer(requestedPlayerToSetLevelOf); @@ -1324,7 +2034,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit entity = requestedPlayer->GetOwner(); } - const auto requestedLevelOptional = GeneralUtils::TryParse<uint32_t>(args[0]); + const auto requestedLevelOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); uint32_t oldLevel; // first check the level is valid @@ -1370,10 +2080,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + u". Relog to see changes."); } - return; } - if (chatCommand == "pos" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto position = entity->GetPosition(); ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(position.x)) + u", " + (GeneralUtils::to_u16string(position.y)) + u", " + (GeneralUtils::to_u16string(position.z)) + u">"); @@ -1381,7 +2090,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit LOG("Position: %f, %f, %f", position.x, position.y, position.z); } - if (chatCommand == "rot" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto rotation = entity->GetRotation(); ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(rotation.w)) + u", " + (GeneralUtils::to_u16string(rotation.x)) + u", " + (GeneralUtils::to_u16string(rotation.y)) + u", " + (GeneralUtils::to_u16string(rotation.z)) + u">"); @@ -1389,23 +2098,26 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit LOG("Rotation: %f, %f, %f, %f", rotation.w, rotation.x, rotation.y, rotation.z); } - if (chatCommand == "locrow" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto position = entity->GetPosition(); const auto rotation = entity->GetRotation(); LOG("<location x=\"%f\" y=\"%f\" z=\"%f\" rw=\"%f\" rx=\"%f\" ry=\"%f\" rz=\"%f\" />", position.x, position.y, position.z, rotation.w, rotation.x, rotation.y, rotation.z); } - if (chatCommand == "playlvlfx" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true); } - if (chatCommand == "playrebuildfx" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { GameMessages::SendPlayFXEffect(entity, 230, u"rebuild", "230", LWOOBJID_EMPTY, 1.0f, 1.0f, true); } - if ((chatCommand == "freemoney" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) && args.size() == 1) { - const auto money = GeneralUtils::TryParse<int64_t>(args[0]); + void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); if (!money) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); @@ -1416,8 +2128,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ch->SetCoins(ch->GetCoins() + money.value(), eLootSourceType::MODERATION); } - if ((chatCommand == "setcurrency") && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto money = GeneralUtils::TryParse<int64_t>(args[0]); + void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); if (!money) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); @@ -1428,84 +2143,53 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ch->SetCoins(money.value(), eLootSourceType::MODERATION); } - // Allow for this on even while not a GM, as it sometimes toggles incorrrectly. - if (chatCommand == "gminvis" && entity->GetCharacter()->GetParentUser()->GetMaxGMLevel() >= eGameMasterLevel::DEVELOPER) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; - return; - } - - if (chatCommand == "gmimmune" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - - const auto state = GeneralUtils::TryParse<int32_t>(args[0]); - - if (!state) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); - return; - } - - if (destroyableComponent) destroyableComponent->SetIsGMImmune(state.value()); - return; - } - - //Testing basic attack immunity - if (chatCommand == "attackimmune" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - - const auto state = GeneralUtils::TryParse<int32_t>(args[0]); - - if (!state) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); - return; - } - - if (destroyableComponent) destroyableComponent->SetIsImmune(state.value()); - return; - } - - if (chatCommand == "buff" && args.size() >= 2 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { auto* buffComponent = entity->GetComponent<BuffComponent>(); - const auto id = GeneralUtils::TryParse<int32_t>(args[0]); + const auto id = GeneralUtils::TryParse<int32_t>(splitArgs[0]); if (!id) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff id."); return; } - const auto duration = GeneralUtils::TryParse<int32_t>(args[1]); + const auto duration = GeneralUtils::TryParse<int32_t>(splitArgs[1]); if (!duration) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff duration."); return; } if (buffComponent) buffComponent->ApplyBuff(id.value(), duration.value(), entity->GetObjectID()); - return; } - if ((chatCommand == "testmap" && args.size() >= 1) && entity->GetGMLevel() >= eGameMasterLevel::FORUM_MODERATOR) { + void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); LWOCLONEID cloneId = 0; bool force = false; - const auto reqZoneOptional = GeneralUtils::TryParse<LWOMAPID>(args[0]); + const auto reqZoneOptional = GeneralUtils::TryParse<LWOMAPID>(splitArgs[0]); if (!reqZoneOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); return; } const LWOMAPID reqZone = reqZoneOptional.value(); - if (args.size() > 1) { + if (splitArgs.size() > 1) { auto index = 1; - if (args[index] == "force") { + if (splitArgs[index] == "force") { index++; force = true; } - if (args.size() > index) { - const auto cloneIdOptional = GeneralUtils::TryParse<LWOCLONEID>(args[index]); + if (splitArgs.size() > index) { + const auto cloneIdOptional = GeneralUtils::TryParse<LWOCLONEID>(splitArgs[index]); if (!cloneIdOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); return; @@ -1542,40 +2226,41 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit }); } else { std::string msg = "ZoneID not found or allowed: "; - msg.append(args[0]); // FIXME: unnecessary utf16 re-encoding just for error + msg.append(splitArgs[0]); // FIXME: unnecessary utf16 re-encoding just for error ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(msg, msg.size())); } } - if (chatCommand == "createprivate" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { - const auto zone = GeneralUtils::TryParse<uint32_t>(args[0]); + void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto zone = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!zone) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); return; } - const auto clone = GeneralUtils::TryParse<uint32_t>(args[1]); + const auto clone = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); if (!clone) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone."); return; } - const auto& password = args[2]; + const auto& password = splitArgs[2]; ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password); ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Sent request for private zone with password: " + password)); - - return; } - if ((chatCommand == "debugui") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args) { ChatPackets::SendSystemMessage(sysAddr, u"Opening UIDebugger..."); - AMFArrayValue args; - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "ToggleUIDebugger;", args); } - if ((chatCommand == "boost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); if (possessorComponent == nullptr) { @@ -1588,8 +2273,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - if (args.size() >= 1) { - const auto time = GeneralUtils::TryParse<float>(args[0]); + if (splitArgs.size() >= 1) { + const auto time = GeneralUtils::TryParse<float>(splitArgs[0]); if (!time) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost time."); @@ -1604,10 +2289,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } else { GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); } - } - if ((chatCommand == "unboost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto* possessorComponent = entity->GetComponent<PossessorComponent>(); if (possessorComponent == nullptr) return; @@ -1617,21 +2301,24 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); } - if (chatCommand == "activatespawner" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto spawners = Game::zoneManager->GetSpawnersByName(args[0]); + void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); for (auto* spawner : spawners) { spawner->Activate(); } - spawners = Game::zoneManager->GetSpawnersInGroup(args[0]); + spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); for (auto* spawner : spawners) { spawner->Activate(); } } - if (chatCommand == "spawnphysicsverts" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { + void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) { //Go tell physics to spawn all the vertices: auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS); for (auto en : entities) { @@ -1641,7 +2328,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "reportproxphys" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { + void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PROXIMITY_MONITOR); for (auto en : entities) { auto phys = static_cast<ProximityMonitorComponent*>(en->GetComponent(eReplicaComponentType::PROXIMITY_MONITOR)); @@ -1657,25 +2344,31 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "triggerspawner" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto spawners = Game::zoneManager->GetSpawnersByName(args[0]); + void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); for (auto* spawner : spawners) { spawner->Spawn(); } - spawners = Game::zoneManager->GetSpawnersInGroup(args[0]); + spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); for (auto* spawner : spawners) { spawner->Spawn(); } } - if (chatCommand == "reforge" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 2) { - const auto baseItem = GeneralUtils::TryParse<LOT>(args[0]); + void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto baseItem = GeneralUtils::TryParse<LOT>(splitArgs[0]); if (!baseItem) return; - const auto reforgedItem = GeneralUtils::TryParse<LOT>(args[1]); + const auto reforgedItem = GeneralUtils::TryParse<LOT>(splitArgs[1]); if (!reforgedItem) return; auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); @@ -1687,16 +2380,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit inventoryComponent->AddItem(baseItem.value(), 1, eLootSourceType::MODERATION, eInventoryType::INVALID, data); } - if (chatCommand == "crash" && entity->GetGMLevel() >= eGameMasterLevel::OPERATOR) { + void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args) { ChatPackets::SendSystemMessage(sysAddr, u"Crashing..."); int* badPtr = nullptr; *badPtr = 0; - - return; } - if (chatCommand == "metrics" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { for (const auto variable : Metrics::GetAllMetrics()) { auto* metric = Metrics::GetMetric(variable); @@ -1729,11 +2420,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit sysAddr, u"Process ID: " + GeneralUtils::to_u16string(Metrics::GetProcessID()) ); - - return; } - if (chatCommand == "reloadconfig" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { Game::config->ReloadConfig(); VanityUtilities::SpawnVanity(); dpWorld::Reload(); @@ -1749,14 +2438,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); } - if (chatCommand == "rollloot" && entity->GetGMLevel() >= eGameMasterLevel::OPERATOR && args.size() >= 3) { - const auto lootMatrixIndex = GeneralUtils::TryParse<uint32_t>(args[0]); + void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto lootMatrixIndex = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!lootMatrixIndex) return; - const auto targetLot = GeneralUtils::TryParse<uint32_t>(args[1]); + const auto targetLot = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); if (!targetLot) return; - const auto loops = GeneralUtils::TryParse<uint32_t>(args[2]); + const auto loops = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); if (!loops) return; uint64_t totalRuns = 0; @@ -1787,16 +2479,19 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, message); } - if (chatCommand == "deleteinven" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + eInventoryType inventoryType = eInventoryType::INVALID; - const auto inventoryTypeOptional = GeneralUtils::TryParse<eInventoryType>(args[0]); + const auto inventoryTypeOptional = GeneralUtils::TryParse<eInventoryType>(splitArgs[0]); if (!inventoryTypeOptional) { // In this case, we treat the input as a string and try to find it in the reflection list - std::transform(args[0].begin(), args[0].end(), args[0].begin(), ::toupper); - LOG("looking for inventory %s", args[0].c_str()); + std::transform(splitArgs[0].begin(), splitArgs[0].end(), splitArgs[0].begin(), ::toupper); + LOG("looking for inventory %s", splitArgs[0].c_str()); for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { - if (std::string_view(args[0]) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) inventoryType = static_cast<eInventoryType>(index); + if (std::string_view(splitArgs[0]) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) inventoryType = static_cast<eInventoryType>(index); } } else { inventoryType = inventoryTypeOptional.value(); @@ -1814,14 +2509,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit if (!inventoryToDelete) return; inventoryToDelete->DeleteAllItems(); - LOG("Deleted inventory %s for user %llu", args[0].c_str(), entity->GetObjectID()); - ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(args[0])); + LOG("Deleted inventory %s for user %llu", splitArgs[0].c_str(), entity->GetObjectID()); + ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); } - if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + auto* skillComponent = entity->GetComponent<SkillComponent>(); if (skillComponent) { - const auto skillId = GeneralUtils::TryParse<uint32_t>(args[0]); + const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!skillId) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill ID."); @@ -1833,15 +2531,18 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "setskillslot" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 2) { + void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + auto* const inventoryComponent = entity->GetComponent<InventoryComponent>(); if (inventoryComponent) { - const auto slot = GeneralUtils::TryParse<BehaviorSlot>(args[0]); + const auto slot = GeneralUtils::TryParse<BehaviorSlot>(splitArgs[0]); if (!slot) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); return; } else { - const auto skillId = GeneralUtils::TryParse<uint32_t>(args[1]); + const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); if (!skillId) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); return; @@ -1853,10 +2554,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); if (destroyableComponent) { - const auto faction = GeneralUtils::TryParse<int32_t>(args[0]); + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); if (!faction) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); @@ -1868,10 +2572,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); if (destroyableComponent) { - const auto faction = GeneralUtils::TryParse<int32_t>(args[0]); + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); if (!faction) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); @@ -1883,7 +2590,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); if (destroyableComponent) { ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); @@ -1898,25 +2605,33 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "setrewardcode" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { + void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* character = entity->GetCharacter(); + if (!character) return; + auto* user = character->GetParentUser(); + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; auto* cdrewardCodes = CDClientManager::GetTable<CDRewardCodesTable>(); - auto id = cdrewardCodes->GetCodeID(args[0]); + auto id = cdrewardCodes->GetCodeID(splitArgs[0]); if (id != -1) Database::Get()->InsertRewardCode(user->GetAccountID(), id); } - if (chatCommand == "inspect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + Entity* closest = nullptr; std::u16string ldf; bool isLDF = false; - auto component = GeneralUtils::TryParse<eReplicaComponentType>(args[0]); + auto component = GeneralUtils::TryParse<eReplicaComponentType>(splitArgs[0]); if (!component) { component = eReplicaComponentType::INVALID; - ldf = GeneralUtils::UTF8ToUTF16(args[0]); + ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]); isLDF = true; } @@ -1977,11 +2692,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(stream.str())); } - if (args.size() >= 2) { - if (args[1] == "-m" && args.size() >= 3) { + if (splitArgs.size() >= 2) { + if (splitArgs[1] == "-m" && splitArgs.size() >= 3) { auto* const movingPlatformComponent = closest->GetComponent<MovingPlatformComponent>(); - const auto mValue = GeneralUtils::TryParse<int32_t>(args[2]); + const auto mValue = GeneralUtils::TryParse<int32_t>(splitArgs[2]); if (!movingPlatformComponent || !mValue) return; @@ -1994,23 +2709,23 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } Game::entityManager->SerializeEntity(closest); - } else if (args[1] == "-a" && args.size() >= 3) { - RenderComponent::PlayAnimation(closest, args.at(2)); - } else if (args[1] == "-s") { + } else if (splitArgs[1] == "-a" && splitArgs.size() >= 3) { + RenderComponent::PlayAnimation(closest, splitArgs.at(2)); + } else if (splitArgs[1] == "-s") { for (auto* entry : closest->GetSettings()) { ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(entry->GetString())); } ChatPackets::SendSystemMessage(sysAddr, u"------"); ChatPackets::SendSystemMessage(sysAddr, u"Spawner ID: " + GeneralUtils::to_u16string(closest->GetSpawnerID())); - } else if (args[1] == "-p") { + } else if (splitArgs[1] == "-p") { const auto postion = closest->GetPosition(); ChatPackets::SendSystemMessage( sysAddr, GeneralUtils::ASCIIToUTF16("< " + std::to_string(postion.x) + ", " + std::to_string(postion.y) + ", " + std::to_string(postion.z) + " >") ); - } else if (args[1] == "-f") { + } else if (splitArgs[1] == "-f") { auto* destuctable = closest->GetComponent<DestroyableComponent>(); if (destuctable == nullptr) { @@ -2030,14 +2745,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); } - if (args.size() >= 3) { - const auto faction = GeneralUtils::TryParse<int32_t>(args[2]); + if (splitArgs.size() >= 3) { + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[2]); if (!faction) return; destuctable->SetFaction(-1); destuctable->AddFaction(faction.value(), true); } - } else if (args[1] == "-cf") { + } else if (splitArgs[1] == "-cf") { auto* destuctable = entity->GetComponent<DestroyableComponent>(); if (!destuctable) { ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); @@ -2045,7 +2760,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } if (destuctable->IsEnemy(closest)) ChatPackets::SendSystemMessage(sysAddr, u"They are our enemy"); else ChatPackets::SendSystemMessage(sysAddr, u"They are NOT our enemy"); - } else if (args[1] == "-t") { + } else if (splitArgs[1] == "-t") { auto* phantomPhysicsComponent = closest->GetComponent<PhantomPhysicsComponent>(); if (phantomPhysicsComponent != nullptr) { @@ -2066,6 +2781,276 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } } +}; + +namespace GMGreaterThanZeroCommands { + + void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() == 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + std::u16string username = GeneralUtils::UTF8ToUTF16(splitArgs[0]); + if (player == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + username); + return; + } + + Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::KICK); + + ChatPackets::SendSystemMessage(sysAddr, u"Kicked: " + username); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /kick <username>"); + } + } + + void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() == 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + uint32_t accountId = 0; + + if (player == nullptr) { + auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); + + if (characterInfo) { + accountId = characterInfo->accountId; + } + + if (accountId == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + + return; + } + } else { + auto* character = player->GetCharacter(); + auto* user = character != nullptr ? character->GetParentUser() : nullptr; + if (user) accountId = user->GetAccountID(); + } + + if (accountId != 0) Database::Get()->UpdateAccountBan(accountId, true); + + if (player != nullptr) { + Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Banned: " + GeneralUtils::ASCIIToUTF16(splitArgs[0])); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /ban <username>"); + } + } + + void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto& playerName = splitArgs[0]; + + auto playerInfo = Database::Get()->GetCharacterInfo(playerName); + + uint32_t receiverID = 0; + if (!playerInfo) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); + + return; + } + + receiverID = playerInfo->id; + + const auto lot = GeneralUtils::TryParse<LOT>(splitArgs.at(1)); + + if (!lot) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot."); + return; + } + + IMail::MailInfo mailInsert; + mailInsert.senderId = entity->GetObjectID(); + mailInsert.senderUsername = "Darkflame Universe"; + mailInsert.receiverId = receiverID; + mailInsert.recipient = playerName; + mailInsert.subject = "Lost item"; + mailInsert.body = "This is a replacement item for one you lost."; + mailInsert.itemID = LWOOBJID_EMPTY; + mailInsert.itemLOT = lot.value(); + mailInsert.itemSubkey = LWOOBJID_EMPTY; + mailInsert.itemCount = 1; + Database::Get()->InsertNewMail(mailInsert); + + ChatPackets::SendSystemMessage(sysAddr, u"Mail sent"); + } + + void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (PropertyManagementComponent::Instance() != nullptr) { + PropertyManagementComponent::Instance()->UpdateApprovedStatus(true); + } + } + + void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() >= 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + uint32_t accountId = 0; + LWOOBJID characterId = 0; + + if (player == nullptr) { + auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); + + if (characterInfo) { + accountId = characterInfo->accountId; + characterId = characterInfo->id; + + GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); + GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); + } + + if (accountId == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + + return; + } + } else { + auto* character = player->GetCharacter(); + auto* user = character != nullptr ? character->GetParentUser() : nullptr; + if (user) accountId = user->GetAccountID(); + characterId = player->GetObjectID(); + } + + time_t expire = 1; // Default to indefinate mute + + if (splitArgs.size() >= 2) { + const auto days = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!days) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid days."); + + return; + } + + std::optional<uint32_t> hours; + if (splitArgs.size() >= 3) { + hours = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); + if (!hours) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid hours."); + + return; + } + } + + expire = time(NULL); + expire += 24 * 60 * 60 * days.value(); + expire += 60 * 60 * hours.value_or(0); + } + + if (accountId != 0) Database::Get()->UpdateAccountUnmuteTime(accountId, expire); + + char buffer[32] = "brought up for review.\0"; + + if (expire != 1) { + std::tm* ptm = std::localtime(&expire); + // Format: Mo, 15.06.2009 20:20:00 + std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm); + } + + const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer)); + + ChatPackets::SendSystemMessage(sysAddr, u"Muted: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]) + u" until " + timeStr); + + //Notify chat about it + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE); + + bitStream.Write(characterId); + bitStream.Write(expire); + + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /mute <username> <days (optional)> <hours (optional)>"); + } + } + + void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + auto* character = entity->GetCharacter(); + + if (character) { + bool isFlying = character->GetIsFlying(); + + if (isFlying) { + GameMessages::SendSetJetPackMode(entity, false); + + character->SetIsFlying(false); + } else { + float speedScale = 1.0f; + + if (splitArgs.size() >= 1) { + const auto tempScaleStore = GeneralUtils::TryParse<float>(splitArgs.at(0)); + + if (tempScaleStore) { + speedScale = tempScaleStore.value(); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse speed scale argument."); + } + } + + float airSpeed = 20 * speedScale; + float maxAirSpeed = 30 * speedScale; + float verticalVelocity = 1.5 * speedScale; + + GameMessages::SendSetJetPackMode(entity, true, true, false, 167, airSpeed, maxAirSpeed, verticalVelocity); + + character->SetIsFlying(true); + } + } + } + + void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!state) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); + return; + } + + if (destroyableComponent) destroyableComponent->SetIsImmune(state.value()); + } + + void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!state) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); + return; + } + + if (destroyableComponent) destroyableComponent->SetIsGMImmune(state.value()); + } + + void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + } + + void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(args), UNASSIGNED_SYSTEM_ADDRESS); + } + + void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + std::string name = entity->GetCharacter()->GetName() + " - " + args; + GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); + } } void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { diff --git a/dGame/dUtilities/SlashCommandHandler.h b/dGame/dUtilities/SlashCommandHandler.h index 85b7c697..e986487b 100644 --- a/dGame/dUtilities/SlashCommandHandler.h +++ b/dGame/dUtilities/SlashCommandHandler.h @@ -7,13 +7,128 @@ #define SLASHCOMMANDHANDLER_H #include "RakNetTypes.h" +#include "eGameMasterLevel.h" #include <string> class Entity; -namespace SlashCommandHandler { - void HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr); - void SendAnnouncement(const std::string& title, const std::string& message); +struct Command { + std::string help; + std::string info; + std::vector<std::string> aliases; + std::function<void(Entity*, const SystemAddress&,const std::string)> handle; + eGameMasterLevel requiredLevel = eGameMasterLevel::OPERATOR; }; +namespace SlashCommandHandler { + void Startup(); + void HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr); + void SendAnnouncement(const std::string& title, const std::string& message); + void RegisterCommand(Command info); +}; + +namespace DEVGMCommands { + void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} + +namespace GMZeroCommands { + void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} + +namespace GMGreaterThanZeroCommands { + void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} + #endif // SLASHCOMMANDHANDLER_H diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 1c85ef22..9f6d63bc 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -79,6 +79,7 @@ #include "PositionUpdate.h" #include "PlayerManager.h" #include "eLoginResponse.h" +#include "SlashCommandHandler.h" namespace Game { Logger* logger = nullptr; @@ -313,6 +314,9 @@ int main(int argc, char** argv) { uint32_t sqlPingTime = 10 * 60 * currentFramerate; // 10 minutes in frames uint32_t emptyShutdownTime = (cloneID == 0 ? 30 : 5) * 60 * currentFramerate; // 30 minutes for main worlds, 5 for all others. + // Register slash commands + SlashCommandHandler::Startup(); + Game::logger->Flush(); // once immediately before the main loop while (true) { Metrics::StartMeasurement(MetricVariable::Frame); diff --git a/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp b/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp index 0ca2e2ea..c47eb489 100644 --- a/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp +++ b/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp @@ -121,7 +121,7 @@ TEST(MagicEnumTest, eGameMessageTypeTest) { namespace { template <typename T> void AssertEnumArraySorted(const T& eArray) { - for (int i = 0; i < eArray->size(); ++i) { + for (int i = 0; i < eArray->size() - 1; ++i) { const auto entryCurr = eArray->at(i).first; LOG_EARRAY(eArray, i, entryCurr); const auto entryNext = eArray->at(++i).first; From 3260a063cb46d2ad24102c225fa3c138046ac82e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:13:19 -0700 Subject: [PATCH 2/4] ignore whitespace in try parse (#1536) --- dCommon/GeneralUtils.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index d502f55a..f59ec71f 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -156,8 +156,11 @@ namespace GeneralUtils { * @returns An std::optional containing the desired value if it is equivalent to the string */ template <Numeric T> - [[nodiscard]] std::optional<T> TryParse(const std::string_view str) { + [[nodiscard]] std::optional<T> TryParse(std::string_view str) { numeric_parse_t<T> result; + if (!str.empty()) { + while (std::isspace(str.front())) str.remove_prefix(1); + } const char* const strEnd = str.data() + str.size(); const auto [parseEnd, ec] = std::from_chars(str.data(), strEnd, result); @@ -181,8 +184,12 @@ namespace GeneralUtils { * @returns An std::optional containing the desired value if it is equivalent to the string */ template <std::floating_point T> - [[nodiscard]] std::optional<T> TryParse(const std::string_view str) noexcept + [[nodiscard]] std::optional<T> TryParse(std::string_view str) noexcept try { + if (!str.empty()) { + while (std::isspace(str.front())) str.remove_prefix(1); + } + size_t parseNum; const T result = details::_parse<T>(str, parseNum); const bool isParsed = str.length() == parseNum; From be0a2f6f14e5acbd8a30bcbcf70de8b2dbb2c057 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:13:31 -0700 Subject: [PATCH 3/4] fix jittering (#1537) --- dGame/Entity.cpp | 4 +--- dGame/dComponents/SkillComponent.cpp | 22 +++++++++++++++++----- dGame/dComponents/SkillComponent.h | 4 ++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 90763b4c..97e86758 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1635,10 +1635,8 @@ void Entity::PickupItem(const LWOOBJID& objectID) { CDObjectSkillsTable* skillsTable = CDClientManager::GetTable<CDObjectSkillsTable>(); std::vector<CDObjectSkills> skills = skillsTable->Query([=](CDObjectSkills entry) {return (entry.objectTemplate == p.second.lot); }); for (CDObjectSkills skill : skills) { - CDSkillBehaviorTable* skillBehTable = CDClientManager::GetTable<CDSkillBehaviorTable>(); - auto* skillComponent = GetComponent<SkillComponent>(); - if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID()); + if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID(), skill.castOnType, NiQuaternion(0, 0, 0, 0)); auto* missionComponent = GetComponent<MissionComponent>(); diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 2e6edacd..8460032c 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -227,7 +227,7 @@ void SkillComponent::RegisterCalculatedProjectile(const LWOOBJID projectileId, B this->m_managedProjectiles.push_back(entry); } -bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LWOOBJID optionalOriginatorID) { +bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LWOOBJID optionalOriginatorID, const int32_t castType, const NiQuaternion rotationOverride) { uint32_t behaviorId = -1; // try to find it via the cache const auto& pair = m_skillBehaviorCache.find(skillId); @@ -247,11 +247,19 @@ bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LW return false; } - return CalculateBehavior(skillId, behaviorId, target, false, false, optionalOriginatorID).success; + return CalculateBehavior(skillId, behaviorId, target, false, false, optionalOriginatorID, castType, rotationOverride).success; } -SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, const uint32_t behaviorId, const LWOOBJID target, const bool ignoreTarget, const bool clientInitalized, const LWOOBJID originatorOverride) { +SkillExecutionResult SkillComponent::CalculateBehavior( + const uint32_t skillId, + const uint32_t behaviorId, + const LWOOBJID target, + const bool ignoreTarget, + const bool clientInitalized, + const LWOOBJID originatorOverride, + const int32_t castType, + const NiQuaternion rotationOverride) { RakNet::BitStream bitStream{}; auto* behavior = Behavior::CreateBehavior(behaviorId); @@ -283,7 +291,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c // Echo start skill EchoStartSkill start; - start.iCastType = 0; + start.iCastType = castType; start.skillID = skillId; start.uiSkillHandle = context->skillUId; start.optionalOriginatorID = context->originator; @@ -294,6 +302,10 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c if (originator != nullptr) { start.originatorRot = originator->GetRotation(); } + + if (rotationOverride != NiQuaternionConstant::IDENTITY) { + start.originatorRot = rotationOverride; + } //start.optionalTargetID = target; start.sBitStream.assign(reinterpret_cast<char*>(bitStream.GetData()), bitStream.GetNumberOfBytesUsed()); @@ -464,7 +476,7 @@ void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID targ behavior->UnCast(&context, { target }); } -SkillComponent::SkillComponent(Entity* parent): Component(parent) { +SkillComponent::SkillComponent(Entity* parent) : Component(parent) { this->m_skillUid = 0; } diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index 2acae5d7..24d92148 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -127,7 +127,7 @@ public: * @param optionalOriginatorID change the originator of the skill * @return if the case succeeded */ - bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY); + bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = NiQuaternionConstant::IDENTITY); /** * Initializes a server-side skill calculation. @@ -139,7 +139,7 @@ public: * @param originatorOverride an override for the originator of the skill calculation * @return the result of the skill calculation */ - SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY); + SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = NiQuaternionConstant::IDENTITY); /** * Register a server-side projectile. From 28ce8ac54d00a36dfd53e086792b62e8a4f46d92 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:13:49 -0700 Subject: [PATCH 4/4] remove usage of xmldoc as a ptr (#1538) resolves a memory leak in BrickDatabase, adds stability to character save doc. Tested that saving manually via force-save, logout and /crash all saved my position and my removed banana as expected. The doc was always deleted on character destruction and on any updates, so this is just a semantic change (and now we no longer have new'd tinyxml2::documents on the heap) --- dGame/Character.cpp | 52 +++++++------------ dGame/Character.h | 4 +- dGame/Entity.cpp | 5 +- dGame/Entity.h | 2 +- dGame/dComponents/BuffComponent.cpp | 12 ++--- dGame/dComponents/BuffComponent.h | 4 +- dGame/dComponents/CharacterComponent.cpp | 14 ++--- dGame/dComponents/CharacterComponent.h | 4 +- dGame/dComponents/Component.cpp | 4 +- dGame/dComponents/Component.h | 4 +- .../ControllablePhysicsComponent.cpp | 8 +-- .../ControllablePhysicsComponent.h | 4 +- dGame/dComponents/DestroyableComponent.cpp | 8 +-- dGame/dComponents/DestroyableComponent.h | 4 +- dGame/dComponents/InventoryComponent.cpp | 35 +++++++------ dGame/dComponents/InventoryComponent.h | 10 ++-- .../dComponents/LevelProgressionComponent.cpp | 8 +-- dGame/dComponents/LevelProgressionComponent.h | 4 +- dGame/dComponents/MissionComponent.cpp | 28 +++++----- dGame/dComponents/MissionComponent.h | 4 +- dGame/dMission/Mission.cpp | 38 +++++++------- dGame/dMission/Mission.h | 4 +- dGame/dUtilities/BrickDatabase.cpp | 9 ++-- 23 files changed, 125 insertions(+), 144 deletions(-) diff --git a/dGame/Character.cpp b/dGame/Character.cpp index eab7583f..59a67462 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -27,12 +27,9 @@ Character::Character(uint32_t id, User* parentUser) { m_ID = id; m_ParentUser = parentUser; m_OurEntity = nullptr; - m_Doc = nullptr; } Character::~Character() { - if (m_Doc) delete m_Doc; - m_Doc = nullptr; m_OurEntity = nullptr; m_ParentUser = nullptr; } @@ -55,8 +52,6 @@ void Character::UpdateInfoFromDatabase() { m_ZoneInstanceID = 0; //These values don't really matter, these are only used on the char select screen and seem unused. m_ZoneCloneID = 0; - m_Doc = nullptr; - //Quickly and dirtly parse the xmlData to get the info we need: DoQuickXMLDataParse(); @@ -70,18 +65,13 @@ void Character::UpdateInfoFromDatabase() { } void Character::UpdateFromDatabase() { - if (m_Doc) delete m_Doc; UpdateInfoFromDatabase(); } void Character::DoQuickXMLDataParse() { if (m_XMLData.size() == 0) return; - delete m_Doc; - m_Doc = new tinyxml2::XMLDocument(); - if (!m_Doc) return; - - if (m_Doc->Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) { + if (m_Doc.Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) { LOG("Loaded xmlData for character %s (%i)!", m_Name.c_str(), m_ID); } else { LOG("Failed to load xmlData!"); @@ -89,7 +79,7 @@ void Character::DoQuickXMLDataParse() { return; } - tinyxml2::XMLElement* mf = m_Doc->FirstChildElement("obj")->FirstChildElement("mf"); + tinyxml2::XMLElement* mf = m_Doc.FirstChildElement("obj")->FirstChildElement("mf"); if (!mf) { LOG("Failed to find mf tag!"); return; @@ -108,7 +98,7 @@ void Character::DoQuickXMLDataParse() { mf->QueryAttribute("ess", &m_Eyes); mf->QueryAttribute("ms", &m_Mouth); - tinyxml2::XMLElement* inv = m_Doc->FirstChildElement("obj")->FirstChildElement("inv"); + tinyxml2::XMLElement* inv = m_Doc.FirstChildElement("obj")->FirstChildElement("inv"); if (!inv) { LOG("Char has no inv!"); return; @@ -141,7 +131,7 @@ void Character::DoQuickXMLDataParse() { } - tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char"); + tinyxml2::XMLElement* character = m_Doc.FirstChildElement("obj")->FirstChildElement("char"); if (character) { character->QueryAttribute("cc", &m_Coins); int32_t gm_level = 0; @@ -205,7 +195,7 @@ void Character::DoQuickXMLDataParse() { character->QueryAttribute("lzrw", &m_OriginalRotation.w); } - auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag"); if (flags) { auto* currentChild = flags->FirstChildElement(); while (currentChild) { @@ -239,12 +229,10 @@ void Character::SetBuildMode(bool buildMode) { } void Character::SaveXMLToDatabase() { - if (!m_Doc) return; - //For metrics, we'll record the time it took to save: auto start = std::chrono::system_clock::now(); - tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char"); + tinyxml2::XMLElement* character = m_Doc.FirstChildElement("obj")->FirstChildElement("char"); if (character) { character->SetAttribute("gm", static_cast<uint32_t>(m_GMLevel)); character->SetAttribute("cc", m_Coins); @@ -266,11 +254,11 @@ void Character::SaveXMLToDatabase() { } auto emotes = character->FirstChildElement("ue"); - if (!emotes) emotes = m_Doc->NewElement("ue"); + if (!emotes) emotes = m_Doc.NewElement("ue"); emotes->DeleteChildren(); for (int emoteID : m_UnlockedEmotes) { - auto emote = m_Doc->NewElement("e"); + auto emote = m_Doc.NewElement("e"); emote->SetAttribute("id", emoteID); emotes->LinkEndChild(emote); @@ -280,15 +268,15 @@ void Character::SaveXMLToDatabase() { } //Export our flags: - auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag"); if (!flags) { - flags = m_Doc->NewElement("flag"); //Create a flags tag if we don't have one - m_Doc->FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time + flags = m_Doc.NewElement("flag"); //Create a flags tag if we don't have one + m_Doc.FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time } flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes for (std::pair<uint32_t, uint64_t> flag : m_PlayerFlags) { - auto* f = m_Doc->NewElement("f"); + auto* f = m_Doc.NewElement("f"); f->SetAttribute("id", flag.first); //Because of the joy that is tinyxml2, it doesn't offer a function to set a uint64 as an attribute. @@ -301,7 +289,7 @@ void Character::SaveXMLToDatabase() { // Prevents the news feed from showing up on world transfers if (GetPlayerFlag(ePlayerFlag::IS_NEWS_SCREEN_VISIBLE)) { - auto* s = m_Doc->NewElement("s"); + auto* s = m_Doc.NewElement("s"); s->SetAttribute("si", ePlayerFlag::IS_NEWS_SCREEN_VISIBLE); flags->LinkEndChild(s); } @@ -326,7 +314,7 @@ void Character::SaveXMLToDatabase() { void Character::SetIsNewLogin() { // If we dont have a flag element, then we cannot have a s element as a child of flag. - auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag"); if (!flags) return; auto* currentChild = flags->FirstChildElement(); @@ -344,7 +332,7 @@ void Character::SetIsNewLogin() { void Character::WriteToDatabase() { //Dump our xml into m_XMLData: tinyxml2::XMLPrinter printer(0, true, 0); - m_Doc->Print(&printer); + m_Doc.Print(&printer); //Finally, save to db: Database::Get()->UpdateCharacterXml(m_ID, printer.CStr()); @@ -421,15 +409,15 @@ void Character::SetRetroactiveFlags() { void Character::SaveXmlRespawnCheckpoints() { //Export our respawn points: - auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res"); + auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res"); if (!points) { - points = m_Doc->NewElement("res"); - m_Doc->FirstChildElement("obj")->LinkEndChild(points); + points = m_Doc.NewElement("res"); + m_Doc.FirstChildElement("obj")->LinkEndChild(points); } points->DeleteChildren(); for (const auto& point : m_WorldRespawnCheckpoints) { - auto* r = m_Doc->NewElement("r"); + auto* r = m_Doc.NewElement("r"); r->SetAttribute("w", point.first); r->SetAttribute("x", point.second.x); @@ -443,7 +431,7 @@ void Character::SaveXmlRespawnCheckpoints() { void Character::LoadXmlRespawnCheckpoints() { m_WorldRespawnCheckpoints.clear(); - auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res"); + auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res"); if (!points) { return; } diff --git a/dGame/Character.h b/dGame/Character.h index b994fb61..77f286f0 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -37,7 +37,7 @@ public: void LoadXmlRespawnCheckpoints(); const std::string& GetXMLData() const { return m_XMLData; } - tinyxml2::XMLDocument* GetXMLDoc() const { return m_Doc; } + const tinyxml2::XMLDocument& GetXMLDoc() const { return m_Doc; } /** * Out of abundance of safety and clarity of what this saves, this is its own function. @@ -623,7 +623,7 @@ private: /** * The character XML belonging to this character */ - tinyxml2::XMLDocument* m_Doc; + tinyxml2::XMLDocument m_Doc; /** * Title of an announcement this character made (reserved for GMs) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 97e86758..00ad8471 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -476,8 +476,7 @@ void Entity::Initialize() { } if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY) > 0 || m_Character) { - auto* xmlDoc = m_Character ? m_Character->GetXMLDoc() : nullptr; - AddComponent<InventoryComponent>(xmlDoc); + AddComponent<InventoryComponent>(); } // if this component exists, then we initialize it. it's value is always 0 if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MULTI_ZONE_ENTRANCE, -1) != -1) { @@ -1244,7 +1243,7 @@ void Entity::WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType outBitStream.Write0(); } -void Entity::UpdateXMLDoc(tinyxml2::XMLDocument* doc) { +void Entity::UpdateXMLDoc(tinyxml2::XMLDocument& doc) { //This function should only ever be called from within Character, meaning doc should always exist when this is called. //Naturally, we don't include any non-player components in this update function. diff --git a/dGame/Entity.h b/dGame/Entity.h index 740d7c92..ffdcb713 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -174,7 +174,7 @@ public: void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); - void UpdateXMLDoc(tinyxml2::XMLDocument* doc); + void UpdateXMLDoc(tinyxml2::XMLDocument& doc); void Update(float deltaTime); // Events diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index 8b76e423..44c88ccb 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -326,9 +326,9 @@ Entity* BuffComponent::GetParent() const { return m_Parent; } -void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { +void BuffComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { // Load buffs - auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); + auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); // Make sure we have a clean buff element. auto* buffElement = dest->FirstChildElement("buff"); @@ -386,15 +386,15 @@ void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { } } -void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) { +void BuffComponent::UpdateXml(tinyxml2::XMLDocument& doc) { // Save buffs - auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); + auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); // Make sure we have a clean buff element. auto* buffElement = dest->FirstChildElement("buff"); if (buffElement == nullptr) { - buffElement = doc->NewElement("buff"); + buffElement = doc.NewElement("buff"); dest->LinkEndChild(buffElement); } else { @@ -402,7 +402,7 @@ void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) { } for (const auto& [id, buff] : m_Buffs) { - auto* buffEntry = doc->NewElement("b"); + auto* buffEntry = doc.NewElement("b"); // TODO: change this if to if (buff.cancelOnZone || buff.cancelOnLogout) handling at some point. No current way to differentiate between zone transfer and logout. if (buff.cancelOnZone) continue; diff --git a/dGame/dComponents/BuffComponent.h b/dGame/dComponents/BuffComponent.h index df3c6a78..507e53a0 100644 --- a/dGame/dComponents/BuffComponent.h +++ b/dGame/dComponents/BuffComponent.h @@ -57,9 +57,9 @@ public: Entity* GetParent() const; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 3eafd924..d706af9c 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -186,9 +186,9 @@ void CharacterComponent::SetGMLevel(eGameMasterLevel gmlevel) { m_GMLevel = gmlevel; } -void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { +void CharacterComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); + auto* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while loading XML!"); return; @@ -299,8 +299,8 @@ void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { } } -void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* minifig = doc->FirstChildElement("obj")->FirstChildElement("mf"); +void CharacterComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* minifig = doc.FirstChildElement("obj")->FirstChildElement("mf"); if (!minifig) { LOG("Failed to find mf tag while updating XML!"); return; @@ -320,7 +320,7 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { // done with minifig - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); + tinyxml2::XMLElement* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while updating XML!"); return; @@ -338,11 +338,11 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { // Set the zone statistics of the form <zs><s/> ... <s/></zs> auto zoneStatistics = character->FirstChildElement("zs"); - if (!zoneStatistics) zoneStatistics = doc->NewElement("zs"); + if (!zoneStatistics) zoneStatistics = doc.NewElement("zs"); zoneStatistics->DeleteChildren(); for (auto pair : m_ZoneStatistics) { - auto zoneStatistic = doc->NewElement("s"); + auto zoneStatistic = doc.NewElement("s"); zoneStatistic->SetAttribute("map", pair.first); zoneStatistic->SetAttribute("ac", pair.second.m_AchievementsCollected); diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index aa5c2e29..7e63b0bd 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -70,8 +70,8 @@ public: CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress); ~CharacterComponent() override; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/Component.cpp b/dGame/dComponents/Component.cpp index 705c44a7..8f38fb61 100644 --- a/dGame/dComponents/Component.cpp +++ b/dGame/dComponents/Component.cpp @@ -21,11 +21,11 @@ void Component::OnUse(Entity* originator) { } -void Component::UpdateXml(tinyxml2::XMLDocument* doc) { +void Component::UpdateXml(tinyxml2::XMLDocument& doc) { } -void Component::LoadFromXml(tinyxml2::XMLDocument* doc) { +void Component::LoadFromXml(const tinyxml2::XMLDocument& doc) { } diff --git a/dGame/dComponents/Component.h b/dGame/dComponents/Component.h index 062924f7..160565fb 100644 --- a/dGame/dComponents/Component.h +++ b/dGame/dComponents/Component.h @@ -34,13 +34,13 @@ public: * Save data from this componennt to character XML * @param doc the document to write data to */ - virtual void UpdateXml(tinyxml2::XMLDocument* doc); + virtual void UpdateXml(tinyxml2::XMLDocument& doc); /** * Load base data for this component from character XML * @param doc the document to read data from */ - virtual void LoadFromXml(tinyxml2::XMLDocument* doc); + virtual void LoadFromXml(const tinyxml2::XMLDocument& doc); virtual void Serialize(RakNet::BitStream& outBitStream, bool isConstruction); diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index dccbe915..18e4b19d 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -158,8 +158,8 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bo } } -void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); +void ControllablePhysicsComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag!"); return; @@ -178,8 +178,8 @@ void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { m_DirtyPosition = true; } -void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); +void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while updating XML!"); return; diff --git a/dGame/dComponents/ControllablePhysicsComponent.h b/dGame/dComponents/ControllablePhysicsComponent.h index 8834128a..6309b8fc 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.h +++ b/dGame/dComponents/ControllablePhysicsComponent.h @@ -28,8 +28,8 @@ public: void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Sets the position of this entity, also ensures this update is serialized next tick. diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 1b9b3285..476235f9 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -185,8 +185,8 @@ void DestroyableComponent::Update(float deltaTime) { m_DamageCooldownTimer -= deltaTime; } -void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); +void DestroyableComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); if (!dest) { LOG("Failed to find dest tag!"); return; @@ -207,8 +207,8 @@ void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { m_DirtyHealth = true; } -void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); +void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); if (!dest) { LOG("Failed to find dest tag!"); return; diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 85a4f941..56f30103 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -26,8 +26,8 @@ public: void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Initializes the component using a different LOT diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 161d7b91..8af7fb34 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -38,7 +38,7 @@ #include "CDObjectSkillsTable.h" #include "CDSkillBehaviorTable.h" -InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent) { +InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) { this->m_Dirty = true; this->m_Equipped = {}; this->m_Pushed = {}; @@ -48,7 +48,8 @@ InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* do const auto lot = parent->GetLOT(); if (lot == 1) { - LoadXml(document); + auto* character = m_Parent->GetCharacter(); + if (character) LoadXml(character->GetXMLDoc()); CheckProxyIntegrity(); @@ -472,10 +473,10 @@ bool InventoryComponent::HasSpaceForLoot(const std::unordered_map<LOT, int32_t>& return true; } -void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) { +void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) { LoadPetXml(document); - auto* inventoryElement = document->FirstChildElement("obj")->FirstChildElement("inv"); + auto* inventoryElement = document.FirstChildElement("obj")->FirstChildElement("inv"); if (inventoryElement == nullptr) { LOG("Failed to find 'inv' xml element!"); @@ -594,10 +595,10 @@ void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) { } } -void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { +void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { UpdatePetXml(document); - auto* inventoryElement = document->FirstChildElement("obj")->FirstChildElement("inv"); + auto* inventoryElement = document.FirstChildElement("obj")->FirstChildElement("inv"); if (inventoryElement == nullptr) { LOG("Failed to find 'inv' xml element!"); @@ -631,7 +632,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { bags->DeleteChildren(); for (const auto* inventory : inventoriesToSave) { - auto* bag = document->NewElement("b"); + auto* bag = document.NewElement("b"); bag->SetAttribute("t", inventory->GetType()); bag->SetAttribute("m", static_cast<unsigned int>(inventory->GetSize())); @@ -654,14 +655,14 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { continue; } - auto* bagElement = document->NewElement("in"); + auto* bagElement = document.NewElement("in"); bagElement->SetAttribute("t", inventory->GetType()); for (const auto& pair : inventory->GetItems()) { auto* item = pair.second; - auto* itemElement = document->NewElement("i"); + auto* itemElement = document.NewElement("i"); itemElement->SetAttribute("l", item->GetLot()); itemElement->SetAttribute("id", item->GetId()); @@ -680,7 +681,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { continue; } - auto* extraInfo = document->NewElement("x"); + auto* extraInfo = document.NewElement("x"); extraInfo->SetAttribute("ma", data->GetString(false).c_str()); @@ -1542,8 +1543,8 @@ void InventoryComponent::PurgeProxies(Item* item) { } } -void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document) { - auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet"); +void InventoryComponent::LoadPetXml(const tinyxml2::XMLDocument& document) { + auto* petInventoryElement = document.FirstChildElement("obj")->FirstChildElement("pet"); if (petInventoryElement == nullptr) { m_Pets.clear(); @@ -1574,19 +1575,19 @@ void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document) { } } -void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument* document) { - auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet"); +void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) { + auto* petInventoryElement = document.FirstChildElement("obj")->FirstChildElement("pet"); if (petInventoryElement == nullptr) { - petInventoryElement = document->NewElement("pet"); + petInventoryElement = document.NewElement("pet"); - document->FirstChildElement("obj")->LinkEndChild(petInventoryElement); + document.FirstChildElement("obj")->LinkEndChild(petInventoryElement); } petInventoryElement->DeleteChildren(); for (const auto& pet : m_Pets) { - auto* petElement = document->NewElement("p"); + auto* petElement = document.NewElement("p"); petElement->SetAttribute("id", pet.first); petElement->SetAttribute("l", pet.second.lot); diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 8f58a523..a1eb14d1 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -38,12 +38,12 @@ enum class eItemType : int32_t; class InventoryComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY; - explicit InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document = nullptr); + InventoryComponent(Entity* parent); void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void LoadXml(tinyxml2::XMLDocument* document); - void UpdateXml(tinyxml2::XMLDocument* document) override; + void LoadXml(const tinyxml2::XMLDocument& document); + void UpdateXml(tinyxml2::XMLDocument& document) override; /** * Returns an inventory of the specified type, if it exists @@ -470,13 +470,13 @@ private: * Saves all the pet information stored in inventory items to the database * @param document the xml doc to save to */ - void LoadPetXml(tinyxml2::XMLDocument* document); + void LoadPetXml(const tinyxml2::XMLDocument& document); /** * Loads all the pet information from an xml doc into items * @param document the xml doc to load from */ - void UpdatePetXml(tinyxml2::XMLDocument* document); + void UpdatePetXml(tinyxml2::XMLDocument& document); }; #endif diff --git a/dGame/dComponents/LevelProgressionComponent.cpp b/dGame/dComponents/LevelProgressionComponent.cpp index 2d3d5144..a6801a40 100644 --- a/dGame/dComponents/LevelProgressionComponent.cpp +++ b/dGame/dComponents/LevelProgressionComponent.cpp @@ -13,8 +13,8 @@ LevelProgressionComponent::LevelProgressionComponent(Entity* parent) : Component m_CharacterVersion = eCharacterVersion::LIVE; } -void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl"); +void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* level = doc.FirstChildElement("obj")->FirstChildElement("lvl"); if (!level) { LOG("Failed to find lvl tag while updating XML!"); return; @@ -24,8 +24,8 @@ void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { level->SetAttribute("cv", static_cast<uint32_t>(m_CharacterVersion)); } -void LevelProgressionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl"); +void LevelProgressionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* level = doc.FirstChildElement("obj")->FirstChildElement("lvl"); if (!level) { LOG("Failed to find lvl tag while loading XML!"); return; diff --git a/dGame/dComponents/LevelProgressionComponent.h b/dGame/dComponents/LevelProgressionComponent.h index a27039f3..e9981ab6 100644 --- a/dGame/dComponents/LevelProgressionComponent.h +++ b/dGame/dComponents/LevelProgressionComponent.h @@ -27,13 +27,13 @@ public: * Save data from this componennt to character XML * @param doc the document to write data to */ - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Load base data for this component from character XML * @param doc the document to read data from */ - void LoadFromXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; /** * Gets the current level of the entity diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index 151fcf2f..1eb02e57 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -504,10 +504,8 @@ bool MissionComponent::RequiresItem(const LOT lot) { } -void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - if (doc == nullptr) return; - - auto* mis = doc->FirstChildElement("obj")->FirstChildElement("mis"); +void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* mis = doc.FirstChildElement("obj")->FirstChildElement("mis"); if (mis == nullptr) return; @@ -523,7 +521,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { auto* mission = new Mission(this, missionId); - mission->LoadFromXml(doneM); + mission->LoadFromXml(*doneM); doneM = doneM->NextSiblingElement(); @@ -540,7 +538,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { auto* mission = new Mission(this, missionId); - mission->LoadFromXml(currentM); + mission->LoadFromXml(*currentM); if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) { mission->SetUniqueMissionOrderID(missionOrder); @@ -554,25 +552,23 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { } -void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - if (doc == nullptr) return; - +void MissionComponent::UpdateXml(tinyxml2::XMLDocument& doc) { auto shouldInsertMis = false; - auto* obj = doc->FirstChildElement("obj"); + auto* obj = doc.FirstChildElement("obj"); auto* mis = obj->FirstChildElement("mis"); if (mis == nullptr) { - mis = doc->NewElement("mis"); + mis = doc.NewElement("mis"); shouldInsertMis = true; } mis->DeleteChildren(); - auto* done = doc->NewElement("done"); - auto* cur = doc->NewElement("cur"); + auto* done = doc.NewElement("done"); + auto* cur = doc.NewElement("cur"); for (const auto& pair : m_Missions) { auto* mission = pair.second; @@ -580,10 +576,10 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { if (mission) { const auto complete = mission->IsComplete(); - auto* m = doc->NewElement("m"); + auto* m = doc.NewElement("m"); if (complete) { - mission->UpdateXml(m); + mission->UpdateXml(*m); done->LinkEndChild(m); @@ -591,7 +587,7 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { } if (mission->IsMission()) m->SetAttribute("o", mission->GetUniqueMissionOrderID()); - mission->UpdateXml(m); + mission->UpdateXml(*m); cur->LinkEndChild(m); } diff --git a/dGame/dComponents/MissionComponent.h b/dGame/dComponents/MissionComponent.h index 866f1650..a01794f0 100644 --- a/dGame/dComponents/MissionComponent.h +++ b/dGame/dComponents/MissionComponent.h @@ -31,8 +31,8 @@ public: explicit MissionComponent(Entity* parent); ~MissionComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate, unsigned int& flags); - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Returns all the missions for this entity, mapped by mission ID diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index 4ed80bf3..c2ed2a42 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -65,24 +65,24 @@ Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { } } -void Mission::LoadFromXml(tinyxml2::XMLElement* element) { +void Mission::LoadFromXml(const tinyxml2::XMLElement& element) { // Start custom XML - if (element->Attribute("state") != nullptr) { - m_State = static_cast<eMissionState>(std::stoul(element->Attribute("state"))); + if (element.Attribute("state") != nullptr) { + m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state"))); } // End custom XML - if (element->Attribute("cct") != nullptr) { - m_Completions = std::stoul(element->Attribute("cct")); + if (element.Attribute("cct") != nullptr) { + m_Completions = std::stoul(element.Attribute("cct")); - m_Timestamp = std::stoul(element->Attribute("cts")); + m_Timestamp = std::stoul(element.Attribute("cts")); if (IsComplete()) { return; } } - auto* task = element->FirstChildElement(); + auto* task = element.FirstChildElement(); auto index = 0U; @@ -132,19 +132,19 @@ void Mission::LoadFromXml(tinyxml2::XMLElement* element) { } } -void Mission::UpdateXml(tinyxml2::XMLElement* element) { +void Mission::UpdateXml(tinyxml2::XMLElement& element) { // Start custom XML - element->SetAttribute("state", static_cast<unsigned int>(m_State)); + element.SetAttribute("state", static_cast<unsigned int>(m_State)); // End custom XML - element->DeleteChildren(); + element.DeleteChildren(); - element->SetAttribute("id", static_cast<unsigned int>(info.id)); + element.SetAttribute("id", static_cast<unsigned int>(info.id)); if (m_Completions > 0) { - element->SetAttribute("cct", static_cast<unsigned int>(m_Completions)); + element.SetAttribute("cct", static_cast<unsigned int>(m_Completions)); - element->SetAttribute("cts", static_cast<unsigned int>(m_Timestamp)); + element.SetAttribute("cts", static_cast<unsigned int>(m_Timestamp)); if (IsComplete()) { return; @@ -155,27 +155,27 @@ void Mission::UpdateXml(tinyxml2::XMLElement* element) { if (task->GetType() == eMissionTaskType::COLLECTION || task->GetType() == eMissionTaskType::VISIT_PROPERTY) { - auto* child = element->GetDocument()->NewElement("sv"); + auto* child = element.GetDocument()->NewElement("sv"); child->SetAttribute("v", static_cast<unsigned int>(task->GetProgress())); - element->LinkEndChild(child); + element.LinkEndChild(child); for (auto unique : task->GetUnique()) { - auto* uniqueElement = element->GetDocument()->NewElement("sv"); + auto* uniqueElement = element.GetDocument()->NewElement("sv"); uniqueElement->SetAttribute("v", static_cast<unsigned int>(unique)); - element->LinkEndChild(uniqueElement); + element.LinkEndChild(uniqueElement); } break; } - auto* child = element->GetDocument()->NewElement("sv"); + auto* child = element.GetDocument()->NewElement("sv"); child->SetAttribute("v", static_cast<unsigned int>(task->GetProgress())); - element->LinkEndChild(child); + element.LinkEndChild(child); } } diff --git a/dGame/dMission/Mission.h b/dGame/dMission/Mission.h index d8c104e8..74b8d352 100644 --- a/dGame/dMission/Mission.h +++ b/dGame/dMission/Mission.h @@ -28,8 +28,8 @@ public: Mission(MissionComponent* missionComponent, uint32_t missionId); ~Mission(); - void LoadFromXml(tinyxml2::XMLElement* element); - void UpdateXml(tinyxml2::XMLElement* element); + void LoadFromXml(const tinyxml2::XMLElement& element); + void UpdateXml(tinyxml2::XMLElement& element); /** * Returns the ID of this mission diff --git a/dGame/dUtilities/BrickDatabase.cpp b/dGame/dUtilities/BrickDatabase.cpp index 7b447b84..61a7341d 100644 --- a/dGame/dUtilities/BrickDatabase.cpp +++ b/dGame/dUtilities/BrickDatabase.cpp @@ -29,15 +29,14 @@ const BrickList& BrickDatabase::GetBricks(const LxfmlPath& lxfmlPath) { return emptyCache; } - auto* doc = new tinyxml2::XMLDocument(); - if (doc->Parse(data.str().c_str(), data.str().size()) != 0) { - delete doc; + tinyxml2::XMLDocument doc; + if (doc.Parse(data.str().c_str(), data.str().size()) != 0) { return emptyCache; } BrickList parts; - auto* lxfml = doc->FirstChildElement("LXFML"); + auto* lxfml = doc.FirstChildElement("LXFML"); auto* bricks = lxfml->FirstChildElement("Bricks"); std::string searchTerm = "Brick"; @@ -86,7 +85,5 @@ const BrickList& BrickDatabase::GetBricks(const LxfmlPath& lxfmlPath) { m_Cache[lxfmlPath] = parts; - delete doc; - return m_Cache[lxfmlPath]; }