diff --git a/src/debug/console.cpp b/src/debug/console.cpp
new file mode 100644
index 0000000..7e6e86f
--- /dev/null
+++ b/src/debug/console.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017-2019 Christopher J. Howard
+ *
+ * This file is part of Antkeeper Source Code.
+ *
+ * Antkeeper Source Code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper Source Code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper Source Code. If not, see .
+ */
+
+#include "console.hpp"
+#include
+
+template<>
+int ArgumentParser::parse(const std::string& argument)
+{
+ return std::stoi(argument);
+}
+
+template<>
+unsigned int ArgumentParser::parse(const std::string& argument)
+{
+ return static_cast(std::stoul(argument));
+}
+
+template<>
+long ArgumentParser::parse(const std::string& argument)
+{
+ return std::stol(argument);
+}
+
+template<>
+unsigned long ArgumentParser::parse(const std::string& argument)
+{
+ return std::stoul(argument);
+}
+
+template<>
+float ArgumentParser::parse(const std::string& argument)
+{
+ return std::stof(argument);
+}
+
+template<>
+double ArgumentParser::parse(const std::string& argument)
+{
+ return std::stod(argument);
+}
+
+template<>
+std::string ArgumentParser::parse(const std::string& argument)
+{
+ return argument;
+}
+
+std::tuple, std::function> CommandInterpreter::interpret(const std::string& line)
+{
+ // Split line into arguments
+ std::vector arguments;
+ std::stringstream stream(line);
+ std::string argument;
+ while (std::getline(stream, argument, ' '))
+ {
+ arguments.push_back(argument);
+ }
+
+ if (arguments.empty())
+ {
+ return {std::string(), std::vector(), nullptr};
+ }
+
+ // Get command name
+ std::string name = arguments[0];
+
+ // Remove command name from arguments
+ arguments.erase(arguments.begin());
+
+ // Find command linker for this command
+ auto linker = linkers.find(name);
+ if (linker == linkers.end())
+ {
+ return {name, arguments, nullptr};
+ }
+
+ // Link command function and its arguments into a callable object
+ std::function call = linker->second(arguments);
+
+ return {name, arguments, call};
+}
+
diff --git a/src/debug/console.hpp b/src/debug/console.hpp
new file mode 100644
index 0000000..a00d9ac
--- /dev/null
+++ b/src/debug/console.hpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017-2019 Christopher J. Howard
+ *
+ * This file is part of Antkeeper Source Code.
+ *
+ * Antkeeper Source Code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper Source Code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper Source Code. If not, see .
+ */
+
+#ifndef CONSOLE_HPP
+#define CONSOLE_HPP
+
+#include
+#include
+#include
+#include
+#include
+
+/**
+ * Parses an argument string into a typed value.
+ */
+template
+class ArgumentParser
+{
+public:
+ static T parse(const std::string& argument);
+};
+
+template<>
+int ArgumentParser::parse(const std::string& argument);
+template<>
+unsigned int ArgumentParser::parse(const std::string& argument);
+template<>
+long ArgumentParser::parse(const std::string& argument);
+template<>
+unsigned long ArgumentParser::parse(const std::string& argument);
+template<>
+float ArgumentParser::parse(const std::string& argument);
+template<>
+double ArgumentParser::parse(const std::string& argument);
+template<>
+std::string ArgumentParser::parse(const std::string& argument);
+
+/**
+ * Parses an argument vector of strings into a tuple of typed values.
+ */
+class ArgumentVectorParser
+{
+public:
+ template
+ static std::tuple parse(const std::vector& arguments)
+ {
+ return parse(arguments, std::make_index_sequence{});
+ }
+
+private:
+ template
+ static std::tuple parse(const std::vector& arguments, std::index_sequence)
+ {
+ return {ArgumentParser::parse(arguments[index])...};
+ }
+};
+
+class CommandLinker
+{
+public:
+ /**
+ * Links a function and its arguments together into a single callable object.
+ */
+ template
+ static std::function link(const std::function& function, const std::vector& arguments)
+ {
+ // Parse argument vectors and store in a tuple
+ auto parsedArguments = ArgumentVectorParser::parse(arguments);
+
+ // Return callable object which will invoke the function with the parsed arguments
+ return std::bind
+ (
+ [function, parsedArguments]()
+ {
+ std::apply(function, parsedArguments);
+ }
+ );
+ }
+};
+
+/**
+ *
+ */
+class CommandInterpreter
+{
+public:
+ /**
+ * Registers a command.
+ *
+ * @param name Name used to invoke the command.
+ * @param function Function associated with the command.
+ */
+ template
+ void registerCommand(const std::string& name, const std::function& function);
+ template
+ void registerCommand(const std::string& name, void(*function)(Args...));
+
+ /**
+ * Interprets a line of text as a function call, returning the interpreted command name, argument vector, and callable function object.
+ *
+ * @param line Line of text to be interpreted.
+ */
+ std::tuple, std::function> interpret(const std::string& line);
+
+private:
+ template
+ void addCommandLinker(const std::string& name, const Function& function, const Linker& linker);
+
+ // A command name-keyed map of command linkers
+ std::unordered_map(const std::vector&)>> linkers;
+};
+
+template
+void CommandInterpreter::registerCommand(const std::string& name, const std::function& function)
+{
+ addCommandLinker(name, function, CommandLinker::link);
+}
+
+template
+void CommandInterpreter::registerCommand(const std::string& name, void(*function)(Args...))
+{
+ addCommandLinker(name, function, CommandLinker::link);
+}
+
+template
+void CommandInterpreter::addCommandLinker(const std::string& name, const Function& function, const Linker& linker)
+{
+ linkers[name] = std::bind(linker, function, std::placeholders::_1);
+}
+
+#endif // CONSOLE_HPP
+
diff --git a/src/game.cpp b/src/game.cpp
index d8b2760..045d3cb 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -54,6 +54,7 @@
#include "entity/systems/steering-system.hpp"
#include "entity/systems/particle-system.hpp"
#include "entity/systems/terrain-system.hpp"
+#include "configuration.hpp"
#include "stb/stb_image_write.h"
#include "menu.hpp"
#include
@@ -63,11 +64,13 @@
#include
#include
+#include "debug/console.hpp"
+
template <>
bool Game::readSetting(const std::string& name, std::string* value) const
{
- auto it = settingsMap.find(name);
- if (it == settingsMap.end())
+ auto it = settingsTableIndex.find(name);
+ if (it == settingsTableIndex.end())
{
return false;
}
@@ -80,8 +83,8 @@ bool Game::readSetting(const std::string& name, std::string* value)
template <>
bool Game::readSetting(const std::string& name, bool* value) const
{
- auto it = settingsMap.find(name);
- if (it == settingsMap.end())
+ auto it = settingsTableIndex.find(name);
+ if (it == settingsTableIndex.end())
{
return false;
}
@@ -104,8 +107,8 @@ bool Game::readSetting(const std::string& name, bool* value) const
template <>
bool Game::readSetting(const std::string& name, int* value) const
{
- auto it = settingsMap.find(name);
- if (it == settingsMap.end())
+ auto it = settingsTableIndex.find(name);
+ if (it == settingsTableIndex.end())
{
return false;
}
@@ -120,8 +123,8 @@ bool Game::readSetting(const std::string& name, int* value) const
template <>
bool Game::readSetting(const std::string& name, float* value) const
{
- auto it = settingsMap.find(name);
- if (it == settingsMap.end())
+ auto it = settingsTableIndex.find(name);
+ if (it == settingsTableIndex.end())
{
return false;
}
@@ -136,8 +139,8 @@ bool Game::readSetting(const std::string& name, float* value) const
template <>
bool Game::readSetting(const std::string& name, Vector2* value) const
{
- auto it = settingsMap.find(name);
- if (it == settingsMap.end())
+ auto it = settingsTableIndex.find(name);
+ if (it == settingsTableIndex.end())
{
return false;
}
@@ -171,11 +174,13 @@ Game::Game(int argc, char* argv[]):
dataPath = getDataPath(applicationName) + "data/";
configPath = getConfigPath(applicationName);
controlsPath = configPath + "controls/";
+ scriptsPath = configPath + "scripts/";
// Create nonexistent config directories
std::vector configPaths;
configPaths.push_back(configPath);
configPaths.push_back(controlsPath);
+ configPaths.push_back(scriptsPath);
for (const std::string& path: configPaths)
{
if (!pathExists(path))
@@ -191,13 +196,11 @@ Game::Game(int argc, char* argv[]):
std::cout.rdbuf(logFileStream.rdbuf());
#endif
- std::cout << "Data path: " << dataPath << std::endl;
- std::cout << "Config path: " << configPath << std::endl;
-
// Setup resource manager
resourceManager = new ResourceManager();
// Include resource search paths in order of priority
+ resourceManager->include(scriptsPath);
resourceManager->include(controlsPath);
resourceManager->include(configPath);
resourceManager->include(dataPath);
@@ -207,6 +210,25 @@ Game::Game(int argc, char* argv[]):
toggleFullscreenDisabled = false;
sandboxState = new SandboxState(this);
+
+
+ cli = new CommandInterpreter();
+
+ std::function exitCommand = std::bind(std::exit, EXIT_SUCCESS);
+ std::function setScaleCommand = [this](int id, float x, float y, float z) {
+ setScale(id, {x, y, z});
+ };
+ std::function toggleWireframeCommand = std::bind(&Game::toggleWireframe, this);
+ std::function shCommand = std::bind(&Game::executeShellScript, this, std::placeholders::_1);
+
+ cli->registerCommand("q", exitCommand);
+ cli->registerCommand("setScale", setScaleCommand);
+ cli->registerCommand("wireframe", toggleWireframeCommand);
+ cli->registerCommand("sh", shCommand);
+
+ // Start CLI thread
+ std::thread cliThread(&Game::interpretCommands, this);
+ cliThread.detach();
}
Game::~Game()
@@ -235,8 +257,8 @@ std::string Game::getString(const std::string& name) const
{
std::string value;
- auto it = stringMap.find(name);
- if (it != stringMap.end())
+ auto it = stringTableIndex.find(name);
+ if (it != stringTableIndex.end())
{
value = (*stringTable)[it->second][languageIndex + 2];
if (value.empty())
@@ -1771,7 +1793,6 @@ void Game::setupControls()
controls.addControl(&changeToolControl);
controls.addControl(&useToolControl);
controls.addControl(&toggleEditModeControl);
- controls.addControl(&toggleWireframeControl);
// Build the system control set
systemControls.addControl(&exitControl);
@@ -1815,7 +1836,6 @@ void Game::setupControls()
exitControl.setActivatedCallback(std::bind(&Application::close, this, EXIT_SUCCESS));
toggleFullscreenControl.setActivatedCallback(std::bind(&Game::toggleFullscreen, this));
takeScreenshotControl.setActivatedCallback(std::bind(&Game::queueScreenshot, this));
- toggleWireframeControl.setActivatedCallback(std::bind(&Game::toggleWireframe, this));
// Build map of control names
controlNameMap["exit"] = &exitControl;
@@ -1841,7 +1861,6 @@ void Game::setupControls()
controlNameMap["change-tool"] = &changeToolControl;
controlNameMap["use-tool"] = &useToolControl;
controlNameMap["toggle-edit-mode"] = &toggleEditModeControl;
- controlNameMap["toggle-wireframe"] = &toggleWireframeControl;
// Load control profile
if (pathExists(controlsPath + controlProfileName + ".csv"))
@@ -1921,12 +1940,8 @@ void Game::loadSettings()
settingsTable = new StringTable();
}
- // Build settings map
- for (std::size_t i = 0; i < settingsTable->size(); ++i)
- {
- const StringTableRow& row = (*settingsTable)[i];
- settingsMap[row[0]] = i;
- }
+ // Build settings table index
+ settingsTableIndex = createIndex(*settingsTable);
// Read settings from table
readSetting("language", &language);
@@ -1947,11 +1962,8 @@ void Game::loadStrings()
// Read strings file
stringTable = resourceManager->load("strings.csv");
- // Build string map
- for (int row = 0; row < stringTable->size(); ++row)
- {
- stringMap[(*stringTable)[row][0]] = row;
- }
+ // Build string table index
+ stringTableIndex = createIndex(*stringTable);
}
void Game::loadFonts()
@@ -3075,6 +3087,7 @@ void Game::enterTitleState()
// Setup scene
Vector3 antHillTranslation = {0, 0, 0};
EntityID antHill = createInstanceOf("ant-hill");
+ std::cout << antHill << std::endl;
setTranslation(antHill, antHillTranslation);
// Setup camera
@@ -3234,6 +3247,28 @@ void Game::returnToMainMenu()
fadeOut(3.0f, Vector3(0.0f), std::bind(&StateMachine::changeState, this, &titleState));
}
+void Game::interpretCommands()
+{
+ std::cout << "Antkeeper " << VERSION_STRING << std::endl;
+
+ while (1)
+ {
+ std::cout << "> " << std::flush;
+ std::string line;
+ std::getline(std::cin, line);
+
+ auto [name, arguments, call] = cli->interpret(line);
+ if (call)
+ {
+ call();
+ }
+ else
+ {
+ std::cout << "ant: Unknown command " << name << std::endl;
+ }
+ }
+}
+
void Game::boxSelect(float x, float y, float w, float h)
{
boxSelectionContainer->setTranslation(Vector2(x, y));
@@ -3415,6 +3450,37 @@ void Game::setTerrainPatchPosition(EntityID entity, const std::tuple&
component->position = position;
}
+void Game::executeShellScript(const std::string& string)
+{
+ TextFile* script = nullptr;
+
+ try
+ {
+ script = resourceManager->load(string);
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Failed to load shell script: \"" << e.what() << "\"" << std::endl;
+ return;
+ }
+
+ for (const std::string& line: *script)
+ {
+ if (!line.empty())
+ {
+ auto [name, arguments, call] = cli->interpret(line);
+ if (call)
+ {
+ call();
+ }
+ else
+ {
+ std::cout << "ant: Unknown command " << name << std::endl;
+ }
+ }
+ }
+}
+
void Game::saveScreenshot(const std::string& filename, unsigned int width, unsigned int height, unsigned char* pixels)
{
stbi_flip_vertically_on_write(1);
diff --git a/src/game.hpp b/src/game.hpp
index 69acf98..7d47742 100644
--- a/src/game.hpp
+++ b/src/game.hpp
@@ -26,6 +26,7 @@ using namespace Emergent;
#include "state-machine.hpp"
#include "entity/entity-id.hpp"
#include "scheduled-function-event.hpp"
+#include "resources/string-table.hpp"
#include
#include