From d6446e18eac5e803840bdf55ab974ffe6e1a2e3d Mon Sep 17 00:00:00 2001 From: John McCardle Date: Thu, 13 Jul 2023 23:01:09 -0400 Subject: [PATCH] Tinkering with input I want to move keyboard input defs to the Python API. I laid the groundwork for it today. From the JANKFILE: - working on API endpoint `_registerInputAction`. it will add "_py" as a suffix to the action string and register it along with other scene actions. - Adding public Scene methods. These are on the base class with default of return `false`. `bool Scene::registerActionInjected(int code, std::string name)` and `unregisterActionInjected` the PythonScene (and other scenes that support injected user input) can override this method, check existing registrations, and return `true` when succeeding. Also, upgraded to C++20 (g++ `c++2a`), mostly because I want to use map::contains. --- JANKFILE.md | 67 +++++++++++++++++++++++++++++++++++++++++++++ build_linux.sh | 4 +-- src/GameEngine.cpp | 2 +- src/McRFPy_API.cpp | 33 ++++++++++++++++++++-- src/McRFPy_API.h | 1 + src/MenuScene.cpp | 2 +- src/PythonScene.cpp | 14 +++++++++- src/PythonScene.h | 4 +++ src/Scene.cpp | 10 +++++++ src/Scene.h | 4 +++ 10 files changed, 134 insertions(+), 7 deletions(-) diff --git a/JANKFILE.md b/JANKFILE.md index 141bea6..4e7d593 100644 --- a/JANKFILE.md +++ b/JANKFILE.md @@ -15,3 +15,70 @@ * ==Buttons don't know their parent...?== So there's arithmetic happening in the event loop to determine it's actual positions. Fix this with the widgets-on-widgets system (so we can go deeper, and just ask the widgets if they or their children were clicked) * Keep aspect ratio correct (and hopefully unbork any mouse issues) when maximizing / full screening the game ``` + +# r/RoguelikeDev Does the Complete Roguelike Tutorial - July 2023 + +## Planning + +Event ends roughly 26 August (last post will be 22 August) + +* Add and remove keystroke registration from Python scripts +* Error checking: raise Python exceptions instead of null reference segfault in C++ +* Proper exception handling: figure out the "any code at REPL shows the unhandled exception" bug thingy +* Extra clarity: display more status info about what Python stuff is being done + - load all files in directory at first + - list modules / classes found + - list Scenes that were found + - Read all Python modules, then call objects & methods at C++-managed events (Engine provided decorators, perhaps??) +* PythonScene version of MenuScene + - instantiate PythonScenes +* Switch Scenes from Python (edge of board / stairs / teleportation feature) +* Update the visible map from Python (fix "map blank until you move" bug) + - update "camera state" and "Recenter camera" buttons' API info as well without a whole Turn +* C++/TCOD Entity pathfinding without calling Python every Turn +* Replace jank .py files that define engine-required objects with C++ definitions inside of `mcrfpy` module + +This actually a pretty big list, but there's about 7 weeks left, so it's just over one item per week. + +I have no bad feelings if these items leak over to EngJam or beyond. + + + +## Notes 12 July + +Some changes to make in McRFPy_API.cpp: +* create a parallel to _registerPyAction which will register a keystroke to a Python label in the current scene. + +## Notes 13 July + +- working on API endpoint `_registerInputAction`. + +it will add "_py" as a suffix to the action string and register it along with other scene actions. + +- Adding public Scene methods. These are on the base class with default of return `false`. + +`bool Scene::registerActionInjected(int code, std::string name)` and `unregisterActionInjected` + +the PythonScene (and other scenes that support injected user input) can override this method, check existing registrations, and return `true` when succeeding. + +- then remove `McRFPy_API::player_input` + +This behavior can be more flexibly implemented in Python using keyboard registration. There's some deduplication as well, since I have a Python implementation of collision detection anyway (for items, doors, and NPCs). + +Also, upgraded to C++20 (g++ `c++2a`), mostly because I want to use map::contains. + +More ideas: + +* Need to make "pan" and "zoom" optional on each grid (for minimap type use cases) +* clicks are handled by C++ and only used on buttons. Grids could have a Python click function (with pixel & grid coords available) +* pixel-on-menu Python click function should be possible too +* Need to call a Python update function when C++ events cause camera following to change: UI is only updating after player input + + +Tomorrow, start with: + +* implement checks in PythonScene::registerActionInjected - Save injected actions, don't let regular actions be overwritten, return success +* Remove PythonScene key definitions and McRFPy_API::player_input +* re-implement walking via keyboard input in Python +* Find a good spot for camera following to update Python immediately +* Find a good spot for grid updates to redraw TCOD line of sight immediately \ No newline at end of file diff --git a/build_linux.sh b/build_linux.sh index ccfc876..cb0cb16 100755 --- a/build_linux.sh +++ b/build_linux.sh @@ -35,7 +35,7 @@ do -I../../deps_linux \ -I../../deps_linux/Python-3.11.1 \ -I../../platform/linux \ - --std=c++17 \ + --std=c++2a \ -c ../../src/$fn.cpp \ -o ../../obj/$fn.o \ -lm \ @@ -53,7 +53,7 @@ done # Final executable g++ \ - --std=c++17 \ + --std=c++2a \ -I../../deps_linux \ -I../../deps_linux/Python-3.11.1 \ -I../../platform/linux \ diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp index 1de8ada..260200a 100644 --- a/src/GameEngine.cpp +++ b/src/GameEngine.cpp @@ -8,7 +8,7 @@ GameEngine::GameEngine() { font.loadFromFile("./assets/JetbrainsMono.ttf"); - window.create(sf::VideoMode(1024, 768), "McRogueFace - LGJ Submission by John McCardle"); + window.create(sf::VideoMode(1024, 768), "McRogueFace - r/RoguelikeDev Tutorial Run"); visible = window.getDefaultView(); window.setFramerateLimit(30); scene = "menu"; diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index 5e02163..cb09dff 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -45,6 +45,9 @@ static PyMethodDef mcrfpyMethods[] = { {"registerPyAction", McRFPy_API::_registerPyAction, METH_VARARGS, "Register a callable Python object to correspond to an action string. (actionstr, callable)"}, + + {"registerInputAction", McRFPy_API::_registerInputAction, METH_VARARGS, + "Register a SFML input code to correspond to an action string. (input_code, actionstr)"}, {"createGrid", McRFPy_API::_createGrid, METH_VARARGS, "create a new grid (title, grid_x, grid_y, grid_size, x, y, w, h). grid_x and grid_y are the width and height in squares. grid_size is the pixel w/h of sprites on the grid. x,y are the grid's screen position. w,h are the grid's screen size" }, @@ -536,6 +539,8 @@ PyObject* McRFPy_API::_registerPyAction(PyObject *self, PyObject *args) PyObject* callable; const char * actionstr; if (!PyArg_ParseTuple(args, "sO", &actionstr, &callable)) return NULL; + //TODO: if the string already exists in the callbacks map, + // decrease our reference count so it can potentially be garbage collected callbacks[std::string(actionstr)] = callable; Py_INCREF(callable); @@ -544,11 +549,35 @@ PyObject* McRFPy_API::_registerPyAction(PyObject *self, PyObject *args) return Py_None; } +PyObject* McRFPy_API::_registerInputAction(PyObject *self, PyObject *args) +{ + int action_code; + const char * actionstr; + if (!PyArg_ParseTuple(args, "iz", &action_code, &actionstr)) return NULL; + + bool success; + + if (actionstr == NULL) { // Action provided is None, i.e. unregister + success = game->currentScene()->unregisterActionInjected(action_code, std::string(actionstr) + "_py"); + } else { + success = game->currentScene()->registerActionInjected(action_code, std::string(actionstr) + "_py"); + } + + success ? Py_INCREF(Py_True) : Py_INCREF(Py_False); + return success ? Py_True : Py_False; + +} + void McRFPy_API::doAction(std::string actionstr) { // hard coded actions that require no registration + //std::cout << "Calling Python Action: " << actionstr; if (!actionstr.compare("startrepl")) return McRFPy_API::REPL(); - if (callbacks.find(actionstr) == callbacks.end()) return; - //std::cout << "Calling: " << PyUnicode_AsUTF8(PyObject_Repr(callbacks[actionstr])) << std::endl; + if (callbacks.find(actionstr) == callbacks.end()) + { + //std::cout << " (not found)" << std::endl; + return; + } + //std::cout << " (" << PyUnicode_AsUTF8(PyObject_Repr(callbacks[actionstr])) << ")" << std::endl; PyObject_Call(callbacks[actionstr], PyTuple_New(0), NULL); } diff --git a/src/McRFPy_API.h b/src/McRFPy_API.h index 6e7669a..b9c07a5 100644 --- a/src/McRFPy_API.h +++ b/src/McRFPy_API.h @@ -82,6 +82,7 @@ public: static PyObject* _createAnimation(PyObject*, PyObject*); static PyObject* _registerPyAction(PyObject*, PyObject*); + static PyObject* _registerInputAction(PyObject*, PyObject*); static PyObject* _createSoundBuffer(PyObject*, PyObject*); static PyObject* _loadMusic(PyObject*, PyObject*); diff --git a/src/MenuScene.cpp b/src/MenuScene.cpp index 28802d2..eeebed1 100644 --- a/src/MenuScene.cpp +++ b/src/MenuScene.cpp @@ -4,7 +4,7 @@ MenuScene::MenuScene(GameEngine* g) : Scene(g) { text.setFont(game->getFont()); - text.setString("McRogueFace Engine - LGJ 2023 (Incomplete)"); + text.setString("McRogueFace Engine - r/RoguelikeDev Tutorial 2023"); text.setCharacterSize(24); //std::cout << "MenuScene Initialized. " << game << std::endl; //std::cout << "Font: " << game->getFont().getInfo().family << std::endl; diff --git a/src/PythonScene.cpp b/src/PythonScene.cpp index c0d04ff..29ed9c3 100644 --- a/src/PythonScene.cpp +++ b/src/PythonScene.cpp @@ -186,7 +186,10 @@ void PythonScene::doZoom(sf::Vector2i mousepos, int value) { void PythonScene::doAction(std::string name, std::string type) { auto mousepos = sf::Mouse::getPosition(game->getWindow()); //std::cout << "name: " << name << ", type: " << type << std::endl; - if (ACTIONONCE("click")) { + if (ACTIONPY) { + McRFPy_API::doAction(name); + } + else if (ACTIONONCE("click")) { // left click start //std::cout << "LClick started at (" << mousepos.x << ", " << mousepos.y << ")" << std::endl; dragstart = mousepos; @@ -237,6 +240,15 @@ void PythonScene::doAction(std::string name, std::string type) { else if (ACTIONONCE("wait")) { McRFPy_API::player_input(+0, +0); } } +bool PythonScene::registerActionInjected(int code, std::string name) { + return false; + +} + +bool PythonScene::unregisterActionInjected(int code, std::string name) { + return false; +} + void PythonScene::sRender() { game->getWindow().clear(); diff --git a/src/PythonScene.h b/src/PythonScene.h index dce0c08..0133ea9 100644 --- a/src/PythonScene.h +++ b/src/PythonScene.h @@ -17,9 +17,13 @@ class PythonScene: public Scene void doZoom(sf::Vector2i, int); //std::list animations; void animate(); + std::map actionInjected; + public: PythonScene(GameEngine*, std::string); void update() override final; void doAction(std::string, std::string) override final; void sRender() override final; + bool registerActionInjected(int, std::string) override final; + bool unregisterActionInjected(int, std::string) override final; }; diff --git a/src/Scene.cpp b/src/Scene.cpp index a6e32ae..7b5bac6 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -23,3 +23,13 @@ std::string Scene::action(int code) { return actions[code]; } + +bool Scene::registerActionInjected(int code, std::string name) +{ + return false; +} + +bool Scene::unregisterActionInjected(int code, std::string name) +{ + return false; +} \ No newline at end of file diff --git a/src/Scene.h b/src/Scene.h index 433c0ae..d3d66fa 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -4,6 +4,7 @@ #define ACTION(X, Y) (name.compare(X) == 0 && type.compare(Y) == 0) #define ACTIONONCE(X) ((name.compare(X) == 0 && type.compare("start") == 0 && !actionState[name])) #define ACTIONAFTER(X) ((name.compare(X) == 0 && type.compare("end") == 0)) +#define ACTIONPY ((name.size() > 3 && name.compare(name.size() - 3, 3, "_py") == 0)) #include "Common.h" //#include "GameEngine.h" @@ -32,5 +33,8 @@ public: bool hasAction(std::string); bool hasAction(int); std::string action(int); + + virtual bool registerActionInjected(int, std::string); + virtual bool unregisterActionInjected(int, std::string); };