diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3dbf2d5b..876f28b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,7 @@ add_library(REGothEngine STATIC RTTI/RTTI_Character.hpp RTTI/RTTI_CharacterAI.hpp RTTI/RTTI_CharacterKeyboardInput.hpp + RTTI/RTTI_Console.hpp RTTI/RTTI_GameClock.hpp RTTI/RTTI_Inventory.hpp RTTI/RTTI_NodeVisuals.hpp @@ -31,8 +32,12 @@ add_library(REGothEngine STATIC RTTI/RTTI_ScriptObjectStorage.hpp RTTI/RTTI_StoryInformation.hpp RTTI/RTTI_TypeIDs.hpp + RTTI/RTTI_UIConsole.hpp + RTTI/RTTI_UIDialogueChoice.hpp RTTI/RTTI_UIElement.hpp RTTI/RTTI_UIFocusText.hpp + RTTI/RTTI_UIInventory.hpp + RTTI/RTTI_UISubtitleBox.hpp RTTI/RTTI_VisualCharacter.hpp RTTI/RTTI_VisualInteractiveObject.hpp RTTI/RTTI_VisualSkeletalAnimation.hpp @@ -55,6 +60,8 @@ add_library(REGothEngine STATIC components/CharacterKeyboardInput.hpp components/CharacterState.cpp components/CharacterState.hpp + components/Console.cpp + components/Console.hpp components/EventQueue.cpp components/EventQueue.hpp components/Focusable.cpp @@ -85,6 +92,8 @@ add_library(REGothEngine STATIC components/StoryInformation.hpp components/ThirdPersonCamera.cpp components/ThirdPersonCamera.hpp + components/UIConsole.cpp + components/UIConsole.hpp components/UIDialogueChoice.cpp components/UIDialogueChoice.hpp components/UIElement.cpp diff --git a/src/RTTI/RTTI_Console.hpp b/src/RTTI/RTTI_Console.hpp new file mode 100644 index 00000000..79bc17af --- /dev/null +++ b/src/RTTI/RTTI_Console.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "RTTIUtil.hpp" +#include + +namespace REGoth +{ + class RTTI_Console : public bs::RTTIType + { + BS_BEGIN_RTTI_MEMBERS + // This class should not be serialized + BS_END_RTTI_MEMBERS + + public: + RTTI_Console() + { + } + + REGOTH_IMPLEMENT_RTTI_CLASS_FOR_COMPONENT(Console) + }; +} // namespace REGoth diff --git a/src/RTTI/RTTI_TypeIDs.hpp b/src/RTTI/RTTI_TypeIDs.hpp index 29472d56..8fc48a7b 100644 --- a/src/RTTI/RTTI_TypeIDs.hpp +++ b/src/RTTI/RTTI_TypeIDs.hpp @@ -73,5 +73,7 @@ namespace REGoth TID_REGOTH_StoryInformation = 600065, TID_REGOTH_Inventory = 600066, TID_REGOTH_UIInventory = 600067, + TID_REGOTH_Console = 600068, + TID_REGOTH_UIConsole = 600069, }; } // namespace REGoth diff --git a/src/RTTI/RTTI_UIConsole.hpp b/src/RTTI/RTTI_UIConsole.hpp new file mode 100644 index 00000000..7b336deb --- /dev/null +++ b/src/RTTI/RTTI_UIConsole.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "RTTIUtil.hpp" +#include + +namespace REGoth +{ + class RTTI_UIConsole : public bs::RTTIType + { + BS_BEGIN_RTTI_MEMBERS + // This class should not be serialized + BS_END_RTTI_MEMBERS + + public: + RTTI_UIConsole() + { + } + + REGOTH_IMPLEMENT_RTTI_CLASS_FOR_COMPONENT(UIConsole) + }; +} // namespace REGoth diff --git a/src/components/Console.cpp b/src/components/Console.cpp new file mode 100644 index 00000000..71fe2b9f --- /dev/null +++ b/src/components/Console.cpp @@ -0,0 +1,599 @@ +#include "Console.hpp" +#include "components/Character.hpp" +#include "components/GameClock.hpp" +#include "components/GameWorld.hpp" +#include "components/GameplayUI.hpp" +#include "components/Inventory.hpp" +#include "components/UIConsole.hpp" +#include +#include +#include + +namespace REGoth +{ + Console::Console(const bs::HSceneObject& parent, HGameWorld gameWorld) + : bs::Component(parent) + , mGameWorld(gameWorld) + { + setName("Console"); + + mConsoleUI = gGameplayUI()->consoleUI(); + + registerAllCommand(); + } + + Console::~Console() + { + } + + void Console::onInitialized() + { + auto valueChangedCallback = [&](const bs::String& input) { + REGOTH_LOG(Info, Uncategorized, "[Console] !EVENT! Input changed to: {0}!", input); + onInputChanged(input); + }; + mConsoleUI->mOnInputChanged.connect(valueChangedCallback); + + auto confirmCallback = [&]() { + const bs::String& input = mConsoleUI->getInput(); + REGOTH_LOG(Info, Uncategorized, "[Console] !EVENT! Command confirmed: {0}!", input); + parseAndExecuteCommand(input); + mConsoleUI->clearInput(); + }; + mConsoleUI->mOnConfirm.connect(confirmCallback); + } + + void Console::onInputChanged(const bs::String& input) + { + // TODO + rename + } + + void Console::parseAndExecuteCommand(const bs::String& input) + { + bs::String sanitized_input = input; + bs::StringUtil::trim(sanitized_input); + bs::Vector tokenized_input = bs::StringUtil::split(sanitized_input, " "); + + for (const auto& token : tokenized_input) + REGOTH_LOG(Info, Uncategorized, "[Console] Token in tokenized input: {0}!", token); + + // detect command + auto it = mCommands.begin(); + for (; it != mCommands.end(); it++) + { + // construct command based on number of tokens + bs::UINT32 num_of_tokens = it->num_of_tokens; + if (tokenized_input.size() < num_of_tokens) continue; + bs::String command; + for (bs::UINT32 i = 0; i < num_of_tokens; i++) + { + if (!command.empty()) + { + command = command + " "; + } + command = command + tokenized_input[i]; + } + + // if the command matches the input we just break and it will contain the correct command + if (bs::StringUtil::compare(command, it->name) == 0) break; + } + + // Command not found + if (it == mCommands.end()) return; + REGOTH_LOG(Info, Uncategorized, "[Console] Command was found and is: {0}!", it->name); + + // call callback and pass arguments + tokenized_input.erase(tokenized_input.begin(), tokenized_input.begin() + it->num_of_tokens); + // FIXME: allow trailing arguments that are ignored for now + if (tokenized_input.size() < it->args.size()) + { + REGOTH_LOG(Info, Uncategorized, + "[Console] Wrong number of arguments: Should be {0}, but is {1}!", it->args.size(), + tokenized_input.size()); + mConsoleUI->setOutput(it->usage); + return; + } + (this->*it->callback)(tokenized_input); + } + + void Console::command_Clear(bs::Vector& args) + { + REGOTH_LOG(Info, Uncategorized, "[Console] Command 'clear' executed!"); + + mConsoleUI->clearOutput(); + } + + void Console::command_List(bs::Vector& args) + { + REGOTH_LOG(Info, Uncategorized, "[Console] Command 'list' executed!"); + bs::Vector outputs; + + outputs.push_back("List of all commands:"); + for (auto it = mCommands.begin(); it != mCommands.end(); it++) + { + bs::String command = it->name; + + outputs.push_back(command); + } + + mConsoleUI->setOutput(outputs); + } + + void Console::command_Help(bs::Vector& args) + { + REGOTH_LOG(Info, Uncategorized, "[Console] Command 'help' executed!"); + bs::Vector outputs; + + // detect command + auto it = mCommands.begin(); + for (; it != mCommands.end(); it++) + { + // construct command based on number of tokens + bs::UINT32 num_of_tokens = it->num_of_tokens; + if (args.size() < num_of_tokens) continue; + bs::String command; + for (bs::UINT32 i = 0; i < num_of_tokens; i++) + { + if (!command.empty()) + { + command = command + " "; + } + command = command + args[i]; + } + + // if the command matches the input we just break and it will contain the correct command + if (bs::StringUtil::compare(command, it->name) == 0) break; + } + + if (it == mCommands.end()) + { + outputs.push_back("Unkown command!"); + } + else + { + bs::String usage = it->usage; + outputs.push_back(usage); + bs::String help = it->help; + outputs.push_back(help); + } + + mConsoleUI->setOutput(outputs); + } + + void Console::command_CheatFull(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'cheat full' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'cheat full' is not implemented yet!"); + } + + void Console::command_CheatGod(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'cheat god' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'cheat god' is not implemented yet!"); + } + + void Console::command_Give(bs::Vector& args) + { + bs::String& instance = args[0]; + bs::StringUtil::replaceAll(instance, "_", ""); + bs::StringUtil::toUpperCase(instance); + const bs::UINT32 amount = bs::parseINT32(args[1]); + + auto inventory = mGameWorld->hero()->SO()->getComponent(); + inventory->giveItem(instance, amount); + + bs::String output = bs::StringUtil::format("Gave {0} {1} to hero.", instance, amount); + mConsoleUI->setOutput(output); + } + + void Console::command_Insert(bs::Vector& args) + { + bs::String& instance = args[0]; + bs::StringUtil::replaceAll(instance, "_", ""); + bs::StringUtil::toUpperCase(instance); + const bs::Transform& transform = mGameWorld->hero()->SO()->getTransform(); + REGOTH_LOG(Info, Uncategorized, "Insert called with {0}.", instance); + + // determine if it is an item or a "creature" + if (bs::StringUtil::startsWith(instance, "it")) // this is not entirely correct :) + { + REGOTH_LOG(Info, Uncategorized, "Is am item!", instance); + mGameWorld->insertItem(instance, transform); + } + else + { + REGOTH_LOG(Info, Uncategorized, "Is a character!", instance); + mGameWorld->insertCharacter(instance, transform); + } + + bs::String output = + bs::StringUtil::format("Inserted {0} at some place.", instance /*, transform*/); + mConsoleUI->setOutput(output); + } + + void Console::command_SpawnMass(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'spawnmass' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'spawnmass' is not implemented yet!"); + } + + void Console::command_Kill(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'kill' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'kill' is not implemented yet!"); + } + + void Console::command_EditAbilities(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'edit abilities' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'edit abilities' is not implemented yet!"); + } + + void Console::command_EditFocus(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'edit focus' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'edit focus' is not implemented yet!"); + } + + void Console::command_GetTime(bs::Vector& args) + { + auto clock = mGameWorld->gameclock(); + const bs::INT32 day = clock->getDay(); + const bs::INT32 hour = clock->getHour(); + const bs::INT32 minute = clock->getMinute(); + + // FIXME: proper formatting for hour and day, maybe move to GameClock + bs::String output = bs::StringUtil::format("Time: Day {0} {1}:{2}", day, hour, minute); + + mConsoleUI->setOutput(output); + REGOTH_LOG(Info, Uncategorized, output); + } + + void Console::command_SetTime(bs::Vector& args) + { + auto clock = mGameWorld->gameclock(); + const bs::INT32 hh = bs::parseINT32(args[0]); + const bs::INT32 mm = bs::parseINT32(args[1]); + + clock->setTime(hh, mm); + + mConsoleUI->setOutput("Set time called!"); + } + + void Console::command_GotoWaypoint(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'goto waypoint' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'goto waypoint' is not implemented yet!"); + } + + void Console::command_GotoCamera(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'goto camera' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'goto camera' is not implemented yet!"); + } + + void Console::command_GotoPos(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'goto pos' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'goto pos' is not implemented yet!"); + } + + void Console::command_AIGoto(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'aigoto' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'aigoto' is not implemented yet!"); + } + + void Console::command_SetClippingfactor(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, + "[Console] Command 'set clippingfactor' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'set clippingfactor' is not implemented yet!"); + } + + void Console::command_ZFogZone(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'zfogzone' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'zfogzone' is not implemented yet!"); + } + + void Console::command_ToggleConsole(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'toggle console' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'toggle console' is not implemented yet!"); + } + + void Console::command_ToggleFrame(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'toggle frame' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'toggle frame' is not implemented yet!"); + } + + void Console::command_ToggleWaynet(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'toggle waynet' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'toggle waynet' is not implemented yet!"); + } + + void Console::command_Firstperson(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'firstperson' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'firstperson' is not implemented yet!"); + } + + void Console::command_HeroExport(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'hero export' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'hero export' is not implemented yet!"); + } + + void Console::command_HeroImport(bs::Vector& args) + { + // TODO: implement + REGOTH_LOG(Warning, Uncategorized, "[Console] Command 'hero import' is not implemented yet!"); + + mConsoleUI->setOutput("Command 'hero import' is not implemented yet!"); + } + + void Console::registerCommand(const Command& command) + { + // TODO: Some auto suggestion stuff can happen here + mCommands.push_back(command); + } + + void Console::registerAllCommand() + { + using This = Console; + Command command; + + command = CommandBuilder() + .name("clear") + .callback((commandCallback)&This::command_Clear) + .usage("Usage: clear") + .help("Clears console output.") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("list") + .callback((commandCallback)&This::command_List) + .usage("Usage: list") + .help("Lists all commands.") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("help") + .callback(&This::command_Help) + .arg(TokenType::Command) + .usage("Usage: help [command]") + .help("Prints out helpful information about the given command.") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("cheat full") + .callback(&This::command_CheatFull) + .usage("Usage: cheat full") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("cheat god") + .callback(&This::command_CheatGod) + .usage("Usage: cheat god") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("give") + .callback(&This::command_Give) + .arg(TokenType::Instance) + .arg(TokenType::Literal) + .usage("Usage: give [name] [amount]") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("insert") + .callback(&This::command_Insert) + .arg(TokenType::Instance) + .usage("Usage: insert [name]") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("spawnmass") + .callback(&This::command_SpawnMass) + .arg(TokenType::Literal) + .usage("Usage: spawnmass {giga} [amount]") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("kill") + .callback(&This::command_Kill) + .usage("Usage: kill") + .help("Kill the NPC you have currently in focus") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("edit abilities") + .callback(&This::command_EditAbilities) + .usage("Usage: edit abilities") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("edit focus") + .callback(&This::command_EditFocus) + .usage("Usage: edit focus") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("get time") + .callback(&This::command_GetTime) + .usage("Usage: get time") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("set time") + .callback(&This::command_SetTime) + .arg(TokenType::Literal) + .arg(TokenType::Literal) + .usage("Usage: set time [hh] [mm]") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("goto waypoint") + .callback(&This::command_GotoWaypoint) + .arg(TokenType::Waypoint) + .usage("Usage: goto waypoint [waypoint]") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("goto camera") + .callback(&This::command_GotoCamera) + .usage("Usage: goto camera") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("goto pos") + .callback(&This::command_GotoPos) + .arg(TokenType::Literal) + .arg(TokenType::Literal) + .arg(TokenType::Literal) + .usage("Usage: goto pos [x] [y] [z]") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("aigoto") + .callback(&This::command_AIGoto) + .arg(TokenType::Waypoint) + .usage("Usage: aigoto [waypoint]") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("set clippingfactor") + .callback(&This::command_SetClippingfactor) + .usage("Usage: set clippingfactor [f]") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("zgofzone") + .callback(&This::command_ZFogZone) + .usage("Usage: zfogzone") + .help("Some Fogzone stuff") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("toggle console") + .callback(&This::command_ToggleConsole) + .usage("Usage: toggle console") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("toggle frame") + .callback(&This::command_ToggleFrame) + .usage("Usage: toggle frame") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("toggle waynet") + .callback(&This::command_ToggleWaynet) + .usage("Usage: toggle waynet") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("firstperson") + .callback(&This::command_Firstperson) + .usage("Usage: firstperson") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("hero export") + .callback(&This::command_HeroExport) + .arg(TokenType::Literal) + .usage("Usage: hero export [filename]") + .help("") + .build(); + registerCommand(command); + + command = CommandBuilder() + .name("hero import") + .callback(&This::command_HeroImport) + .arg(TokenType::Literal) + .usage("Usage: hero import [filename]") + .help("") + .build(); + registerCommand(command); + } + + REGOTH_DEFINE_RTTI(Console) +} // namespace REGoth diff --git a/src/components/Console.hpp b/src/components/Console.hpp new file mode 100644 index 00000000..5eeaa907 --- /dev/null +++ b/src/components/Console.hpp @@ -0,0 +1,374 @@ +#pragma once +#include + +namespace REGoth +{ + class Console; + using HConsole = bs::GameObjectHandle; + + class UIConsole; + using HUIConsole = bs::GameObjectHandle; + + class GameWorld; + using HGameWorld = bs::GameObjectHandle; + + /** + * This class handles the logical side of the in-game Console. + * This is where the command functions are defined and are registered to the corresponding command. + * Executes correct command based on textual input. + */ + class Console : public bs::Component + { + public: + Console(const bs::HSceneObject& parent, HGameWorld gameWorld); + virtual ~Console(); + + /** + * Keeps track of where we are in the current command to offer 'content-aware' autocomplete + * suggestions. + * + * @note To be used in the callback of the inputChanged event of the inputBox of the ConsoleUI + * component. + * + * @param input + * Current input - usually provided by the inputChanged event of the inputBox of the + * ConsoleUI component. + */ + void onInputChanged(const bs::String& input); + + /** + * Identifies the correct command based on the input string and executes its callback. + * + * @note To be used in the callback of the onInputConfirmed event of the inputBox of the + * ConsoleUI component. + * + * @param input + * Current input - Needs to be provided manually by the ConsoleUI component. + */ + void parseAndExecuteCommand(const bs::String& input); + + protected: + void onInitialized() override; + + private: + /** + * Clears console output + * + * @param args + * Does not use any arguments. + */ + void command_Clear(bs::Vector& args); + + /** + * Lists all commands that are registered to this Console. + * + * @param args + * Does not use any arguments. + */ + void command_List(bs::Vector& args); + + /** + * Prints a 'helpful' help message (aka a short description) of the given command. + * + * @param args + * Name of the command. + */ + void command_Help(bs::Vector& args); + + /** + * Restores health and mana of the currently controlled(?) character. + * TODO: currently controlled or PC_HERO only? + * + * @param args + * Does not use any arguments. + */ + void command_CheatFull(bs::Vector& args); + + /** + * Turns on god mode for the currently controlled(?) character. + * TODO: currently controlled or PC_HERO only? + * TODO: Is god mode only invulnerability? + * + * @param args + * Does not use any arguments. + */ + void command_CheatGod(bs::Vector& args); + + /** + * Give the given item to pc_hero + * + * @param args + * Name of an item. + * Amount. + */ + void command_Give(bs::Vector& args); + + /** + * Insert the given Item/NPC infront of the currently controlled character. + * + * @param args + * Name of an item or NPC. + * TODO: Anything else you can insert with this? + */ + void command_Insert(bs::Vector& args); + + /** + * Spawns (???) around(?) the currently controlled character- + * + * @param args + * Amount of entities to spawn. + */ + void command_SpawnMass(bs::Vector& args); + + /** + * Kills target currently in focus. + * + * @param args + * Does not use any arguments. + */ + void command_Kill(bs::Vector& args); + + /** + * Allows editing various attributes and abilities of the currently controlled character. + * + * @param args + * Does not use any arguments. + */ + void command_EditAbilities(bs::Vector& args); + + /** + * Allows editing various attributes and abilities of the character currently in focus. + * + * @param args + * Does not use any arguments. + */ + void command_EditFocus(bs::Vector& args); + + /** + * Gets the current time. + * + * @param args + * Does not use any arguments. + */ + void command_GetTime(bs::Vector& args); + + /** + * Sets the current time. + * + * @param args + * Two literal arguments in the form of [hh] [mm]. + */ + void command_SetTime(bs::Vector& args); + + /** + * Teleport the currently controlled character to the given waypoint. + * + * @param args + * Waypoint to teleport to. + */ + void command_GotoWaypoint(bs::Vector& args); + + /** + * Teleports PC_HERO(?) to the free floating camera. + * + * @param args + * Does not use any arguments. + */ + void command_GotoCamera(bs::Vector& args); + + /** + * Teleport the currently controlled character to the given position in the world. + * + * @param args + * Three literal arguments in the form of [x] [y] [z] to repesent a position. + */ + void command_GotoPos(bs::Vector& args); + + /** + * ??? + * + * @param args + * Waypoint. + */ + void command_AIGoto(bs::Vector& args); + + /** + * ??? + * + * @param args + * Literal float argument. + */ + void command_SetClippingfactor(bs::Vector& args); + + /** + * ??? + * + * @param args + * Does not use any arguments. + */ + void command_ZFogZone(bs::Vector& args); + + /** + * Toggles the visuals of the console. + * + * @param args + * Does not use any arguments. + */ + void command_ToggleConsole(bs::Vector& args); + + /** + * Toggles the visuals of the frame (whole gameui). + * + * @param args + * Does not use any arguments. + */ + void command_ToggleFrame(bs::Vector& args); + + /** + * Toggles the visuals of the waynet (debug). + * + * @param args + * Does not use any arguments. + */ + void command_ToggleWaynet(bs::Vector& args); + + /** + * Toggles firstperson mode. + * + * @param args + * Does not use any arguments. + */ + void command_Firstperson(bs::Vector& args); + + /** + * Exports attributes and abilities of currently controlled characters to a file with the given + * name in the save folder. + * + * @param args + * Name of the file. + */ + void command_HeroExport(bs::Vector& args); + + /** + * Import attributes and abilities from the file with the given name in the save folder and apply + * them to the currently controlled character. + * + * @param args + * Name of the file. + */ + void command_HeroImport(bs::Vector& args); + + struct Command; // forward declaration + /** + * Register a command by + * + * @param name + * Name of the command to identify it in the ingame console. + * + * @param command + * The command itself with its reference to the callback and other useful information. + */ + void registerCommand(const Command& command); + + /** + * Registers all commands. + */ + void registerAllCommand(); + + private: + HGameWorld mGameWorld; + HUIConsole mConsoleUI; + bs::Vector mCommands; + using commandCallback = void (Console::*)(bs::Vector&); + + /** + * A collection of all types of tokesn that can appear in a command like the command itself and + * various types of arguments. Used to describe the arguments for a command and to have + * 'context-aware' autocomplete suggestions. + */ + enum class TokenType + { + Literal, + Command, + Instance, // FIXME: Yeah I am not so sure about this in relation to the original commands + Waypoint, + Freepoint, + }; + + /** + * @struct Command + * + * @var Command::callback + * Member 'callback' holds the function pointer to a method of Component Console. + * @var Command::args + * Member 'args' is a Vector of TokenTypes that specifies how many arguments and of which + * type the corresponding function in 'callback' takes + * @var Command::usage + * Member 'usage' describes how this command is supposed to be used + * @var Command::help + * Member 'help' gives a short description of what this command does + */ + struct Command + { + bs::String name; + bs::UINT32 num_of_tokens; + commandCallback callback; + bs::Vector args = {}; + bs::String usage; // TODO: Maybe I can generate this from the args? + bs::String help; + }; + + /** + * This class is a simple buildern pattern implementation for the Command struct. + * Makes the instantiation a lot more readable, since designated initializers are not available. + */ + class CommandBuilder + { + public: + CommandBuilder& name(const bs::String& name) + { + // sanitize name and determine num of tokens aka how many words in the command + bs::String sanitized_name = name; + bs::StringUtil::trim(sanitized_name); + bs::UINT32 num_of_tokens = bs::StringUtil::split(sanitized_name, " ").size(); + + cmd.name = sanitized_name; + cmd.num_of_tokens = num_of_tokens; + return *this; + } + CommandBuilder& callback(commandCallback callback) + { + cmd.callback = callback; + return *this; + } + CommandBuilder& arg(TokenType arg) + { + cmd.args.push_back(arg); + return *this; + } + CommandBuilder& usage(bs::String usage) + { + cmd.usage = usage; + return *this; + } + CommandBuilder& help(bs::String help) + { + cmd.help = help; + return *this; + } + Command build() + { + return cmd; + } + + private: + Command cmd = {}; + }; + + public: + REGOTH_DECLARE_RTTI(Console) + + protected: + Console() = default; // For RTTI + }; +} // namespace REGoth diff --git a/src/components/GameplayUI.cpp b/src/components/GameplayUI.cpp index 378195f4..0b2cfeaf 100644 --- a/src/components/GameplayUI.cpp +++ b/src/components/GameplayUI.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,11 @@ namespace REGoth { mInventoryUI = addChildElement("UIInventory"); } + + if (!mConsoleUI) + { + mConsoleUI = addChildElement("UIConsole"); + } } void GameplayUI::createGlobal(bs::HCamera camera) diff --git a/src/components/GameplayUI.hpp b/src/components/GameplayUI.hpp index cdd05795..72ad4761 100644 --- a/src/components/GameplayUI.hpp +++ b/src/components/GameplayUI.hpp @@ -16,6 +16,9 @@ namespace REGoth class UIInventory; using HUIInventory = bs::GameObjectHandle; + class UIConsole; + using HUIConsole = bs::GameObjectHandle; + class GameplayUI; using HGameplayUI = bs::GameObjectHandle; @@ -104,6 +107,11 @@ namespace REGoth return mInventoryUI; } + HUIConsole consoleUI() const + { + return mConsoleUI; + } + protected: void onInitialized() override; @@ -111,6 +119,7 @@ namespace REGoth HUISubtitleBox mSubtitleBox; HUIFocusText mFocusText; HUIInventory mInventoryUI; + HUIConsole mConsoleUI; private: public: diff --git a/src/components/Inventory.cpp b/src/components/Inventory.cpp index ee637661..aa0cc136 100644 --- a/src/components/Inventory.cpp +++ b/src/components/Inventory.cpp @@ -44,7 +44,7 @@ namespace REGoth REGOTH_THROW(InvalidParametersException, "Count cannot be 0"); } - mItemCountByInstance[instance] += 1; + mItemCountByInstance[instance] += count; OnItemChanged(instance); } diff --git a/src/components/UIConsole.cpp b/src/components/UIConsole.cpp new file mode 100644 index 00000000..e68d8c56 --- /dev/null +++ b/src/components/UIConsole.cpp @@ -0,0 +1,129 @@ +#include "UIConsole.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace REGoth +{ + UIConsole::UIConsole(const bs::HSceneObject& parent, HUIElement parentUIElement) + : UIElement(parent, parentUIElement, new bs::GUIPanel()) + { + setName("UIConsole"); + + bs::GUILayoutY* layoutY = layout().addNewElement(); + + bs::GUIPanel* topPanel = layoutY->addNewElement(); + bs::GUIPanel* backgroundPanel = topPanel->addNewElement(1); + mBackground = backgroundPanel->addNewElement("GothicConsoleBackground"); + + bs::GUIPanel* foregroundPanel = topPanel->addNewElement(0); + mScrollArea = foregroundPanel->addNewElement(); + + // FIXME: Input box does not appear properly ontop of the texture so i moved it below everything + mInputBox = layoutY->addNewElement(false, "GothicConsoleInputBox"); + } + + UIConsole::~UIConsole() + { + } + + void UIConsole::onInitialized() + { + mOnConfirm = mInputBox->onConfirm; + mOnInputChanged = mInputBox->onValueChanged; + mToggleConsole = bs::VirtualButton("ToggleConsole"); + + setOutput("Welcome to the REGoth Console :)"); + } + + void UIConsole::update() + { + UIElement::update(); + + bs::Rect2I parentBounds = parentLayout().getBounds(); + + layout().setPosition(0, 0); + // TODO: Consider whole screen for this layout because of suggestion box and edit box + layout().setWidth(parentBounds.width); + layout().setHeight(parentBounds.height * 0.2); + + // Activation Input Handling + if (bs::gVirtualInput().isButtonDown(mToggleConsole)) + { + mState = (mState == State::Closed) ? State::Open : State::Closed; + } + + // State handling + switch (mState) + { + case State::Closed: + mBackground->setVisible(false); + mScrollArea->setVisible(false); + mInputBox->setVisible(false); + mInputBox->setFocus(false); + break; + + case State::Open: + mBackground->setVisible(true); + mScrollArea->setVisible(true); + mInputBox->setVisible(true); + mInputBox->setFocus(true); + break; + } + + if (mState == State::Open) + { + // Input Handling + } + } + + const bs::String& UIConsole::getInput() + { + return mInputBox->getText(); + } + + void UIConsole::clearInput() + { + mInputBox->setText(""); + } + + void UIConsole::setOutput(const bs::Vector& outputs) + { + for (auto output : outputs) + { + setOutput(output); + } + } + + void UIConsole::setOutput(const bs::String& output) + { + auto label = mScrollArea->getLayout().addNewElement(bs::HString(output)); + label->setHeight(20); + mOutputLabels.push_back(label); + + mScrollArea->_updateLayout(mScrollArea->_getLayoutData()); + mScrollArea->scrollDownPct(1.0); // Move scrollbar to the very bottom ; FIXME: Does not work + // correctly, call above is workaround + } + + void UIConsole::clearOutput() + { + // TODO: does not work correctly + for (bs::GUILabel* pLabel : mOutputLabels) + { + mScrollArea->getLayout().removeElement(pLabel); + } + + mOutputLabels.clear(); + } + + REGOTH_DEFINE_RTTI(UIConsole) +} // namespace REGoth diff --git a/src/components/UIConsole.hpp b/src/components/UIConsole.hpp new file mode 100644 index 00000000..8553ed3a --- /dev/null +++ b/src/components/UIConsole.hpp @@ -0,0 +1,91 @@ +#pragma once +#include "components/UIElement.hpp" +#include +#include +#include + +namespace REGoth +{ + class UIConsole; + using HUIConsole = bs::GameObjectHandle; + + /** + * This class handles the visual side of the in-game Console. It consists of + * scrollable output windows and an input box to enter the console commands. Additionally there are + * UIElements for autocomplete suggestions and extra functionality such as a UI for editing npc + * properties (TODO). + * + * The component is largely autonomous and works solely on the input of the user by delegated it to + * the actual Console component via events. + */ + class UIConsole : public UIElement + { + public: + UIConsole(const bs::HSceneObject& parent, HUIElement parentUiElement); + virtual ~UIConsole(); + + /** + * Get the current Input. + */ + const bs::String& getInput(); + + /** + * Resets the input of the internal InputBox UIElement to the empty String "". + * Used to clear the input after a command has been entered. + */ + void clearInput(); + + /** + * Creates UI Labels for each of the Strings in \p outputs to be put into the Scrollable Area. + * + * @param outputs A Vector of Strings with the ouputs to be added to the Scrollable Area. + */ + void setOutput(const bs::Vector& outputs); + + /** + * Creates a UI Label for \p output to be put into the Scrollable Area. + * + * @param output a String with the output to be added to the Scrollable Area. + */ + void setOutput(const bs::String& output); + + /** + * Resets the input of the internal ScrollArea UIElement. + * Used to clear the output. + */ + void clearOutput(); + + protected: + enum class State + { + Closed, + Open, + }; + + /** + * Sets internal variables for VirtualButtons + */ + void onInitialized() override; + + /** Triggered once per frame. Allows the component to handle input and move. */ + void update() override; + + public: + bs::Event mOnConfirm; + bs::Event mOnInputChanged; + + private: + State mState = State::Closed; + bs::GUITexture* mBackground = nullptr; + bs::GUIScrollArea* mScrollArea = nullptr; + bs::Vector mOutputLabels; + bs::GUIInputBox* mInputBox = nullptr; + bs::VirtualButton mToggleConsole; + + public: + REGOTH_DECLARE_RTTI(UIConsole) + + protected: + UIConsole() = default; // For RTTI + }; +} // namespace REGoth diff --git a/src/components/UIInventory.cpp b/src/components/UIInventory.cpp index 794612d9..3a5e79bb 100644 --- a/src/components/UIInventory.cpp +++ b/src/components/UIInventory.cpp @@ -70,8 +70,8 @@ namespace REGoth bs::Rect2I bounds = parentLayout().getBounds(); - bounds.y = 0; bounds.height = parentLayout().getBounds().height; + bounds.y = bounds.height / 2; bounds.width = 180; layout().setBounds(bounds); diff --git a/src/core/Engine.cpp b/src/core/Engine.cpp index 9c9f9104..c5171f03 100644 --- a/src/core/Engine.cpp +++ b/src/core/Engine.cpp @@ -141,6 +141,7 @@ void Engine::setupInput() inputConfig->registerButton("ToggleMeleeWeapon", BC_1); inputConfig->registerButton("Action", BC_LCONTROL); inputConfig->registerButton("QuickSave", BC_F5); + inputConfig->registerButton("ToggleConsole", BC_F2); // Camera controls for axes (analog input, e.g. mouse or gamepad thumbstick) // These return values in [-1.0, 1.0] range. diff --git a/src/gui/skin_gothic.cpp b/src/gui/skin_gothic.cpp index c41d45b1..5627887a 100644 --- a/src/gui/skin_gothic.cpp +++ b/src/gui/skin_gothic.cpp @@ -101,13 +101,21 @@ namespace REGoth skin->setStyle("GothicSubtitleBoxCharacterName", labelDefaultHighlighted); skin->setStyle("GothicSubtitleBoxText", labelDefault); - auto button = *skin->getStyle("Button"); - + auto button = *skin->getStyle("Button"); // Commented out: That font is hard to read // button.font = fontGothicOld10WhiteHi; // button.fontSize = 17; skin->setStyle("Button", button); + bs::GUIElementStyle consoleBackground = baseStyle; + consoleBackground.normal.texture = gOriginalGameResources().sprite("CONSOLE.TGA"); + skin->setStyle("GothicConsoleBackground", consoleBackground); + + auto consoleInputBox = *skin->getStyle("InputBox"); + // consoleInputBox.font = fontGothicOld10WhiteHi; + // consoleInputBox.fontSize = 17; + skin->setStyle("GothicConsoleInputBox", consoleInputBox); + // Cache the skin for later use s_SkinGothic = skin; diff --git a/src/main_WorldViewer.cpp b/src/main_WorldViewer.cpp index 33836750..4f667a40 100644 --- a/src/main_WorldViewer.cpp +++ b/src/main_WorldViewer.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,8 @@ class REGothWorldViewer : public REGoth::Engine REGoth::GameplayUI::createGlobal(mMainCamera); + world->SO()->addComponent(world); + auto inventory = hero->SO()->getComponent(); inventory->giveItem("ITFOAPPLE");