Compare commits
4 Commits
master
...
reprs_and_
Author | SHA1 | Date |
---|---|---|
|
43fac8f4f3 | |
|
3fd5ad93e2 | |
|
03376897b8 | |
|
48af072a33 |
|
@ -46,7 +46,7 @@ endif()
|
||||||
|
|
||||||
# Add the directory where the linker should look for the libraries
|
# Add the directory where the linker should look for the libraries
|
||||||
#link_directories(${CMAKE_SOURCE_DIR}/deps_linux)
|
#link_directories(${CMAKE_SOURCE_DIR}/deps_linux)
|
||||||
link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
link_directories(${CMAKE_SOURCE_DIR}/lib)
|
||||||
|
|
||||||
# Define the executable target before linking libraries
|
# Define the executable target before linking libraries
|
||||||
add_executable(mcrogueface ${SOURCES})
|
add_executable(mcrogueface ${SOURCES})
|
||||||
|
@ -67,9 +67,9 @@ add_custom_command(TARGET mcrogueface POST_BUILD
|
||||||
# Copy Python standard library to build directory
|
# Copy Python standard library to build directory
|
||||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
add_custom_command(TARGET mcrogueface POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib)
|
${CMAKE_SOURCE_DIR}/lib $<TARGET_FILE_DIR:mcrogueface>/lib)
|
||||||
|
|
||||||
# rpath for including shared libraries
|
# rpath for including shared libraries
|
||||||
set_target_properties(mcrogueface PROPERTIES
|
set_target_properties(mcrogueface PROPERTIES
|
||||||
INSTALL_RPATH "$ORIGIN/./lib")
|
INSTALL_RPATH "./lib")
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 181 KiB |
Binary file not shown.
Before Width: | Height: | Size: 674 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -9,10 +9,10 @@ GameEngine::GameEngine()
|
||||||
{
|
{
|
||||||
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
|
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
|
||||||
Resources::game = this;
|
Resources::game = this;
|
||||||
window_title = "Crypt of Sokoban - 7DRL 2025, McRogueface Engine";
|
window_title = "McRogueFace - 7DRL 2024 Engine Demo";
|
||||||
window.create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
|
window.create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
|
||||||
visible = window.getDefaultView();
|
visible = window.getDefaultView();
|
||||||
window.setFramerateLimit(60);
|
window.setFramerateLimit(30);
|
||||||
scene = "uitest";
|
scene = "uitest";
|
||||||
scenes["uitest"] = new UITestScene(this);
|
scenes["uitest"] = new UITestScene(this);
|
||||||
|
|
||||||
|
@ -63,10 +63,7 @@ void GameEngine::run()
|
||||||
currentFrame++;
|
currentFrame++;
|
||||||
frameTime = clock.restart().asSeconds();
|
frameTime = clock.restart().asSeconds();
|
||||||
fps = 1 / frameTime;
|
fps = 1 / frameTime;
|
||||||
int whole_fps = (int)fps;
|
window.setTitle(window_title + " " + std::to_string(fps) + " FPS");
|
||||||
int tenth_fps = int(fps * 100) % 10;
|
|
||||||
//window.setTitle(window_title + " " + std::to_string(fps) + " FPS");
|
|
||||||
window.setTitle(window_title + " " + std::to_string(whole_fps) + "." + std::to_string(tenth_fps) + " FPS");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,15 +84,9 @@ PyObject* PyInit_mcrfpy()
|
||||||
auto t = pytypes[i];
|
auto t = pytypes[i];
|
||||||
while (t != nullptr)
|
while (t != nullptr)
|
||||||
{
|
{
|
||||||
std::cout << "Registering type: " << t->tp_name << std::endl;
|
/*std::cout << */ PyType_Ready(t); /*<< std::endl; */
|
||||||
if (PyType_Ready(t) < 0) {
|
|
||||||
std::cout << "ERROR: PyType_Ready failed for " << t->tp_name << std::endl;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
std::cout << " tp_alloc after PyType_Ready: " << (void*)t->tp_alloc << std::endl;
|
|
||||||
PyModule_AddType(m, t);
|
PyModule_AddType(m, t);
|
||||||
i++;
|
t = pytypes[i++];
|
||||||
t = pytypes[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add default_font and default_texture to module
|
// Add default_font and default_texture to module
|
||||||
|
@ -257,7 +251,7 @@ PyObject* McRFPy_API::_registerInputAction(PyObject *self, PyObject *args)
|
||||||
std::cout << "Unregistering\n";
|
std::cout << "Unregistering\n";
|
||||||
success = game->currentScene()->unregisterActionInjected(action_code, std::string(actionstr) + "_py");
|
success = game->currentScene()->unregisterActionInjected(action_code, std::string(actionstr) + "_py");
|
||||||
} else {
|
} else {
|
||||||
std::cout << "Registering " << actionstr << "_py to " << action_code << "\n";
|
std::cout << "Registering" << actionstr << "_py to " << action_code << "\n";
|
||||||
success = game->currentScene()->registerActionInjected(action_code, std::string(actionstr) + "_py");
|
success = game->currentScene()->registerActionInjected(action_code, std::string(actionstr) + "_py");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#include "PyColor.h"
|
#include "PyColor.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyObjectUtils.h"
|
|
||||||
#include "PyRAII.h"
|
|
||||||
|
|
||||||
PyGetSetDef PyColor::getsetters[] = {
|
PyGetSetDef PyColor::getsetters[] = {
|
||||||
{"r", (getter)PyColor::get_member, (setter)PyColor::set_member, "Red component", (void*)0},
|
{"r", (getter)PyColor::get_member, (setter)PyColor::set_member, "Red component", (void*)0},
|
||||||
|
@ -16,16 +14,11 @@ PyColor::PyColor(sf::Color target)
|
||||||
|
|
||||||
PyObject* PyColor::pyObject()
|
PyObject* PyColor::pyObject()
|
||||||
{
|
{
|
||||||
PyTypeObject* type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color");
|
PyObject* obj = PyType_GenericAlloc(&mcrfpydef::PyColorType, 0);
|
||||||
if (!type) return nullptr;
|
Py_INCREF(obj);
|
||||||
|
PyColorObject* self = (PyColorObject*)obj;
|
||||||
PyColorObject* obj = (PyColorObject*)type->tp_alloc(type, 0);
|
self->data = data;
|
||||||
Py_DECREF(type);
|
return obj;
|
||||||
|
|
||||||
if (obj) {
|
|
||||||
obj->data = data;
|
|
||||||
}
|
|
||||||
return (PyObject*)obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sf::Color PyColor::fromPy(PyObject* obj)
|
sf::Color PyColor::fromPy(PyObject* obj)
|
||||||
|
@ -145,30 +138,13 @@ int PyColor::set_member(PyObject* obj, PyObject* value, void* closure)
|
||||||
|
|
||||||
PyColorObject* PyColor::from_arg(PyObject* args)
|
PyColorObject* PyColor::from_arg(PyObject* args)
|
||||||
{
|
{
|
||||||
// Use RAII for type reference management
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color");
|
||||||
PyRAII::PyTypeRef type("Color", McRFPy_API::mcrf_module);
|
if (PyObject_IsInstance(args, (PyObject*)type)) return (PyColorObject*)args;
|
||||||
if (!type) {
|
auto obj = (PyColorObject*)type->tp_alloc(type, 0);
|
||||||
return NULL;
|
int err = init(obj, args, NULL);
|
||||||
}
|
|
||||||
|
|
||||||
// Check if args is already a Color instance
|
|
||||||
if (PyObject_IsInstance(args, (PyObject*)type.get())) {
|
|
||||||
return (PyColorObject*)args;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new Color object using RAII
|
|
||||||
PyRAII::PyObjectRef obj(type->tp_alloc(type.get(), 0), true);
|
|
||||||
if (!obj) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the object
|
|
||||||
int err = init((PyColorObject*)obj.get(), args, NULL);
|
|
||||||
if (err) {
|
if (err) {
|
||||||
// obj will be automatically cleaned up when it goes out of scope
|
Py_DECREF(obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
return obj;
|
||||||
// Release ownership and return
|
|
||||||
return (PyColorObject*)obj.release();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,6 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyColorType = {
|
static PyTypeObject PyColorType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
|
||||||
.tp_name = "mcrfpy.Color",
|
.tp_name = "mcrfpy.Color",
|
||||||
.tp_basicsize = sizeof(PyColorObject),
|
.tp_basicsize = sizeof(PyColorObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -25,7 +25,6 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyFontType = {
|
static PyTypeObject PyFontType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
|
||||||
.tp_name = "mcrfpy.Font",
|
.tp_name = "mcrfpy.Font",
|
||||||
.tp_basicsize = sizeof(PyFontObject),
|
.tp_basicsize = sizeof(PyFontObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "Common.h"
|
|
||||||
#include "Python.h"
|
|
||||||
#include "McRFPy_API.h"
|
|
||||||
#include "PyRAII.h"
|
|
||||||
|
|
||||||
namespace PyObjectUtils {
|
|
||||||
|
|
||||||
// Template for getting Python type object from module
|
|
||||||
template<typename T>
|
|
||||||
PyTypeObject* getPythonType(const char* typeName) {
|
|
||||||
PyTypeObject* type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, typeName);
|
|
||||||
if (!type) {
|
|
||||||
PyErr_Format(PyExc_RuntimeError, "Could not find %s type in module", typeName);
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic function to create a Python object of given type
|
|
||||||
inline PyObject* createPyObjectGeneric(const char* typeName) {
|
|
||||||
PyTypeObject* type = getPythonType<void>(typeName);
|
|
||||||
if (!type) return nullptr;
|
|
||||||
|
|
||||||
PyObject* obj = type->tp_alloc(type, 0);
|
|
||||||
Py_DECREF(type);
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to allocate and initialize a Python object with data
|
|
||||||
template<typename PyObjType, typename DataType>
|
|
||||||
PyObject* createPyObjectWithData(const char* typeName, DataType data) {
|
|
||||||
PyTypeObject* type = getPythonType<void>(typeName);
|
|
||||||
if (!type) return nullptr;
|
|
||||||
|
|
||||||
PyObjType* obj = (PyObjType*)type->tp_alloc(type, 0);
|
|
||||||
Py_DECREF(type);
|
|
||||||
|
|
||||||
if (obj) {
|
|
||||||
obj->data = data;
|
|
||||||
}
|
|
||||||
return (PyObject*)obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to convert UIDrawable to appropriate Python object
|
|
||||||
// This is moved to UICollection.cpp to avoid circular dependencies
|
|
||||||
|
|
||||||
// RAII-based object creation example
|
|
||||||
inline PyObject* createPyObjectGenericRAII(const char* typeName) {
|
|
||||||
PyRAII::PyTypeRef type(typeName, McRFPy_API::mcrf_module);
|
|
||||||
if (!type) {
|
|
||||||
PyErr_Format(PyExc_RuntimeError, "Could not find %s type in module", typeName);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* obj = type->tp_alloc(type.get(), 0);
|
|
||||||
// Return the new reference (caller owns it)
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Example of using PyObjectRef for safer reference management
|
|
||||||
template<typename PyObjType, typename DataType>
|
|
||||||
PyObject* createPyObjectWithDataRAII(const char* typeName, DataType data) {
|
|
||||||
PyRAII::PyObjectRef obj = PyRAII::createObject<PyObjType>(typeName, McRFPy_API::mcrf_module);
|
|
||||||
if (!obj) {
|
|
||||||
PyErr_Format(PyExc_RuntimeError, "Could not create %s object", typeName);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Access the object through the RAII wrapper
|
|
||||||
((PyObjType*)obj.get())->data = data;
|
|
||||||
|
|
||||||
// Release ownership to return to Python
|
|
||||||
return obj.release();
|
|
||||||
}
|
|
||||||
}
|
|
138
src/PyRAII.h
138
src/PyRAII.h
|
@ -1,138 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "Python.h"
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace PyRAII {
|
|
||||||
|
|
||||||
// RAII wrapper for PyObject* that automatically manages reference counting
|
|
||||||
class PyObjectRef {
|
|
||||||
private:
|
|
||||||
PyObject* ptr;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Constructors
|
|
||||||
PyObjectRef() : ptr(nullptr) {}
|
|
||||||
|
|
||||||
explicit PyObjectRef(PyObject* p, bool steal_ref = false) : ptr(p) {
|
|
||||||
if (ptr && !steal_ref) {
|
|
||||||
Py_INCREF(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy constructor
|
|
||||||
PyObjectRef(const PyObjectRef& other) : ptr(other.ptr) {
|
|
||||||
if (ptr) {
|
|
||||||
Py_INCREF(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move constructor
|
|
||||||
PyObjectRef(PyObjectRef&& other) noexcept : ptr(other.ptr) {
|
|
||||||
other.ptr = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destructor
|
|
||||||
~PyObjectRef() {
|
|
||||||
Py_XDECREF(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy assignment
|
|
||||||
PyObjectRef& operator=(const PyObjectRef& other) {
|
|
||||||
if (this != &other) {
|
|
||||||
Py_XDECREF(ptr);
|
|
||||||
ptr = other.ptr;
|
|
||||||
if (ptr) {
|
|
||||||
Py_INCREF(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move assignment
|
|
||||||
PyObjectRef& operator=(PyObjectRef&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
Py_XDECREF(ptr);
|
|
||||||
ptr = other.ptr;
|
|
||||||
other.ptr = nullptr;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Access operators
|
|
||||||
PyObject* get() const { return ptr; }
|
|
||||||
PyObject* operator->() const { return ptr; }
|
|
||||||
PyObject& operator*() const { return *ptr; }
|
|
||||||
operator bool() const { return ptr != nullptr; }
|
|
||||||
|
|
||||||
// Release ownership (for returning to Python)
|
|
||||||
PyObject* release() {
|
|
||||||
PyObject* temp = ptr;
|
|
||||||
ptr = nullptr;
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset with new pointer
|
|
||||||
void reset(PyObject* p = nullptr, bool steal_ref = false) {
|
|
||||||
if (p != ptr) {
|
|
||||||
Py_XDECREF(ptr);
|
|
||||||
ptr = p;
|
|
||||||
if (ptr && !steal_ref) {
|
|
||||||
Py_INCREF(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper class for managing PyTypeObject* references from module lookups
|
|
||||||
class PyTypeRef {
|
|
||||||
private:
|
|
||||||
PyTypeObject* type;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PyTypeRef() : type(nullptr) {}
|
|
||||||
|
|
||||||
explicit PyTypeRef(const char* typeName, PyObject* module) {
|
|
||||||
type = (PyTypeObject*)PyObject_GetAttrString(module, typeName);
|
|
||||||
// GetAttrString returns a new reference, so we own it
|
|
||||||
}
|
|
||||||
|
|
||||||
~PyTypeRef() {
|
|
||||||
Py_XDECREF((PyObject*)type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete copy operations to prevent accidental reference issues
|
|
||||||
PyTypeRef(const PyTypeRef&) = delete;
|
|
||||||
PyTypeRef& operator=(const PyTypeRef&) = delete;
|
|
||||||
|
|
||||||
// Allow move operations
|
|
||||||
PyTypeRef(PyTypeRef&& other) noexcept : type(other.type) {
|
|
||||||
other.type = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyTypeRef& operator=(PyTypeRef&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
Py_XDECREF((PyObject*)type);
|
|
||||||
type = other.type;
|
|
||||||
other.type = nullptr;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyTypeObject* get() const { return type; }
|
|
||||||
PyTypeObject* operator->() const { return type; }
|
|
||||||
operator bool() const { return type != nullptr; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convenience function to create a new object with RAII
|
|
||||||
template<typename PyObjType>
|
|
||||||
PyObjectRef createObject(const char* typeName, PyObject* module) {
|
|
||||||
PyTypeRef type(typeName, module);
|
|
||||||
if (!type) {
|
|
||||||
return PyObjectRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* obj = type->tp_alloc(type.get(), 0);
|
|
||||||
// tp_alloc returns a new reference, so we steal it
|
|
||||||
return PyObjectRef(obj, true);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,8 +11,7 @@ PyScene::PyScene(GameEngine* g) : Scene(g)
|
||||||
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_DEL, "wheel_up");
|
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_DEL, "wheel_up");
|
||||||
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_NEG + ActionCode::WHEEL_DEL, "wheel_down");
|
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_NEG + ActionCode::WHEEL_DEL, "wheel_down");
|
||||||
|
|
||||||
// console (` / ~ key) - don't hard code.
|
registerAction(ActionCode::KEY + sf::Keyboard::Grave, "debug_menu");
|
||||||
//registerAction(ActionCode::KEY + sf::Keyboard::Grave, "debug_menu");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PyScene::update()
|
void PyScene::update()
|
||||||
|
|
|
@ -28,6 +28,7 @@ sf::Sprite PyTexture::sprite(int index, sf::Vector2f pos, sf::Vector2f s)
|
||||||
|
|
||||||
PyObject* PyTexture::pyObject()
|
PyObject* PyTexture::pyObject()
|
||||||
{
|
{
|
||||||
|
std::cout << "Find type" << std::endl;
|
||||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
|
||||||
PyObject* obj = PyTexture::pynew(type, Py_None, Py_None);
|
PyObject* obj = PyTexture::pynew(type, Py_None, Py_None);
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyTextureType = {
|
static PyTypeObject PyTextureType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
|
||||||
.tp_name = "mcrfpy.Texture",
|
.tp_name = "mcrfpy.Texture",
|
||||||
.tp_basicsize = sizeof(PyTextureObject),
|
.tp_basicsize = sizeof(PyTextureObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyObjectUtils.h"
|
|
||||||
|
|
||||||
PyGetSetDef PyVector::getsetters[] = {
|
PyGetSetDef PyVector::getsetters[] = {
|
||||||
{"x", (getter)PyVector::get_member, (setter)PyVector::set_member, "X/horizontal component", (void*)0},
|
{"x", (getter)PyVector::get_member, (setter)PyVector::set_member, "X/horizontal component", (void*)0},
|
||||||
|
@ -12,16 +11,11 @@ PyVector::PyVector(sf::Vector2f target)
|
||||||
|
|
||||||
PyObject* PyVector::pyObject()
|
PyObject* PyVector::pyObject()
|
||||||
{
|
{
|
||||||
PyTypeObject* type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
PyObject* obj = PyType_GenericAlloc(&mcrfpydef::PyVectorType, 0);
|
||||||
if (!type) return nullptr;
|
Py_INCREF(obj);
|
||||||
|
PyVectorObject* self = (PyVectorObject*)obj;
|
||||||
PyVectorObject* obj = (PyVectorObject*)type->tp_alloc(type, 0);
|
self->data = data;
|
||||||
Py_DECREF(type);
|
return obj;
|
||||||
|
|
||||||
if (obj) {
|
|
||||||
obj->data = data;
|
|
||||||
}
|
|
||||||
return (PyObject*)obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sf::Vector2f PyVector::fromPy(PyObject* obj)
|
sf::Vector2f PyVector::fromPy(PyObject* obj)
|
||||||
|
@ -115,16 +109,3 @@ int PyVector::set_member(PyObject* obj, PyObject* value, void* closure)
|
||||||
// TODO
|
// TODO
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyVectorObject* PyVector::from_arg(PyObject* args)
|
|
||||||
{
|
|
||||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
|
||||||
if (PyObject_IsInstance(args, (PyObject*)type)) return (PyVectorObject*)args;
|
|
||||||
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
|
|
||||||
int err = init(obj, args, NULL);
|
|
||||||
if (err) {
|
|
||||||
Py_DECREF(obj);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include "McRFPy_API.h"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
|
@ -23,14 +22,12 @@ public:
|
||||||
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
|
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
|
||||||
static PyObject* get_member(PyObject*, void*);
|
static PyObject* get_member(PyObject*, void*);
|
||||||
static int set_member(PyObject*, PyObject*, void*);
|
static int set_member(PyObject*, PyObject*, void*);
|
||||||
static PyVectorObject* from_arg(PyObject*);
|
|
||||||
|
|
||||||
static PyGetSetDef getsetters[];
|
static PyGetSetDef getsetters[];
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyVectorType = {
|
static PyTypeObject PyVectorType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
|
||||||
.tp_name = "mcrfpy.Vector",
|
.tp_name = "mcrfpy.Vector",
|
||||||
.tp_basicsize = sizeof(PyVectorObject),
|
.tp_basicsize = sizeof(PyVectorObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -13,11 +13,10 @@ UIDrawable* UICaption::click_at(sf::Vector2f point)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UICaption::render(sf::Vector2f offset, sf::RenderTarget& target)
|
void UICaption::render(sf::Vector2f offset)
|
||||||
{
|
{
|
||||||
text.move(offset);
|
text.move(offset);
|
||||||
//Resources::game->getWindow().draw(text);
|
Resources::game->getWindow().draw(text);
|
||||||
target.draw(text);
|
|
||||||
text.move(-offset);
|
text.move(-offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,33 +222,20 @@ PyObject* UICaption::repr(PyUICaptionObject* self)
|
||||||
int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
{
|
{
|
||||||
using namespace mcrfpydef;
|
using namespace mcrfpydef;
|
||||||
// Constructor switch to Vector position
|
static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", nullptr };
|
||||||
//static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", nullptr };
|
float x = 0.0f, y = 0.0f, outline = 0.0f;
|
||||||
//float x = 0.0f, y = 0.0f, outline = 0.0f;
|
|
||||||
static const char* keywords[] = { "pos", "text", "font", "fill_color", "outline_color", "outline", nullptr };
|
|
||||||
PyObject* pos;
|
|
||||||
float outline = 0.0f;
|
|
||||||
char* text;
|
char* text;
|
||||||
PyObject* font=NULL, *fill_color=NULL, *outline_color=NULL;
|
PyObject* font=NULL, *fill_color=NULL, *outline_color=NULL;
|
||||||
|
|
||||||
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
|
||||||
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
|
const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|zOOOf",
|
|
||||||
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline))
|
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyVectorObject* pos_result = PyVector::from_arg(pos);
|
|
||||||
if (!pos_result)
|
|
||||||
{
|
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
self->data->text.setPosition(pos_result->data);
|
|
||||||
// check types for font, fill_color, outline_color
|
// check types for font, fill_color, outline_color
|
||||||
|
|
||||||
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
||||||
if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
|
if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
|
||||||
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance");
|
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance");
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -265,6 +251,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
//self->data->text.setFont(Resources::game->getFont());
|
//self->data->text.setFont(Resources::game->getFont());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self->data->text.setPosition(sf::Vector2f(x, y));
|
||||||
self->data->text.setString((std::string)text);
|
self->data->text.setString((std::string)text);
|
||||||
self->data->text.setOutlineThickness(outline);
|
self->data->text.setOutlineThickness(outline);
|
||||||
if (fill_color) {
|
if (fill_color) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ class UICaption: public UIDrawable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
sf::Text text;
|
sf::Text text;
|
||||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
void render(sf::Vector2f) override final;
|
||||||
PyObjectsEnum derived_type() override final;
|
PyObjectsEnum derived_type() override final;
|
||||||
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUICaptionType = {
|
static PyTypeObject PyUICaptionType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
|
||||||
.tp_name = "mcrfpy.Caption",
|
.tp_name = "mcrfpy.Caption",
|
||||||
.tp_basicsize = sizeof(PyUICaptionObject),
|
.tp_basicsize = sizeof(PyUICaptionObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -5,76 +5,9 @@
|
||||||
#include "UISprite.h"
|
#include "UISprite.h"
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyObjectUtils.h"
|
|
||||||
|
|
||||||
using namespace mcrfpydef;
|
using namespace mcrfpydef;
|
||||||
|
|
||||||
// Local helper function to convert UIDrawable to appropriate Python object
|
|
||||||
static PyObject* convertDrawableToPython(std::shared_ptr<UIDrawable> drawable) {
|
|
||||||
if (!drawable) {
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyTypeObject* type = nullptr;
|
|
||||||
PyObject* obj = nullptr;
|
|
||||||
|
|
||||||
switch (drawable->derived_type()) {
|
|
||||||
case PyObjectsEnum::UIFRAME:
|
|
||||||
{
|
|
||||||
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame");
|
|
||||||
if (!type) return nullptr;
|
|
||||||
auto pyObj = (PyUIFrameObject*)type->tp_alloc(type, 0);
|
|
||||||
if (pyObj) {
|
|
||||||
pyObj->data = std::static_pointer_cast<UIFrame>(drawable);
|
|
||||||
}
|
|
||||||
obj = (PyObject*)pyObj;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PyObjectsEnum::UICAPTION:
|
|
||||||
{
|
|
||||||
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption");
|
|
||||||
if (!type) return nullptr;
|
|
||||||
auto pyObj = (PyUICaptionObject*)type->tp_alloc(type, 0);
|
|
||||||
if (pyObj) {
|
|
||||||
pyObj->data = std::static_pointer_cast<UICaption>(drawable);
|
|
||||||
pyObj->font = nullptr;
|
|
||||||
}
|
|
||||||
obj = (PyObject*)pyObj;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PyObjectsEnum::UISPRITE:
|
|
||||||
{
|
|
||||||
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite");
|
|
||||||
if (!type) return nullptr;
|
|
||||||
auto pyObj = (PyUISpriteObject*)type->tp_alloc(type, 0);
|
|
||||||
if (pyObj) {
|
|
||||||
pyObj->data = std::static_pointer_cast<UISprite>(drawable);
|
|
||||||
}
|
|
||||||
obj = (PyObject*)pyObj;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PyObjectsEnum::UIGRID:
|
|
||||||
{
|
|
||||||
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid");
|
|
||||||
if (!type) return nullptr;
|
|
||||||
auto pyObj = (PyUIGridObject*)type->tp_alloc(type, 0);
|
|
||||||
if (pyObj) {
|
|
||||||
pyObj->data = std::static_pointer_cast<UIGrid>(drawable);
|
|
||||||
}
|
|
||||||
obj = (PyObject*)pyObj;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
PyErr_SetString(PyExc_TypeError, "Unknown UIDrawable derived type");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type) {
|
|
||||||
Py_DECREF(type);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
int UICollectionIter::init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds)
|
int UICollectionIter::init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds)
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
|
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
|
||||||
|
@ -83,12 +16,6 @@ int UICollectionIter::init(PyUICollectionIterObject* self, PyObject* args, PyObj
|
||||||
|
|
||||||
PyObject* UICollectionIter::next(PyUICollectionIterObject* self)
|
PyObject* UICollectionIter::next(PyUICollectionIterObject* self)
|
||||||
{
|
{
|
||||||
// Check if self and self->data are valid
|
|
||||||
if (!self || !self->data) {
|
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Iterator object or data is null");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->data->size() != self->start_size)
|
if (self->data->size() != self->start_size)
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration");
|
PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration");
|
||||||
|
@ -108,8 +35,9 @@ PyObject* UICollectionIter::next(PyUICollectionIterObject* self)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
auto target = (*vec)[self->index-1];
|
auto target = (*vec)[self->index-1];
|
||||||
// Return the proper Python object for this UIDrawable
|
// TODO build PyObject* of the correct UIDrawable subclass to return
|
||||||
return convertDrawableToPython(target);
|
//return py_instance(target);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UICollectionIter::repr(PyUICollectionIterObject* self)
|
PyObject* UICollectionIter::repr(PyUICollectionIterObject* self)
|
||||||
|
@ -143,7 +71,8 @@ PyObject* UICollection::getitem(PyUICollectionObject* self, Py_ssize_t index) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
auto target = (*vec)[index];
|
auto target = (*vec)[index];
|
||||||
return convertDrawableToPython(target);
|
RET_PY_INSTANCE(target);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -260,18 +189,9 @@ int UICollection::init(PyUICollectionObject* self, PyObject* args, PyObject* kwd
|
||||||
|
|
||||||
PyObject* UICollection::iter(PyUICollectionObject* self)
|
PyObject* UICollection::iter(PyUICollectionObject* self)
|
||||||
{
|
{
|
||||||
// Get the iterator type from the module to ensure we have the registered version
|
PyUICollectionIterObject* iterObj;
|
||||||
PyTypeObject* iterType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollectionIter");
|
iterObj = (PyUICollectionIterObject*)PyUICollectionIterType.tp_alloc(&PyUICollectionIterType, 0);
|
||||||
if (!iterType) {
|
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Could not find UICollectionIter type in module");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate new iterator instance
|
|
||||||
PyUICollectionIterObject* iterObj = (PyUICollectionIterObject*)iterType->tp_alloc(iterType, 0);
|
|
||||||
|
|
||||||
if (iterObj == NULL) {
|
if (iterObj == NULL) {
|
||||||
Py_DECREF(iterType);
|
|
||||||
return NULL; // Failed to allocate memory for the iterator object
|
return NULL; // Failed to allocate memory for the iterator object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,6 +199,5 @@ PyObject* UICollection::iter(PyUICollectionObject* self)
|
||||||
iterObj->index = 0;
|
iterObj->index = 0;
|
||||||
iterObj->start_size = self->data->size();
|
iterObj->start_size = self->data->size();
|
||||||
|
|
||||||
Py_DECREF(iterType);
|
|
||||||
return (PyObject*)iterObj;
|
return (PyObject*)iterObj;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUICollectionIterType = {
|
static PyTypeObject PyUICollectionIterType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.UICollectionIter",
|
.tp_name = "mcrfpy.UICollectionIter",
|
||||||
.tp_basicsize = sizeof(PyUICollectionIterObject),
|
.tp_basicsize = sizeof(PyUICollectionIterObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
@ -44,11 +44,9 @@ namespace mcrfpydef {
|
||||||
.tp_repr = (reprfunc)UICollectionIter::repr,
|
.tp_repr = (reprfunc)UICollectionIter::repr,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Iterator for a collection of UI objects"),
|
.tp_doc = PyDoc_STR("Iterator for a collection of UI objects"),
|
||||||
.tp_iter = PyObject_SelfIter,
|
|
||||||
.tp_iternext = (iternextfunc)UICollectionIter::next,
|
.tp_iternext = (iternextfunc)UICollectionIter::next,
|
||||||
//.tp_getset = PyUICollection_getset,
|
//.tp_getset = PyUICollection_getset,
|
||||||
.tp_init = (initproc)UICollectionIter::init, // just raise an exception
|
.tp_init = (initproc)UICollectionIter::init, // just raise an exception
|
||||||
.tp_alloc = PyType_GenericAlloc,
|
|
||||||
//TODO - as static method, not inline lambda def, please
|
//TODO - as static method, not inline lambda def, please
|
||||||
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
||||||
{
|
{
|
||||||
|
@ -58,7 +56,7 @@ namespace mcrfpydef {
|
||||||
};
|
};
|
||||||
|
|
||||||
static PyTypeObject PyUICollectionType = {
|
static PyTypeObject PyUICollectionType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_/HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.UICollection",
|
.tp_name = "mcrfpy.UICollection",
|
||||||
.tp_basicsize = sizeof(PyUICollectionObject),
|
.tp_basicsize = sizeof(PyUICollectionObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "UICaption.h"
|
#include "UICaption.h"
|
||||||
#include "UISprite.h"
|
#include "UISprite.h"
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "GameEngine.h"
|
|
||||||
|
|
||||||
UIDrawable::UIDrawable() { click_callable = NULL; }
|
UIDrawable::UIDrawable() { click_callable = NULL; }
|
||||||
|
|
||||||
|
@ -14,7 +13,7 @@ void UIDrawable::click_unregister()
|
||||||
|
|
||||||
void UIDrawable::render()
|
void UIDrawable::render()
|
||||||
{
|
{
|
||||||
render(sf::Vector2f(), Resources::game->getWindow());
|
render(sf::Vector2f());
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIDrawable::get_click(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_click(PyObject* self, void* closure) {
|
||||||
|
|
|
@ -28,8 +28,7 @@ class UIDrawable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void render();
|
void render();
|
||||||
//virtual void render(sf::Vector2f) = 0;
|
virtual void render(sf::Vector2f) = 0;
|
||||||
virtual void render(sf::Vector2f, sf::RenderTarget&) = 0;
|
|
||||||
virtual PyObjectsEnum derived_type() = 0;
|
virtual PyObjectsEnum derived_type() = 0;
|
||||||
|
|
||||||
// Mouse input handling - callable object, methods to find event's destination
|
// Mouse input handling - callable object, methods to find event's destination
|
||||||
|
@ -57,9 +56,59 @@ typedef struct {
|
||||||
} PyUICollectionIterObject;
|
} PyUICollectionIterObject;
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
// DEPRECATED: RET_PY_INSTANCE macro has been replaced with template functions in PyObjectUtils.h
|
//PyObject* py_instance(std::shared_ptr<UIDrawable> source);
|
||||||
// The macro was difficult to debug and used static type references that could cause initialization order issues.
|
// This function segfaults on tp_alloc for an unknown reason, but works inline with mcrfpydef:: methods.
|
||||||
// Use PyObjectUtils::convertDrawableToPython() or PyObjectUtils::createPyObject<T>() instead.
|
|
||||||
|
#define RET_PY_INSTANCE(target) { \
|
||||||
|
switch (target->derived_type()) \
|
||||||
|
{ \
|
||||||
|
case PyObjectsEnum::UIFRAME: \
|
||||||
|
{ \
|
||||||
|
PyUIFrameObject* o = (PyUIFrameObject*)((&PyUIFrameType)->tp_alloc(&PyUIFrameType, 0)); \
|
||||||
|
if (o) \
|
||||||
|
{ \
|
||||||
|
auto p = std::static_pointer_cast<UIFrame>(target); \
|
||||||
|
o->data = p; \
|
||||||
|
auto utarget = o->data; \
|
||||||
|
} \
|
||||||
|
return (PyObject*)o; \
|
||||||
|
} \
|
||||||
|
case PyObjectsEnum::UICAPTION: \
|
||||||
|
{ \
|
||||||
|
PyUICaptionObject* o = (PyUICaptionObject*)((&PyUICaptionType)->tp_alloc(&PyUICaptionType, 0)); \
|
||||||
|
if (o) \
|
||||||
|
{ \
|
||||||
|
auto p = std::static_pointer_cast<UICaption>(target); \
|
||||||
|
o->data = p; \
|
||||||
|
auto utarget = o->data; \
|
||||||
|
} \
|
||||||
|
return (PyObject*)o; \
|
||||||
|
} \
|
||||||
|
case PyObjectsEnum::UISPRITE: \
|
||||||
|
{ \
|
||||||
|
PyUISpriteObject* o = (PyUISpriteObject*)((&PyUISpriteType)->tp_alloc(&PyUISpriteType, 0)); \
|
||||||
|
if (o) \
|
||||||
|
{ \
|
||||||
|
auto p = std::static_pointer_cast<UISprite>(target); \
|
||||||
|
o->data = p; \
|
||||||
|
auto utarget = o->data; \
|
||||||
|
} \
|
||||||
|
return (PyObject*)o; \
|
||||||
|
} \
|
||||||
|
case PyObjectsEnum::UIGRID: \
|
||||||
|
{ \
|
||||||
|
PyUIGridObject* o = (PyUIGridObject*)((&PyUIGridType)->tp_alloc(&PyUIGridType, 0)); \
|
||||||
|
if (o) \
|
||||||
|
{ \
|
||||||
|
auto p = std::static_pointer_cast<UIGrid>(target); \
|
||||||
|
o->data = p; \
|
||||||
|
auto utarget = o->data; \
|
||||||
|
} \
|
||||||
|
return (PyObject*)o; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
// end macro definition
|
||||||
|
|
||||||
//TODO: add this method to class scope; move implementation to .cpp file
|
//TODO: add this method to class scope; move implementation to .cpp file
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "UIEntity.h"
|
#include "UIEntity.h"
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyObjectUtils.h"
|
|
||||||
|
|
||||||
UIEntity::UIEntity() {} // this will not work lol. TODO remove default constructor by finding the shared pointer inits that use it
|
UIEntity::UIEntity() {} // this will not work lol. TODO remove default constructor by finding the shared pointer inits that use it
|
||||||
|
|
||||||
|
@ -35,30 +34,18 @@ PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||||
//static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr };
|
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr };
|
||||||
//float x = 0.0f, y = 0.0f, scale = 1.0f;
|
float x = 0.0f, y = 0.0f, scale = 1.0f;
|
||||||
static const char* keywords[] = { "pos", "texture", "sprite_index", "grid", nullptr };
|
|
||||||
PyObject* pos;
|
|
||||||
float scale = 1.0f;
|
|
||||||
int sprite_index = -1;
|
int sprite_index = -1;
|
||||||
PyObject* texture = NULL;
|
PyObject* texture = NULL;
|
||||||
PyObject* grid = NULL;
|
PyObject* grid = NULL;
|
||||||
|
|
||||||
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
|
||||||
// const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
|
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOi|O",
|
|
||||||
const_cast<char**>(keywords), &pos, &texture, &sprite_index, &grid))
|
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyVectorObject* pos_result = PyVector::from_arg(pos);
|
|
||||||
if (!pos_result)
|
|
||||||
{
|
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check types for texture
|
// check types for texture
|
||||||
//
|
//
|
||||||
// Set Texture
|
// Set Texture
|
||||||
|
@ -88,7 +75,7 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
|
||||||
// TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers
|
// TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers
|
||||||
self->data->sprite = UISprite(pytexture->data, sprite_index, sf::Vector2f(0,0), 1.0);
|
self->data->sprite = UISprite(pytexture->data, sprite_index, sf::Vector2f(0,0), 1.0);
|
||||||
self->data->position = pos_result->data;
|
self->data->position = sf::Vector2f(x, y);
|
||||||
if (grid != NULL) {
|
if (grid != NULL) {
|
||||||
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
|
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
|
||||||
self->data->grid = pygrid->data;
|
self->data->grid = pygrid->data;
|
||||||
|
@ -108,10 +95,6 @@ PyObject* sfVector2f_to_PyObject(sf::Vector2f vector) {
|
||||||
return Py_BuildValue("(ff)", vector.x, vector.y);
|
return Py_BuildValue("(ff)", vector.x, vector.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* sfVector2i_to_PyObject(sf::Vector2i vector) {
|
|
||||||
return Py_BuildValue("(ii)", vector.x, vector.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) {
|
sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) {
|
||||||
float x, y;
|
float x, y;
|
||||||
if (!PyArg_ParseTuple(obj, "ff", &x, &y)) {
|
if (!PyArg_ParseTuple(obj, "ff", &x, &y)) {
|
||||||
|
@ -120,19 +103,9 @@ sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) {
|
||||||
return sf::Vector2f(x, y);
|
return sf::Vector2f(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
sf::Vector2i PyObject_to_sfVector2i(PyObject* obj) {
|
|
||||||
int x, y;
|
|
||||||
if (!PyArg_ParseTuple(obj, "ii", &x, &y)) {
|
|
||||||
return sf::Vector2i(); // TODO / reconsider this default: Return default vector on parse error
|
|
||||||
}
|
|
||||||
return sf::Vector2i(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - deprecate / remove this helper
|
// TODO - deprecate / remove this helper
|
||||||
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
|
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
|
||||||
// This function is incomplete - it creates an empty object without setting state data
|
return PyObject_New(PyObject, (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState"));
|
||||||
// Should use PyObjectUtils::createGridPointState() instead
|
|
||||||
return PyObjectUtils::createPyObjectGeneric("GridPointState");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec) {
|
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec) {
|
||||||
|
@ -152,19 +125,11 @@ PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>&
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) {
|
PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) {
|
||||||
if (reinterpret_cast<long>(closure) == 0) {
|
|
||||||
return sfVector2f_to_PyObject(self->data->position);
|
return sfVector2f_to_PyObject(self->data->position);
|
||||||
} else {
|
|
||||||
return sfVector2i_to_PyObject(self->data->collision_pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIEntity::set_position(PyUIEntityObject* self, PyObject* value, void* closure) {
|
int UIEntity::set_position(PyUIEntityObject* self, PyObject* value, void* closure) {
|
||||||
if (reinterpret_cast<long>(closure) == 0) {
|
|
||||||
self->data->position = PyObject_to_sfVector2f(value);
|
self->data->position = PyObject_to_sfVector2f(value);
|
||||||
} else {
|
|
||||||
self->data->collision_pos = PyObject_to_sfVector2i(value);
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,8 +158,7 @@ PyMethodDef UIEntity::methods[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
PyGetSetDef UIEntity::getsetters[] = {
|
PyGetSetDef UIEntity::getsetters[] = {
|
||||||
{"draw_pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (graphically)", (void*)0},
|
{"position", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position", NULL},
|
||||||
{"pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (integer grid coordinates)", (void*)1},
|
|
||||||
{"gridstate", (getter)UIEntity::get_gridstate, NULL, "Grid point states for the entity", NULL},
|
{"gridstate", (getter)UIEntity::get_gridstate, NULL, "Grid point states for the entity", NULL},
|
||||||
{"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite number (index) on the texture on the display", NULL},
|
{"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite number (index) on the texture on the display", NULL},
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
|
|
|
@ -40,8 +40,7 @@ public:
|
||||||
std::vector<UIGridPointState> gridstate;
|
std::vector<UIGridPointState> gridstate;
|
||||||
UISprite sprite;
|
UISprite sprite;
|
||||||
sf::Vector2f position; //(x,y) in grid coordinates; float for animation
|
sf::Vector2f position; //(x,y) in grid coordinates; float for animation
|
||||||
sf::Vector2i collision_pos; //(x, y) in grid coordinates: int for collision
|
void render(sf::Vector2f); //override final;
|
||||||
//void render(sf::Vector2f); //override final;
|
|
||||||
|
|
||||||
UIEntity();
|
UIEntity();
|
||||||
UIEntity(UIGrid&);
|
UIEntity(UIGrid&);
|
||||||
|
@ -61,12 +60,12 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUIEntityType = {
|
static PyTypeObject PyUIEntityType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.Entity",
|
.tp_name = "mcrfpy.Entity",
|
||||||
.tp_basicsize = sizeof(PyUIEntityObject),
|
.tp_basicsize = sizeof(PyUIEntityObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
.tp_repr = (reprfunc)UIEntity::repr,
|
.tp_repr = (reprfunc)UIEntity::repr,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = "UIEntity objects",
|
.tp_doc = "UIEntity objects",
|
||||||
.tp_methods = UIEntity::methods,
|
.tp_methods = UIEntity::methods,
|
||||||
.tp_getset = UIEntity::getsetters,
|
.tp_getset = UIEntity::getsetters,
|
||||||
|
|
|
@ -44,15 +44,14 @@ PyObjectsEnum UIFrame::derived_type()
|
||||||
return PyObjectsEnum::UIFRAME;
|
return PyObjectsEnum::UIFRAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target)
|
void UIFrame::render(sf::Vector2f offset)
|
||||||
{
|
{
|
||||||
box.move(offset);
|
box.move(offset);
|
||||||
//Resources::game->getWindow().draw(box);
|
Resources::game->getWindow().draw(box);
|
||||||
target.draw(box);
|
|
||||||
box.move(-offset);
|
box.move(-offset);
|
||||||
|
|
||||||
for (auto drawable : *children) {
|
for (auto drawable : *children) {
|
||||||
drawable->render(offset + box.getPosition(), target);
|
drawable->render(offset + box.getPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ public:
|
||||||
sf::RectangleShape box;
|
sf::RectangleShape box;
|
||||||
float outline;
|
float outline;
|
||||||
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> children;
|
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> children;
|
||||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
void render(sf::Vector2f) override final;
|
||||||
void move(sf::Vector2f);
|
void move(sf::Vector2f);
|
||||||
PyObjectsEnum derived_type() override final;
|
PyObjectsEnum derived_type() override final;
|
||||||
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
||||||
|
@ -46,7 +46,7 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUIFrameType = {
|
static PyTypeObject PyUIFrameType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.Frame",
|
.tp_name = "mcrfpy.Frame",
|
||||||
.tp_basicsize = sizeof(PyUIFrameObject),
|
.tp_basicsize = sizeof(PyUIFrameObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -32,9 +32,9 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _x
|
||||||
void UIGrid::update() {}
|
void UIGrid::update() {}
|
||||||
|
|
||||||
|
|
||||||
void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
void UIGrid::render(sf::Vector2f)
|
||||||
{
|
{
|
||||||
output.setPosition(box.getPosition() + offset); // output sprite can move; update position when drawing
|
output.setPosition(box.getPosition()); // output sprite can move; update position when drawing
|
||||||
// output size can change; update size when drawing
|
// output size can change; update size when drawing
|
||||||
output.setTextureRect(
|
output.setTextureRect(
|
||||||
sf::IntRect(0, 0,
|
sf::IntRect(0, 0,
|
||||||
|
@ -172,8 +172,7 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
||||||
|
|
||||||
// render to window
|
// render to window
|
||||||
renderTexture.display();
|
renderTexture.display();
|
||||||
//Resources::game->getWindow().draw(output);
|
Resources::game->getWindow().draw(output);
|
||||||
target.draw(output);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,28 +204,12 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
|
||||||
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
int grid_x, grid_y;
|
int grid_x, grid_y;
|
||||||
PyObject* textureObj;
|
PyObject* textureObj;
|
||||||
//float box_x, box_y, box_w, box_h;
|
float box_x, box_y, box_w, box_h;
|
||||||
PyObject* pos, *size;
|
|
||||||
|
|
||||||
//if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) {
|
if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) {
|
||||||
if (!PyArg_ParseTuple(args, "iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
|
|
||||||
return -1; // If parsing fails, return an error
|
return -1; // If parsing fails, return an error
|
||||||
}
|
}
|
||||||
|
|
||||||
PyVectorObject* pos_result = PyVector::from_arg(pos);
|
|
||||||
if (!pos_result)
|
|
||||||
{
|
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyVectorObject* size_result = PyVector::from_arg(size);
|
|
||||||
if (!size_result)
|
|
||||||
{
|
|
||||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert PyObject texture to IndexTexture*
|
// Convert PyObject texture to IndexTexture*
|
||||||
// This requires the texture object to have been initialized similar to UISprite's texture handling
|
// This requires the texture object to have been initialized similar to UISprite's texture handling
|
||||||
|
|
||||||
|
@ -241,9 +224,8 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
|
||||||
// Initialize UIGrid
|
// Initialize UIGrid
|
||||||
//self->data = new UIGrid(grid_x, grid_y, texture, sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
|
//self->data = new UIGrid(grid_x, grid_y, texture, sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
|
||||||
//self->data = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data,
|
self->data = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data,
|
||||||
// sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
|
sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
|
||||||
self->data = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data, pos_result->data, size_result->data);
|
|
||||||
return 0; // Success
|
return 0; // Success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,6 +423,22 @@ PyObject* UIGrid::get_children(PyUIGridObject* self, void* closure)
|
||||||
|
|
||||||
PyObject* UIGrid::repr(PyUIGridObject* self)
|
PyObject* UIGrid::repr(PyUIGridObject* self)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// if (member_ptr == 0) // x
|
||||||
|
// self->data->box.setPosition(val, self->data->box.getPosition().y);
|
||||||
|
// else if (member_ptr == 1) // y
|
||||||
|
// self->data->box.setPosition(self->data->box.getPosition().x, val);
|
||||||
|
// else if (member_ptr == 2) // w
|
||||||
|
// self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y));
|
||||||
|
// else if (member_ptr == 3) // h
|
||||||
|
// self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val));
|
||||||
|
// else if (member_ptr == 4) // center_x
|
||||||
|
// self->data->center_x = val;
|
||||||
|
// else if (member_ptr == 5) // center_y
|
||||||
|
// self->data->center_y = val;
|
||||||
|
// else if (member_ptr == 6) // zoom
|
||||||
|
// self->data->zoom = val;
|
||||||
|
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
if (!self->data) ss << "<Grid (invalid internal object)>";
|
if (!self->data) ss << "<Grid (invalid internal object)>";
|
||||||
else {
|
else {
|
||||||
|
@ -487,17 +485,14 @@ PyObject* UIEntityCollectionIter::next(PyUIEntityCollectionIterObject* self)
|
||||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
// Advance list iterator since Entities are stored in a list, not a vector
|
// Advance list iterator since Entities are not stored in a vector (if this code even worked)
|
||||||
auto l_begin = (*vec).begin();
|
// vectors only: //auto target = (*vec)[self->index-1];
|
||||||
std::advance(l_begin, self->index-1);
|
//auto l_front = (*vec).begin();
|
||||||
auto target = *l_begin;
|
//std::advance(l_front, self->index-1);
|
||||||
|
|
||||||
// Create and return a Python Entity object
|
// TODO build PyObject* of the correct UIDrawable subclass to return
|
||||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
//return py_instance(target);
|
||||||
auto o = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
return NULL;
|
||||||
auto p = std::static_pointer_cast<UIEntity>(target);
|
|
||||||
o->data = p;
|
|
||||||
return (PyObject*)o;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIEntityCollectionIter::repr(PyUIEntityCollectionIterObject* self)
|
PyObject* UIEntityCollectionIter::repr(PyUIEntityCollectionIterObject* self)
|
||||||
|
@ -628,18 +623,11 @@ int UIEntityCollection::init(PyUIEntityCollectionObject* self, PyObject* args, P
|
||||||
|
|
||||||
PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
|
PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
|
||||||
{
|
{
|
||||||
// Get the iterator type from the module to ensure we have the registered version
|
//PyUIEntityCollectionIterObject* iterObj;
|
||||||
PyTypeObject* iterType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UIEntityCollectionIter");
|
//iterObj = (PyUIEntityCollectionIterObject*)PyUIEntityCollectionIterType.tp_alloc(&PyUIEntityCollectionIterType, 0);
|
||||||
if (!iterType) {
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "EntityCollectionIter");
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Could not find UIEntityCollectionIter type in module");
|
auto iterObj = (PyUIEntityCollectionIterObject*)type->tp_alloc(type, 0);
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate new iterator instance
|
|
||||||
PyUIEntityCollectionIterObject* iterObj = (PyUIEntityCollectionIterObject*)iterType->tp_alloc(iterType, 0);
|
|
||||||
|
|
||||||
if (iterObj == NULL) {
|
if (iterObj == NULL) {
|
||||||
Py_DECREF(iterType);
|
|
||||||
return NULL; // Failed to allocate memory for the iterator object
|
return NULL; // Failed to allocate memory for the iterator object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -647,6 +635,5 @@ PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
|
||||||
iterObj->index = 0;
|
iterObj->index = 0;
|
||||||
iterObj->start_size = self->data->size();
|
iterObj->start_size = self->data->size();
|
||||||
|
|
||||||
Py_DECREF(iterType);
|
|
||||||
return (PyObject*)iterObj;
|
return (PyObject*)iterObj;
|
||||||
}
|
}
|
||||||
|
|
12
src/UIGrid.h
12
src/UIGrid.h
|
@ -26,7 +26,7 @@ public:
|
||||||
//UIGrid(int, int, IndexTexture*, float, float, float, float);
|
//UIGrid(int, int, IndexTexture*, float, float, float, float);
|
||||||
UIGrid(int, int, std::shared_ptr<PyTexture>, sf::Vector2f, sf::Vector2f);
|
UIGrid(int, int, std::shared_ptr<PyTexture>, sf::Vector2f, sf::Vector2f);
|
||||||
void update();
|
void update();
|
||||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
void render(sf::Vector2f) override final;
|
||||||
UIGridPoint& at(int, int);
|
UIGridPoint& at(int, int);
|
||||||
PyObjectsEnum derived_type() override final;
|
PyObjectsEnum derived_type() override final;
|
||||||
//void setSprite(int);
|
//void setSprite(int);
|
||||||
|
@ -99,7 +99,7 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUIGridType = {
|
static PyTypeObject PyUIGridType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.Grid",
|
.tp_name = "mcrfpy.Grid",
|
||||||
.tp_basicsize = sizeof(PyUIGridObject),
|
.tp_basicsize = sizeof(PyUIGridObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
@ -130,8 +130,8 @@ namespace mcrfpydef {
|
||||||
};
|
};
|
||||||
|
|
||||||
static PyTypeObject PyUIEntityCollectionIterType = {
|
static PyTypeObject PyUIEntityCollectionIterType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.UIEntityCollectionIter",
|
.tp_name = "mcrfpy.UICollectionIter",
|
||||||
.tp_basicsize = sizeof(PyUIEntityCollectionIterObject),
|
.tp_basicsize = sizeof(PyUIEntityCollectionIterObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
.tp_dealloc = (destructor)[](PyObject* self)
|
.tp_dealloc = (destructor)[](PyObject* self)
|
||||||
|
@ -143,11 +143,9 @@ namespace mcrfpydef {
|
||||||
.tp_repr = (reprfunc)UIEntityCollectionIter::repr,
|
.tp_repr = (reprfunc)UIEntityCollectionIter::repr,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Iterator for a collection of UI objects"),
|
.tp_doc = PyDoc_STR("Iterator for a collection of UI objects"),
|
||||||
.tp_iter = PyObject_SelfIter,
|
|
||||||
.tp_iternext = (iternextfunc)UIEntityCollectionIter::next,
|
.tp_iternext = (iternextfunc)UIEntityCollectionIter::next,
|
||||||
//.tp_getset = UIEntityCollection::getset,
|
//.tp_getset = UIEntityCollection::getset,
|
||||||
.tp_init = (initproc)UIEntityCollectionIter::init, // just raise an exception
|
.tp_init = (initproc)UIEntityCollectionIter::init, // just raise an exception
|
||||||
.tp_alloc = PyType_GenericAlloc,
|
|
||||||
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
|
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
|
||||||
|
@ -156,7 +154,7 @@ namespace mcrfpydef {
|
||||||
};
|
};
|
||||||
|
|
||||||
static PyTypeObject PyUIEntityCollectionType = {
|
static PyTypeObject PyUIEntityCollectionType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_/HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.EntityCollection",
|
.tp_name = "mcrfpy.EntityCollection",
|
||||||
.tp_basicsize = sizeof(PyUIEntityCollectionObject),
|
.tp_basicsize = sizeof(PyUIEntityCollectionObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -66,7 +66,7 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUIGridPointType = {
|
static PyTypeObject PyUIGridPointType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.GridPoint",
|
.tp_name = "mcrfpy.GridPoint",
|
||||||
.tp_basicsize = sizeof(PyUIGridPointObject),
|
.tp_basicsize = sizeof(PyUIGridPointObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
@ -79,7 +79,7 @@ namespace mcrfpydef {
|
||||||
};
|
};
|
||||||
|
|
||||||
static PyTypeObject PyUIGridPointStateType = {
|
static PyTypeObject PyUIGridPointStateType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.GridPointState",
|
.tp_name = "mcrfpy.GridPointState",
|
||||||
.tp_basicsize = sizeof(PyUIGridPointStateObject),
|
.tp_basicsize = sizeof(PyUIGridPointStateObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -18,16 +18,14 @@ UISprite::UISprite(std::shared_ptr<PyTexture> _ptex, int _sprite_index, sf::Vect
|
||||||
sprite = ptex->sprite(sprite_index, _pos, sf::Vector2f(_scale, _scale));
|
sprite = ptex->sprite(sprite_index, _pos, sf::Vector2f(_scale, _scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
void UISprite::render(sf::Vector2f offset)
|
void UISprite::render(sf::Vector2f offset)
|
||||||
{
|
{
|
||||||
sprite.move(offset);
|
sprite.move(offset);
|
||||||
Resources::game->getWindow().draw(sprite);
|
Resources::game->getWindow().draw(sprite);
|
||||||
sprite.move(-offset);
|
sprite.move(-offset);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
void UISprite::render(sf::Vector2f offset, sf::RenderTarget& target)
|
void UISprite::render(sf::Vector2f offset, sf::RenderTexture& target)
|
||||||
{
|
{
|
||||||
sprite.move(offset);
|
sprite.move(offset);
|
||||||
target.draw(sprite);
|
target.draw(sprite);
|
||||||
|
|
|
@ -25,10 +25,10 @@ public:
|
||||||
UISprite();
|
UISprite();
|
||||||
UISprite(std::shared_ptr<PyTexture>, int, sf::Vector2f, float);
|
UISprite(std::shared_ptr<PyTexture>, int, sf::Vector2f, float);
|
||||||
void update();
|
void update();
|
||||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
void render(sf::Vector2f) override final;
|
||||||
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
||||||
|
|
||||||
//void render(sf::Vector2f, sf::RenderTexture&);
|
void render(sf::Vector2f, sf::RenderTexture&);
|
||||||
|
|
||||||
void setPosition(sf::Vector2f);
|
void setPosition(sf::Vector2f);
|
||||||
sf::Vector2f getPosition();
|
sf::Vector2f getPosition();
|
||||||
|
@ -57,7 +57,7 @@ public:
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUISpriteType = {
|
static PyTypeObject PyUISpriteType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
//PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "mcrfpy.Sprite",
|
.tp_name = "mcrfpy.Sprite",
|
||||||
.tp_basicsize = sizeof(PyUISpriteObject),
|
.tp_basicsize = sizeof(PyUISpriteObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
|
|
|
@ -1,539 +0,0 @@
|
||||||
import mcrfpy
|
|
||||||
import random
|
|
||||||
from cos_itemdata import itemdata
|
|
||||||
#t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
||||||
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
|
||||||
#def iterable_entities(grid):
|
|
||||||
# """Workaround for UIEntityCollection bug; see issue #72"""
|
|
||||||
# entities = []
|
|
||||||
# for i in range(len(grid.entities)):
|
|
||||||
# entities.append(grid.entities[i])
|
|
||||||
# return entities
|
|
||||||
|
|
||||||
class COSEntity(): #mcrfpy.Entity): # Fake mcrfpy.Entity integration; engine bugs workarounds
|
|
||||||
def __init__(self, g:mcrfpy.Grid, x=0, y=0, sprite_num=86, *, game):
|
|
||||||
#self.e = mcrfpy.Entity((x, y), t, sprite_num)
|
|
||||||
#super().__init__((x, y), t, sprite_num)
|
|
||||||
self._entity = mcrfpy.Entity((x, y), t, sprite_num)
|
|
||||||
#grid.entities.append(self.e)
|
|
||||||
self.grid = g
|
|
||||||
#g.entities.append(self._entity)
|
|
||||||
self.game = game
|
|
||||||
self.game.add_entity(self)
|
|
||||||
|
|
||||||
## Wrapping mcfrpy.Entity properties to emulate derived class... see issue #76
|
|
||||||
@property
|
|
||||||
def draw_pos(self):
|
|
||||||
return self._entity.draw_pos
|
|
||||||
|
|
||||||
@draw_pos.setter
|
|
||||||
def draw_pos(self, value):
|
|
||||||
self._entity.draw_pos = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sprite_number(self):
|
|
||||||
return self._entity.sprite_number
|
|
||||||
|
|
||||||
@sprite_number.setter
|
|
||||||
def sprite_number(self, value):
|
|
||||||
self._entity.sprite_number = value
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<{self.__class__.__name__} ({self.draw_pos})>"
|
|
||||||
|
|
||||||
def die(self):
|
|
||||||
# ugly workaround! grid.entities isn't really iterable (segfaults)
|
|
||||||
for i in range(len(self.grid.entities)):
|
|
||||||
e = self.grid.entities[i]
|
|
||||||
if e == self._entity:
|
|
||||||
#if e == self:
|
|
||||||
self.grid.entities.remove(i)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print(f"!!! {self!r} wasn't removed from grid on call to die()")
|
|
||||||
|
|
||||||
def bump(self, other, dx, dy, test=False):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def do_move(self, tx, ty):
|
|
||||||
"""Base class method to move this entity
|
|
||||||
Assumes try_move succeeded, for everyone!
|
|
||||||
from: self._entity.draw_pos
|
|
||||||
to: (tx, ty)
|
|
||||||
calls ev_exit for every entity at (draw_pos)
|
|
||||||
calls ev_enter for every entity at (tx, ty)
|
|
||||||
"""
|
|
||||||
old_pos = self.draw_pos
|
|
||||||
self.draw_pos = (tx, ty)
|
|
||||||
for e in self.game.entities:
|
|
||||||
if e is self: continue
|
|
||||||
if e.draw_pos == old_pos: e.ev_exit(self)
|
|
||||||
for e in self.game.entities:
|
|
||||||
if e is self: continue
|
|
||||||
if e.draw_pos == (tx, ty): e.ev_enter(self)
|
|
||||||
|
|
||||||
def act(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def ev_enter(self, other):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def ev_exit(self, other):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def try_move(self, dx, dy, test=False):
|
|
||||||
x_max, y_max = self.grid.grid_size
|
|
||||||
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
|
|
||||||
#for e in iterable_entities(self.grid):
|
|
||||||
|
|
||||||
# sorting entities to test against the boulder instead of the button when they overlap.
|
|
||||||
for e in sorted(self.game.entities, key = lambda i: i.draw_order, reverse = True):
|
|
||||||
if e.draw_pos == (tx, ty):
|
|
||||||
#print(f"bumping {e}")
|
|
||||||
return e.bump(self, dx, dy)
|
|
||||||
|
|
||||||
if tx < 0 or tx >= x_max:
|
|
||||||
return False
|
|
||||||
if ty < 0 or ty >= y_max:
|
|
||||||
return False
|
|
||||||
if self.grid.at((tx, ty)).walkable == True:
|
|
||||||
if not test:
|
|
||||||
#self.draw_pos = (tx, ty)
|
|
||||||
self.do_move(tx, ty)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
#print("Bonk")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _relative_move(self, dx, dy):
|
|
||||||
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
|
|
||||||
#self.draw_pos = (tx, ty)
|
|
||||||
self.do_move(tx, ty)
|
|
||||||
|
|
||||||
class Equippable:
|
|
||||||
def __init__(self, hands = 0, hp_healing = 0, damage = 0, defense = 0, zap_damage = 1, zap_cooldown = 10, sprite = 129, boost=None, text="", text_color=(255, 255, 255), value=0):
|
|
||||||
self.hands = hands
|
|
||||||
self.hp_healing = hp_healing
|
|
||||||
self.damage = damage
|
|
||||||
self.defense = defense
|
|
||||||
self.zap_damage = zap_damage
|
|
||||||
self.zap_cooldown = zap_cooldown
|
|
||||||
self.zap_cooldown_remaining = 0
|
|
||||||
self.sprite = sprite
|
|
||||||
self.quality = 0
|
|
||||||
self.text = text
|
|
||||||
self.text_color = text_color
|
|
||||||
self.boost = boost
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def tick(self):
|
|
||||||
if self.zap_cooldown_remaining:
|
|
||||||
self.zap_cooldown_remaining -= 1
|
|
||||||
if self.zap_cooldown_remaining < 0: self.zap_cooldown_remaining = 0
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
cooldown_str = f'({self.zap_cooldown_remaining} rounds until ready)'
|
|
||||||
return f"<Equippable text={self.text}, hands={self.hands}, hp_healing={self.hp_healing}, damage={self.damage}, defense={self.defense}, zap_damage={self.zap_damage}, zap_cooldown={self.zap_cooldown}{cooldown_str if self.zap_cooldown_remaining else ''}, sprite={self.sprite}>"
|
|
||||||
|
|
||||||
def classify(self):
|
|
||||||
categories = []
|
|
||||||
if self.hands==0:
|
|
||||||
categories.append("consumable")
|
|
||||||
elif self.damage > 0:
|
|
||||||
categories.append(f"{self.hands}-handed weapon")
|
|
||||||
elif self.defense > 0:
|
|
||||||
categories.append(f"defense")
|
|
||||||
elif self.zap_damage > 0:
|
|
||||||
categories.append("{self.hands}-handed magic weapon")
|
|
||||||
if len(categories) == 0:
|
|
||||||
return "unclassifiable"
|
|
||||||
elif len(categories) == 1:
|
|
||||||
return categories[0]
|
|
||||||
else:
|
|
||||||
return "Erratic: " + ', '.join(categories)
|
|
||||||
|
|
||||||
def consume(self, consumer):
|
|
||||||
if self.boost == "green_pot":
|
|
||||||
consumer.base_damage += self.value
|
|
||||||
elif self.boost == "blue_pot":
|
|
||||||
b = self.value
|
|
||||||
while b: #split bonus between damage and faster cooldown
|
|
||||||
bonus = random.choice(["damage", "cooldown", "range"])
|
|
||||||
if bonus == "damage":
|
|
||||||
consumer.base_zap_damage += 1
|
|
||||||
elif bonus == "cooldown":
|
|
||||||
consumer.base_zap_cooldown += 1
|
|
||||||
else:
|
|
||||||
consumer.base_zap_range += 1
|
|
||||||
b -= 1
|
|
||||||
elif self.boost == "grey_pot":
|
|
||||||
consumer.base_defense += self.value
|
|
||||||
elif self.boost == "sm_grey_pot":
|
|
||||||
consumer.luck += self.value
|
|
||||||
elif self.hp_healing:
|
|
||||||
consumer.hp += self.hp_healing
|
|
||||||
if consumer.hp > consumer.max_hp: consumer.hp = consumer.max_hp
|
|
||||||
|
|
||||||
def do_zap(self, caster, entities):
|
|
||||||
if self.zap_damage == 0:
|
|
||||||
print("This item can't zap.")
|
|
||||||
return False
|
|
||||||
if self.zap_cooldown_remaining != 0:
|
|
||||||
print("zap is cooling down.")
|
|
||||||
return False
|
|
||||||
fx, fy = caster.draw_pos
|
|
||||||
x, y = int(fx), int (fy)
|
|
||||||
dist = lambda tx, ty: abs(int(tx) - x) + abs(int(ty) - y)
|
|
||||||
targets = []
|
|
||||||
for e in entities:
|
|
||||||
if type(e) != EnemyEntity: continue
|
|
||||||
if dist(*e.draw_pos) > caster.base_zap_range:
|
|
||||||
continue
|
|
||||||
if e.hp <= 0: continue
|
|
||||||
targets.append(e)
|
|
||||||
if not targets:
|
|
||||||
print("No targets found in range.")
|
|
||||||
return False
|
|
||||||
target = random.choice(targets)
|
|
||||||
print(f"Zap! {target}")
|
|
||||||
target.get_zapped(self.zap_damage)
|
|
||||||
self.zap_cooldown_remaining = self.zap_cooldown
|
|
||||||
return True
|
|
||||||
|
|
||||||
#def compare(self, other):
|
|
||||||
# my_class = self.classify()
|
|
||||||
# o_class = other.classify()
|
|
||||||
# if my_class == "unclassifiable" or o_class == "unclassifiable":
|
|
||||||
# return None
|
|
||||||
# if my_class == "consumable":
|
|
||||||
# return other.hp_healing - self.hp_healing
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerEntity(COSEntity):
|
|
||||||
def __init__(self, *, game):
|
|
||||||
#print(f"spawn at origin")
|
|
||||||
self.draw_order = 10
|
|
||||||
super().__init__(game.grid, 0, 0, sprite_num=84, game=game)
|
|
||||||
self.hp = 10
|
|
||||||
self.max_hp = 10
|
|
||||||
self.base_damage = 1
|
|
||||||
self.base_defense = 0
|
|
||||||
self.luck = 0
|
|
||||||
self.archetype = None
|
|
||||||
self.equipped = []
|
|
||||||
self.inventory = []
|
|
||||||
self.base_zap_damage = 0
|
|
||||||
self.base_zap_cooldown = 0
|
|
||||||
self.base_zap_range = 4
|
|
||||||
|
|
||||||
def tick(self):
|
|
||||||
for i in self.equipped:
|
|
||||||
i.tick()
|
|
||||||
|
|
||||||
def calc_damage(self):
|
|
||||||
dmg = self.base_damage
|
|
||||||
for i in self.equipped:
|
|
||||||
dmg += i.damage
|
|
||||||
return dmg
|
|
||||||
|
|
||||||
def calc_defense(self):
|
|
||||||
defense = self.base_defense
|
|
||||||
for i in self.equipped:
|
|
||||||
defense += i.defense
|
|
||||||
return defense
|
|
||||||
|
|
||||||
def do_zap(self):
|
|
||||||
for i in self.equipped:
|
|
||||||
if i.zap_damage and i.zap_cooldown_remaining == 0:
|
|
||||||
if i.do_zap(self, self.game.entities):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print("Couldn't zap")
|
|
||||||
|
|
||||||
def bump(self, other, dx, dy, test=False):
|
|
||||||
if type(other) == BoulderEntity:
|
|
||||||
print("Boulder hit w/ knockback!")
|
|
||||||
return self.game.pull_boulder_move((-dx, -dy), other)
|
|
||||||
#print(f"oof, ouch, {other} bumped the player - {other.base_damage} damage from {other}")
|
|
||||||
self.hp = max(self.hp - max(other.base_damage - self.calc_defense(), 0), 0)
|
|
||||||
|
|
||||||
def receive(self, equip):
|
|
||||||
print(equip)
|
|
||||||
if (equip.hands == 0):
|
|
||||||
if len([i for i in self.inventory if i is not None]) < 3:
|
|
||||||
if None in self.inventory:
|
|
||||||
self.inventory[self.inventory.index(None)] = equip
|
|
||||||
else:
|
|
||||||
self.inventory.append(equip)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
print("something something, consumable GUI")
|
|
||||||
elif (equip.hands == 1):
|
|
||||||
if len(self.equipped) < 2:
|
|
||||||
self.equipped.append(equip)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
print("Something something, 1h GUI")
|
|
||||||
else: # equip.hands == 2:
|
|
||||||
if len(self.equipped) == 0:
|
|
||||||
self.equipped.append(equip)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
print("Something something, 2h GUI")
|
|
||||||
|
|
||||||
def respawn(self, avoid=None):
|
|
||||||
# find spawn point
|
|
||||||
x_max, y_max = g.size
|
|
||||||
spawn_points = []
|
|
||||||
for x in range(x_max):
|
|
||||||
for y in range(y_max):
|
|
||||||
if g.at((x, y)).walkable:
|
|
||||||
spawn_points.append((x, y))
|
|
||||||
random.shuffle(spawn_points)
|
|
||||||
## TODO - find other entities to avoid spawning on top of
|
|
||||||
for spawn in spawn_points:
|
|
||||||
for e in avoid or []:
|
|
||||||
if e.draw_pos == spawn: break
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
self.draw_pos = spawn
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<PlayerEntity {self.draw_pos}, {self.grid}>"
|
|
||||||
|
|
||||||
|
|
||||||
class BoulderEntity(COSEntity):
|
|
||||||
def __init__(self, x, y, *, game):
|
|
||||||
self.draw_order = 8
|
|
||||||
super().__init__(game.grid, x, y, 66, game=game)
|
|
||||||
|
|
||||||
def bump(self, other, dx, dy, test=False):
|
|
||||||
if type(other) == BoulderEntity:
|
|
||||||
#print("Boulders can't push boulders")
|
|
||||||
return False
|
|
||||||
elif type(other) == EnemyEntity:
|
|
||||||
if not other.can_push: return False
|
|
||||||
#tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
|
|
||||||
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
|
|
||||||
# Is the boulder blocked the same direction as the bumper? If not, let's both move
|
|
||||||
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
|
|
||||||
if self.try_move(dx, dy, test=test):
|
|
||||||
if not test:
|
|
||||||
other.do_move(*old_pos)
|
|
||||||
#other.draw_pos = old_pos
|
|
||||||
return True
|
|
||||||
|
|
||||||
class ButtonEntity(COSEntity):
|
|
||||||
def __init__(self, x, y, exit_entity, *, game):
|
|
||||||
self.draw_order = 1
|
|
||||||
super().__init__(game.grid, x, y, 250, game=game)
|
|
||||||
self.exit = exit_entity
|
|
||||||
|
|
||||||
def ev_enter(self, other):
|
|
||||||
print("Button makes a satisfying click!")
|
|
||||||
self.exit.unlock()
|
|
||||||
|
|
||||||
def ev_exit(self, other):
|
|
||||||
print("Button makes a disappointing click.")
|
|
||||||
self.exit.lock()
|
|
||||||
|
|
||||||
def bump(self, other, dx, dy, test=False):
|
|
||||||
#if type(other) == BoulderEntity:
|
|
||||||
# self.exit.unlock()
|
|
||||||
# TODO: unlock, and then lock again, when player steps on/off
|
|
||||||
if not test:
|
|
||||||
pos = int(self.draw_pos[0]), int(self.draw_pos[1])
|
|
||||||
other.do_move(*pos)
|
|
||||||
return True
|
|
||||||
|
|
||||||
class ExitEntity(COSEntity):
|
|
||||||
def __init__(self, x, y, bx, by, *, game):
|
|
||||||
self.draw_order = 2
|
|
||||||
super().__init__(game.grid, x, y, 45, game=game)
|
|
||||||
self.my_button = ButtonEntity(bx, by, self, game=game)
|
|
||||||
self.unlocked = False
|
|
||||||
#global cos_entities
|
|
||||||
#cos_entities.append(self.my_button)
|
|
||||||
|
|
||||||
def unlock(self):
|
|
||||||
self.sprite_number = 21
|
|
||||||
self.unlocked = True
|
|
||||||
|
|
||||||
def lock(self):
|
|
||||||
self.sprite_number = 45
|
|
||||||
self.unlocked = False
|
|
||||||
|
|
||||||
def bump(self, other, dx, dy, test=False):
|
|
||||||
if type(other) == BoulderEntity:
|
|
||||||
return False
|
|
||||||
if self.unlocked:
|
|
||||||
if not test:
|
|
||||||
other._relative_move(dx, dy)
|
|
||||||
#TODO - player go down a level logic
|
|
||||||
if type(other) == PlayerEntity:
|
|
||||||
self.game.depth += 1
|
|
||||||
#print(f"welcome to level {self.game.depth}")
|
|
||||||
self.game.create_level(self.game.depth)
|
|
||||||
self.game.swap_level(self.game.level, self.game.spawn_point)
|
|
||||||
|
|
||||||
class EnemyEntity(COSEntity):
|
|
||||||
def __init__(self, x, y, hp=2, base_damage=1, base_defense=0, sprite=123, can_push=False, crushable=True, sight=8, move_cooldown=1, *, game):
|
|
||||||
self.draw_order = 7
|
|
||||||
super().__init__(game.grid, x, y, sprite, game=game)
|
|
||||||
self.hp = hp
|
|
||||||
self.base_damage = base_damage
|
|
||||||
self.base_defense = base_defense
|
|
||||||
self.base_sprite = sprite
|
|
||||||
self.can_push = can_push
|
|
||||||
self.crushable = crushable
|
|
||||||
self.sight = sight
|
|
||||||
self.move_cooldown = move_cooldown
|
|
||||||
self.moved_last = 0
|
|
||||||
|
|
||||||
def bump(self, other, dx, dy, test=False):
|
|
||||||
if self.hp == 0:
|
|
||||||
if not test:
|
|
||||||
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
|
|
||||||
other.do_move(*old_pos)
|
|
||||||
return True
|
|
||||||
if type(other) == PlayerEntity:
|
|
||||||
# TODO - get damage from player, take damage, decide to die or not
|
|
||||||
d = other.calc_damage()
|
|
||||||
self.hp -= d
|
|
||||||
self.hp = max(self.hp, 0)
|
|
||||||
if self.hp == 0:
|
|
||||||
self._entity.sprite_number = self.base_sprite + 246
|
|
||||||
self.draw_order = 1
|
|
||||||
print(f"Player hit for {d}. HP = {self.hp}")
|
|
||||||
#self.hp = 0
|
|
||||||
return False
|
|
||||||
elif type(other) == BoulderEntity:
|
|
||||||
if not self.crushable and self.hp > 0:
|
|
||||||
print("Uncrushable!")
|
|
||||||
return False
|
|
||||||
if self.hp > 0:
|
|
||||||
print("Ouch, my entire body!!")
|
|
||||||
self._entity.sprite_number = self.base_sprite + 246
|
|
||||||
self.hp = 0
|
|
||||||
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
|
|
||||||
if not test:
|
|
||||||
other.do_move(*old_pos)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def act(self):
|
|
||||||
if self.hp > 0:
|
|
||||||
# if player nearby: attack
|
|
||||||
x, y = self.draw_pos
|
|
||||||
px, py = self.game.player.draw_pos
|
|
||||||
for d in ((1, 0), (0, 1), (-1, 0), (1, 0)):
|
|
||||||
if int(x + d[0]) == int(px) and int(y + d[1]) == int(py):
|
|
||||||
self.try_move(*d)
|
|
||||||
return
|
|
||||||
|
|
||||||
# slow movement (doesn't affect ability to attack)
|
|
||||||
if self.moved_last > 0:
|
|
||||||
self.moved_last -= 1
|
|
||||||
#print(f"Deducting move cooldown, now {self.moved_last} / {self.move_cooldown}")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
#print(f"Restaring move cooldown - {self.move_cooldown}")
|
|
||||||
self.moved_last = self.move_cooldown
|
|
||||||
|
|
||||||
# if player is not nearby, wander
|
|
||||||
if abs(x - px) + abs(y - py) > self.sight:
|
|
||||||
d = random.choice(((1, 0), (0, 1), (-1, 0), (1, 0)))
|
|
||||||
self.try_move(*d)
|
|
||||||
|
|
||||||
# if can_push and player in a line: KICK
|
|
||||||
if self.can_push:
|
|
||||||
if int(x) == int(px):# vertical kick
|
|
||||||
self.try_move(0, 1 if y < py else -1)
|
|
||||||
elif int(y) == int(py):# horizontal kick
|
|
||||||
self.try_move(1 if x < px else -1, 0)
|
|
||||||
|
|
||||||
# else, nearby pursue
|
|
||||||
towards = []
|
|
||||||
dist = lambda dx, dy: abs(px - (x + dx)) + abs(py - (y + dy))
|
|
||||||
#current_dist = dist(0, 0)
|
|
||||||
#for d in ((1, 0), (0, 1), (-1, 0), (1, 0)):
|
|
||||||
# if dist(*d) <= current_dist + 0.75: towards.append(d)
|
|
||||||
#print(current_dist, towards)
|
|
||||||
if px >= x:
|
|
||||||
towards.append((1, 0))
|
|
||||||
if px <= x:
|
|
||||||
towards.append((-1, 0))
|
|
||||||
if py >= y:
|
|
||||||
towards.append((0, 1))
|
|
||||||
if py <= y:
|
|
||||||
towards.append((0, -1))
|
|
||||||
towards = [p for p in towards if self.game.grid.at((int(x + p[0]), int(y + p[1]))).walkable]
|
|
||||||
towards.sort(key = lambda p: dist(*p))
|
|
||||||
target_dir = towards[0]
|
|
||||||
self.try_move(*target_dir)
|
|
||||||
|
|
||||||
def get_zapped(self, d):
|
|
||||||
self.hp -= d
|
|
||||||
self.hp = max(self.hp, 0)
|
|
||||||
if self.hp == 0:
|
|
||||||
self._entity.sprite_number = self.base_sprite + 246
|
|
||||||
self.draw_order = 1
|
|
||||||
print(f"Player zapped for {d}. HP = {self.hp}")
|
|
||||||
|
|
||||||
|
|
||||||
class TreasureEntity(COSEntity):
|
|
||||||
def __init__(self, x, y, treasure_table=None, *, game):
|
|
||||||
self.draw_order = 6
|
|
||||||
super().__init__(game.grid, x, y, 89, game=game)
|
|
||||||
self.popped = False
|
|
||||||
self.treasure_table = treasure_table
|
|
||||||
|
|
||||||
def generate(self):
|
|
||||||
items = list(self.treasure_table.keys())
|
|
||||||
weights = [self.treasure_table[k] for k in items]
|
|
||||||
item = random.choices(items, weights=weights)[0]
|
|
||||||
bonus_stats_max = (self.game.depth + (self.game.player.luck*2)) * 0.66
|
|
||||||
bonus_stats = random.randint(0, int(bonus_stats_max))
|
|
||||||
bonus_colors = {1: (192, 255, 192), 2: (128, 128, 192), 3: (255, 192, 255),
|
|
||||||
4: (255, 192, 192), 5: (255, 0, 0)}
|
|
||||||
|
|
||||||
data = itemdata[item]
|
|
||||||
if item in ("sword", "sword2", "sword3", "axe", "axe2", "axe3"):
|
|
||||||
equip = Equippable(hands=data.handedness, sprite=data.sprite, damage=data.base_value+bonus_stats, text=data.base_name)
|
|
||||||
elif item in ("buckler", "shield"):
|
|
||||||
equip = Equippable(hands=data.handedness, sprite=data.sprite, defense=data.base_value+bonus_stats, text=data.base_name)
|
|
||||||
elif item in ("wand", "staff", "staff2"):
|
|
||||||
equip = Equippable(hands=data.handedness, sprite=data.sprite, zap_damage=data.base_value[0], zap_cooldown=data.base_value[1], text=data.base_name)
|
|
||||||
if bonus_stats:
|
|
||||||
b = bonus_stats
|
|
||||||
while b: # split bonus between damage and faster cooldown
|
|
||||||
if equip.zap_cooldown == 2 or random.random() > 0.66:
|
|
||||||
equip.zap_damage += 1
|
|
||||||
else:
|
|
||||||
equip.zap_cooldown -= 1
|
|
||||||
b -= 1
|
|
||||||
elif item == "red_pot":
|
|
||||||
equip = Equippable(hands=data.handedness, sprite=data.sprite, hp_healing=data.base_value+bonus_stats, text=data.base_name)
|
|
||||||
elif item in ("blue_pot", "green_pot", "grey_pot", "sm_grey_pot"):
|
|
||||||
print(f"Permanent stat boost ({item})")
|
|
||||||
equip = Equippable(hands=data.handedness, sprite=data.sprite, text=data.base_name, boost=item, value=data.base_value + bonus_stats)
|
|
||||||
else:
|
|
||||||
print(f"Unfound item: {item}")
|
|
||||||
equip = Equippable()
|
|
||||||
|
|
||||||
if bonus_stats:
|
|
||||||
equip.text = equip.text + f" (+{bonus_stats})"
|
|
||||||
equip.text_color = bonus_colors[bonus_stats if bonus_stats <=5 else 5]
|
|
||||||
return equip
|
|
||||||
|
|
||||||
def bump(self, other, dx, dy, test=False):
|
|
||||||
if type(other) != PlayerEntity:
|
|
||||||
return False
|
|
||||||
if self.popped:
|
|
||||||
print("It's already open.")
|
|
||||||
return True
|
|
||||||
print("Take me, I'm yours!")
|
|
||||||
self._entity.sprite_number = 91
|
|
||||||
self.popped = True
|
|
||||||
#print(self.treasure_table)
|
|
||||||
other.receive(self.generate())
|
|
||||||
return False
|
|
|
@ -1,62 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ItemData:
|
|
||||||
min_lv: int
|
|
||||||
max_lv: int
|
|
||||||
base_wt: float
|
|
||||||
sprite: int
|
|
||||||
base_value: int
|
|
||||||
base_name: str
|
|
||||||
affinity: str # player archetype that makes it more common
|
|
||||||
handedness: int
|
|
||||||
|
|
||||||
itemdata = {
|
|
||||||
"buckler": ItemData(min_lv = 1, max_lv = 10, base_wt = 0.25, sprite=101, base_value=1,
|
|
||||||
base_name="Buckler", affinity="knight", handedness=1),
|
|
||||||
|
|
||||||
"shield": ItemData(min_lv = 2, max_lv = 99, base_wt = 0.15, sprite=102, base_value=2,
|
|
||||||
base_name="Shield", affinity="knight", handedness=1),
|
|
||||||
|
|
||||||
"sword": ItemData(min_lv = 1, max_lv = 10, base_wt = 0.25, sprite=103, base_value=1,
|
|
||||||
base_name="Shortsword", affinity="knight", handedness=1),
|
|
||||||
|
|
||||||
"sword2": ItemData(min_lv = 2, max_lv = 16, base_wt = 0.15, sprite=104, base_value=2,
|
|
||||||
base_name="Longsword", affinity="knight", handedness=1),
|
|
||||||
|
|
||||||
"sword3": ItemData(min_lv = 5, max_lv = 99, base_wt = 0.08, sprite=105, base_value=5,
|
|
||||||
base_name="Claymore", affinity="knight", handedness=2),
|
|
||||||
|
|
||||||
"axe": ItemData(min_lv = 1, max_lv = 10, base_wt = 0.25, sprite=119, base_value=1,
|
|
||||||
base_name="Hatchet", affinity="viking", handedness=1),
|
|
||||||
|
|
||||||
"axe2": ItemData(min_lv = 2, max_lv = 16, base_wt = 0.15, sprite=120, base_value=4,
|
|
||||||
base_name="Broad Axe", affinity="viking", handedness=2),
|
|
||||||
|
|
||||||
"axe3": ItemData(min_lv = 5, max_lv = 99, base_wt = 0.08, sprite=121, base_value=6,
|
|
||||||
base_name="Bearded Axe", affinity="viking", handedness=2),
|
|
||||||
|
|
||||||
"wand": ItemData(min_lv = 1, max_lv = 10, base_wt = 0.25, sprite=132, base_value=(1, 10),
|
|
||||||
base_name="Wand", affinity="wizard", handedness=1),
|
|
||||||
|
|
||||||
"staff": ItemData(min_lv = 2, max_lv = 16, base_wt = 0.15, sprite=130, base_value=(2, 8),
|
|
||||||
base_name="Sceptre", affinity="wizard", handedness=2),
|
|
||||||
|
|
||||||
"staff2": ItemData(min_lv = 5, max_lv = 99, base_wt = 0.08, sprite=131, base_value=(3, 7),
|
|
||||||
base_name="Wizard's Staff", affinity="wizard", handedness=2),
|
|
||||||
|
|
||||||
"red_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.25, sprite=115, base_value=1,
|
|
||||||
base_name="Health Potion", affinity=None, handedness=0),
|
|
||||||
|
|
||||||
"blue_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.10, sprite=116, base_value=1,
|
|
||||||
base_name="Sorcery Potion", affinity="wizard", handedness=0),
|
|
||||||
|
|
||||||
"green_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.10, sprite=114, base_value=1,
|
|
||||||
base_name="Strength Potion", affinity="viking", handedness=0),
|
|
||||||
|
|
||||||
"grey_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.10, sprite=113, base_value=1,
|
|
||||||
base_name="Defense Potion", affinity="knight", handedness=0),
|
|
||||||
|
|
||||||
"sm_grey_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.05, sprite=125, base_value=1,
|
|
||||||
base_name="Luck Potion", affinity=None, handedness=0),
|
|
||||||
}
|
|
|
@ -1,292 +0,0 @@
|
||||||
import random
|
|
||||||
import mcrfpy
|
|
||||||
import cos_tiles as ct
|
|
||||||
|
|
||||||
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
|
||||||
|
|
||||||
def binary_space_partition(x, y, w, h):
|
|
||||||
d = random.choices(["vert", "horiz"], weights=[w/(w+h), h/(w+h)])[0]
|
|
||||||
split = random.randint(30, 70) / 100.0
|
|
||||||
if d == "vert":
|
|
||||||
coord = int(w * split)
|
|
||||||
return (x, y, coord, h), (x+coord, y, w-coord, h)
|
|
||||||
else: # horizontal
|
|
||||||
coord = int(h * split)
|
|
||||||
return (x, y, w, coord), (x, y+coord, w, h-coord)
|
|
||||||
|
|
||||||
room_area = lambda x, y, w, h: w * h
|
|
||||||
|
|
||||||
class BinaryRoomNode:
|
|
||||||
def __init__(self, xywh):
|
|
||||||
self.data = xywh
|
|
||||||
self.left = None
|
|
||||||
self.right = None
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<RoomNode {self.data}>"
|
|
||||||
|
|
||||||
def center(self):
|
|
||||||
x, y, w, h = self.data
|
|
||||||
return (x + w // 2, y + h // 2)
|
|
||||||
|
|
||||||
def split(self):
|
|
||||||
new_data = binary_space_partition(*self.data)
|
|
||||||
self.left = BinaryRoomNode(new_data[0])
|
|
||||||
self.right = BinaryRoomNode(new_data[1])
|
|
||||||
|
|
||||||
def walk(self):
|
|
||||||
if self.left and self.right:
|
|
||||||
return self.left.walk() + self.right.walk()
|
|
||||||
return [self]
|
|
||||||
|
|
||||||
def contains(self, pt):
|
|
||||||
x, y, w, h = self.data
|
|
||||||
tx, ty = pt
|
|
||||||
return x <= tx <= x + w and y <= ty <= y + h
|
|
||||||
|
|
||||||
class RoomGraph:
|
|
||||||
def __init__(self, xywh):
|
|
||||||
self.root = BinaryRoomNode(xywh)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<RoomGraph, root={self.root}, {len(self.walk())} rooms>"
|
|
||||||
|
|
||||||
def walk(self):
|
|
||||||
w = self.root.walk() if self.root else []
|
|
||||||
#print(w)
|
|
||||||
return w
|
|
||||||
|
|
||||||
def room_coord(room, margin=0):
|
|
||||||
x, y, w, h = room.data
|
|
||||||
#print(x,y,w,h, f'{margin=}', end=';')
|
|
||||||
w -= 1
|
|
||||||
h -= 1
|
|
||||||
margin += 1
|
|
||||||
x += margin
|
|
||||||
y += margin
|
|
||||||
w -= margin
|
|
||||||
h -= margin
|
|
||||||
if w < 0: w = 0
|
|
||||||
if h < 0: h = 0
|
|
||||||
#print(x,y,w,h, end=' -> ')
|
|
||||||
tx = x if w==0 else random.randint(x, x+w)
|
|
||||||
ty = y if h==0 else random.randint(y, y+h)
|
|
||||||
#print((tx, ty))
|
|
||||||
return (tx, ty)
|
|
||||||
|
|
||||||
def adjacent_rooms(r, rooms):
|
|
||||||
x, y, w, h = r.data
|
|
||||||
adjacents = {}
|
|
||||||
|
|
||||||
for i, other_r in enumerate(rooms):
|
|
||||||
rx, ry, rw, rh = other_r.data
|
|
||||||
if (rx, ry, rw, rh) == r:
|
|
||||||
continue # Skip self
|
|
||||||
|
|
||||||
# Check vertical adjacency (above or below)
|
|
||||||
if rx < x + w and x < rx + rw: # Overlapping width
|
|
||||||
if ry + rh == y: # Above
|
|
||||||
adjacents[i] = (x + w // 2, y - 1)
|
|
||||||
elif y + h == ry: # Below
|
|
||||||
adjacents[i] = (x + w // 2, y + h + 1)
|
|
||||||
|
|
||||||
# Check horizontal adjacency (left or right)
|
|
||||||
if ry < y + h and y < ry + rh: # Overlapping height
|
|
||||||
if rx + rw == x: # Left
|
|
||||||
adjacents[i] = (x - 1, y + h // 2)
|
|
||||||
elif x + w == rx: # Right
|
|
||||||
adjacents[i] = (x + w + 1, y + h // 2)
|
|
||||||
|
|
||||||
return adjacents
|
|
||||||
|
|
||||||
class Level:
|
|
||||||
def __init__(self, width, height):
|
|
||||||
self.width = width
|
|
||||||
self.height = height
|
|
||||||
#self.graph = [(0, 0, width, height)]
|
|
||||||
self.graph = RoomGraph( (0, 0, width, height) )
|
|
||||||
self.grid = mcrfpy.Grid(width, height, t, (10, 5), (1014, 700))
|
|
||||||
self.highlighted = -1 #debug view feature
|
|
||||||
self.walled_rooms = [] # for tracking "hallway rooms" vs "walled rooms"
|
|
||||||
|
|
||||||
def fill(self, xywh, highlight = False):
|
|
||||||
if highlight:
|
|
||||||
ts = 0
|
|
||||||
else:
|
|
||||||
ts = room_area(*xywh) % 131
|
|
||||||
X, Y, W, H = xywh
|
|
||||||
for x in range(X, X+W):
|
|
||||||
for y in range(Y, Y+H):
|
|
||||||
self.grid.at((x, y)).tilesprite = ts
|
|
||||||
|
|
||||||
def highlight(self, delta):
|
|
||||||
rooms = self.graph.walk()
|
|
||||||
if self.highlighted < len(rooms):
|
|
||||||
#print(f"reset {self.highlighted}")
|
|
||||||
self.fill(rooms[self.highlighted].data) # reset
|
|
||||||
self.highlighted += delta
|
|
||||||
print(f"highlight {self.highlighted}")
|
|
||||||
self.highlighted = self.highlighted % len(rooms)
|
|
||||||
self.fill(rooms[self.highlighted].data, highlight = True)
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.graph = RoomGraph( (0, 0, self.width, self.height) )
|
|
||||||
for x in range(self.width):
|
|
||||||
for y in range(self.height):
|
|
||||||
self.grid.at((x, y)).walkable = True
|
|
||||||
self.grid.at((x, y)).transparent = True
|
|
||||||
self.grid.at((x, y)).tilesprite = 0 #random.choice([40, 28])
|
|
||||||
|
|
||||||
def split(self, single=False):
|
|
||||||
if single:
|
|
||||||
areas = {g.data: room_area(*g.data) for g in self.graph.walk()}
|
|
||||||
largest = sorted(self.graph.walk(), key=lambda g: areas[g.data])[-1]
|
|
||||||
largest.split()
|
|
||||||
else:
|
|
||||||
for room in self.graph.walk(): room.split()
|
|
||||||
self.fill_rooms()
|
|
||||||
|
|
||||||
def fill_rooms(self, features=None):
|
|
||||||
rooms = self.graph.walk()
|
|
||||||
#print(f"rooms: {len(rooms)}")
|
|
||||||
for i, g in enumerate(rooms):
|
|
||||||
X, Y, W, H = g.data
|
|
||||||
#c = [random.randint(0, 255) for _ in range(3)]
|
|
||||||
ts = room_area(*g.data) % 131 + i # modulo - consistent tile pick
|
|
||||||
for x in range(X, X+W):
|
|
||||||
for y in range(Y, Y+H):
|
|
||||||
self.grid.at((x, y)).tilesprite = ts
|
|
||||||
|
|
||||||
def wall_rooms(self):
|
|
||||||
self.walled_rooms = []
|
|
||||||
rooms = self.graph.walk()
|
|
||||||
for i, g in enumerate(rooms):
|
|
||||||
# unwalled / hallways: not selected for small dungeons, first, last, and 65% of all other rooms
|
|
||||||
if len(rooms) > 3 and i > 1 and i < len(rooms) - 2 and random.random() < 0.35:
|
|
||||||
self.walled_rooms.append(False)
|
|
||||||
continue
|
|
||||||
self.walled_rooms.append(True)
|
|
||||||
X, Y, W, H = g.data
|
|
||||||
for x in range(X, X+W):
|
|
||||||
self.grid.at((x, Y)).walkable = False
|
|
||||||
#self.grid.at((x, Y+H-1)).walkable = False
|
|
||||||
for y in range(Y, Y+H):
|
|
||||||
self.grid.at((X, y)).walkable = False
|
|
||||||
#self.grid.at((X+W-1, y)).walkable = False
|
|
||||||
# boundary of entire level
|
|
||||||
for x in range(0, self.width):
|
|
||||||
# self.grid.at((x, 0)).walkable = False
|
|
||||||
self.grid.at((x, self.height-1)).walkable = False
|
|
||||||
for y in range(0, self.height):
|
|
||||||
# self.grid.at((0, y)).walkable = False
|
|
||||||
self.grid.at((self.width-1, y)).walkable = False
|
|
||||||
|
|
||||||
def dig_path(self, start:"Tuple[int, int]", end:"Tuple[int, int]", walkable=True, color=None, sprite=None):
|
|
||||||
print(f"Digging: {start} -> {end}")
|
|
||||||
# get x1,y1 and x2,y2 coordinates: top left and bottom right points on the rect formed by two random points, one from each of the 2 rooms
|
|
||||||
x1 = min([start[0], end[0]])
|
|
||||||
x2 = max([start[0], end[0]])
|
|
||||||
dw = x2 - x1
|
|
||||||
y1 = min([start[1], end[1]])
|
|
||||||
y2 = max([start[1], end[1]])
|
|
||||||
dh = y2 - y1
|
|
||||||
|
|
||||||
# random: top left or bottom right as the corner between the paths
|
|
||||||
tx, ty = (x1, y1) if random.random() >= 0.5 else (x2, y2)
|
|
||||||
|
|
||||||
for x in range(x1, x1+dw):
|
|
||||||
try:
|
|
||||||
if walkable:
|
|
||||||
self.grid.at((x, ty)).walkable = walkable
|
|
||||||
if color:
|
|
||||||
self.grid.at((x, ty)).color = color
|
|
||||||
if sprite is not None:
|
|
||||||
self.grid.at((x, ty)).tilesprite = sprite
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
for y in range(y1, y1+dh):
|
|
||||||
try:
|
|
||||||
if walkable:
|
|
||||||
self.grid.at((tx, y)).walkable = True
|
|
||||||
if color:
|
|
||||||
self.grid.at((tx, y)).color = color
|
|
||||||
if sprite is not None:
|
|
||||||
self.grid.at((tx, y)).tilesprite = sprite
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def generate(self, level_plan): #target_rooms = 5, features=None):
|
|
||||||
self.reset()
|
|
||||||
target_rooms = len(level_plan)
|
|
||||||
if type(level_plan) is set:
|
|
||||||
level_plan = random.choice(list(level_plan))
|
|
||||||
while len(self.graph.walk()) < target_rooms:
|
|
||||||
self.split(single=len(self.graph.walk()) > target_rooms * .5)
|
|
||||||
|
|
||||||
# Player path planning
|
|
||||||
#self.fill_rooms()
|
|
||||||
self.wall_rooms()
|
|
||||||
rooms = self.graph.walk()
|
|
||||||
feature_coords = []
|
|
||||||
prev_room = None
|
|
||||||
print(level_plan)
|
|
||||||
for room_num, room in enumerate(rooms):
|
|
||||||
room_plan = level_plan[room_num]
|
|
||||||
if type(room_plan) == str: room_plan = [room_plan] # single item plans became single-character plans...
|
|
||||||
for f in room_plan:
|
|
||||||
#feature_coords.append((f, room_coord(room, margin=4 if f in ("boulder",) else 1)))
|
|
||||||
# boulders are breaking my brain. If I can't get boulders away from walls with margin, I'm just going to dig them out.
|
|
||||||
#if f == "boulder":
|
|
||||||
# x, y = room_coord(room, margin=0)
|
|
||||||
# if x < 2: x += 1
|
|
||||||
# if y < 2: y += 1
|
|
||||||
# if x > self.grid.grid_size[0] - 2: x -= 1
|
|
||||||
# if y > self.grid.grid_size[1] - 2: y -= 1
|
|
||||||
# for _x in (1, 0, -1):
|
|
||||||
# for _y in (1, 0, -1):
|
|
||||||
# self.grid.at((x + _x, y + _y)).walkable = True
|
|
||||||
# feature_coords.append((f, (x, y)))
|
|
||||||
#else:
|
|
||||||
# feature_coords.append((f, room_coord(room, margin=0)))
|
|
||||||
fcoord = None
|
|
||||||
while not fcoord:
|
|
||||||
fc = room_coord(room, margin=0)
|
|
||||||
if not self.grid.at(fc).walkable: continue
|
|
||||||
if fc in [_i[1] for _i in feature_coords]: continue
|
|
||||||
fcoord = fc
|
|
||||||
feature_coords.append((f, fcoord))
|
|
||||||
print(feature_coords[-1])
|
|
||||||
|
|
||||||
## Hallway generation
|
|
||||||
# plow an inelegant path
|
|
||||||
if prev_room:
|
|
||||||
start = room_coord(prev_room, margin=2)
|
|
||||||
end = room_coord(room, margin=2)
|
|
||||||
self.dig_path(start, end, color=(0, 64, 0))
|
|
||||||
prev_room = room
|
|
||||||
|
|
||||||
# Tile painting
|
|
||||||
possibilities = None
|
|
||||||
while possibilities or possibilities is None:
|
|
||||||
possibilities = ct.wfc_pass(self.grid, possibilities)
|
|
||||||
|
|
||||||
## "hallway room" repainting
|
|
||||||
#for i, hall_room in enumerate(rooms):
|
|
||||||
# if self.walled_rooms[i]:
|
|
||||||
# print(f"walled room: {hall_room}")
|
|
||||||
# continue
|
|
||||||
# print(f"hall room: {hall_room}")
|
|
||||||
# x, y, w, h = hall_room.data
|
|
||||||
# for _x in range(x+1, x+w-1):
|
|
||||||
# for _y in range(y+1, y+h-1):
|
|
||||||
# self.grid.at((_x, _y)).walkable = False
|
|
||||||
# self.grid.at((_x, _y)).tilesprite = -1
|
|
||||||
# self.grid.at((_x, _y)).color = (0, 0, 0) # pit!
|
|
||||||
# targets = adjacent_rooms(hall_room, rooms)
|
|
||||||
# print(targets)
|
|
||||||
# for k, v in targets.items():
|
|
||||||
# self.dig_path(hall_room.center(), v, color=(64, 32, 32))
|
|
||||||
# for _, p in feature_coords:
|
|
||||||
# if hall_room.contains(p): self.dig_path(hall_room.center(), p, color=(92, 48, 48))
|
|
||||||
|
|
||||||
return feature_coords
|
|
|
@ -0,0 +1,300 @@
|
||||||
|
import mcrfpy
|
||||||
|
mcrfpy.createScene("play")
|
||||||
|
ui = mcrfpy.sceneUI("play")
|
||||||
|
t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) # 12, 11)
|
||||||
|
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
||||||
|
|
||||||
|
frame_color = (64, 64, 128)
|
||||||
|
|
||||||
|
grid = mcrfpy.Grid(20, 15, t, 10, 10, 800, 595)
|
||||||
|
grid.zoom = 2.0
|
||||||
|
entity_frame = mcrfpy.Frame(815, 10, 194, 595, fill_color = frame_color)
|
||||||
|
inventory_frame = mcrfpy.Frame(10, 610, 800, 143, fill_color = frame_color)
|
||||||
|
stats_frame = mcrfpy.Frame(815, 610, 194, 143, fill_color = frame_color)
|
||||||
|
|
||||||
|
begin_btn = mcrfpy.Frame(350,250,100,100, fill_color = (255,0,0))
|
||||||
|
begin_btn.children.append(mcrfpy.Caption(5, 5, "Begin", font))
|
||||||
|
def cos_keys(key, state):
|
||||||
|
if key == 'M' and state == 'start':
|
||||||
|
mapgen()
|
||||||
|
elif state == "end": return
|
||||||
|
elif key == "W":
|
||||||
|
player.move("N")
|
||||||
|
elif key == "A":
|
||||||
|
player.move("W")
|
||||||
|
elif key == "S":
|
||||||
|
player.move("S")
|
||||||
|
elif key == "D":
|
||||||
|
player.move("E")
|
||||||
|
|
||||||
|
|
||||||
|
def cos_init(*args):
|
||||||
|
if args[3] != "start": return
|
||||||
|
mcrfpy.keypressScene(cos_keys)
|
||||||
|
ui.remove(4)
|
||||||
|
|
||||||
|
begin_btn.click = cos_init
|
||||||
|
|
||||||
|
[ui.append(e) for e in (grid, entity_frame, inventory_frame, stats_frame, begin_btn)]
|
||||||
|
|
||||||
|
import random
|
||||||
|
def rcolor():
|
||||||
|
return tuple([random.randint(0, 255) for i in range(3)]) # TODO list won't work with GridPoint.color, so had to cast to tuple
|
||||||
|
|
||||||
|
x_max, y_max = grid.grid_size
|
||||||
|
for x in range(x_max):
|
||||||
|
for y in range(y_max):
|
||||||
|
grid.at((x,y)).color = rcolor()
|
||||||
|
|
||||||
|
from math import pi, cos, sin
|
||||||
|
def mapgen(room_size_max = 7, room_size_min = 3, room_count = 4):
|
||||||
|
# reset map
|
||||||
|
for x in range(x_max):
|
||||||
|
for y in range(y_max):
|
||||||
|
grid.at((x, y)).walkable = False
|
||||||
|
grid.at((x, y)).transparent= False
|
||||||
|
grid.at((x,y)).tilesprite = random.choices([40, 28], weights=[.8, .2])[0]
|
||||||
|
global cos_entities
|
||||||
|
for e in cos_entities:
|
||||||
|
e.e.position = (999,999) # TODO
|
||||||
|
e.die()
|
||||||
|
cos_entities = []
|
||||||
|
|
||||||
|
#Dungeon generation
|
||||||
|
centers = []
|
||||||
|
attempts = 0
|
||||||
|
while len(centers) < room_count:
|
||||||
|
# Leaving this attempt here for later comparison. These rooms sucked.
|
||||||
|
# overlapping, uninteresting hallways, crowded into the corners sometimes, etc.
|
||||||
|
attempts += 1
|
||||||
|
if attempts > room_count * 15: break
|
||||||
|
# room_left = random.randint(1, x_max)
|
||||||
|
# room_top = random.randint(1, y_max)
|
||||||
|
|
||||||
|
# Take 2 - circular distribution of rooms
|
||||||
|
angle_mid = (len(centers) / room_count) * 2 * pi + 0.785
|
||||||
|
angle = random.uniform(angle_mid - 0.25, angle_mid + 0.25)
|
||||||
|
radius = random.uniform(3, 14)
|
||||||
|
room_left = int(radius * cos(angle)) + int(x_max/2)
|
||||||
|
if room_left <= 1: room_left = 1
|
||||||
|
if room_left > x_max - 1: room_left = x_max - 2
|
||||||
|
room_top = int(radius * sin(angle)) + int(y_max/2)
|
||||||
|
if room_top <= 1: room_top = 1
|
||||||
|
if room_top > y_max - 1: room_top = y_max - 2
|
||||||
|
room_w = random.randint(room_size_min, room_size_max)
|
||||||
|
if room_w + room_left >= x_max: room_w = x_max - room_left - 2
|
||||||
|
room_h = random.randint(room_size_min, room_size_max)
|
||||||
|
if room_h + room_top >= y_max: room_h = y_max - room_top - 2
|
||||||
|
#print(room_left, room_top, room_left + room_w, room_top + room_h)
|
||||||
|
if any( # centers contained in this randomly generated room
|
||||||
|
[c[0] >= room_left and c[0] <= room_left + room_w and c[1] >= room_top and c[1] <= room_top + room_h for c in centers]
|
||||||
|
):
|
||||||
|
continue # re-randomize the room position
|
||||||
|
centers.append(
|
||||||
|
(int(room_left + (room_w/2)), int(room_top + (room_h/2)))
|
||||||
|
)
|
||||||
|
|
||||||
|
for x in range(room_w):
|
||||||
|
for y in range(room_h):
|
||||||
|
grid.at((room_left+x, room_top+y)).walkable=True
|
||||||
|
grid.at((room_left+x, room_top+y)).transparent=True
|
||||||
|
grid.at((room_left+x, room_top+y)).tilesprite = random.choice([48, 49, 50, 51, 52, 53])
|
||||||
|
|
||||||
|
# generate a boulder
|
||||||
|
if (room_w > 2 and room_h > 2):
|
||||||
|
room_boulder_x, room_boulder_y = random.randint(room_left+1, room_left+room_w-1), random.randint(room_top+1, room_top+room_h-1)
|
||||||
|
cos_entities.append(BoulderEntity(room_boulder_x, room_boulder_y))
|
||||||
|
|
||||||
|
print(f"{room_count} rooms generated after {attempts} attempts.")
|
||||||
|
#print(centers)
|
||||||
|
# hallways
|
||||||
|
pairs = []
|
||||||
|
for c1 in centers:
|
||||||
|
for c2 in centers:
|
||||||
|
if c1 == c2: continue
|
||||||
|
if (c2, c1) in pairs or (c1, c2) in pairs: continue
|
||||||
|
left = min(c1[0], c2[0])
|
||||||
|
right = max(c1[0], c2[0])
|
||||||
|
top = min(c1[1], c2[1])
|
||||||
|
bottom = max(c1[1], c2[1])
|
||||||
|
|
||||||
|
corners = [(left, top), (left, bottom), (right, top), (right, bottom)]
|
||||||
|
corners.remove(c1)
|
||||||
|
corners.remove(c2)
|
||||||
|
random.shuffle(corners)
|
||||||
|
target, other = corners
|
||||||
|
for x in range(target[0], other[0], -1 if target[0] > other[0] else 1):
|
||||||
|
was_walkable = grid.at((x, target[1])).walkable
|
||||||
|
grid.at((x, target[1])).walkable=True
|
||||||
|
grid.at((x, target[1])).transparent=True
|
||||||
|
if not was_walkable:
|
||||||
|
grid.at((x, target[1])).tilesprite = random.choices([0, 12, 24], weights=[.6, .3, .1])[0]
|
||||||
|
for y in range(target[1], other[1], -1 if target[1] > other[1] else 1):
|
||||||
|
was_walkable = grid.at((target[0], y)).walkable
|
||||||
|
grid.at((target[0], y)).walkable=True
|
||||||
|
grid.at((target[0], y)).transparent=True
|
||||||
|
if not was_walkable:
|
||||||
|
grid.at((target[0], y)).tilesprite = random.choices([0, 12, 24], weights=[0.4, 0.3, 0.3])[0]
|
||||||
|
pairs.append((c1, c2))
|
||||||
|
|
||||||
|
|
||||||
|
# spawn exit and button
|
||||||
|
spawn_points = []
|
||||||
|
for x in range(x_max):
|
||||||
|
for y in range(y_max):
|
||||||
|
if grid.at((x, y)).walkable:
|
||||||
|
spawn_points.append((x, y))
|
||||||
|
random.shuffle(spawn_points)
|
||||||
|
door_spawn, button_spawn = spawn_points[:2]
|
||||||
|
cos_entities.append(ExitEntity(*door_spawn, *button_spawn))
|
||||||
|
|
||||||
|
# respawn player
|
||||||
|
global player
|
||||||
|
if player:
|
||||||
|
player.position = (999,999) # TODO - die() is broken and I don't know why
|
||||||
|
player = PlayerEntity()
|
||||||
|
|
||||||
|
|
||||||
|
#for x in range(x_max):
|
||||||
|
# for y in range(y_max):
|
||||||
|
# if grid.at((x, y)).walkable:
|
||||||
|
# #grid.at((x,y)).tilesprite = random.choice([48, 49, 50, 51, 52, 53])
|
||||||
|
# pass
|
||||||
|
# else:
|
||||||
|
# #grid.at((x,y)).tilesprite = random.choices([40, 28], weights=[.8, .2])[0]
|
||||||
|
|
||||||
|
#131 - last sprite
|
||||||
|
#123, 124 - brown, grey rats
|
||||||
|
#121 - ghost
|
||||||
|
#114, 115, 116 - green, red, blue potion
|
||||||
|
#102 - shield
|
||||||
|
#98 - low armor guy, #97 - high armor guy
|
||||||
|
#89 - chest, #91 - empty chest
|
||||||
|
#84 - wizard
|
||||||
|
#82 - barrel
|
||||||
|
#66 - boulder
|
||||||
|
#64, 65 - graves
|
||||||
|
#48 - 53: ground (not going to figure out how they fit together tonight)
|
||||||
|
#42 - button-looking ground
|
||||||
|
#40 - basic solid wall
|
||||||
|
#36, 37, 38 - wall (left, middle, right)
|
||||||
|
#28 solid wall but with a grate
|
||||||
|
#21 - wide open door, 33 medium open, 45 closed door
|
||||||
|
#0 - basic dirt
|
||||||
|
class MyEntity:
|
||||||
|
def __init__(self, x=0, y=0, sprite_num=86):
|
||||||
|
self.e = mcrfpy.Entity(x, y, t, sprite_num)
|
||||||
|
grid.entities.append(self.e)
|
||||||
|
def die(self):
|
||||||
|
for i in range(len(grid.entities)):
|
||||||
|
e = grid.entities[i]
|
||||||
|
if e == self.e:
|
||||||
|
grid.entities.remove(i)
|
||||||
|
break
|
||||||
|
def bump(self, other, dx, dy):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def try_move(self, dx, dy):
|
||||||
|
tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
|
||||||
|
for e in cos_entities:
|
||||||
|
if e.e.position == (tx, ty):
|
||||||
|
#print(f"bumping {e}")
|
||||||
|
return e.bump(self, dx, dy)
|
||||||
|
if tx < 0 or tx >= x_max:
|
||||||
|
#print("out of bounds horizontally")
|
||||||
|
return False
|
||||||
|
if ty < 0 or ty >= y_max:
|
||||||
|
#print("out of bounds vertically")
|
||||||
|
return False
|
||||||
|
if grid.at((tx, ty)).walkable == True:
|
||||||
|
#print("Motion!")
|
||||||
|
self.e.position = (tx, ty)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
#print("Bonk")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _relative_move(self, dx, dy):
|
||||||
|
tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
|
||||||
|
self.e.position = (tx, ty)
|
||||||
|
|
||||||
|
|
||||||
|
def move(self, direction):
|
||||||
|
if direction == "N":
|
||||||
|
self.try_move(0, -1)
|
||||||
|
elif direction == "E":
|
||||||
|
self.try_move(1, 0)
|
||||||
|
elif direction == "S":
|
||||||
|
self.try_move(0, 1)
|
||||||
|
elif direction == "W":
|
||||||
|
self.try_move(-1, 0)
|
||||||
|
|
||||||
|
cos_entities = []
|
||||||
|
|
||||||
|
class PlayerEntity(MyEntity):
|
||||||
|
def __init__(self):
|
||||||
|
# find spawn point
|
||||||
|
spawn_points = []
|
||||||
|
for x in range(x_max):
|
||||||
|
for y in range(y_max):
|
||||||
|
if grid.at((x, y)).walkable:
|
||||||
|
spawn_points.append((x, y))
|
||||||
|
random.shuffle(spawn_points)
|
||||||
|
for spawn in spawn_points:
|
||||||
|
for e in cos_entities:
|
||||||
|
if e.e.position == spawn: break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
#print(f"spawn at {spawn}")
|
||||||
|
super().__init__(spawn[0], spawn[1], sprite_num=84)
|
||||||
|
|
||||||
|
class BoulderEntity(MyEntity):
|
||||||
|
def __init__(self, x, y):
|
||||||
|
super().__init__(x, y, 66)
|
||||||
|
|
||||||
|
def bump(self, other, dx, dy):
|
||||||
|
if type(other) == BoulderEntity:
|
||||||
|
#print("Boulders can't push boulders")
|
||||||
|
return False
|
||||||
|
tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
|
||||||
|
# Is the boulder blocked the same direction as the bumper? If not, let's both move
|
||||||
|
old_pos = int(self.e.position[0]), int(self.e.position[1])
|
||||||
|
if self.try_move(dx, dy):
|
||||||
|
other.e.position = old_pos
|
||||||
|
return True
|
||||||
|
|
||||||
|
class ButtonEntity(MyEntity):
|
||||||
|
def __init__(self, x, y, exit):
|
||||||
|
super().__init__(x, y, 42)
|
||||||
|
self.exit = exit
|
||||||
|
|
||||||
|
def bump(self, other, dx, dy):
|
||||||
|
if type(other) == BoulderEntity:
|
||||||
|
self.exit.unlock()
|
||||||
|
other._relative_move(dx, dy)
|
||||||
|
return True
|
||||||
|
|
||||||
|
class ExitEntity(MyEntity):
|
||||||
|
def __init__(self, x, y, bx, by):
|
||||||
|
super().__init__(x, y, 45)
|
||||||
|
self.my_button = ButtonEntity(bx, by, self)
|
||||||
|
self.unlocked = False
|
||||||
|
global cos_entities
|
||||||
|
cos_entities.append(self.my_button)
|
||||||
|
|
||||||
|
def unlock(self):
|
||||||
|
self.e.sprite_number = 21
|
||||||
|
self.unlocked = True
|
||||||
|
|
||||||
|
def lock(self):
|
||||||
|
self.e.sprite_number = 45
|
||||||
|
self.unlocked = True
|
||||||
|
|
||||||
|
def bump(self, other, dx, dy):
|
||||||
|
if type(other) == BoulderEntity:
|
||||||
|
return False
|
||||||
|
if self.unlocked:
|
||||||
|
other._relative_move(dx, dy)
|
||||||
|
|
||||||
|
player = None
|
|
@ -1,223 +0,0 @@
|
||||||
tiles = {}
|
|
||||||
deltas = [
|
|
||||||
(-1, -1), ( 0, -1), (+1, -1),
|
|
||||||
(-1, 0), ( 0, 0), (+1, 0),
|
|
||||||
(-1, +1), ( 0, +1), (+1, +1)
|
|
||||||
]
|
|
||||||
|
|
||||||
class TileInfo:
|
|
||||||
GROUND, WALL, DONTCARE = True, False, None
|
|
||||||
chars = {
|
|
||||||
"X": WALL,
|
|
||||||
"_": GROUND,
|
|
||||||
"?": DONTCARE
|
|
||||||
}
|
|
||||||
symbols = {v: k for k, v in chars.items()}
|
|
||||||
|
|
||||||
def __init__(self, values:dict):
|
|
||||||
self._values = values
|
|
||||||
self.rules = []
|
|
||||||
self.chance = 1.0
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_grid(grid, xy:tuple):
|
|
||||||
values = {}
|
|
||||||
for d in deltas:
|
|
||||||
tx, ty = d[0] + xy[0], d[1] + xy[1]
|
|
||||||
try:
|
|
||||||
values[d] = grid.at((tx, ty)).walkable
|
|
||||||
except ValueError:
|
|
||||||
values[d] = True
|
|
||||||
return TileInfo(values)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_string(s):
|
|
||||||
values = {}
|
|
||||||
for d, c in zip(deltas, s):
|
|
||||||
values[d] = TileInfo.chars[c]
|
|
||||||
return TileInfo(values)
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
"""for use as a dictionary key"""
|
|
||||||
return hash(tuple(self._values.items()))
|
|
||||||
|
|
||||||
def match(self, other:"TileInfo"):
|
|
||||||
for d, rule in self._values.items():
|
|
||||||
if rule is TileInfo.DONTCARE: continue
|
|
||||||
if other._values[d] is TileInfo.DONTCARE: continue
|
|
||||||
if rule != other._values[d]: return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
nine = ['', '', '\n'] * 3
|
|
||||||
for k, end in zip(deltas, nine):
|
|
||||||
c = TileInfo.symbols[self._values[k]]
|
|
||||||
print(c, end=end)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<TileInfo {self._values}>"
|
|
||||||
|
|
||||||
cardinal_directions = {
|
|
||||||
"N": ( 0, -1),
|
|
||||||
"S": ( 0, +1),
|
|
||||||
"E": (-1, 0),
|
|
||||||
"W": (+1, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
def special_rule_verify(rule, grid, xy, unverified_tiles, pass_unverified=False):
|
|
||||||
cardinal, allowed_tile = rule
|
|
||||||
dxy = cardinal_directions[cardinal.upper()]
|
|
||||||
tx, ty = xy[0] + dxy[0], xy[1] + dxy[1]
|
|
||||||
#print(f"Special rule: {cardinal} {allowed_tile} {type(allowed_tile)} -> ({tx}, {ty}) [{grid.at((tx, ty)).tilesprite}]{'*' if (tx, ty) in unverified_tiles else ''}")
|
|
||||||
if (tx, ty) in unverified_tiles and cardinal in "nsew": return pass_unverified
|
|
||||||
try:
|
|
||||||
return grid.at((tx, ty)).tilesprite == allowed_tile
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
import random
|
|
||||||
tile_of_last_resort = 431
|
|
||||||
|
|
||||||
def find_possible_tiles(grid, x, y, unverified_tiles=None, pass_unverified=False):
|
|
||||||
ti = TileInfo.from_grid(grid, (x, y))
|
|
||||||
if unverified_tiles is None: unverified_tiles = []
|
|
||||||
matches = [(k, v) for k, v in tiles.items() if k.match(ti)]
|
|
||||||
if not matches:
|
|
||||||
return []
|
|
||||||
possible = []
|
|
||||||
if not any([tileinfo.rules for tileinfo, _ in matches]):
|
|
||||||
# make weighted choice, as the tile does not depend on verification
|
|
||||||
wts = [k.chance for k, v in matches]
|
|
||||||
tileinfo, tile = random.choices(matches, weights=wts)[0]
|
|
||||||
return [tile]
|
|
||||||
|
|
||||||
for tileinfo, tile in matches:
|
|
||||||
if not tileinfo.rules:
|
|
||||||
possible.append(tile)
|
|
||||||
continue
|
|
||||||
for r in tileinfo.rules: #for r in ...: if ... continue == more readable than an "any" 1-liner
|
|
||||||
p = special_rule_verify(r, grid, (x,y),
|
|
||||||
unverified_tiles=unverified_tiles,
|
|
||||||
pass_unverified = pass_unverified
|
|
||||||
)
|
|
||||||
if p:
|
|
||||||
possible.append(tile)
|
|
||||||
continue
|
|
||||||
return list(set(list(possible)))
|
|
||||||
|
|
||||||
def wfc_first_pass(grid):
|
|
||||||
w, h = grid.grid_size
|
|
||||||
possibilities = {}
|
|
||||||
for x in range(0, w):
|
|
||||||
for y in range(0, h):
|
|
||||||
matches = find_possible_tiles(grid, x, y, pass_unverified=True)
|
|
||||||
if len(matches) == 0:
|
|
||||||
grid.at((x, y)).tilesprite = tile_of_last_resort
|
|
||||||
possibilities[(x,y)] = matches
|
|
||||||
elif len(matches) == 1:
|
|
||||||
grid.at((x, y)).tilesprite = matches[0]
|
|
||||||
else:
|
|
||||||
possibilities[(x,y)] = matches
|
|
||||||
return possibilities
|
|
||||||
|
|
||||||
def wfc_pass(grid, possibilities=None):
|
|
||||||
w, h = grid.grid_size
|
|
||||||
if possibilities is None:
|
|
||||||
#print("first pass results:")
|
|
||||||
possibilities = wfc_first_pass(grid)
|
|
||||||
counts = {}
|
|
||||||
for v in possibilities.values():
|
|
||||||
if len(v) in counts: counts[len(v)] += 1
|
|
||||||
else: counts[len(v)] = 1
|
|
||||||
#print(counts)
|
|
||||||
return possibilities
|
|
||||||
elif len(possibilities) == 0:
|
|
||||||
print("We're done!")
|
|
||||||
return
|
|
||||||
old_possibilities = possibilities
|
|
||||||
possibilities = {}
|
|
||||||
for (x, y) in old_possibilities.keys():
|
|
||||||
matches = find_possible_tiles(grid, x, y, unverified_tiles=old_possibilities.keys(), pass_unverified = True)
|
|
||||||
if len(matches) == 0:
|
|
||||||
print((x,y), matches)
|
|
||||||
grid.at((x, y)).tilesprite = tile_of_last_resort
|
|
||||||
possibilities[(x,y)] = matches
|
|
||||||
elif len(matches) == 1:
|
|
||||||
grid.at((x, y)).tilesprite = matches[0]
|
|
||||||
else:
|
|
||||||
grid.at((x, y)).tilesprite = -1
|
|
||||||
grid.at((x, y)).color = (32 * len(matches), 32 * len(matches), 32 * len(matches))
|
|
||||||
possibilities[(x,y)] = matches
|
|
||||||
|
|
||||||
if len(possibilities) == len(old_possibilities):
|
|
||||||
#print("No more tiles could be solved without collapse")
|
|
||||||
counts = {}
|
|
||||||
for v in possibilities.values():
|
|
||||||
if len(v) in counts: counts[len(v)] += 1
|
|
||||||
else: counts[len(v)] = 1
|
|
||||||
#print(counts)
|
|
||||||
if 0 in counts: del counts[0]
|
|
||||||
if len(counts) == 0:
|
|
||||||
print("Contrats! You broke it! (insufficient tile defs to solve remaining tiles)")
|
|
||||||
return []
|
|
||||||
target = min(list(counts.keys()))
|
|
||||||
while possibilities:
|
|
||||||
for (x, y) in possibilities.keys():
|
|
||||||
if len(possibilities[(x, y)]) != target:
|
|
||||||
continue
|
|
||||||
ti = TileInfo.from_grid(grid, (x, y))
|
|
||||||
matches = [(k, v) for k, v in tiles.items() if k.match(ti)]
|
|
||||||
verifiable_matches = find_possible_tiles(grid, x, y, unverified_tiles=possibilities.keys())
|
|
||||||
if not verifiable_matches: continue
|
|
||||||
#print(f"collapsing {(x, y)} ({target} choices)")
|
|
||||||
matches = [(k, v) for k, v in matches if v in verifiable_matches]
|
|
||||||
wts = [k.chance for k, v in matches]
|
|
||||||
tileinfo, tile = random.choices(matches, weights=wts)[0]
|
|
||||||
grid.at((x, y)).tilesprite = tile
|
|
||||||
del possibilities[(x, y)]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
selected = random.choice(list(possibilities.keys()))
|
|
||||||
#print(f"No tiles have verifable solutions: QUANTUM -> {selected}")
|
|
||||||
# sprinkle some quantumness on it
|
|
||||||
ti = TileInfo.from_grid(grid, (x, y))
|
|
||||||
matches = [(k, v) for k, v in tiles.items() if k.match(ti)]
|
|
||||||
wts = [k.chance for k, v in matches]
|
|
||||||
if not wts:
|
|
||||||
print(f"This one: {(x,y)} {matches}\n{wts}")
|
|
||||||
del possibilities[(x, y)]
|
|
||||||
return possibilities
|
|
||||||
tileinfo, tile = random.choices(matches, weights=wts)[0]
|
|
||||||
grid.at((x, y)).tilesprite = tile
|
|
||||||
del possibilities[(x, y)]
|
|
||||||
|
|
||||||
return possibilities
|
|
||||||
|
|
||||||
#with open("scripts/tile_def.txt", "r") as f:
|
|
||||||
with open("scripts/simple_tiles.txt", "r") as f:
|
|
||||||
for block in f.read().split('\n\n'):
|
|
||||||
info, constraints = block.split('\n', 1)
|
|
||||||
if '#' in info:
|
|
||||||
info, comment = info.split('#', 1)
|
|
||||||
rules = []
|
|
||||||
if '@' in info:
|
|
||||||
info, *block_rules = info.split('@')
|
|
||||||
#print(block_rules)
|
|
||||||
for r in block_rules:
|
|
||||||
rules.append((r[0], int(r[1:])))
|
|
||||||
#cardinal_dir = block_rules[0]
|
|
||||||
#partner
|
|
||||||
if ':' not in info:
|
|
||||||
tile_id = int(info)
|
|
||||||
chance = 1.0
|
|
||||||
else:
|
|
||||||
tile_id, chance = info.split(':')
|
|
||||||
tile_id = int(tile_id)
|
|
||||||
chance = float(chance.strip())
|
|
||||||
constraints = constraints.replace('\n', '')
|
|
||||||
k = TileInfo.from_string(constraints)
|
|
||||||
k.rules = rules
|
|
||||||
k.chance = chance
|
|
||||||
tiles[k] = tile_id
|
|
||||||
|
|
||||||
|
|
|
@ -1,677 +1,60 @@
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import code
|
|
||||||
|
|
||||||
#t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) # 12, 11)
|
|
||||||
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) # 12, 11)
|
|
||||||
btn_tex = mcrfpy.Texture("assets/48px_ui_icons-KenneyNL.png", 48, 48)
|
|
||||||
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
||||||
|
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||||
|
|
||||||
|
print("[game.py] Default texture:")
|
||||||
|
print(mcrfpy.default_texture)
|
||||||
|
print(type(mcrfpy.default_texture))
|
||||||
|
|
||||||
|
# build test widgets
|
||||||
|
|
||||||
|
mcrfpy.createScene("pytest")
|
||||||
|
mcrfpy.setScene("pytest")
|
||||||
|
ui = mcrfpy.sceneUI("pytest")
|
||||||
|
|
||||||
|
# Frame
|
||||||
|
f = mcrfpy.Frame(25, 19, 462, 346, fill_color=(255, 92, 92))
|
||||||
|
print("Frame alive")
|
||||||
|
# fill (LinkedColor / Color): f.fill_color
|
||||||
|
# outline (LinkedColor / Color): f.outline_color
|
||||||
|
# pos (LinkedVector / Vector): f.pos
|
||||||
|
# size (LinkedVector / Vector): f.size
|
||||||
|
|
||||||
|
# Caption
|
||||||
|
print("Caption attempt w/ fill_color:")
|
||||||
|
#c = mcrfpy.Caption(512+25, 19, "Hi.", font)
|
||||||
|
#c = mcrfpy.Caption(512+25, 19, "Hi.", font, fill_color=(255, 128, 128))
|
||||||
|
c = mcrfpy.Caption(512+25, 19, "Hi.", font, fill_color=mcrfpy.Color(255, 128, 128), outline_color=(128, 255, 128))
|
||||||
|
print("Caption alive")
|
||||||
|
# fill (LinkedColor / Color): c.fill_color
|
||||||
|
#color_val = c.fill_color
|
||||||
|
print(c.fill_color)
|
||||||
|
#print("Set a fill color")
|
||||||
|
#c.fill_color = (255, 255, 255)
|
||||||
|
print("Lol, did it segfault?")
|
||||||
|
# outline (LinkedColor / Color): c.outline_color
|
||||||
|
# font (Font): c.font
|
||||||
|
# pos (LinkedVector / Vector): c.pos
|
||||||
|
|
||||||
|
# Sprite
|
||||||
|
s = mcrfpy.Sprite(25, 384+19, texture, 86, 9.0)
|
||||||
|
# pos (LinkedVector / Vector): s.pos
|
||||||
|
# texture (Texture): s.texture
|
||||||
|
|
||||||
|
# Grid
|
||||||
|
g = mcrfpy.Grid(10, 10, texture, 512+25, 384+19, 462, 346)
|
||||||
|
# texture (Texture): g.texture
|
||||||
|
# pos (LinkedVector / Vector): g.pos
|
||||||
|
# size (LinkedVector / Vector): g.size
|
||||||
|
|
||||||
|
for _x in range(10):
|
||||||
|
for _y in range(10):
|
||||||
|
g.at((_x, _y)).color = (255 - _x*25, 255 - _y*25, 255)
|
||||||
|
g.zoom = 2.0
|
||||||
|
|
||||||
|
[ui.append(d) for d in (f, c, s, g)]
|
||||||
|
|
||||||
|
print("built!")
|
||||||
|
|
||||||
|
# tests
|
||||||
|
|
||||||
frame_color = (64, 64, 128)
|
|
||||||
|
|
||||||
import random
|
|
||||||
import cos_entities as ce
|
|
||||||
import cos_level as cl
|
|
||||||
from cos_itemdata import itemdata
|
|
||||||
#import cos_tiles as ct
|
|
||||||
|
|
||||||
class Resources:
|
|
||||||
def __init__(self):
|
|
||||||
self.music_enabled = True
|
|
||||||
self.music_volume = 40
|
|
||||||
self.sfx_enabled = True
|
|
||||||
self.sfx_volume = 100
|
|
||||||
self.master_volume = 100
|
|
||||||
|
|
||||||
# load the music/sfx files here
|
|
||||||
self.splats = []
|
|
||||||
for i in range(1, 10):
|
|
||||||
mcrfpy.createSoundBuffer(f"assets/sfx/splat{i}.ogg")
|
|
||||||
|
|
||||||
|
|
||||||
def play_sfx(self, sfx_id):
|
|
||||||
if self.sfx_enabled and self.sfx_volume and self.master_volume:
|
|
||||||
mcrfpy.setSoundVolume(self.master_volume/100 * self.sfx_volume)
|
|
||||||
mcrfpy.playSound(sfx_id)
|
|
||||||
|
|
||||||
def play_music(self, track_id):
|
|
||||||
if self.music_enabled and self.music_volume and self.master_volume:
|
|
||||||
mcrfpy.setMusicVolume(self.master_volume/100 * self.music_volume)
|
|
||||||
mcrfpy.playMusic(...)
|
|
||||||
|
|
||||||
resources = Resources()
|
|
||||||
|
|
||||||
class Crypt:
|
|
||||||
def __init__(self):
|
|
||||||
mcrfpy.createScene("play")
|
|
||||||
self.ui = mcrfpy.sceneUI("play")
|
|
||||||
|
|
||||||
entity_frame = mcrfpy.Frame(815, 10, 194, 595, fill_color = frame_color)
|
|
||||||
inventory_frame = mcrfpy.Frame(10, 610, 800, 143, fill_color = frame_color)
|
|
||||||
stats_frame = mcrfpy.Frame(815, 610, 194, 143, fill_color = frame_color)
|
|
||||||
|
|
||||||
#self.level = cl.Level(30, 23)
|
|
||||||
self.entities = []
|
|
||||||
self.depth=1
|
|
||||||
self.stuck_btn = SweetButton(self.ui, (810, 700), "Stuck", icon=19, box_width=150, box_height = 60, click=self.stuck)
|
|
||||||
|
|
||||||
self.level_plan = {
|
|
||||||
1: [("spawn", "button", "boulder"), ("exit")],
|
|
||||||
2: [("spawn", "button", "treasure", "treasure", "treasure", "rat", "rat", "boulder"), ("exit")],
|
|
||||||
#2: [("spawn", "button", "boulder"), ("rat"), ("exit")],
|
|
||||||
3: [("spawn", "button", "boulder"), ("rat"), ("exit")],
|
|
||||||
4: [("spawn", "button", "rat"), ("boulder", "rat", "treasure"), ("exit")],
|
|
||||||
5: [("spawn", "button", "rat"), ("boulder", "rat"), ("exit")],
|
|
||||||
6: {(("spawn", "button"), ("boulder", "treasure", "exit")),
|
|
||||||
(("spawn", "boulder"), ("button", "treasure", "exit"))},
|
|
||||||
7: {(("spawn", "button"), ("boulder", "treasure", "exit")),
|
|
||||||
(("spawn", "boulder"), ("button", "treasure", "exit"))},
|
|
||||||
8: {(("spawn", "treasure", "button"), ("boulder", "treasure", "exit")),
|
|
||||||
(("spawn", "treasure", "boulder"), ("button", "treasure", "exit"))}
|
|
||||||
#9: self.lv_planner
|
|
||||||
}
|
|
||||||
|
|
||||||
# empty void for the player to initialize into
|
|
||||||
self.headsup = mcrfpy.Frame(10, 684, 320, 64, fill_color = (0, 0, 0, 0))
|
|
||||||
self.sidebar = mcrfpy.Frame(860, 4, 160, 600, fill_color = (96, 96, 160))
|
|
||||||
|
|
||||||
# Heads Up (health bar, armor bar) config
|
|
||||||
self.health_bar = [mcrfpy.Sprite(32*i, 2, t, 659, 2) for i in range(10)]
|
|
||||||
[self.headsup.children.append(i) for i in self.health_bar]
|
|
||||||
self.armor_bar = [mcrfpy.Sprite(32*i, 42, t, 659, 2) for i in range(10)]
|
|
||||||
[self.headsup.children.append(i) for i in self.armor_bar]
|
|
||||||
# (40, 3), caption, font, fill_color=font_color
|
|
||||||
self.stat_captions = mcrfpy.Caption((325,0), "HP:10\nDef:0(+0)", font, fill_color=(255, 255, 255))
|
|
||||||
self.stat_captions.outline = 3
|
|
||||||
self.stat_captions.outline_color = (0, 0, 0)
|
|
||||||
self.headsup.children.append(self.stat_captions)
|
|
||||||
|
|
||||||
# Side Bar (inventory, level info) config
|
|
||||||
self.level_caption = mcrfpy.Caption((5,5), "Level: 1", font, fill_color=(255, 255, 255))
|
|
||||||
self.level_caption.size = 26
|
|
||||||
self.level_caption.outline = 3
|
|
||||||
self.level_caption.outline_color = (0, 0, 0)
|
|
||||||
self.sidebar.children.append(self.level_caption)
|
|
||||||
self.inv_sprites = [mcrfpy.Sprite(15, 70 + 95*i, t, 659, 6.0) for i in range(5)]
|
|
||||||
for i in self.inv_sprites:
|
|
||||||
self.sidebar.children.append(i)
|
|
||||||
self.key_captions = [
|
|
||||||
mcrfpy.Sprite(75, 130 + (95*2) + 95 * i, t, 384 + i, 3.0) for i in range(3)
|
|
||||||
]
|
|
||||||
for i in self.key_captions:
|
|
||||||
self.sidebar.children.append(i)
|
|
||||||
self.inv_captions = [
|
|
||||||
mcrfpy.Caption((25, 130 + 95 * i), "x", font, fill_color=(255, 255, 255)) for i in range(5)
|
|
||||||
]
|
|
||||||
for i in self.inv_captions:
|
|
||||||
i.size = 16
|
|
||||||
self.sidebar.children.append(i)
|
|
||||||
|
|
||||||
liminal_void = mcrfpy.Grid(1, 1, t, (0, 0), (16, 16))
|
|
||||||
self.grid = liminal_void
|
|
||||||
self.player = ce.PlayerEntity(game=self)
|
|
||||||
self.spawn_point = (0, 0)
|
|
||||||
|
|
||||||
# level creation moves player to the game level at the generated spawn point
|
|
||||||
self.create_level(self.depth)
|
|
||||||
#self.grid = mcrfpy.Grid(20, 15, t, (10, 10), (1014, 758))
|
|
||||||
self.swap_level(self.level, self.spawn_point)
|
|
||||||
|
|
||||||
# Test Entities
|
|
||||||
#ce.BoulderEntity(9, 7, game=self)
|
|
||||||
#ce.BoulderEntity(9, 8, game=self)
|
|
||||||
#ce.ExitEntity(12, 6, 14, 4, game=self)
|
|
||||||
# scene setup
|
|
||||||
|
|
||||||
## might be done by self.swap_level
|
|
||||||
#[self.ui.append(e) for e in (self.grid, self.stuck_btn.base_frame)] # entity_frame, inventory_frame, stats_frame)]
|
|
||||||
|
|
||||||
self.possibilities = None # track WFC possibilities between rounds
|
|
||||||
self.enemies = []
|
|
||||||
#mcrfpy.setTimer("enemy_test", self.enemy_movement, 750)
|
|
||||||
|
|
||||||
#mcrfpy.Frame(x, y, box_width+shadow_offset, box_height, fill_color = (0, 0, 0, 255))
|
|
||||||
#Sprite(0, 3, btn_tex, icon, icon_scale)
|
|
||||||
|
|
||||||
#def enemy_movement(self, *args):
|
|
||||||
# for e in self.enemies: e.act()
|
|
||||||
|
|
||||||
#def spawn_test_rat(self):
|
|
||||||
# success = False
|
|
||||||
# while not success:
|
|
||||||
# x, y = [random.randint(0, i-1) for i in self.grid.grid_size]
|
|
||||||
# success = self.grid.at((x,y)).walkable
|
|
||||||
# self.enemies.append(ce.EnemyEntity(x, y, game=self))
|
|
||||||
|
|
||||||
def gui_update(self):
|
|
||||||
self.stat_captions.text = f"HP:{self.player.hp}\nDef:{self.player.calc_defense()}(+{self.player.calc_defense() - self.player.base_defense})"
|
|
||||||
for i, hs in enumerate(self.health_bar):
|
|
||||||
full_hearts = self.player.hp - (i*2)
|
|
||||||
empty_hearts = self.player.max_hp - (i*2)
|
|
||||||
hs.sprite_number = 659
|
|
||||||
if empty_hearts >= 2:
|
|
||||||
hs.sprite_number = 208
|
|
||||||
if full_hearts >= 2:
|
|
||||||
hs.sprite_number = 210
|
|
||||||
elif full_hearts == 1:
|
|
||||||
hs.sprite_number = 209
|
|
||||||
for i, arm_s in enumerate(self.armor_bar):
|
|
||||||
full_hearts = self.player.calc_defense() - (i*2)
|
|
||||||
arm_s.sprite_number = 659
|
|
||||||
if full_hearts >= 2:
|
|
||||||
arm_s.sprite_number = 211
|
|
||||||
elif full_hearts == 1:
|
|
||||||
arm_s.sprite_number = 212
|
|
||||||
|
|
||||||
#items = self.player.equipped[:] + self.player.inventory[:]
|
|
||||||
for i in range(5):
|
|
||||||
if i == 0:
|
|
||||||
item = self.player.equipped[0] if len(self.player.equipped) > 0 else None
|
|
||||||
elif i == 1:
|
|
||||||
item = self.player.equipped[1] if len(self.player.equipped) > 1 else None
|
|
||||||
elif i == 2:
|
|
||||||
item = self.player.inventory[0] if len(self.player.inventory) > 0 else None
|
|
||||||
elif i == 3:
|
|
||||||
item = self.player.inventory[1] if len(self.player.inventory) > 1 else None
|
|
||||||
elif i == 4:
|
|
||||||
item = self.player.inventory[2] if len(self.player.inventory) > 2 else None
|
|
||||||
if item is None:
|
|
||||||
self.inv_sprites[i].sprite_number = 659
|
|
||||||
if i > 1: self.key_captions[i - 2].sprite_number = 659
|
|
||||||
self.inv_captions[i].text = ""
|
|
||||||
continue
|
|
||||||
self.inv_sprites[i].sprite_number = item.sprite
|
|
||||||
if i > 1:
|
|
||||||
self.key_captions[i - 2].sprite_number = 384 + (i - 2)
|
|
||||||
if item.zap_cooldown_remaining:
|
|
||||||
self.inv_captions[i].text = f"[{item.zap_cooldown_remaining}] {item.text})"
|
|
||||||
else:
|
|
||||||
self.inv_captions[i].text = item.text
|
|
||||||
self.inv_captions[i].fill_color = item.text_color
|
|
||||||
|
|
||||||
def lv_planner(self, target_level):
|
|
||||||
"""Plan room sequence in levels > 9"""
|
|
||||||
monsters = (target_level - 6) // 2
|
|
||||||
target_rooms = min(int(target_level // 2), 6)
|
|
||||||
target_treasure = min(int(target_level // 3), 4)
|
|
||||||
rooms = []
|
|
||||||
for i in range(target_rooms):
|
|
||||||
rooms.append([])
|
|
||||||
for o in ("spawn", "boulder", "button", "exit"):
|
|
||||||
r = random.randint(0, target_rooms-1)
|
|
||||||
rooms[r].append(o)
|
|
||||||
monster_table = {
|
|
||||||
"rat": int(monsters * 0.8) + 2,
|
|
||||||
"big rat": max(int(monsters * 0.2) - 2, 0),
|
|
||||||
"cyclops": max(int(monsters * 0.1) - 3, 0)
|
|
||||||
}
|
|
||||||
monster_table = {k: v for k, v in monster_table.items() if v > 0}
|
|
||||||
monster_names = list(monster_table.keys())
|
|
||||||
monster_weights = [monster_table[k] for k in monster_names]
|
|
||||||
for m in range(monsters):
|
|
||||||
r = random.randint(0, target_rooms - 1)
|
|
||||||
rooms[r].append(random.choices(monster_names, weights = monster_weights)[0])
|
|
||||||
|
|
||||||
for t in range(target_treasure):
|
|
||||||
r = random.randint(0, target_rooms - 1)
|
|
||||||
rooms[r].append("treasure")
|
|
||||||
|
|
||||||
return rooms
|
|
||||||
|
|
||||||
def treasure_planner(self, treasure_level):
|
|
||||||
"""Plan treasure contents at all levels"""
|
|
||||||
|
|
||||||
# find item name in base_wts key (base weight of the category)
|
|
||||||
#base_weight = lambda s: base_wts[list([t for t in base_wts.keys() if s in t])[0]]
|
|
||||||
#weights = {d[0]: base_weight(d[0]) for d in item_minlv.items() if treasure_level > d[1]}
|
|
||||||
#if self.player.archetype is None:
|
|
||||||
# prefs = []
|
|
||||||
#elif self.player.archetype == "viking":
|
|
||||||
# prefs = ["axe2", "axe3", "green_pot"]
|
|
||||||
#elif self.player.archetype == "knight":
|
|
||||||
# prefs = ["sword2", "shield", "grey_pot"]
|
|
||||||
#elif self.player.archetype == "wizard":
|
|
||||||
# prefs = ["staff", "staff2", "blue_pot"]
|
|
||||||
#for i in prefs:
|
|
||||||
# if i in weights: weights[i] *= 3
|
|
||||||
weights = {}
|
|
||||||
for item in itemdata:
|
|
||||||
data = itemdata[item]
|
|
||||||
if data.min_lv > treasure_level or treasure_level > data.max_lv: continue
|
|
||||||
weights[item] = data.base_wt
|
|
||||||
if self.player.archetype is not None and data.affinity == self.player.archetype:
|
|
||||||
weights[item] *= 3
|
|
||||||
return weights
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
resources.play_sfx(1)
|
|
||||||
mcrfpy.setScene("play")
|
|
||||||
mcrfpy.keypressScene(self.cos_keys)
|
|
||||||
|
|
||||||
def add_entity(self, e:ce.COSEntity):
|
|
||||||
self.entities.append(e)
|
|
||||||
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
|
|
||||||
# hack / workaround for grid.entities not interable
|
|
||||||
while len(self.grid.entities): # while there are entities on the grid,
|
|
||||||
self.grid.entities.remove(0) # remove the 1st ("0th")
|
|
||||||
for e in self.entities:
|
|
||||||
self.grid.entities.append(e._entity)
|
|
||||||
|
|
||||||
def create_level(self, depth, _luck = 0):
|
|
||||||
#if depth < 3:
|
|
||||||
# features = None
|
|
||||||
self.level = cl.Level(20, 20)
|
|
||||||
self.grid = self.level.grid
|
|
||||||
if depth in self.level_plan:
|
|
||||||
plan = self.level_plan[depth]
|
|
||||||
else:
|
|
||||||
plan = self.lv_planner(depth)
|
|
||||||
coords = self.level.generate(plan)
|
|
||||||
self.entities = []
|
|
||||||
if self.player:
|
|
||||||
luck = self.player.luck
|
|
||||||
else:
|
|
||||||
luck = 0
|
|
||||||
buttons = []
|
|
||||||
for k, v in sorted(coords, key=lambda i: i[0]): # "button" before "exit"; "button", "button", "door", "exit" -> alphabetical is correct sequence
|
|
||||||
if k == "spawn":
|
|
||||||
if self.player:
|
|
||||||
self.add_entity(self.player)
|
|
||||||
#self.player.draw_pos = v
|
|
||||||
self.spawn_point = v
|
|
||||||
elif k == "boulder":
|
|
||||||
ce.BoulderEntity(v[0], v[1], game=self)
|
|
||||||
elif k == "treasure":
|
|
||||||
ce.TreasureEntity(v[0], v[1], treasure_table = self.treasure_planner(depth + luck), game=self)
|
|
||||||
elif k == "button":
|
|
||||||
buttons.append(v)
|
|
||||||
elif k == "exit":
|
|
||||||
btn = buttons.pop(0)
|
|
||||||
ce.ExitEntity(v[0], v[1], btn[0], btn[1], game=self)
|
|
||||||
elif k == "rat":
|
|
||||||
ce.EnemyEntity(*v, game=self)
|
|
||||||
elif k == "big rat":
|
|
||||||
ce.EnemyEntity(*v, game=self, base_damage=2, hp=4, sprite=130)
|
|
||||||
elif k == "cyclops":
|
|
||||||
ce.EnemyEntity(*v, game=self, base_damage=3, hp=8, sprite=109, base_defense=2)
|
|
||||||
|
|
||||||
#if self.depth > 2:
|
|
||||||
#for i in range(10):
|
|
||||||
# self.spawn_test_rat()
|
|
||||||
|
|
||||||
def stuck(self, sweet_btn, args):
|
|
||||||
if args[3] == "end": return
|
|
||||||
self.create_level(self.depth)
|
|
||||||
self.swap_level(self.level, self.spawn_point)
|
|
||||||
|
|
||||||
def cos_keys(self, key, state):
|
|
||||||
d = None
|
|
||||||
if state == "end": return
|
|
||||||
elif key == "Grave":
|
|
||||||
code.InteractiveConsole(locals=globals()).interact()
|
|
||||||
return
|
|
||||||
elif key == "Z":
|
|
||||||
self.player.do_zap()
|
|
||||||
self.enemy_turn()
|
|
||||||
return
|
|
||||||
elif key == "W": d = (0, -1)
|
|
||||||
elif key == "A": d = (-1, 0)
|
|
||||||
elif key == "S": d = (0, 1)
|
|
||||||
elif key == "D": d = (1, 0)
|
|
||||||
elif key == "Num1":
|
|
||||||
if len(self.player.inventory) > 0:
|
|
||||||
self.player.inventory[0].consume(self.player)
|
|
||||||
self.player.inventory[0] = None
|
|
||||||
self.enemy_turn()
|
|
||||||
else:
|
|
||||||
print("No item")
|
|
||||||
elif key == "Num2":
|
|
||||||
if len(self.player.inventory) > 1:
|
|
||||||
self.player.inventory[1].consume(self.player)
|
|
||||||
self.player.inventory[1] = None
|
|
||||||
else:
|
|
||||||
print("No item")
|
|
||||||
elif key == "Num3":
|
|
||||||
if len(self.player.inventory) > 2:
|
|
||||||
self.player.inventory[2].consume(self.player)
|
|
||||||
self.player.inventory[2] = None
|
|
||||||
else:
|
|
||||||
print("No item")
|
|
||||||
|
|
||||||
#elif key == "M": self.level.generate()
|
|
||||||
#elif key == "R":
|
|
||||||
# self.level.reset()
|
|
||||||
# self.possibilities = None
|
|
||||||
#elif key == "T":
|
|
||||||
# self.level.split()
|
|
||||||
# self.possibilities = None
|
|
||||||
#elif key == "Y": self.level.split(single=True)
|
|
||||||
#elif key == "U": self.level.highlight(+1)
|
|
||||||
#elif key == "I": self.level.highlight(-1)
|
|
||||||
#elif key == "O":
|
|
||||||
# self.level.wall_rooms()
|
|
||||||
# self.possibilities = None
|
|
||||||
#elif key == "P": ct.format_tiles(self.grid)
|
|
||||||
#elif key == "P":
|
|
||||||
#self.possibilities = ct.wfc_pass(self.grid, self.possibilities)
|
|
||||||
elif key == "P":
|
|
||||||
self.depth += 1
|
|
||||||
print(f"Descending: lv {self.depth}")
|
|
||||||
self.stuck(None, [1,2,3,4])
|
|
||||||
elif key == "Period":
|
|
||||||
self.enemy_turn()
|
|
||||||
elif key == "X":
|
|
||||||
self.pull_boulder_search()
|
|
||||||
else:
|
|
||||||
print(key)
|
|
||||||
if d:
|
|
||||||
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
|
|
||||||
self.player.try_move(*d)
|
|
||||||
self.enemy_turn()
|
|
||||||
|
|
||||||
def enemy_turn(self):
|
|
||||||
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
|
|
||||||
for e in self.entities:
|
|
||||||
e.act()
|
|
||||||
# end of enemy turn = player turn
|
|
||||||
for i in self.player.equipped:
|
|
||||||
i.tick()
|
|
||||||
self.gui_update()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def pull_boulder_search(self):
|
|
||||||
for dx, dy in ( (0, -1), (-1, 0), (1, 0), (0, 1) ):
|
|
||||||
for e in self.entities:
|
|
||||||
if e.draw_pos != (self.player.draw_pos[0] + dx, self.player.draw_pos[1] + dy): continue
|
|
||||||
if type(e) == ce.BoulderEntity:
|
|
||||||
self.pull_boulder_move((dx, dy), e)
|
|
||||||
return self.enemy_turn()
|
|
||||||
else:
|
|
||||||
print("No boulder found to pull.")
|
|
||||||
|
|
||||||
def pull_boulder_move(self, p, target_boulder):
|
|
||||||
print(p, target_boulder)
|
|
||||||
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
|
|
||||||
if self.player.try_move(-p[0], -p[1], test=True):
|
|
||||||
old_pos = self.player.draw_pos
|
|
||||||
self.player.try_move(-p[0], -p[1])
|
|
||||||
target_boulder.do_move(*old_pos)
|
|
||||||
|
|
||||||
def swap_level(self, new_level, spawn_point):
|
|
||||||
self.level = new_level
|
|
||||||
self.grid = self.level.grid
|
|
||||||
self.grid.zoom = 2.0
|
|
||||||
# TODO, make an entity mover function
|
|
||||||
#self.add_entity(self.player)
|
|
||||||
self.player.grid = self.grid
|
|
||||||
self.player.draw_pos = spawn_point
|
|
||||||
#self.grid.entities.append(self.player._entity)
|
|
||||||
|
|
||||||
# reform UI (workaround to ui collection iterators crashing)
|
|
||||||
while len(self.ui) > 0:
|
|
||||||
try:
|
|
||||||
self.ui.remove(0)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.ui.append(self.grid)
|
|
||||||
self.ui.append(self.stuck_btn.base_frame)
|
|
||||||
self.ui.append(self.headsup)
|
|
||||||
|
|
||||||
self.level_caption.text = f"Level: {self.depth}"
|
|
||||||
self.ui.append(self.sidebar)
|
|
||||||
self.gui_update()
|
|
||||||
|
|
||||||
class SweetButton:
|
|
||||||
def __init__(self, ui:mcrfpy.UICollection,
|
|
||||||
pos:"Tuple[int, int]",
|
|
||||||
caption:str, font=font, font_size=24, font_color=(255,255,255), font_outline_color=(0, 0, 0), font_outline_width=2,
|
|
||||||
shadow_offset = 8, box_width=200, box_height = 80, shadow_color=(64, 64, 86), box_color=(96, 96, 160),
|
|
||||||
icon=4, icon_scale=1.75, shadow=True, click=lambda *args: None):
|
|
||||||
self.ui = ui
|
|
||||||
#self.shadow_box = mcrfpy.Frame
|
|
||||||
x, y = pos
|
|
||||||
|
|
||||||
# box w/ drop shadow
|
|
||||||
self.shadow_offset = shadow_offset
|
|
||||||
self.base_frame = mcrfpy.Frame(x, y, box_width+shadow_offset, box_height, fill_color = (0, 0, 0, 255))
|
|
||||||
self.base_frame.click = self.do_click
|
|
||||||
|
|
||||||
# drop shadow won't need configured, append directly
|
|
||||||
if shadow:
|
|
||||||
self.base_frame.children.append(mcrfpy.Frame(0, 0, box_width, box_height, fill_color = shadow_color))
|
|
||||||
|
|
||||||
# main button is where the content lives
|
|
||||||
self.main_button = mcrfpy.Frame(shadow_offset, shadow_offset, box_width, box_height, fill_color = box_color)
|
|
||||||
self.click = click
|
|
||||||
self.base_frame.children.append(self.main_button)
|
|
||||||
|
|
||||||
# main button icon
|
|
||||||
self.icon = mcrfpy.Sprite(0, 3, btn_tex, icon, icon_scale)
|
|
||||||
self.main_button.children.append(self.icon)
|
|
||||||
|
|
||||||
# main button caption
|
|
||||||
self.caption = mcrfpy.Caption((40, 3), caption, font, fill_color=font_color)
|
|
||||||
self.caption.size = font_size
|
|
||||||
self.caption.outline_color=font_outline_color
|
|
||||||
self.caption.outline=font_outline_width
|
|
||||||
self.main_button.children.append(self.caption)
|
|
||||||
|
|
||||||
def unpress(self):
|
|
||||||
"""Helper func for when graphics changes or glitches make the button stuck down"""
|
|
||||||
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
|
|
||||||
|
|
||||||
def do_click(self, x, y, mousebtn, event):
|
|
||||||
if event == "start":
|
|
||||||
self.main_button.x, self.main_button.y = (0, 0)
|
|
||||||
elif event == "end":
|
|
||||||
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
|
|
||||||
result = self.click(self, (x, y, mousebtn, event))
|
|
||||||
if result: # return True from event function to instantly un-pop
|
|
||||||
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self):
|
|
||||||
return self.caption.text
|
|
||||||
|
|
||||||
@text.setter
|
|
||||||
def text(self, value):
|
|
||||||
self.caption.text = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sprite_number(self):
|
|
||||||
return self.icon.sprite_number
|
|
||||||
|
|
||||||
@sprite_number.setter
|
|
||||||
def sprite_number(self, value):
|
|
||||||
self.icon.sprite_number = value
|
|
||||||
|
|
||||||
class MainMenu:
|
|
||||||
def __init__(self):
|
|
||||||
mcrfpy.createScene("menu")
|
|
||||||
self.ui = mcrfpy.sceneUI("menu")
|
|
||||||
mcrfpy.setScene("menu")
|
|
||||||
self.crypt = None
|
|
||||||
|
|
||||||
components = []
|
|
||||||
# demo grid
|
|
||||||
self.demo = cl.Level(20, 20)
|
|
||||||
self.grid = self.demo.grid
|
|
||||||
self.grid.zoom = 1.75
|
|
||||||
coords = self.demo.generate(
|
|
||||||
[("boulder", "boulder", "rat", "cyclops", "boulder"), ("spawn"), ("rat", "big rat"), ("button", "boulder", "exit")]
|
|
||||||
)
|
|
||||||
self.entities = []
|
|
||||||
self.add_entity = lambda e: self.entities.append(e)
|
|
||||||
#self.create_level = lambda *args: None
|
|
||||||
buttons = []
|
|
||||||
#self.depth = 20
|
|
||||||
for k, v in sorted(coords, key=lambda i: i[0]): # "button" before "exit"; "button", "button", "door", "exit" -> alphabetical is correct sequence
|
|
||||||
if k == "spawn":
|
|
||||||
self.player = ce.PlayerEntity(game=self)
|
|
||||||
self.player.draw_pos = v
|
|
||||||
#if self.player:
|
|
||||||
# self.add_entity(self.player)
|
|
||||||
# #self.player.draw_pos = v
|
|
||||||
# self.spawn_point = v
|
|
||||||
elif k == "boulder":
|
|
||||||
ce.BoulderEntity(v[0], v[1], game=self)
|
|
||||||
elif k == "treasure":
|
|
||||||
ce.TreasureEntity(v[0], v[1], treasure_table = {}, game=self)
|
|
||||||
elif k == "button":
|
|
||||||
buttons.append(v)
|
|
||||||
elif k == "exit":
|
|
||||||
btn = buttons.pop(0)
|
|
||||||
ce.ExitEntity(v[0], v[1], btn[0], btn[1], game=self)
|
|
||||||
elif k == "rat":
|
|
||||||
ce.EnemyEntity(*v, game=self)
|
|
||||||
elif k == "big rat":
|
|
||||||
ce.EnemyEntity(*v, game=self, base_damage=2, hp=4, sprite=124)
|
|
||||||
elif k == "cyclops":
|
|
||||||
ce.EnemyEntity(*v, game=self, base_damage=3, hp=8, sprite=109, base_defense=2, can_push=True, move_cooldown=0)
|
|
||||||
#self.demo = cl.Level(20, 20)
|
|
||||||
#self.create_level(self.depth)
|
|
||||||
for e in self.entities:
|
|
||||||
self.grid.entities.append(e._entity)
|
|
||||||
def just_wiggle(*args):
|
|
||||||
try:
|
|
||||||
self.player.try_move(*random.choice(((1, 0),(-1, 0),(0, 1),(0, -1))))
|
|
||||||
for e in self.entities:
|
|
||||||
e.act()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
mcrfpy.setTimer("demo_motion", just_wiggle, 100)
|
|
||||||
components.append(
|
|
||||||
self.demo.grid
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# title text
|
|
||||||
drop_shadow = mcrfpy.Caption((150, 10), "Crypt Of Sokoban", font, fill_color=(96, 96, 96), outline_color=(192, 0, 0))
|
|
||||||
drop_shadow.outline = 3
|
|
||||||
drop_shadow.size = 64
|
|
||||||
components.append(
|
|
||||||
drop_shadow
|
|
||||||
)
|
|
||||||
|
|
||||||
title_txt = mcrfpy.Caption((158, 18), "Crypt Of Sokoban", font, fill_color=(255, 255, 255))
|
|
||||||
title_txt.size = 64
|
|
||||||
components.append(
|
|
||||||
title_txt
|
|
||||||
)
|
|
||||||
|
|
||||||
# toast: text over the demo grid that fades out on a timer
|
|
||||||
self.toast = mcrfpy.Caption((150, 400), "", font, fill_color=(0, 0, 0))
|
|
||||||
self.toast.size = 28
|
|
||||||
self.toast.outline = 2
|
|
||||||
self.toast.outline_color = (255, 255, 255)
|
|
||||||
self.toast_event = None
|
|
||||||
components.append(self.toast)
|
|
||||||
|
|
||||||
# button - PLAY
|
|
||||||
#playbtn = mcrfpy.Frame(284, 548, 456, 120, fill_color =
|
|
||||||
play_btn = SweetButton(self.ui, (20, 248), "PLAY", box_width=200, box_height=110, icon=1, icon_scale=2.0, click=self.play)
|
|
||||||
components.append(play_btn.base_frame)
|
|
||||||
|
|
||||||
# button - config menu pane
|
|
||||||
#self.config = lambda self, sweet_btn, *args: print(f"boop, sweet button {sweet_btn} config {args}")
|
|
||||||
config_btn = SweetButton(self.ui, (10, 678), "Settings", icon=2, click=self.show_config)
|
|
||||||
components.append(config_btn.base_frame)
|
|
||||||
|
|
||||||
# button - insta-1080p scaling
|
|
||||||
scale_btn = SweetButton(self.ui, (10+256, 678), "Scale up\nto 1080p", icon=15, click=self.scale)
|
|
||||||
self.scaled = False
|
|
||||||
components.append(scale_btn.base_frame)
|
|
||||||
|
|
||||||
# button - music toggle
|
|
||||||
music_btn = SweetButton(self.ui, (10+256*2, 678), "Music\nON", icon=12, click=self.music_toggle)
|
|
||||||
resources.music_enabled = True
|
|
||||||
resources.music_volume = 40
|
|
||||||
components.append(music_btn.base_frame)
|
|
||||||
|
|
||||||
# button - sfx toggle
|
|
||||||
sfx_btn = SweetButton(self.ui, (10+256*3, 678), "SFX\nON", icon=0, click=self.sfx_toggle)
|
|
||||||
resources.sfx_enabled = True
|
|
||||||
resources.sfx_volume = 40
|
|
||||||
components.append(sfx_btn.base_frame)
|
|
||||||
|
|
||||||
|
|
||||||
[self.ui.append(e) for e in components]
|
|
||||||
|
|
||||||
def toast_say(self, txt, delay=10):
|
|
||||||
"kick off a toast event"
|
|
||||||
if self.toast_event is not None:
|
|
||||||
mcrfpy.delTimer("toast_timer")
|
|
||||||
self.toast.text = txt
|
|
||||||
self.toast_event = 350
|
|
||||||
self.toast.fill_color = (255, 255, 255, 255)
|
|
||||||
self.toast.outline = 2
|
|
||||||
self.toast.outline_color = (0, 0, 0, 255)
|
|
||||||
mcrfpy.setTimer("toast_timer", self.toast_callback, 100)
|
|
||||||
|
|
||||||
def toast_callback(self, *args):
|
|
||||||
"fade out the toast text"
|
|
||||||
self.toast_event -= 5
|
|
||||||
if self.toast_event < 0:
|
|
||||||
self.toast_event = None
|
|
||||||
mcrfpy.delTimer("toast_timer")
|
|
||||||
mcrfpy.text = ""
|
|
||||||
return
|
|
||||||
a = min(self.toast_event, 255)
|
|
||||||
self.toast.fill_color = (255, 255, 255, a)
|
|
||||||
self.toast.outline_color = (0, 0, 0, a)
|
|
||||||
|
|
||||||
def show_config(self, sweet_btn, args):
|
|
||||||
self.toast_say("Beep, Boop! Configurations will go here.")
|
|
||||||
|
|
||||||
def play(self, sweet_btn, args):
|
|
||||||
#if args[3] == "start": return # DRAMATIC on release action!
|
|
||||||
if args[3] == "end": return
|
|
||||||
self.crypt = Crypt()
|
|
||||||
#mcrfpy.setScene("play")
|
|
||||||
self.crypt.start()
|
|
||||||
|
|
||||||
def scale(self, sweet_btn, args, window_scale=None):
|
|
||||||
if args[3] == "end": return
|
|
||||||
if not window_scale:
|
|
||||||
self.scaled = not self.scaled
|
|
||||||
window_scale = 1.3
|
|
||||||
else:
|
|
||||||
self.scaled = True
|
|
||||||
sweet_btn.unpress()
|
|
||||||
if self.scaled:
|
|
||||||
self.toast_say("Windowed mode only, sorry!\nCheck Settings for for fine-tuned controls.")
|
|
||||||
mcrfpy.setScale(window_scale)
|
|
||||||
sweet_btn.text = "Scale down\n to 1.0x"
|
|
||||||
else:
|
|
||||||
mcrfpy.setScale(1.0)
|
|
||||||
sweet_btn.text = "Scale up\nto 1080p"
|
|
||||||
|
|
||||||
def music_toggle(self, sweet_btn, args):
|
|
||||||
if args[3] == "end": return
|
|
||||||
resources.music_enabled = not resources.music_enabled
|
|
||||||
print(f"music: {resources.music_enabled}")
|
|
||||||
if resources.music_enabled:
|
|
||||||
mcrfpy.setMusicVolume(self.music_volume)
|
|
||||||
sweet_btn.text = "Music is ON"
|
|
||||||
sweet_btn.sprite_number = 12
|
|
||||||
else:
|
|
||||||
self.toast_say("Use your volume keys or\nlook in Settings for a volume meter.")
|
|
||||||
mcrfpy.setMusicVolume(0)
|
|
||||||
sweet_btn.text = "Music is OFF"
|
|
||||||
sweet_btn.sprite_number = 17
|
|
||||||
|
|
||||||
def sfx_toggle(self, sweet_btn, args):
|
|
||||||
if args[3] == "end": return
|
|
||||||
resources.sfx_enabled = not resources.sfx_enabled
|
|
||||||
#print(f"sfx: {resources.sfx_enabled}")
|
|
||||||
if resources.sfx_enabled:
|
|
||||||
mcrfpy.setSoundVolume(self.sfx_volume)
|
|
||||||
sweet_btn.text = "SFX are ON"
|
|
||||||
sweet_btn.sprite_number = 0
|
|
||||||
else:
|
|
||||||
self.toast_say("Use your volume keys or\nlook in Settings for a volume meter.")
|
|
||||||
mcrfpy.setSoundVolume(0)
|
|
||||||
sweet_btn.text = "SFX are OFF"
|
|
||||||
sweet_btn.sprite_number = 17
|
|
||||||
|
|
||||||
mainmenu = MainMenu()
|
|
||||||
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
#print("Hello mcrogueface")
|
||||||
|
import mcrfpy
|
||||||
|
import cos_play
|
||||||
|
# Universal stuff
|
||||||
|
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
||||||
|
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) #12, 11)
|
||||||
|
texture_cold = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) #12, 11)
|
||||||
|
texture_hot = mcrfpy.Texture("assets/kenney_lava.png", 16, 16) #12, 11)
|
||||||
|
|
||||||
|
# Test stuff
|
||||||
|
mcrfpy.createScene("boom")
|
||||||
|
mcrfpy.setScene("boom")
|
||||||
|
ui = mcrfpy.sceneUI("boom")
|
||||||
|
box = mcrfpy.Frame(40, 60, 200, 300, fill_color=(255,128,0), outline=4.0, outline_color=(64,64,255,96))
|
||||||
|
ui.append(box)
|
||||||
|
|
||||||
|
#caption = mcrfpy.Caption(10, 10, "Clicky", font, (255, 255, 255, 255), (0, 0, 0, 255))
|
||||||
|
#box.click = lambda x, y, btn, type: print("Hello callback: ", x, y, btn, type)
|
||||||
|
#box.children.append(caption)
|
||||||
|
|
||||||
|
test_sprite_number = 86
|
||||||
|
sprite = mcrfpy.Sprite(20, 60, texture, test_sprite_number, 4.0)
|
||||||
|
spritecap = mcrfpy.Caption(5, 5, "60", font)
|
||||||
|
def click_sprite(x, y, btn, action):
|
||||||
|
global test_sprite_number
|
||||||
|
if action != "start": return
|
||||||
|
if btn in ("left", "wheel_up"):
|
||||||
|
test_sprite_number -= 1
|
||||||
|
elif btn in ("right", "wheel_down"):
|
||||||
|
test_sprite_number += 1
|
||||||
|
sprite.sprite_number = test_sprite_number # TODO - inconsistent naming for __init__, __repr__ and getsetter: sprite_number vs sprite_index
|
||||||
|
spritecap.text = test_sprite_number
|
||||||
|
|
||||||
|
sprite.click = click_sprite # TODO - sprites don't seem to correct for screen position or scale when clicking
|
||||||
|
box.children.append(sprite)
|
||||||
|
box.children.append(spritecap)
|
||||||
|
box.click = click_sprite
|
||||||
|
|
||||||
|
f_a = mcrfpy.Frame(250, 60, 80, 80, fill_color=(255, 92, 92))
|
||||||
|
f_a_txt = mcrfpy.Caption(5, 5, "0", font)
|
||||||
|
|
||||||
|
f_b = mcrfpy.Frame(340, 60, 80, 80, fill_color=(92, 255, 92))
|
||||||
|
f_b_txt = mcrfpy.Caption(5, 5, "0", font)
|
||||||
|
|
||||||
|
f_c = mcrfpy.Frame(430, 60, 80, 80, fill_color=(92, 92, 255))
|
||||||
|
f_c_txt = mcrfpy.Caption(5, 5, "0", font)
|
||||||
|
|
||||||
|
|
||||||
|
ui.append(f_a)
|
||||||
|
f_a.children.append(f_a_txt)
|
||||||
|
ui.append(f_b)
|
||||||
|
f_b.children.append(f_b_txt)
|
||||||
|
ui.append(f_c)
|
||||||
|
f_c.children.append(f_c_txt)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
def ding(*args):
|
||||||
|
f_a_txt.text = str(sys.getrefcount(ding)) + " refs"
|
||||||
|
f_b_txt.text = sys.getrefcount(dong)
|
||||||
|
f_c_txt.text = sys.getrefcount(stress_test)
|
||||||
|
|
||||||
|
def dong(*args):
|
||||||
|
f_a_txt.text = str(sys.getrefcount(ding)) + " refs"
|
||||||
|
f_b_txt.text = sys.getrefcount(dong)
|
||||||
|
f_c_txt.text = sys.getrefcount(stress_test)
|
||||||
|
|
||||||
|
running = False
|
||||||
|
timers = []
|
||||||
|
|
||||||
|
def add_ding():
|
||||||
|
global timers
|
||||||
|
n = len(timers)
|
||||||
|
mcrfpy.setTimer(f"timer{n}", ding, 100)
|
||||||
|
print("+1 ding:", timers)
|
||||||
|
|
||||||
|
def add_dong():
|
||||||
|
global timers
|
||||||
|
n = len(timers)
|
||||||
|
mcrfpy.setTimer(f"timer{n}", dong, 100)
|
||||||
|
print("+1 dong:", timers)
|
||||||
|
|
||||||
|
def remove_random():
|
||||||
|
global timers
|
||||||
|
target = random.choice(timers)
|
||||||
|
print("-1 timer:", target)
|
||||||
|
print("remove from list")
|
||||||
|
timers.remove(target)
|
||||||
|
print("delTimer")
|
||||||
|
mcrfpy.delTimer(target)
|
||||||
|
print("done")
|
||||||
|
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
def stress_test(*args):
|
||||||
|
global running
|
||||||
|
global timers
|
||||||
|
if not running:
|
||||||
|
print("stress test initial")
|
||||||
|
running = True
|
||||||
|
timers.append("recurse")
|
||||||
|
add_ding()
|
||||||
|
add_dong()
|
||||||
|
mcrfpy.setTimer("recurse", stress_test, 1000)
|
||||||
|
mcrfpy.setTimer("terminate", lambda *args: mcrfpy.delTimer("recurse"), 30000)
|
||||||
|
ding(); dong()
|
||||||
|
else:
|
||||||
|
#print("stress test random activity")
|
||||||
|
#random.choice([
|
||||||
|
# add_ding,
|
||||||
|
# add_dong,
|
||||||
|
# remove_random
|
||||||
|
# ])()
|
||||||
|
#print(timers)
|
||||||
|
print("Segfaultin' time")
|
||||||
|
mcrfpy.delTimer("recurse")
|
||||||
|
print("Does this still work?")
|
||||||
|
time.sleep(0.5)
|
||||||
|
print("How about now?")
|
||||||
|
|
||||||
|
|
||||||
|
stress_test()
|
||||||
|
|
||||||
|
|
||||||
|
# Loading Screen
|
||||||
|
mcrfpy.createScene("loading")
|
||||||
|
ui = mcrfpy.sceneUI("loading")
|
||||||
|
#mcrfpy.setScene("loading")
|
||||||
|
logo_texture = mcrfpy.Texture("assets/temp_logo.png", 1024, 1024)#1, 1)
|
||||||
|
logo_sprite = mcrfpy.Sprite(50, 50, logo_texture, 0, 0.5)
|
||||||
|
ui.append(logo_sprite)
|
||||||
|
logo_sprite.click = lambda *args: mcrfpy.setScene("menu")
|
||||||
|
logo_caption = mcrfpy.Caption(70, 600, "Click to Proceed", font, (255, 0, 0, 255), (0, 0, 0, 255))
|
||||||
|
#logo_caption.fill_color =(255, 0, 0, 255)
|
||||||
|
ui.append(logo_caption)
|
||||||
|
|
||||||
|
|
||||||
|
# menu screen
|
||||||
|
mcrfpy.createScene("menu")
|
||||||
|
|
||||||
|
for e in [
|
||||||
|
mcrfpy.Caption(10, 10, "Crypt of Sokoban", font, (255, 255, 255), (0, 0, 0)),
|
||||||
|
mcrfpy.Caption(20, 55, "a McRogueFace demo project", font, (192, 192, 192), (0, 0, 0)),
|
||||||
|
mcrfpy.Frame(15, 70, 150, 60, fill_color=(64, 64, 128)),
|
||||||
|
mcrfpy.Frame(15, 145, 150, 60, fill_color=(64, 64, 128)),
|
||||||
|
mcrfpy.Frame(15, 220, 150, 60, fill_color=(64, 64, 128)),
|
||||||
|
mcrfpy.Frame(15, 295, 150, 60, fill_color=(64, 64, 128)),
|
||||||
|
#mcrfpy.Frame(900, 10, 100, 100, fill_color=(255, 0, 0)),
|
||||||
|
]:
|
||||||
|
mcrfpy.sceneUI("menu").append(e)
|
||||||
|
|
||||||
|
def click_once(fn):
|
||||||
|
def wraps(*args, **kwargs):
|
||||||
|
#print(args)
|
||||||
|
action = args[3]
|
||||||
|
if action != "start": return
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
return wraps
|
||||||
|
|
||||||
|
@click_once
|
||||||
|
def asdf(x, y, btn, action):
|
||||||
|
print(f"clicky @({x},{y}) {action}->{btn}")
|
||||||
|
|
||||||
|
@click_once
|
||||||
|
def clicked_exit(*args):
|
||||||
|
mcrfpy.exit()
|
||||||
|
|
||||||
|
menu_btns = [
|
||||||
|
("Boom", lambda *args: 1 / 0),
|
||||||
|
("Exit", clicked_exit),
|
||||||
|
("About", lambda *args: mcrfpy.setScene("about")),
|
||||||
|
("Settings", lambda *args: mcrfpy.setScene("settings")),
|
||||||
|
("Start", lambda *args: mcrfpy.setScene("play"))
|
||||||
|
]
|
||||||
|
for i in range(len(mcrfpy.sceneUI("menu"))):
|
||||||
|
e = mcrfpy.sceneUI("menu")[i] # TODO - fix iterator
|
||||||
|
#print(e, type(e))
|
||||||
|
if type(e) is not mcrfpy.Frame: continue
|
||||||
|
label, fn = menu_btns.pop()
|
||||||
|
#print(label)
|
||||||
|
e.children.append(mcrfpy.Caption(5, 5, label, font, (192, 192, 255), (0,0,0)))
|
||||||
|
e.click = fn
|
||||||
|
|
||||||
|
|
||||||
|
# settings screen
|
||||||
|
mcrfpy.createScene("settings")
|
||||||
|
window_scaling = 1.0
|
||||||
|
|
||||||
|
scale_caption = mcrfpy.Caption(180, 70, "1.0x", font, (255, 255, 255), (0, 0, 0))
|
||||||
|
#scale_caption.fill_color = (255, 255, 255) # TODO - mcrfpy.Caption.__init__ is not setting colors
|
||||||
|
for e in [
|
||||||
|
mcrfpy.Caption(10, 10, "Settings", font, (255, 255, 255), (0, 0, 0)),
|
||||||
|
mcrfpy.Frame(15, 70, 150, 60, fill_color=(64, 64, 128)), # +
|
||||||
|
mcrfpy.Frame(300, 70, 150, 60, fill_color=(64, 64, 128)), # -
|
||||||
|
mcrfpy.Frame(15, 295, 150, 60, fill_color=(64, 64, 128)),
|
||||||
|
scale_caption,
|
||||||
|
]:
|
||||||
|
mcrfpy.sceneUI("settings").append(e)
|
||||||
|
|
||||||
|
@click_once
|
||||||
|
def game_scale(x, y, btn, action, delta):
|
||||||
|
global window_scaling
|
||||||
|
print(f"WIP - scale the window from {window_scaling:.1f} to {window_scaling+delta:.1f}")
|
||||||
|
window_scaling += delta
|
||||||
|
scale_caption.text = f"{window_scaling:.1f}x"
|
||||||
|
mcrfpy.setScale(window_scaling)
|
||||||
|
#mcrfpy.setScale(2)
|
||||||
|
|
||||||
|
settings_btns = [
|
||||||
|
("back", lambda *args: mcrfpy.setScene("menu")),
|
||||||
|
("-", lambda x, y, btn, action: game_scale(x, y, btn, action, -0.1)),
|
||||||
|
("+", lambda x, y, btn, action: game_scale(x, y, btn, action, +0.1))
|
||||||
|
]
|
||||||
|
|
||||||
|
for i in range(len(mcrfpy.sceneUI("settings"))):
|
||||||
|
e = mcrfpy.sceneUI("settings")[i] # TODO - fix iterator
|
||||||
|
#print(e, type(e))
|
||||||
|
if type(e) is not mcrfpy.Frame: continue
|
||||||
|
label, fn = settings_btns.pop()
|
||||||
|
#print(label, fn)
|
||||||
|
e.children.append(mcrfpy.Caption(5, 5, label, font, (192, 192, 255), (0,0,0)))
|
||||||
|
e.click = fn
|
|
@ -1,144 +0,0 @@
|
||||||
145# open space
|
|
||||||
???
|
|
||||||
?_?
|
|
||||||
???
|
|
||||||
|
|
||||||
184:0.03# open space variant
|
|
||||||
???
|
|
||||||
?_?
|
|
||||||
???
|
|
||||||
|
|
||||||
146# lone wall / pillar
|
|
||||||
___
|
|
||||||
_X_
|
|
||||||
___
|
|
||||||
|
|
||||||
132# top left corner
|
|
||||||
?_?
|
|
||||||
_XX
|
|
||||||
?X?
|
|
||||||
|
|
||||||
133# plain horizontal wall
|
|
||||||
???
|
|
||||||
XXX
|
|
||||||
?_?
|
|
||||||
|
|
||||||
182:0.04# plain horizontal wall variant
|
|
||||||
???
|
|
||||||
XXX
|
|
||||||
?_?
|
|
||||||
|
|
||||||
183:0.04# plain horizontal wall variant
|
|
||||||
???
|
|
||||||
XXX
|
|
||||||
?_?
|
|
||||||
|
|
||||||
157:0.01# plain horizontal wall variant
|
|
||||||
???
|
|
||||||
XXX
|
|
||||||
?_?
|
|
||||||
|
|
||||||
135# top right corner
|
|
||||||
?_?
|
|
||||||
XX_
|
|
||||||
?X?
|
|
||||||
|
|
||||||
144@N132@s144@n144@n192@s192@S156@n171@s169@n180# Left side wall. Space on both sides rule may make the dungeon less robust (no double-walls allowed)
|
|
||||||
?X?
|
|
||||||
?X_
|
|
||||||
?X?
|
|
||||||
|
|
||||||
147@N135@s147@n147@n193@s193@S159@n170@s168@n181# Right side wall
|
|
||||||
?X?
|
|
||||||
_X?
|
|
||||||
?X?
|
|
||||||
|
|
||||||
156# bottom left corner
|
|
||||||
?X?
|
|
||||||
_XX
|
|
||||||
?_?
|
|
||||||
|
|
||||||
159# bottom right corner
|
|
||||||
?X?
|
|
||||||
XX_
|
|
||||||
?_?
|
|
||||||
|
|
||||||
192@n144@s144@s169# vertical T, left wall
|
|
||||||
?X?
|
|
||||||
?XX
|
|
||||||
?X?
|
|
||||||
|
|
||||||
193@n147@s147@s168# vertical T, right wall
|
|
||||||
?X?
|
|
||||||
XX?
|
|
||||||
?X?
|
|
||||||
|
|
||||||
180@s144@s144@s169# horizontal T, left wall
|
|
||||||
???
|
|
||||||
XXX
|
|
||||||
?X?
|
|
||||||
|
|
||||||
181@s147@s147@s168# horizontal T, right wall
|
|
||||||
???
|
|
||||||
XXX
|
|
||||||
?X?
|
|
||||||
|
|
||||||
195@W133@W182@W183@W157# wall for edge of a gap
|
|
||||||
??_
|
|
||||||
XX_
|
|
||||||
?__
|
|
||||||
|
|
||||||
195
|
|
||||||
?__
|
|
||||||
XX_
|
|
||||||
?__
|
|
||||||
|
|
||||||
194@E133@E182@E183@E157# wall for edge of a gap (R)
|
|
||||||
_??
|
|
||||||
_XX
|
|
||||||
__?
|
|
||||||
|
|
||||||
194
|
|
||||||
__?
|
|
||||||
_XX
|
|
||||||
__?
|
|
||||||
|
|
||||||
195@W133@W182@W183@W157# wall for edge of a gap
|
|
||||||
?__
|
|
||||||
XX_
|
|
||||||
??_
|
|
||||||
|
|
||||||
194@E133@E182@E183@E157# wall for edge of a gap (R)
|
|
||||||
__?
|
|
||||||
_XX
|
|
||||||
_??
|
|
||||||
|
|
||||||
195@W133@W182@W183@W157# wall for edge of a gap
|
|
||||||
??_
|
|
||||||
XX_
|
|
||||||
?__
|
|
||||||
|
|
||||||
194@E133@E182@E183@E157# wall for edge of a gap (R)
|
|
||||||
_??
|
|
||||||
_XX
|
|
||||||
__?
|
|
||||||
|
|
||||||
168@n147@n170@n135@n181@n193# right vertical wall, gap below
|
|
||||||
?X?
|
|
||||||
_X_
|
|
||||||
?_?
|
|
||||||
|
|
||||||
169@n144@n171@n132@n192@n180# left vertical wall, gap below
|
|
||||||
?X?
|
|
||||||
_X_
|
|
||||||
?_?
|
|
||||||
|
|
||||||
170@s147@s168@s133@s182@s183@s157@s193@s181# right vertical wall, gap above
|
|
||||||
?_?
|
|
||||||
_X_
|
|
||||||
?X?
|
|
||||||
|
|
||||||
171@s144@s169@s133@s182@s183@s157@s171@s180# left vertical wall, gap above
|
|
||||||
?_?
|
|
||||||
_X_
|
|
||||||
?X?
|
|
Loading…
Reference in New Issue