Compare commits

..

85 Commits

Author SHA1 Message Date
John McCardle 3325e4895e Refactor: Python 3.12, build libtcod & SFML from source. Cmake build. Directory cleanup
directories needed:
* build - for cmake output
* deps - stuff needed to compile McRogueface (headers)
	libtcod -> ../modules/libtcod/src/libtcod
	sfml -> ../modules/SFML/include/SFML
	python -> ../modules/cpython/Include
* lib - stuff needed to link McRogueFace (shared objects); also required at runtime
	libtcod -> `../modules/libtcod/buildsys/autotools/.libs/libtcod.so.1.0.24`
	sfml -> `../modules/SFML/build/lib/*`
	python -> `../modules/cpython/libpython3.12.so`; standard lib at ../modules/cpython/build/lib.linux-x86_64-3.12 & ../modules/cpython/Lib

You can get dependencies by:
 - Build from source (i.e. all submodules)
 - Go download them from each project's website
 - install packages from your distro and symlink them to deps/lib directories
2024-02-24 20:40:40 -05:00
John McCardle 705943abba initial cmake config (builds, python standard library not available) 2024-02-23 21:55:16 -05:00
John McCardle 47b485ca7d Submodules: Add dependencies
This is the prerequisite to:
* complete from-source build
* removing compilation libraries from the git repo
* rebasing the project for "alpha 0.1" release
2024-02-23 08:37:36 -05:00
John McCardle f2eaee95ec (minor) sprite object enabled in collection 2023-10-20 22:23:20 -04:00
John McCardle 494658e5c3 Sprite fixes. Changing sprite_number now changes the visible texture. Repr fixed. 2023-09-15 22:09:29 -04:00
John McCardle 6e820af8c4 mcrfpy.Sprite / PyUISpriteType compiles; too excited to fully test it (todo: modify the macro to get instances into and out of collections) 2023-09-13 23:23:08 -04:00
John McCardle cf485ef327 Refactoring UISprite to play well with Python API and match existing code styles. Default constructor will have to go, which complicates the Python class slightly for mcrfpy.Sprite 2023-09-11 20:30:10 -04:00
John McCardle 3a1432212f UICollection.remove 2023-09-09 10:14:11 -04:00
John McCardle 38b6a3cade UICollection.append, tests are good for Caption and Frame objects created by Python to be drawn by the UITestScene 2023-09-09 08:49:02 -04:00
John McCardle 1bbb0aa5b8 Caption object seems to be instantiable with a Font object now. Can't test actual rendering without a way to add objects to a collection. 2023-09-07 23:10:21 -04:00
John McCardle bec2b3294d Added PyFont/mcrfpy.Font object 2023-09-07 22:25:19 -04:00
John McCardle 9486104377 Converted py_instance to a macro (don't ask me why it doesn't work as a function) and first pass at UICaption functionality. UISprite C++ tests. 2023-09-03 20:40:52 -04:00
John McCardle 5267287b05 checkpoint: found that py_instance causes segfaults when called separately, but the same exact code inside of the _getitem method works fine. I can't explain that. I guess I'll turn it into a macro so the actions are inline and I can move on to finishing the other UI classes. 2023-09-03 12:46:23 -04:00
John McCardle b8af8bc870 UIDrawable to Python object (untested, but compiling) 2023-09-02 20:02:07 -04:00
John McCardle 0ef0a5d506 Switched UIFrame and Scene to store their UIDrawables in shared_ptr to vector, instead of directly to vector. Every object that can be exposed to Python has to be safely shareable so it doesn't become a segfault, and that includes the UIDrawable collections AND the UIDrawable members. So we get the terrifying type for collections of child elements: 'std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>>'. May I be forgiven for my sins 2023-09-02 14:00:48 -04:00
John McCardle a41d3d4a54 Iterator class fleshed out. Several implementations left to do, but it compiles, and I think every function definition for the PyUICollectionType and PyUICollectionIterType is now in place 2023-09-02 09:22:34 -04:00
John McCardle 6d4bc2989c UICollection work: fixed compilation, still quite a bit of stubs 2023-09-02 04:40:05 -04:00
John McCardle d5a7cbca85 In progress: UICollection. mcrfpydef::PyUICollection_sqmethods needs to be made static or moved somewhere it won't be multiply defined 2023-09-01 23:31:31 -04:00
John McCardle 5d8510747c Color and Frame classes pretty well integrated from the Python perspective now 2023-09-01 18:28:59 -04:00
John McCardle 06052c81c9 PyUIFrame looking not half bad. There seems to be a glitch with the color values. 2023-09-01 12:14:24 -04:00
John McCardle 6fe7b842ef Successful use of a copy-modify-paste template of Python type (UIFrame -> PyUIFrameObject) 2023-08-31 22:32:58 -04:00
John McCardle 50d926fe37 Aug 30/31 updates. Tinkering with shared_ptr. Working towards exposing UI objects to Python UI. Color object updated for shared_ptr structure that will be repeated for the rest of the UI objects. Still a lot of open questions, but committing here to get back on track after a few hours wasted trying to solve this problem too generally via templates. 2023-08-31 13:51:56 -04:00
John McCardle 795701c986 Cleanup: less curly braces in destructor 2023-08-30 14:50:27 -04:00
John McCardle 884a49a63a Switching UIFrame to sf::Color* for outline and fill members. Haven't tested with Python integration, but I wrote the methods to take a crack at it 2023-08-30 14:38:49 -04:00
John McCardle c4d5a497d4 Color container type for Python working. I still need to implement UIFrame using it. 2023-08-28 05:44:26 -04:00
John McCardle ba97aebf3e Showing FPS on title bar (GameEngine) 2023-08-27 19:58:15 -04:00
John McCardle ac0ec4bb71 In-work: Python segfaults when adding new objects to module 2023-08-25 21:57:42 -04:00
John McCardle a455c44b34 Debugging & build with debug symbols scripts 2023-08-25 21:56:27 -04:00
John McCardle 96e78e6150 Prepwork: marked the spot for adding more types to the Python module 2023-08-21 20:27:47 -04:00
John McCardle 0dd86056a8 Cleanup: remove python embedding test file 2023-08-12 20:06:16 -04:00
John McCardle 26cb410b8e Cleanup: Remove UITestScene. I believe test functionality will be better expressed as Python scripts 2023-08-12 19:48:29 -04:00
John McCardle d09fc87499 Cleanup: remove Item class/component. This may be added back later, but it's not in the EngJam 2023 plan and is being removed as a noisy, underdeveloped concept. 2023-08-12 15:37:46 -04:00
John McCardle b022dfa6e8 Cleanup: remove VectorShape class (it'll return, someday...) 2023-08-12 15:07:48 -04:00
John McCardle 232ce34d54 Cleanup: remove references to DrawSprite API method (debug method to draw on SFML window for a single frame) 2023-08-12 10:57:53 -04:00
John McCardle c1c17bab69 Basic, buggy movement purely from Python API 2023-07-17 22:08:06 -04:00
John McCardle e85861cbb2 I've worked keybinding functionality into Python, but there are some workarounds and notes (See the Jankfile) 2023-07-17 16:15:35 -04:00
John McCardle d6446e18ea Tinkering with input
I want to move keyboard input defs to the Python API. I laid the groundwork for it today.

From the JANKFILE:

- working on API endpoint `_registerInputAction`.

it will add "_py" as a suffix to the action string and register it along with other scene actions.

- Adding public Scene methods. These are on the base class with default of return `false`.

`bool Scene::registerActionInjected(int code, std::string name)` and `unregisterActionInjected`

the PythonScene (and other scenes that support injected user input) can override this method, check existing registrations, and return `true` when succeeding.

Also, upgraded to C++20 (g++ `c++2a`), mostly because I want to use map::contains.
2023-07-13 23:01:09 -04:00
John McCardle d3826804a0 (Minor) It's CO*M*P4300 2023-07-08 20:00:30 -04:00
John McCardle b4c49c4619 Giving myself credit for LGJ2023 tech demo features 2023-07-08 19:47:06 -04:00
John McCardle 76ac236be3 Linux Game Jam 2023 mini-contribution 2023-07-08 19:42:47 -04:00
John McCardle 97793fb26b Clean up console output for 7DRL submission 2023-03-12 00:32:27 -05:00
John McCardle b3134f0890 Basic hallways 2023-03-11 23:34:34 -05:00
John McCardle dfcc39dd43 toggleable camera following that allows for pan action 2023-03-11 21:54:54 -05:00
John McCardle 29ac89b489 bugfix: use float instead of int for modMenu/listMenus API calls, as this was corrupting the coordinates of sprites on uimenus 2023-03-11 17:15:06 -05:00
John McCardle b4daac6e0c Camera following functionality, first pass 2023-03-11 16:11:10 -05:00
John McCardle 3fd60d76ea Making empty space transparent to fix FOV, more generous FOV algorithm 2023-03-10 21:42:50 -05:00
John McCardle 99fa92f8ba Field of view, discovered tiles, opaque fog of war rendering 2023-03-10 19:39:44 -05:00
John McCardle 9441f357df 'entity only' grid update, saves lots of throughput on larger grids 2023-03-10 12:50:49 -05:00
John McCardle 34feb226e4 collision 2023-03-10 11:35:46 -05:00
John McCardle 486a1cd17c Keyboard control 2023-03-10 09:21:56 -05:00
John McCardle 8d9148b88d Music tester & animation work 2023-03-09 20:40:47 -05:00
John McCardle f1798189f0 Animation testing w/ Miniworld sprites 2023-03-09 14:29:37 -05:00
John McCardle 5b168737ce License update and bug list. Final Github release during 7DRL 2023. 2023-03-09 08:50:11 -05:00
John McCardle 6875cb5fe1 Spawning & drawing entities from Python API 2023-03-09 08:44:04 -05:00
John McCardle 87483cc8ad Sound APIs for Python 2023-03-08 12:34:03 -05:00
John McCardle 620def19f1 Fixed animations, jank-noted some issues & workarounds with the Animation python API 2023-03-08 09:20:57 -05:00
John McCardle c9b97b9b35 TestScene script for animation testing 2023-03-07 20:11:11 -05:00
John McCardle 8e59152a8f Windows fixes 2023-03-07 17:09:54 -08:00
John McCardle fedfcd46a3 Test animation now moves the entire UIMenu object (and children) 2023-03-07 20:03:09 -05:00
John McCardle c551c721ce Animation work: removing pointers from the entire class in favor of std::function/lambdas to write values. This actually works with SFML classes, because I can wrap the setter class 2023-03-07 07:39:41 -05:00
John McCardle d74635ee4e Check in... animations are roughly half built 2023-03-06 20:54:23 -05:00
John McCardle 47e823d5b9 Animation class (not tested) 2023-03-05 22:44:39 -05:00
John McCardle 6dbf8a5119 Pan/Zoom grids, Python basic generation template provided 2023-03-05 19:58:20 -05:00
John McCardle a53ae29467 Python object models 2023-03-04 23:18:21 -05:00
John McCardle 257aa3c3d2 Fully python-driven scene. Lots of interaction needs testing but the broad strokes are there for mouse pan/zoom on multiple grids and any number of UIs 2023-03-04 23:04:16 -05:00
John McCardle a4b6c2c428 Pan, zoom, and mouse-to-gridsquare translation that I can be proud of. Cleanup required, though 2023-03-04 19:04:05 -05:00
John McCardle b0ef1d2303 Tweak point conversion to prevent off-grid (negative) points from registering as on the grid 2023-03-04 00:18:19 -05:00
John McCardle b3f946ecb2 API endpoints: Create and retrieve grid/gridpoint objects 2023-03-03 23:57:42 -05:00
John McCardle 6a4150ec05 Screen to Grid is working pretty reliably, even if switching to float coordinates did make zoom at high values a bit wobbly. 2023-03-03 22:26:38 -05:00
John McCardle e295bfb742 python callbacks, working on grid 2023-03-03 22:16:47 -05:00
John McCardle 2ec97dfb1c Full modification of UI items seems to be working 2023-03-02 22:07:23 -05:00
John McCardle f89896176c Modify UI objects by calling mcrfpy.modMenu(m) 2023-03-02 20:41:43 -05:00
John McCardle de753713d5 UI from Python now working fairly comprehensively 2023-03-02 18:57:09 -05:00
John McCardle c8124e84dc Updated UIMenu to a map on the C++ side, Python gets the title property so changes can be properly, jankily, looked up 2023-03-02 06:35:13 -05:00
John McCardle a1e9129923 Send Menu color to Python 2023-03-02 05:53:17 -05:00
John McCardle f23dfbe4ba Another checkpoint. Compiling/building/running - Python API tests from in-engine REPL are not passing, though 2023-03-01 21:37:42 -05:00
John McCardle 1e9fd77a13 JANK MODE: Messy / broken commit - in progress
Needed to make a checkpoint, gods forgive me for committing known broken code straight to master. The jam has in a sense already begun.

I tested a smaller solution in the xplat_concept repo earlier today.
In short, I'm going to build a janky method to add new + report existing UI elements. After that's done, the UI building should be done from python modules, hastening the UI design.

This is ugly and bad, I am truly sorry. We just need to get through 7DRL, so I can't make it pretty today.
2023-02-28 23:19:43 -05:00
John McCardle 2c1946c29b Grid - widget for holding multi-layer map data 2023-02-27 07:02:34 -05:00
John McCardle 6d05f8bc63 Return NONE properly from test API point 2023-02-27 07:01:46 -05:00
John McCardle a4d0efe334 To-do list progress updated 2023-02-26 10:53:58 -05:00
John McCardle 6a47bc1e28 Windows/msvc lockstep changes 2023-02-26 07:51:03 -08:00
John McCardle 6a2c3c6c36 McRogueFace Python API (McRFPy_API) demo class 2023-02-26 10:23:44 -05:00
John McCardle a6f59085eb Update MSVC project for Windows build 2023-02-25 03:17:05 -08:00
John McCardle d2499a67f8 Porting in old gamejam code. Removed SOME cruft, more likely remains. Sound + sprite test. 2023-02-24 23:46:34 -05:00
John McCardle 1784489dfb Windows / MSVC commit. Bring your own Python PCBuild directory, I don't want to upload it to git. 2023-02-23 19:38:41 -08:00
101 changed files with 5635 additions and 8039 deletions

View File

@ -17,6 +17,10 @@ include_directories(${CMAKE_SOURCE_DIR}/deps/libtcod)
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
# TODO: Move this into the WIN32 if block below (as 'else')
#include_directories(${CMAKE_SOURCE_DIR}/platform/linux)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
# Collect all the source files
file(GLOB_RECURSE SOURCES "src/*.cpp")
@ -39,14 +43,11 @@ if(WIN32)
# include_directories(path_to_additional_includes)
# link_directories(path_to_additional_libs)
# list(APPEND LINK_LIBS additional_windows_libs)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
else()
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
endif()
# Add the directory where the linker should look for the libraries
#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
add_executable(mcrogueface ${SOURCES})
@ -67,9 +68,9 @@ add_custom_command(TARGET mcrogueface POST_BUILD
# Copy Python standard library to build directory
add_custom_command(TARGET mcrogueface POST_BUILD
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
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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

View File

@ -1,157 +0,0 @@
aqua #00FFFF
black #000000
blue #0000FF
fuchsia #FF00FF
gray #808080
green #008000
lime #00FF00
maroon #800000
navy #000080
olive #808000
purple #800080
red #FF0000
silver #C0C0C0
teal #008080
white #FFFFFF
yellow #FFFF00
aliceblue #F0F8FF
antiquewhite #FAEBD7
aqua #00FFFF
aquamarine #7FFFD4
azure #F0FFFF
beige #F5F5DC
bisque #FFE4C4
black #000000
blanchedalmond #FFEBCD
blue #0000FF
blueviolet #8A2BE2
brown #A52A2A
burlywood #DEB887
cadetblue #5F9EA0
chartreuse #7FFF00
chocolate #D2691E
coral #FF7F50
cornflowerblue #6495ED
cornsilk #FFF8DC
crimson #DC143C
cyan #00FFFF
darkblue #00008B
darkcyan #008B8B
darkgoldenrod #B8860B
darkgray #A9A9A9
darkgreen #006400
darkkhaki #BDB76B
darkmagenta #8B008B
darkolivegreen #556B2F
darkorange #FF8C00
darkorchid #9932CC
darkred #8B0000
darksalmon #E9967A
darkseagreen #8FBC8F
darkslateblue #483D8B
darkslategray #2F4F4F
darkturquoise #00CED1
darkviolet #9400D3
deeppink #FF1493
deepskyblue #00BFFF
dimgray #696969
dodgerblue #1E90FF
firebrick #B22222
floralwhite #FFFAF0
forestgreen #228B22
fuchsia #FF00FF
gainsboro #DCDCDC
ghostwhite #F8F8FF
gold #FFD700
goldenrod #DAA520
gray #7F7F7F
green #008000
greenyellow #ADFF2F
honeydew #F0FFF0
hotpink #FF69B4
indianred #CD5C5C
indigo #4B0082
ivory #FFFFF0
khaki #F0E68C
lavender #E6E6FA
lavenderblush #FFF0F5
lawngreen #7CFC00
lemonchiffon #FFFACD
lightblue #ADD8E6
lightcoral #F08080
lightcyan #E0FFFF
lightgoldenrodyellow #FAFAD2
lightgreen #90EE90
lightgrey #D3D3D3
lightpink #FFB6C1
lightsalmon #FFA07A
lightseagreen #20B2AA
lightskyblue #87CEFA
lightslategray #778899
lightsteelblue #B0C4DE
lightyellow #FFFFE0
lime #00FF00
limegreen #32CD32
linen #FAF0E6
magenta #FF00FF
maroon #800000
mediumaquamarine #66CDAA
mediumblue #0000CD
mediumorchid #BA55D3
mediumpurple #9370DB
mediumseagreen #3CB371
mediumslateblue #7B68EE
mediumspringgreen #00FA9A
mediumturquoise #48D1CC
mediumvioletred #C71585
midnightblue #191970
mintcream #F5FFFA
mistyrose #FFE4E1
moccasin #FFE4B5
navajowhite #FFDEAD
navy #000080
navyblue #9FAFDF
oldlace #FDF5E6
olive #808000
olivedrab #6B8E23
orange #FFA500
orangered #FF4500
orchid #DA70D6
palegoldenrod #EEE8AA
palegreen #98FB98
paleturquoise #AFEEEE
palevioletred #DB7093
papayawhip #FFEFD5
peachpuff #FFDAB9
peru #CD853F
pink #FFC0CB
plum #DDA0DD
powderblue #B0E0E6
purple #800080
red #FF0000
rosybrown #BC8F8F
royalblue #4169E1
saddlebrown #8B4513
salmon #FA8072
sandybrown #FA8072
seagreen #2E8B57
seashell #FFF5EE
sienna #A0522D
silver #C0C0C0
skyblue #87CEEB
slateblue #6A5ACD
slategray #708090
snow #FFFAFA
springgreen #00FF7F
steelblue #4682B4
tan #D2B48C
teal #008080
thistle #D8BFD8
tomato #FF6347
turquoise #40E0D0
violet #EE82EE
wheat #F5DEB3
white #FFFFFF
whitesmoke #F5F5F5
yellow #FFFF00
yellowgreen #9ACD32

View File

@ -1,40 +0,0 @@
#ifndef __PLATFORM
#define __PLATFORM
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 1
std::wstring executable_path()
{
/*
wchar_t buffer[MAX_PATH];
GetModuleFileName(NULL, buffer, MAX_PATH);
std::wstring exec_path = buffer;
*/
auto exec_path = std::filesystem::canonical("/proc/self/exe").parent_path();
return exec_path.wstring();
//size_t path_index = exec_path.find_last_of('/');
//return exec_path.substr(0, path_index);
}
std::wstring executable_filename()
{
auto exec_path = std::filesystem::canonical("/proc/self/exe");
return exec_path.wstring();
}
std::wstring working_path()
{
auto cwd = std::filesystem::current_path();
return cwd.wstring();
}
std::string narrow_string(std::wstring convertme)
{
//setup converter
using convert_type = std::codecvt_utf8<wchar_t>;
std::wstring_convert<convert_type, wchar_t> converter;
//use converter (.to_bytes: wstr->str, .from_bytes: str->wstr)
return converter.to_bytes(convertme);
}
#endif

View File

@ -1,39 +0,0 @@
#ifndef __PLATFORM
#define __PLATFORM
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 0
#include <Windows.h>
std::wstring executable_path()
{
wchar_t buffer[MAX_PATH];
GetModuleFileName(NULL, buffer, MAX_PATH);
std::wstring exec_path = buffer;
size_t path_index = exec_path.find_last_of(L"\\/");
return exec_path.substr(0, path_index);
}
std::wstring executable_filename()
{
wchar_t buffer[MAX_PATH];
GetModuleFileName(NULL, buffer, MAX_PATH);
std::wstring exec_path = buffer;
return exec_path;
}
std::wstring working_path()
{
auto cwd = std::filesystem::current_path();
return cwd.wstring();
}
std::string narrow_string(std::wstring convertme)
{
//setup converter
using convert_type = std::codecvt_utf8<wchar_t>;
std::wstring_convert<convert_type, wchar_t> converter;
//use converter (.to_bytes: wstr->str, .from_bytes: str->wstr)
return converter.to_bytes(convertme);
}
#endif

View File

@ -1,111 +0,0 @@
# data sources: CSS docs, jennyscrayoncollection 2017 article on Crayola colors, XKCD color survey
# target: Single C++ header file to provide a struct of color RGB codes and names.
# This file pre-computes the nearest neighbor of every color.
# if an RGB code being searched for is closer than the nearest neighbor, it's the closest color name.
def hex_to_rgb(txt):
if '#' in txt: txt = txt.replace('#', '')
r = txt[0:2]
g = txt[2:4]
b = txt[4:6]
return tuple([int(s, 16) for s in (r,g,b)])
class palette:
def __init__(self, name, filename, priority):
self.name = name
self.priority = priority
with open(filename, "r") as f:
print(f"scanning {filename}")
self.colors = {}
for line in f.read().split('\n'):
if len(line.split('\t')) < 2: continue
name, code = line.split('\t')
#print(name, code)
self.colors[name] = hex_to_rgb(code)
def __repr__(self):
return f"<Palette '{self.name}' - {len(self.colors)} colors, priority = {self.priority}>"
palettes = [
#palette("jenny", "jenny_colors.txt", 3), # I should probably use wikipedia as a source for copyright reasons
palette("crayon", "wikicrayons_colors.txt", 2),
palette("xkcd", "xkcd_colors.txt", 1),
palette("css", "css_colors.txt", 0),
#palette("matplotlib", "matplotlib_colors.txt", 2) # there's like 10 colors total, I think we'll survive without them
]
all_colors = []
from math import sqrt
def rgbdist(c1, c2):
return sqrt((c1.r - c2.r)**2 + (c1.g - c2.g)**2 + (c1.b - c2.b)**2)
class Color:
def __init__(self, r, g, b, name, prefix, priority):
self.r = r
self.g = g
self.b = b
self.name = name
self.prefix = prefix
self.priority = priority
self.nearest_neighbor = None
def __repr__(self):
return f"<Color ({self.r}, {self.g}, {self.b}) - '{self.prefix}:{self.name}', priority = {self.priority}, nearest_neighbor={self.nearest_neighbor.name if self.nearest_neighbor is not None else None}>"
def nn(self, colors):
nearest = None
nearest_dist = 999999
for c in colors:
dist = rgbdist(self, c)
if dist == 0: continue
if dist < nearest_dist:
nearest = c
nearest_dist = dist
self.nearest_neighbor = nearest
for p in palettes:
prefix = p.name
priority = p.priority
for name, rgb in p.colors.items():
all_colors.append(Color(*rgb, name, prefix, priority))
print(f"{prefix}->{len(all_colors)}")
for c in all_colors:
c.nn(all_colors)
smallest_dist = 9999999999999
largest_dist = 0
for c in all_colors:
dist = rgbdist(c, c.nearest_neighbor)
if dist > largest_dist: largest_dist = dist
if dist < smallest_dist: smallest_dist = dist
#print(f"{c.prefix}:{c.name} -> {c.nearest_neighbor.prefix}:{c.nearest_neighbor.name}\t{rgbdist(c, c.nearest_neighbor):.2f}")
# questions -
# are there any colors where their nearest neighbor's nearest neighbor isn't them? (There should be)
nonnear_pairs = 0
for c in all_colors:
neighbor = c.nearest_neighbor
their_neighbor = neighbor.nearest_neighbor
if c is not their_neighbor:
#print(f"{c.prefix}:{c.name} -> {neighbor.prefix}:{neighbor.name} -> {their_neighbor.prefix}:{their_neighbor.name}")
nonnear_pairs += 1
print("Non-near pairs:", nonnear_pairs)
#print(f"{c.prefix}:{c.name} -> {c.nearest_neighbor.prefix}:{c.nearest_neighbor.name}\t{rgbdist(c, c.nearest_neighbor):.2f}")
# Are there duplicates? They should be removed from the palette that won't be used
dupes = 0
for c in all_colors:
for c2 in all_colors:
if c is c2: continue
if c.r == c2.r and c.g == c2.g and c.b == c2.b:
dupes += 1
print("dupes:", dupes, "this many will need to be removed:", dupes / 2)
# What order to put them in? Do we want large radiuses first, or some sort of "common color" table?
# does manhattan distance change any answers over the 16.7M RGB values?
# What's the worst case lookup? (Checking all 1200 colors to find the name?)

50
scripts/Grid.py Normal file
View File

@ -0,0 +1,50 @@
class GridPoint:
def __init__(self, color, walkable, tilesprite, transparent, visible, discovered, color_overlay, tile_overlay, uisprite):
self.color = color
self.walkable = walkable
self.tilesprite = tilesprite
self.transparent = transparent
self.visible = visible
self.discovered = discovered
self.color_overlay = color_overlay
self.tile_overlay = tile_overlay
self.uisprite = uisprite
def __repr__(self):
return f"<GridPoint {self.color}, {self.tilesprite}/{self.uisprite} {'W' if self.walkable else '-'}{'T' if self.transparent else '-'}{'V' if self.visible else '-'}{'D' if self.discovered else '-'} {self.color_overlay}/{self.tile_overlay}>"
class Grid:
def __init__(self, title, gx, gy, gs, x, y, w, h, visible=False):
self.title = title
self.grid_x = gx
self.grid_y = gy
self.grid_size = gs
self.x = x
self.y = y
self.w = w
self.h = h
self.visible = visible
self.points = []
self.entities = []
def at(self, x, y):
if not (x > 0 and y > 0 and x < self.grid_x and y < self.grid_y): return None
return self.points[y * self.grid_y + x]
def __repr__(self):
return f"<Grid {self.grid_x}x{self.grid_y}, grid_size={self.grid_size}, (({self.x},{self.y}), ({self.w}, {self.h})), visible={self.visible}>"
# CGrid(Grid* _g, int _ti, int _si, int _x, int _y, bool _v)
class Entity:
def __init__(self, parent, tex_index, sprite_index, x, y, visible=True):
self.parent = parent
self.tex_index = tex_index
self.sprite_index = sprite_index
self.x = x
self.y = y
self.visible = visible
def __repr__(self):
return f"<Entity on grid {repr(self.parent)}@({self.x},{self.y}), TI={self.tex_index}, SI={self.sprite_index}, visible={self.visible}>"

79
scripts/MusicScene.py Normal file
View File

@ -0,0 +1,79 @@
import mcrfpy
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED, GREEN, BLUE = (255, 0, 0), (0, 255, 0), (0, 0, 255)
DARKRED, DARKGREEN, DARKBLUE = (192, 0, 0), (0, 192, 0), (0, 0, 192)
class MusicScene:
def __init__(self, ui_name = "demobox1", grid_name = "demogrid"):
# Texture & Sound Loading
print("Load textures")
mcrfpy.createTexture("./assets/test_portraits.png", 32, 8, 8) #0 - portraits
mcrfpy.createTexture("./assets/alives_other.png", 16, 64, 64) #1 - TinyWorld NPCs
mcrfpy.createTexture("./assets/alives_other.png", 32, 32, 32) #2 - TinyWorld NPCs - 2x2 sprite
# {"createSoundBuffer", McRFPy_API::_createSoundBuffer, METH_VARARGS, "(filename)"},
#{"loadMusic", McRFPy_API::_loadMusic, METH_VARARGS, "(filename)"},
#{"setMusicVolume", McRFPy_API::_setMusicVolume, METH_VARARGS, "(int)"},
#{"setSoundVolume", McRFPy_API::_setSoundVolume, METH_VARARGS, "(int)"},
#{"playSound", McRFPy_API::_playSound, METH_VARARGS, "(int)"},
#{"getMusicVolume", McRFPy_API::_getMusicVolume, METH_VARARGS, ""},
#{"getSoundVolume", McRFPy_API::_getSoundVolume, METH_VARARGS, ""},
mcrfpy.loadMusic("./assets/ultima.ogg")
mcrfpy.createSoundBuffer("./assets/boom.wav")
self.ui_name = ui_name
self.grid_name = grid_name
print("Create UI")
# Create dialog UI
mcrfpy.createMenu(ui_name, 20, 540, 500, 200)
mcrfpy.createCaption(ui_name, f"Music Volume: {mcrfpy.getMusicVolume()}", 24, RED)
mcrfpy.createCaption(ui_name, f"SFX Volume: {mcrfpy.getSoundVolume()}", 24, RED)
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (0, 0, 0), "clicky", "testaction")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKRED, (0, 0, 0), "Music+", "mvol+")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKGREEN, (0, 0, 0), "Music-", "mvol-")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, GREEN, "SFX+", "svol+")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, RED, "SFX-", "svol-")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKRED, (0, 0, 0), "REPL", "startrepl")
mcrfpy.createSprite(ui_name, 1, 0, 20, 40, 3.0)
print("Create UI 2")
entitymenu = "entitytestmenu"
mcrfpy.createMenu(entitymenu, 840, 20, 20, 500)
mcrfpy.createButton(entitymenu, 0, 10, 150, 40, DARKBLUE, BLACK, "PlayM", "playm")
mcrfpy.createButton(entitymenu, 0, 60, 150, 40, DARKBLUE, BLACK, "StopM", "stopm")
mcrfpy.createButton(entitymenu, 0, 110, 150, 40, DARKBLUE, BLACK, "SFX", "boom")
print("Make UIs visible")
self.menus = mcrfpy.listMenus()
self.menus[0].visible = True
self.menus[1].w = 170
self.menus[1].visible = True
mcrfpy.modMenu(self.menus[0])
mcrfpy.modMenu(self.menus[1])
self.mvol = mcrfpy.getMusicVolume()
self.svol = mcrfpy.getSoundVolume()
mcrfpy.registerPyAction("mvol+", lambda: self.setmvol(self.mvol+10))
mcrfpy.registerPyAction("mvol-", lambda: self.setmvol(self.mvol-10))
mcrfpy.registerPyAction("svol+", lambda: self.setsvol(self.svol+10))
mcrfpy.registerPyAction("svol-", lambda: self.setsvol(self.svol-10))
mcrfpy.registerPyAction("playm", lambda: None)
mcrfpy.registerPyAction("stopm", lambda: None)
mcrfpy.registerPyAction("boom", lambda: mcrfpy.playSound(0))
def setmvol(self, v):
mcrfpy.setMusicVolume(int(v))
self.menus[0].captions[0].text = f"Music Volume: {mcrfpy.getMusicVolume():.1f}"
mcrfpy.modMenu(self.menus[0])
self.mvol = mcrfpy.getMusicVolume()
def setsvol(self, v):
mcrfpy.setSoundVolume(int(v))
self.menus[0].captions[1].text = f"Sound Volume: {mcrfpy.getSoundVolume():.1f}"
mcrfpy.modMenu(self.menus[0])
self.svol = mcrfpy.getSoundVolume()
scene = None
def start():
global scene
scene = MusicScene()

575
scripts/TestScene.py Normal file
View File

@ -0,0 +1,575 @@
import UIMenu
import Grid
import mcrfpy
from random import randint, choice
from pprint import pprint
#print("TestScene imported")
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED, GREEN, BLUE = (255, 0, 0), (0, 255, 0), (0, 0, 255)
DARKRED, DARKGREEN, DARKBLUE = (192, 0, 0), (0, 192, 0), (0, 0, 192)
animations_in_progress = 0
# don't load grid over and over, use the global scene
scene = None
class TestEntity:
def __init__(self, grid, label, tex_index, basesprite, x, y, texture_width=64, walk_frames=5, attack_frames=6, do_fov=False):
self.grid = grid
self.basesprite = basesprite
self.texture_width = texture_width
self.walk_frames = walk_frames
self.attack_frames = attack_frames
self.x = x
self.y = y
self.facing_direction = 0
self.do_fov = do_fov
self.label = label
self.inventory = []
#print(f"Calling C++ with: {repr((self.grid, label, tex_index, self.basesprite, x, y, self))}")
grids = mcrfpy.listGrids()
for g in grids:
if g.title == self.grid:
self.entity_index = len(g.entities)
mcrfpy.createEntity(self.grid, label, tex_index, self.basesprite, x, y, self)
def ai_act(self):
return # no AI motion
#if self.label == "player": return
self.move(randint(-1, 1), randint(-1, 1))
scene.actors += 1
def player_act(self):
#print("I'M INTERVENING")
mcrfpy.unlockPlayerInput()
scene.updatehints()
def die(self):
#self.x = -2
#self.y = -2
self.move(-1000,-1000)
self.animate(0,animove=(-1000,-1000))
self.x = -1000
self.y = -1000
self.label = "dead"
def interact(self, initiator, callback):
print(f"Interacted with {self.label}. ", end='')
if self.label == 'item':
print("'taking' item.")
callback()
self.die()
else:
print("blocking movement.")
def move(self, dx, dy, force=False):
# select animation direction
# prefer left or right for diagonals.
#grids = mcrfpy.listGrids()
for g in scene.grids:
if g.title == self.grid:
if not force and g.at(self.x + dx, self.y + dy) is None or not g.at(self.x + dx, self.y + dy).walkable:
#print("Blocked at target location.")
return
if not force: # entities can be stepped on when force=True (like collecting items!)
for entity in scene.tes:
if (entity.x, entity.y) == (self.x + dx, self.y + dy):
print(f"Blocked by entity {entity.label} at ({entity.x}, {entity.y})")
return entity.interact(self, lambda: self.move(dx, dy, force=True))
if self.label == "player":
mcrfpy.lockPlayerInput()
scene.updatehints()
if (dx == 0 and dy == 0):
direction = self.facing_direction # TODO, jump straight to computer turn
elif (dx):
direction = 2 if dx == +1 else 3
else:
direction = 0 if dy == +1 else 1
self.animate(direction, move=True, animove=(self.x + dx, self.y+dy))
self.facing_direction = direction
if (self.do_fov): mcrfpy.refreshFov()
def animate(self, direction, attacking=False, move=False, animove=None):
start_sprite = self.basesprite + (self.texture_width * (direction + (4 if attacking else 0)))
animation_frames = [start_sprite + i for i in range((self.attack_frames if attacking else self.walk_frames))]
mcrfpy.createAnimation(
0.15, # duration, seconds
self.grid, # parent: a UIMenu or Grid key
"entity", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
self.entity_index, # target id: integer index for menu or grid objs; None for grid/menu
"sprite", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
self.animation_done, #callback: callable once animation is complete
False, #loop: repeat indefinitely
animation_frames # values: iterable of frames for 'sprite', lerp target for others
)
#global animations_in_progress
#animations_in_progress += 1
if move:
pos = [self.x, self.y]
if (direction == 0): pos[1] += 1
elif (direction == 1): pos[1] -= 1
elif (direction == 2): pos[0] += 1
elif (direction == 3): pos[0] -= 1
if not animove:
self.x, self.y = pos
animove = pos
else:
pos = animove
self.x, self.y = animove
#scene.move_entity(self.grid, self.entity_index, pos)
#for g in mcrfpy.listGrids():
for g in scene.grids:
if g.title == self.grid:
g.entities[self.entity_index].x = pos[0]
g.entities[self.entity_index].y = pos[1]
mcrfpy.modGrid(g, True)
if animove:
mcrfpy.createAnimation(
0.25, # duration, seconds
self.grid, # parent: a UIMenu or Grid key
"entity", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
self.entity_index, # target id: integer index for menu or grid objs; None for grid/menu
"position", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
self.animation_done, #callback: callable once animation is complete
False, #loop: repeat indefinitely
animove # values: iterable of frames for 'sprite', lerp target for others
)
#animations_in_progress += 1
def animation_done(self):
#global animations_in_progress
#animations_in_progress -= 1
scene.actors -= 1
#print(f"{self} done animating - {scene.actors} remaining")
if scene.actors <= 0:
scene.actors = 0
mcrfpy.unlockPlayerInput()
scene.updatehints()
class TestItemEntity(TestEntity):
def __init__(self, grid, label, tex_index, basesprite, x, y, texture_width=64, walk_frames=5, attack_frames=6, do_fov=False, item="Nothing"):
super().__init__(grid, label, tex_index, basesprite, x, y, texture_width, walk_frames, attack_frames, do_fov)
self.item = item
def interact(self, initiator, callback):
if self.label == 'dead': return super().interact(initiator, callback)
print(f"Interacted with {self.label}, an item. Adding {self.item} to {initiator.label}'s inventory")
initiator.inventory.append(self.item)
callback()
scene.itemguis()
self.die()
class TestDoorEntity(TestEntity):
def __init__(self, grid, label, tex_index, basesprite, x, y, texture_width=64, walk_frames=5, attack_frames=6, do_fov=False, key="Nothing"):
super().__init__(grid, label, tex_index, basesprite, x, y, texture_width, walk_frames, attack_frames, do_fov)
self.key = key
def interact(self, initiator, callback):
if self.label == 'dead': return super().interact(initiator, callback)
print(f"Interacted with {self.label}, a Door. ", end='')
if self.key in initiator.inventory:
initiator.inventory.remove(self.key)
print("Taking key & passing.")
callback()
scene.itemguis()
self.die()
else:
print("The door is locked.")
class TestScene:
def __init__(self, ui_name = "demobox1", grid_name = "demogrid"):
# Texture & Sound Loading
self.actors = 0
#print("Load textures")
mcrfpy.createTexture("./assets/test_portraits.png", 32, 8, 8) #0 - portraits
mcrfpy.createTexture("./assets/alives_other.png", 16, 64, 64) #1 - TinyWorld NPCs
mcrfpy.createTexture("./assets/alives_other.png", 32, 32, 32) #2 - TinyWorld NPCs - 2x2 sprite
mcrfpy.createTexture("./assets/custom_player.png", 16, 5, 13) #3 - player
mcrfpy.createTexture("./assets/Sprite-0001.png", 80, 10, 10) #4 - LGJ2023 keycard + other icons
mcrfpy.createTexture("./assets/tiny_keycards.png", 16, 8, 8) #5 - tiny keycards (ground items)
self.ui_name = ui_name
self.grid_name = grid_name
# Menu index = 0
#print("Create UI")
# Create dialog UI
mcrfpy.createMenu(ui_name, 20, 540, 500, 200)
#mcrfpy.createCaption(ui_name, "Hello There", 18, BLACK)
mcrfpy.createCaption(ui_name, "", 24, RED)
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (0, 0, 0), "clicky", "testaction")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKRED, (0, 0, 0), "REPL", "startrepl")
##mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKGREEN, (0, 0, 0), "map gen", "gridgen")
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKGREEN, (0, 0, 0), "mapL", "gridgen2")
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (192, 0, 0), "a_menu", "animtest")
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKRED, GREEN, "a_spr", "animtest2")
#mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, GREEN, "Next sp", "nextsp")
#mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, RED, "Prev sp", "prevsp")
#mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, DARKGREEN, "+16 sp", "skipsp")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKGREEN, (0, 0, 0), "Next", "nextsp")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, (0, 0, 0), "Prev", "prevsp")
mcrfpy.createSprite(ui_name, 4, 1, 10, 10, 2.0)
# Menu index = 1
#print("Create UI 2")
entitymenu = "entitytestmenu"
mcrfpy.createMenu(entitymenu, 840, 20, 20, 500)
mcrfpy.createButton(entitymenu, 0, 10, 150, 40, DARKBLUE, BLACK, "Up", "test_up")
mcrfpy.createButton(entitymenu, 0, 60, 150, 40, DARKBLUE, BLACK, "Down", "test_down")
mcrfpy.createButton(entitymenu, 0, 110, 150, 40, DARKBLUE, BLACK, "Left", "test_left")
mcrfpy.createButton(entitymenu, 0, 160, 150, 40, DARKBLUE, BLACK, "Right", "test_right")
mcrfpy.createButton(entitymenu, 0, 210, 150, 40, DARKBLUE, BLACK, "Attack", "test_attack")
mcrfpy.createButton(entitymenu, 0, 210, 150, 40, DARKBLUE, RED, "TE", "testent")
# Menu index = 2
mcrfpy.createMenu( "gridtitlemenu", 0, -10, 0, 0)
mcrfpy.createCaption("gridtitlemenu", "<grid name>", 18, WHITE)
#mcrfpy.createCaption("gridtitlemenu", "<camstate>", 16, WHITE)
# Menu index = 3
mcrfpy.createMenu( "hintsmenu", 0, 505, 0, 0)
mcrfpy.createCaption("hintsmenu", "<hintline>", 16, WHITE)
# Menu index = 4
# menu names must be created in alphabetical order (?!) - thanks, C++ hash map
mcrfpy.createMenu( "i", 600, 20, 0, 0)
#mcrfpy.createMenu( "camstatemenu", 600, 20, 0, 0)
mcrfpy.createCaption("i", "<camstate>", 16, WHITE)
# Menu index = 5
mcrfpy.createMenu( "j", 600, 500, 0, 0)
mcrfpy.createButton( "j", 0, 0, 80, 40, DARKBLUE, WHITE, "Recenter", "activatecamfollow")
# Menu index = 6, 7, 8, 9, 10: keycard sprites
mcrfpy.createMenu("k", 540, 540, 80, 80) #6
mcrfpy.createSprite("k", 4, 0, 10, 10, 1.0)
mcrfpy.createMenu("l", 540 + (80 * 1), 540, 80, 80) #7
mcrfpy.createSprite("l", 4, 1, 10, 10, 1.0)
mcrfpy.createMenu("m", 540 + (80 * 2), 540, 80, 80) #8
mcrfpy.createSprite("m", 4, 2, 10, 10, 1.0)
mcrfpy.createMenu("n", 540 + (80 * 3), 540, 80, 80) #9
mcrfpy.createSprite("n", 4, 3, 10, 10, 1.0)
mcrfpy.createMenu("o", 540 + (80 * 4), 540, 80, 80) #10
mcrfpy.createSprite("o", 4, 4, 10, 10, 1.0)
mcrfpy.createMenu("p", 20, 20, 40, 40) #11
#mcrfpy.createButton("p", 0, 0, 130, 40, DARKGREEN, (0, 0, 0), "Register", "keyregistration")
mcrfpy.createButton("p", 0, 0, 130, 40, DARKGREEN, (0, 0, 0), "Register", "startrepl")
mcrfpy.registerPyAction("keyregistration", keyregistration)
#print("Make UIs visible")
self.menus = mcrfpy.listMenus()
self.menus[0].visible = True
self.menus[1].w = 170
self.menus[1].visible = True
self.menus[2].visible = True
for mn in range(2, 6):
self.menus[mn].bgcolor = BLACK
self.menus[mn].visible = True
mcrfpy.modMenu(self.menus[mn])
for mn in range(6, 11):
self.menus[mn].bgcolor = BLACK
self.menus[mn].visible = False
mcrfpy.modMenu(self.menus[mn])
self.menus[11].visible=True
mcrfpy.modMenu(self.menus[11])
#self.menus[2].bgcolor = BLACK
#self.menus[3].visible = True
#self.menus[3].bgcolor = BLACK
#self.menus[4].visible = True
#self.menus[4].bgcolor = BLACK
#self.menus[5].visible = True
#mcrfpy.modMenu(self.menus[0])
#mcrfpy.modMenu(self.menus[1])
#mcrfpy.modMenu(self.menus[2])
#mcrfpy.modMenu(self.menus[3])
#mcrfpy.modMenu(self.menus[4])
#mcrfpy.modMenu(self.menus[5])
#pprint(mcrfpy.listMenus())
#print(f"UI 1 gave back this sprite: {self.menus[0].sprites}")
#print("Create grid")
# create grid (title, gx, gy, gs, x, y, w, h)
mcrfpy.createGrid(grid_name, 100, 100, 16, 20, 20, 800, 500)
self.grids = mcrfpy.listGrids()
#print(self.grids)
#print("Create entities")
#mcrfpy.createEntity("demogrid", "dragon", 2, 545, 10, 10, lambda: None)
#mcrfpy.createEntity("demogrid", "tinyenemy", 1, 1538, 3, 6, lambda: None)
#print("Create fancy entity")
self.player = TestEntity("demogrid", "player", 3, 20, 17, 3, 5, walk_frames=4, attack_frames=5, do_fov=True)
self.tes = [
TestEntity("demogrid", "classtest", 1, 1538, 5, 7, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 1545, 7, 9, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 1552, 9, 11, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 1566, 11, 13, 64, walk_frames=4, attack_frames=6),
#TestEntity("demogrid", "item", 1, 1573, 13, 15, 64, walk_frames=4, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 1583, 15, 17, 64, walk_frames=4, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 130, 9, 7, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 136, 11, 9, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 143, 13, 11, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 158, 15, 13, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 165, 17, 15, 64, walk_frames=5, attack_frames=6),
self.player,
TestItemEntity("demogrid", "GreenKeyCard", 5, 0, 19, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Green Keycard"),
TestItemEntity("demogrid", "BlueKeyCard", 5, 1, 21, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Blue Keycard"),
TestItemEntity("demogrid", "RedKeyCard", 5, 2, 23, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Red Keycard"),
TestItemEntity("demogrid", "OrangeKeyCard", 5, 3, 25, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Orange Keycard"),
TestItemEntity("demogrid", "DevilKeyCard", 5, 4, 27, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Devil Keycard"),
TestDoorEntity("demogrid", "GreenKeyDoor", 5, 8, 19, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Green Keycard"),
TestDoorEntity("demogrid", "BlueKeyDoor", 5, 9, 21, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Blue Keycard"),
TestDoorEntity("demogrid", "RedKeyDoor", 5, 10, 23, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Red Keycard"),
TestDoorEntity("demogrid", "OrangeKeyDoor", 5, 11, 25, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Orange Keycard"),
TestDoorEntity("demogrid", "DevilKeyDoor", 5, 12, 27, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Devil Keycard")
]
self.grids = mcrfpy.listGrids()
self.entity_direction = 0
mcrfpy.registerPyAction("test_down", lambda: [te.animate(0, False, True) for te in self.tes])
mcrfpy.registerPyAction("test_up", lambda: [te.animate(1, False, True) for te in self.tes])
mcrfpy.registerPyAction("test_right", lambda: [te.animate(2, False, True) for te in self.tes])
mcrfpy.registerPyAction("test_left", lambda: [te.animate(3, False, True) for te in self.tes])
mcrfpy.registerPyAction("test_attack", lambda: [te.animate(0, True) for te in self.tes])
mcrfpy.registerPyAction("testent", lambda: [te.animate(2, True) for te in self.tes])
mcrfpy.registerPyAction("activatecamfollow", lambda: mcrfpy.camFollow(True))
# Button behavior
self.clicks = 0
self.sprite_index = 0
#mcrfpy.registerPyAction("testaction", self.click)
mcrfpy.registerPyAction("gridgen", self.gridgen)
#mcrfpy.registerPyAction("gridgen2", lambda: self.gridgen())
#mcrfpy.registerPyAction("animtest", lambda: self.createAnimation())
#mcrfpy.registerPyAction("animtest2", lambda: self.createAnimation2())
mcrfpy.registerPyAction("nextsp", lambda: self.changesprite(1))
mcrfpy.registerPyAction("prevsp", lambda: self.changesprite(-1))
mcrfpy.registerPyAction("skipsp", lambda: self.changesprite(16))
mcrfpy.unlockPlayerInput()
mcrfpy.setActiveGrid("demogrid")
self.gridgen()
self.updatehints()
mcrfpy.camFollow(True)
def itemguis(self):
print(self.player.inventory)
items = ["Green Keycard", "Blue Keycard", "Red Keycard", "Orange Keycard", "Devil Keycard"]
for mn in range(6, 11):
self.menus[mn].visible = items[mn-6] in self.player.inventory
mcrfpy.modMenu(self.menus[mn])
def animate_entity(self, direction, attacking=False):
if direction is None:
direction = self.entity_direction
else:
self.entity_direction = direction
dragon_sprite = 545 + (32 * (direction + (4 if attacking else 0)))
dragon_animation = [dragon_sprite + i for i in range((5 if attacking else 4))]
mcrfpy.createAnimation(
1.0, # duration, seconds
"demogrid", # parent: a UIMenu or Grid key
"entity", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
0, # target id: integer index for menu or grid objs; None for grid/menu
"sprite", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
lambda: self.animation_done("demobox1"), #callback: callable once animation is complete
False, #loop: repeat indefinitely
dragon_animation # values: iterable of frames for 'sprite', lerp target for others
)
orc_sprite = 1538 + (64 * (direction + (4 if attacking else 0)))
orc_animation = [orc_sprite + i for i in range((5 if attacking else 4))]
mcrfpy.createAnimation(
1.0, # duration, seconds
"demogrid", # parent: a UIMenu or Grid key
"entity", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
1, # target id: integer index for menu or grid objs; None for grid/menu
"sprite", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
lambda: self.animation_done("demobox1"), #callback: callable once animation is complete
False, #loop: repeat indefinitely
orc_animation # values: iterable of frames for 'sprite', lerp target for others
)
#def move_entity(self, targetgrid, entity_index, position):
# for i, g in enumerate(self.grids):
# if g.title == targetgrid:
# g.entities[entity_index].x = position[0]
# g.entities[entity_index].y = position[1]
# #pts = g.points
# g.visible = True
# mcrfpy.modGrid(g)
# self.grids = mcrfpy.listGrids()
# #self.grids[i].points = pts
# return
def changesprite(self, n):
self.sprite_index += n
self.menus[0].captions[0].text = f"Sprite #{self.sprite_index}"
self.menus[0].sprites[0].sprite_index = self.sprite_index
mcrfpy.modMenu(self.menus[0])
def click(self):
self.clicks += 1
self.menus[0].captions[0].text = f"Clicks: {self.clicks}"
self.menus[0].sprites[0].sprite_index = randint(0, 3)
mcrfpy.modMenu(self.menus[0])
def updatehints(self):
self.menus[2].captions[0].text=mcrfpy.activeGrid()
mcrfpy.modMenu(self.menus[2])
self.menus[3].captions[0].text=mcrfpy.inputMode()
mcrfpy.modMenu(self.menus[3])
#self.menus[4].captions[0].text=f"follow: {mcrfpy.camFollow()}"
self.menus[4].captions[0].text="following" if mcrfpy.camFollow() else "free"
mcrfpy.modMenu(self.menus[4])
self.menus[5].visible = not mcrfpy.camFollow()
mcrfpy.modMenu(self.menus[5])
def gridgen(self):
#print(f"[Python] modifying {len(self.grids[0].points)} grid points")
for p in self.grids[0].points:
#p.color = (randint(0, 255), randint(64, 192), 0)
p.color = (0,0,0)
p.walkable = False
p.transparent = False
room_centers = [(randint(0, self.grids[0].grid_x-1), randint(0, self.grids[0].grid_y-1)) for i in range(20)] + [(17, 3), (20,10) + (20,5)]
#room_centers.append((3, 5))
for r in room_centers:
# random hallway
target = choice(room_centers)
length1 = abs(target[0] - r[0])
xbase = min(target[0], r[0])
for x in range(length1):
gpoint = self.grids[0].at(x, r[1])
if gpoint is None: continue
gpoint.walkable = True
gpoint.transparent = True
gpoint.color = (192, 192, 192)
length2 = abs(target[1] - r[1])
ybase = min(target[1], r[1])
for y in range(length2):
gpoint = self.grids[0].at(r[0], y)
if gpoint is None: continue
gpoint.walkable = True
gpoint.transparent = True
gpoint.color = (192, 192, 192)
for r in room_centers:
#print(r)
room_color = (randint(16, 24)*8, randint(16, 24)*8, randint(16, 24)*8)
#self.grids[0].at(r[0], r[1]).walkable = True
#self.grids[0].at(r[0], r[1]).color = room_color
halfx, halfy = randint(2, 11), randint(2,11)
for p_x in range(r[0] - halfx, r[0] + halfx):
for p_y in range(r[1] - halfy, r[1] + halfy):
gpoint = self.grids[0].at(p_x, p_y)
if gpoint is None: continue
gpoint.walkable = True
gpoint.transparent = True
gpoint.color = room_color
#print()
#print("[Python] Modifying:")
self.grids[0].at(10, 10).color = (255, 255, 255)
self.grids[0].at(10, 10).walkable = False
self.grids[0].visible = True
mcrfpy.modGrid(self.grids[0])
#self.grids = mcrfpy.listGrids()
#print(f"Sent grid: {repr(self.grids[0])}")
#print(f"Received grid: {repr(mcrfpy.listGrids()[0])}")
def animation_done(self, s):
print(f"The `{s}` animation completed.")
#self.menus = mcrfpy.listMenus()
# if (!PyArg_ParseTuple(args, "fsssiOOO", &duration, &parent, &target_type, &target_id, &field, &callback, &loop_obj, &values_obj)) return NULL;
def createAnimation(self):
print(self.menus)
self.menus = mcrfpy.listMenus()
self.menus[0].w = 500
self.menus[0].h = 200
print(self.menus)
mcrfpy.modMenu(self.menus[0])
print(self.menus)
mcrfpy.createAnimation(
3.0, # duration, seconds
"demobox1", # parent: a UIMenu or Grid key
"menu", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
0, # target id: integer index for menu or grid objs; None for grid/menu
"size", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
lambda: self.animation_done("demobox1"), #callback: callable once animation is complete
False, #loop: repeat indefinitely
[150, 100] # values: iterable of frames for 'sprite', lerp target for others
)
def createAnimation2(self):
mcrfpy.createAnimation(
5,
"demobox1",
"sprite",
0,
"sprite",
lambda: self.animation_done("sprite change"),
False,
[0, 1, 2, 1, 2, 0]
)
def keytest():
print("Key tested.")
def keyregistration():
print("Registering 'keytest'")
mcrfpy.registerPyAction("keytest", keytest)
print("Registering input")
print(mcrfpy.registerInputAction(15, "keytest")) # 15 = P
mcrfpy.registerPyAction("player_move_up", lambda: scene.player.move(0, -1))
mcrfpy.registerPyAction("player_move_left", lambda: scene.player.move(-1, 0))
mcrfpy.registerPyAction("player_move_down", lambda: scene.player.move(0, 1))
mcrfpy.registerPyAction("player_move_right", lambda: scene.player.move(1, 0))
mcrfpy.registerInputAction(ord('w') - ord('a'), "player_move_up")
mcrfpy.registerInputAction(ord('a') - ord('a'), "player_move_left")
mcrfpy.registerInputAction(ord('s') - ord('a'), "player_move_down")
mcrfpy.registerInputAction(ord('d') - ord('a'), "player_move_right")
def start():
global scene
#print("TestScene.start called")
scene = TestScene()
mcrfpy.refreshFov()
scene.updatehints()

48
scripts/UIMenu.py Normal file
View File

@ -0,0 +1,48 @@
class Caption:
def __init__(self, text, textsize, color):
self.text = text
self.textsize = textsize
self.color = color
def __repr__(self):
return f"<Caption text={self.text}, textsize={self.textsize}, color={self.color}>"
class Button:
def __init__(self, x, y, w, h, bgcolor, textcolor, text, actioncode):
self.x = x
self.y = y
self.w = w
self.h = h
self.bgcolor = bgcolor
self.textcolor = textcolor
self.text = text
self.actioncode = actioncode
def __repr__(self):
return f"<Button ({self.x}, {self.y}, {self.w}, {self.h}), bgcolor={self.bgcolor}, textcolor={self.textcolor}, actioncode={self.actioncode}>"
class Sprite:
def __init__(self, tex_index, sprite_index, x, y):
self.tex_index = tex_index
self.sprite_index = sprite_index
self.x = x
self.y = y
def __repr__(self):
return f"<Sprite tex_index={self.tex_index}, self.sprite_index={self.sprite_index}, x={self.x}, y={self.y}>"
class UIMenu:
def __init__(self, title, x, y, w, h, bgcolor, visible=False):
self.title = title
self.x = x
self.y = y
self.w = w
self.h = h
self.bgcolor = bgcolor
self.visible = visible
self.captions = []
self.buttons = []
self.sprites = []
def __repr__(self):
return f"<UIMenu title={repr(self.title)}, x={self.x}, y={self.y}, w={self.w}, h={self.h}, bgcolor={self.bgcolor}, visible={self.visible}, {len(self.captions)} captions, {len(self.buttons)} buttons, {len(self.sprites)} sprites>"

10
scripts/engine_user.py Normal file
View File

@ -0,0 +1,10 @@
print("[Python] Attempting import")
import scriptable
print(f"[Python] calling fibonacci(8): {scriptable.fibonacci(8)}")
print(f"[Python] calling fibonacci(15): {scriptable.fibonacci(15)}")
import venv
print("[Python] Importing library installed with pip")
import numpy

37
scripts/test_ui.py Normal file
View File

@ -0,0 +1,37 @@
import mcrfpy
mcrfpy.createTexture("./assets/test_portraits.png", 32, 8, 8)
from random import choice, randint
box_colors = [
(0, 0, 192),
(0, 192, 0),
(192, 0, 0),
(192, 192, 0),
(0, 192, 192),
(192, 0, 192)
]
text_colors = [
(0, 0, 255),
(0, 255, 0),
(255, 0, 0),
(255, 255, 0),
(0, 255, 255),
(255, 0, 255)
]
test_x = 500
test_y = 10
for i in range(40):
ui_name = f"test{i}"
mcrfpy.createMenu(ui_name, test_x, test_y, 400, 200)
mcrfpy.createCaption(ui_name, "Hello There", 18, choice(text_colors))
mcrfpy.createButton(ui_name, 250, 20, 100, 50, choice(box_colors), (0, 0, 0), "asdf", "testaction")
mcrfpy.createSprite(ui_name, 0, randint(0, 3), 650, 60, 5.0)
test_x -= 50
test_y += 50
if (test_x <= 50):
test_x = 500
#print(test_x)

View File

@ -31,116 +31,4 @@ public:
if (a & WHEEL_NEG) factor = -1;
return (a & WHEEL_DEL) * factor;
}
static std::string key_str(sf::Keyboard::Key& keycode)
{
switch(keycode)
{
case sf::Keyboard::Key::Unknown: return "Unknown"; break;
case sf::Keyboard::Key::A: return "A"; break;
case sf::Keyboard::Key::B: return "B"; break;
case sf::Keyboard::Key::C: return "C"; break;
case sf::Keyboard::Key::D: return "D"; break;
case sf::Keyboard::Key::E: return "E"; break;
case sf::Keyboard::Key::F: return "F"; break;
case sf::Keyboard::Key::G: return "G"; break;
case sf::Keyboard::Key::H: return "H"; break;
case sf::Keyboard::Key::I: return "I"; break;
case sf::Keyboard::Key::J: return "J"; break;
case sf::Keyboard::Key::K: return "K"; break;
case sf::Keyboard::Key::L: return "L"; break;
case sf::Keyboard::Key::M: return "M"; break;
case sf::Keyboard::Key::N: return "N"; break;
case sf::Keyboard::Key::O: return "O"; break;
case sf::Keyboard::Key::P: return "P"; break;
case sf::Keyboard::Key::Q: return "Q"; break;
case sf::Keyboard::Key::R: return "R"; break;
case sf::Keyboard::Key::S: return "S"; break;
case sf::Keyboard::Key::T: return "T"; break;
case sf::Keyboard::Key::U: return "U"; break;
case sf::Keyboard::Key::V: return "V"; break;
case sf::Keyboard::Key::W: return "W"; break;
case sf::Keyboard::Key::X: return "X"; break;
case sf::Keyboard::Key::Y: return "Y"; break;
case sf::Keyboard::Key::Z: return "Z"; break;
case sf::Keyboard::Key::Num0: return "Num0"; break;
case sf::Keyboard::Key::Num1: return "Num1"; break;
case sf::Keyboard::Key::Num2: return "Num2"; break;
case sf::Keyboard::Key::Num3: return "Num3"; break;
case sf::Keyboard::Key::Num4: return "Num4"; break;
case sf::Keyboard::Key::Num5: return "Num5"; break;
case sf::Keyboard::Key::Num6: return "Num6"; break;
case sf::Keyboard::Key::Num7: return "Num7"; break;
case sf::Keyboard::Key::Num8: return "Num8"; break;
case sf::Keyboard::Key::Num9: return "Num9"; break;
case sf::Keyboard::Key::Escape: return "Escape"; break;
case sf::Keyboard::Key::LControl: return "LControl"; break;
case sf::Keyboard::Key::LShift: return "LShift"; break;
case sf::Keyboard::Key::LAlt: return "LAlt"; break;
case sf::Keyboard::Key::LSystem: return "LSystem"; break;
case sf::Keyboard::Key::RControl: return "RControl"; break;
case sf::Keyboard::Key::RShift: return "RShift"; break;
case sf::Keyboard::Key::RAlt: return "RAlt"; break;
case sf::Keyboard::Key::RSystem: return "RSystem"; break;
case sf::Keyboard::Key::Menu: return "Menu"; break;
case sf::Keyboard::Key::LBracket: return "LBracket"; break;
case sf::Keyboard::Key::RBracket: return "RBracket"; break;
case sf::Keyboard::Key::Semicolon: return "Semicolon"; break;
case sf::Keyboard::Key::Comma: return "Comma"; break;
case sf::Keyboard::Key::Period: return "Period"; break;
case sf::Keyboard::Key::Apostrophe: return "Apostrophe"; break;
case sf::Keyboard::Key::Slash: return "Slash"; break;
case sf::Keyboard::Key::Backslash: return "Backslash"; break;
case sf::Keyboard::Key::Grave: return "Grave"; break;
case sf::Keyboard::Key::Equal: return "Equal"; break;
case sf::Keyboard::Key::Hyphen: return "Hyphen"; break;
case sf::Keyboard::Key::Space: return "Space"; break;
case sf::Keyboard::Key::Enter: return "Enter"; break;
case sf::Keyboard::Key::Backspace: return "Backspace"; break;
case sf::Keyboard::Key::Tab: return "Tab"; break;
case sf::Keyboard::Key::PageUp: return "PageUp"; break;
case sf::Keyboard::Key::PageDown: return "PageDown"; break;
case sf::Keyboard::Key::End: return "End"; break;
case sf::Keyboard::Key::Home: return "Home"; break;
case sf::Keyboard::Key::Insert: return "Insert"; break;
case sf::Keyboard::Key::Delete: return "Delete"; break;
case sf::Keyboard::Key::Add: return "Add"; break;
case sf::Keyboard::Key::Subtract: return "Subtract"; break;
case sf::Keyboard::Key::Multiply: return "Multiply"; break;
case sf::Keyboard::Key::Divide: return "Divide"; break;
case sf::Keyboard::Key::Left: return "Left"; break;
case sf::Keyboard::Key::Right: return "Right"; break;
case sf::Keyboard::Key::Up: return "Up"; break;
case sf::Keyboard::Key::Down: return "Down"; break;
case sf::Keyboard::Key::Numpad0: return "Numpad0"; break;
case sf::Keyboard::Key::Numpad1: return "Numpad1"; break;
case sf::Keyboard::Key::Numpad2: return "Numpad2"; break;
case sf::Keyboard::Key::Numpad3: return "Numpad3"; break;
case sf::Keyboard::Key::Numpad4: return "Numpad4"; break;
case sf::Keyboard::Key::Numpad5: return "Numpad5"; break;
case sf::Keyboard::Key::Numpad6: return "Numpad6"; break;
case sf::Keyboard::Key::Numpad7: return "Numpad7"; break;
case sf::Keyboard::Key::Numpad8: return "Numpad8"; break;
case sf::Keyboard::Key::Numpad9: return "Numpad9"; break;
case sf::Keyboard::Key::F1: return "F1"; break;
case sf::Keyboard::Key::F2: return "F2"; break;
case sf::Keyboard::Key::F3: return "F3"; break;
case sf::Keyboard::Key::F4: return "F4"; break;
case sf::Keyboard::Key::F5: return "F5"; break;
case sf::Keyboard::Key::F6: return "F6"; break;
case sf::Keyboard::Key::F7: return "F7"; break;
case sf::Keyboard::Key::F8: return "F8"; break;
case sf::Keyboard::Key::F9: return "F9"; break;
case sf::Keyboard::Key::F10: return "F10"; break;
case sf::Keyboard::Key::F11: return "F11"; break;
case sf::Keyboard::Key::F12: return "F12"; break;
case sf::Keyboard::Key::F13: return "F13"; break;
case sf::Keyboard::Key::F14: return "F14"; break;
case sf::Keyboard::Key::F15: return "F15"; break;
case sf::Keyboard::Key::Pause: return "Pause"; break;
default:
return "Any";
break;
}
}
};

124
src/Animation.cpp Normal file
View File

@ -0,0 +1,124 @@
#include "Animation.h"
Animation::Animation(float _d, std::function<void()> _cb, bool _l)
:duration(_d), callback(_cb), loop(_l), elapsed(0.0f) {}
// linear interpolation constructor
template<typename T>
LerpAnimation<T>::LerpAnimation(float _d, T _ev, T _sv, std::function<void()> _cb, std::function<void(T)> _w, bool _l)
:Animation(_d, _cb, _l), //duration(_d), target(_t), callback(_cb), loop(_l),elapsed(0.0f),
startvalue(_sv), endvalue(_ev), write(_w) {}
// discrete values constructor
template<typename T>
DiscreteAnimation<T>::DiscreteAnimation(float _d, std::vector<T> _v, std::function<void()> _cb, std::function<void(T)> _w, bool _l)
:Animation(_d, _cb, _l), //duration(_d), target(_t), callback(_cb), loop(_l), elapsed(0.0f),
index(0), nonelapsed(0.0f), values(_v), write(_w) {
timestep = _d / _v.size();
}
/* // don't call virtual functions (like cancel()) from base class destructor
* // child classes destructors' are called first anyway
Animation::~Animation() {
// deconstructor sets target to desired end state (no partial values)
cancel();
}
*/
template<>
void LerpAnimation<std::string>::lerp() {
//*(std::string*)target = ;
write(endvalue.substr(0, endvalue.length() * (elapsed / duration)));
}
template<>
void LerpAnimation<int>::lerp() {
int delta = endvalue - startvalue;
//*(int*)target = ;
write(startvalue + (elapsed / duration * delta));
}
template<>
void LerpAnimation<float>::lerp() {
int delta = endvalue - startvalue;
//*(float*)target = ;
write(startvalue + (elapsed / duration * delta));
}
template<>
void LerpAnimation<sf::Vector2f>::lerp() {
//std::cout << "sf::Vector2f implementation of lerp." << std::endl;
int delta_x = endvalue.x - startvalue.x;
int delta_y = endvalue.y - startvalue.y;
//std::cout << "Start: " << startvalue.x << ", " << startvalue.y << "; End: " << endvalue.x << ", " << endvalue.y << std::endl;
//std::cout << "Delta: " << delta_x << ", " << delta_y << std::endl;
//((sf::Vector2f*)target)->x = startvalue.x + (elapsed / duration * delta_x);
//((sf::Vector2f*)target)->y = startvalue.y + (elapsed / duration * delta_y);
write(sf::Vector2f(startvalue.x + (elapsed / duration * delta_x),
startvalue.y + (elapsed / duration * delta_y)));
}
template<>
void LerpAnimation<sf::Vector2i>::lerp() {
int delta_x = endvalue.x - startvalue.y;
int delta_y = endvalue.y - startvalue.y;
//((sf::Vector2i*)target)->x = startvalue.x + (elapsed / duration * delta_x);
//((sf::Vector2i*)target)->y = startvalue.y + (elapsed / duration * delta_y);
write(sf::Vector2i(startvalue.x + (elapsed / duration * delta_x),
startvalue.y + (elapsed / duration * delta_y)));
}
template<typename T>
void LerpAnimation<T>::step(float delta) {
if (complete) return;
elapsed += delta;
//std::cout << "LerpAnimation step function. Elapsed: " << elapsed <<std::endl;
lerp();
if (isDone()) { callback(); complete = true; cancel(); }; //use the exact value, not my math
}
template<typename T>
void DiscreteAnimation<T>::step(float delta)
{
if (complete) return;
nonelapsed += delta;
//std::cout << "Nonelapsed: " << nonelapsed << " elapsed (pre-add): " << elapsed << " timestep: " << timestep << " duration: " << duration << " index: " << index << std::endl;
if (nonelapsed < timestep) return;
//std::cout << "values size: " << values.size() << " isDone(): " << isDone() << std::endl;
if (elapsed > duration && !complete) {callback(); complete = true; return; }
elapsed += nonelapsed; // or should it be += timestep?
if (index == values.size() - 1) return;
nonelapsed = 0; // or should it -= timestep?
index++;
//*(T*)target = values[index];
write(values[index]);
}
template<typename T>
void LerpAnimation<T>::cancel() {
//*(T*)target = endvalue;
write(endvalue);
}
template<typename T>
void DiscreteAnimation<T>::cancel() {
//*(T*)target = values[values.size() - 1];
write(values[values.size() - 1]);
}
bool Animation::isDone() {
return elapsed + Animation::EPSILON >= duration;
}
namespace animation_template_implementations {
// instantiate to compile concrete templates
//LerpAnimation<sf::Vector2f> implement_vector2f;
auto implement_v2f_const = LerpAnimation<sf::Vector2<float>>(4.0, sf::Vector2<float>(), sf::Vector2f(1,1), [](){}, [](sf::Vector2f v){}, false);
auto implement_disc_i = DiscreteAnimation<int>(3.0, std::vector<int>{0},[](){},[](int){},false);
//LerpAnimation<sf::Vector2i> implement_vector2i;
//LerpAnimation<int> implment_int;
//LerpAnimation<std::string> implment_string;
//LerpAnimation<float> implement_float;
//DiscreteAnimation<int> implement_int_discrete;
}

50
src/Animation.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include "Common.h"
#include <functional>
class Animation
{
protected:
static constexpr float EPSILON = 0.05;
float duration, elapsed;
std::function<void()> callback;
bool loop;
bool complete=false;
public:
//Animation(float, T, T*, std::function<void()>, bool); // lerp
//Animation(float, std::vector<T>, T*, std::function<void()>, bool); // discrete
Animation(float, std::function<void()>, bool);
//Animation() {};
virtual void step(float) = 0;
virtual void cancel() = 0;
bool isDone();
};
template<typename T>
class LerpAnimation: public Animation
{
T startvalue, endvalue;
std::function<void(T)> write;
void lerp();
public:
~LerpAnimation() { cancel(); }
LerpAnimation(float, T, T, std::function<void()>, std::function<void(T)>, bool);
//LerpAnimation() {};
void step(float) override final;
void cancel() override final;
};
template<typename T>
class DiscreteAnimation: public Animation
{
std::vector<T> values;
std::function<void(T)> write;
float nonelapsed, timestep;
int index;
public:
DiscreteAnimation(float, std::vector<T>, std::function<void()>, std::function<void(T)>, bool);
DiscreteAnimation() {};
~DiscreteAnimation() { cancel(); }
void step(float) override final;
void cancel() override final;
};

24
src/Button.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "Button.h"
void Button::render(sf::RenderWindow & window)
{
window.draw(rect);
window.draw(caption);
}
Button::Button(int x, int y, int w, int h,
sf::Color _background, sf::Color _textcolor,
const char * _caption, sf::Font & font,
const char * _action)
{
rect.setPosition(sf::Vector2f(x, y));
rect.setSize(sf::Vector2f(w, h));
rect.setFillColor(_background);
caption.setFillColor(_textcolor);
caption.setPosition(sf::Vector2f(x, y));
caption.setString(_caption);
caption.setFont(font);
action = _action;
}

35
src/Button.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include "Common.h"
class Button
{
protected:
public:
// TODO / JankMode: setter & getter for these three fields
// were protected, but directly changing them should be...fine?
sf::RectangleShape rect;
sf::Text caption;
std::string action;
Button() {};
Button(int x, int y, int w, int h,
sf::Color _background, sf::Color _textcolor,
const char * _caption, sf::Font & font,
const char * _action);
void setPosition(sf::Vector2f v) { rect.setPosition(v); caption.setPosition(v); }
void setSize(sf::Vector2f & v) { rect.setSize(v); }
void setBackground(sf::Color c) { rect.setFillColor(c); }
void setCaption(std::string & s) { caption.setString(s); }
void setTextColor(sf::Color c) { caption.setFillColor(c); }
void render(sf::RenderWindow & window);
auto contains(sf::Vector2i p) { return rect.getGlobalBounds().contains(p.x, p.y); }
auto contains(sf::Vector2f rel, sf::Vector2i p) {
return rect.getGlobalBounds().contains(p.x - rel.x, p.y - rel.y);
}
auto getAction() { return action; }
private:
};

138
src/Components.h Normal file
View File

@ -0,0 +1,138 @@
#pragma once
#include "Common.h"
#include "IndexSprite.h"
#include "Grid.h"
//#include "Item.h"
#include "Python.h"
#include <list>
class CGrid
{
public:
bool visible;
int x, y;
IndexSprite indexsprite;
Grid* grid;
CGrid(Grid* _g, int _ti, int _si, int _x, int _y, bool _v)
: visible(_v), x(_x), y(_y), grid(_g), indexsprite(_ti, _si, _x, _y, 1.0) {}
};
class CInventory
{
public:
//std::list<std::shared_ptr<Item>>;
int x;
};
class CBehavior
{
public:
PyObject* object;
CBehavior(PyObject* p): object(p) {}
};
/*
class CCombatant
{
public:
int hp;
int maxhp;
}
class CCaster
{
public:
int mp;
int maxmp;
}
class CLevel
{
int constitution; // +HP, resist effects
int strength; // +damage, block/parry
int dexterity; // +speed, dodge
int intelligence; // +MP, spell resist
int wisdom; // +damage, deflect
int luck; // crit, loot
}
*/
/*
class CTransform
{
public:
Vec2 pos = { 0.0, 0.0 };
Vec2 velocity = { 0.0, 0.0 };
float angle = 0;
CTransform(const Vec2 & p, const Vec2 & v, float a)
: pos(p), velocity(v), angle(a) {}
};
*/
/*
class CShape
{
public:
sf::CircleShape circle;
CShape(float radius, int points, const sf::Color & fill, const sf::Color & outline, float thickness)
: circle(radius, points)
{
circle.setFillColor(fill);
circle.setOutlineColor(outline);
circle.setOutlineThickness(thickness);
circle.setOrigin(radius, radius);
}
};
class CCollision
{
public:
float radius = 0;
CCollision(float r)
: radius(r) {}
};
class CScore
{
public:
int score = 0;
CScore(int s)
: score(s) {}
};
class CLifespan
{
public:
int remaining = 0;
int total = 0;
CLifespan(int t)
: remaining(t), total(t) {}
};
class CInput
{
public:
bool up = false;
bool left = false;
bool right = false;
bool down = false;
bool fire = false;
CInput() {}
};
class CSteer
{
public:
sf::Vector2f position;
sf::Vector2f velocity;
float v_max;
float dv_max;
float theta_max;
float dtheta_max;
};
*/

25
src/Entity.cpp Normal file
View File

@ -0,0 +1,25 @@
#include "Entity.h"
Entity::Entity(const size_t i, const std::string & t)
: m_id(i), m_tag(t) {}
bool Entity::isActive() const
{
return m_active;
}
const std::string & Entity::tag() const
{
return m_tag;
}
const size_t Entity::id() const
{
return m_id;
}
void Entity::destroy()
{
m_active = false;
}

35
src/Entity.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include "Common.h"
#include "Components.h"
class Entity
{
friend class EntityManager;
bool m_active = true;
size_t m_id = 0;
std::string m_tag = "default";
//constructor and destructor
Entity(const size_t id, const std::string & t);
public:
// component pointers
//std::shared_ptr<CTransform> cTransform;
//std::shared_ptr<CShape> cShape;
//std::shared_ptr<CCollision> cCollision;
//std::shared_ptr<CInput> cInput;
//std::shared_ptr<CScore> cScore;
//std::shared_ptr<CLifespan> cLifespan;
std::shared_ptr<CGrid> cGrid;
std::shared_ptr<CInventory> cInventory;
std::shared_ptr<CBehavior> cBehavior;
//private member access functions
bool isActive() const;
const std::string & tag() const;
const size_t id() const;
void destroy();
};

73
src/EntityManager.cpp Normal file
View File

@ -0,0 +1,73 @@
#include "EntityManager.h"
EntityManager::EntityManager()
:m_totalEntities(0) {}
void EntityManager::update()
{
//TODO: add entities from m_entitiesToAdd to all vector / tag map
removeDeadEntities(m_entities);
// C++17 way of iterating!
for (auto& [tag, entityVec] : m_entityMap)
{
removeDeadEntities(entityVec);
}
for (auto& e : m_entitiesToAdd)
{
m_entities.push_back(e);
m_entityMap[e->tag()].push_back(e);
}
//if (m_entitiesToAdd.size())
// m_entitiesToAdd.erase(m_entitiesToAdd.begin(), m_entitiesToAdd.end());
m_entitiesToAdd = EntityVec();
}
void EntityManager::removeDeadEntities(EntityVec & vec)
{
EntityVec survivors; // New vector
for (auto& e : m_entities){
if (e->isActive()) survivors.push_back(e); // populate new vector
else if (e->cGrid) { // erase vector from grid
for( auto it = e->cGrid->grid->entities.begin(); it != e->cGrid->grid->entities.end(); it++){
if( *it == e ){
e->cGrid->grid->entities.erase( it );
break;
}
}
}
}
//std::cout << "All entities: " << m_entities.size() << " Survivors: " << survivors.size() << std::endl;
m_entities = survivors; // point to new vector
for (auto& [tag, entityVec] : m_entityMap)
{
EntityVec tag_survivors; // New vector
for (auto& e : m_entityMap[tag])
{
if (e->isActive()) tag_survivors.push_back(e); // populate new vector
}
m_entityMap[tag] = tag_survivors; // point to new vector
//std::cout << tag << " entities: " << m_entityMap[tag].size() << " Survivors: " << tag_survivors.size() << std::endl;
}
}
std::shared_ptr<Entity> EntityManager::addEntity(const std::string & tag)
{
// create the entity shared pointer
auto entity = std::shared_ptr<Entity>(new Entity(m_totalEntities++, tag));
m_entitiesToAdd.push_back(entity);
return entity;
}
const EntityVec & EntityManager::getEntities()
{
return m_entities;
}
const EntityVec & EntityManager::getEntities(const std::string & tag)
{
return m_entityMap[tag];
}

25
src/EntityManager.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include "Common.h"
#include "Entity.h"
typedef std::vector<std::shared_ptr<Entity>> EntityVec;
typedef std::map<std::string, EntityVec> EntityMap;
class EntityManager
{
EntityVec m_entities;
EntityVec m_entitiesToAdd;
EntityMap m_entityMap;
size_t m_totalEntities;
void removeDeadEntities(EntityVec & vec);
public:
EntityManager();
void update();
std::shared_ptr<Entity> addEntity(const std::string & tag);
const EntityVec & getEntities();
const EntityVec & getEntities(const std::string & tag);
};

View File

@ -1,7 +1,9 @@
#include "GameEngine.h"
#include "MenuScene.h"
//#include "UITestScene.h"
#include "ActionCode.h"
#include "McRFPy_API.h"
#include "PyScene.h"
#include "PythonScene.h"
#include "UITestScene.h"
#include "Resources.h"
@ -9,44 +11,39 @@ GameEngine::GameEngine()
{
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
Resources::game = this;
window_title = "Crypt of Sokoban - 7DRL 2025, McRogueface Engine";
window.create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
window_title = "McRogueFace - r/RoguelikeDev Tutorial Run";
window.create(sf::VideoMode(1024, 768), window_title);
visible = window.getDefaultView();
window.setFramerateLimit(60);
window.setFramerateLimit(30);
scene = "uitest";
//std::cout << "Constructing MenuScene" << std::endl;
scenes["menu"] = new MenuScene(this);
scenes["uitest"] = new UITestScene(this);
//std::cout << "Constructed MenuScene" <<std::endl;
//scenes["play"] = new UITestScene(this);
//api = new McRFPy_API(this);
McRFPy_API::game = this;
McRFPy_API::api_init();
McRFPy_API::executePyString("import mcrfpy");
McRFPy_API::executeScript("scripts/game.py");
//McRFPy_API::executePyString("from UIMenu import *");
//McRFPy_API::executePyString("from Grid import *");
//scenes["py"] = new PythonScene(this, "TestScene");
IndexSprite::game = this;
clock.restart();
runtime.restart();
}
Scene* GameEngine::currentScene() { return scenes[scene]; }
void GameEngine::changeScene(std::string s)
{
/*std::cout << "Current scene is now '" << s << "'\n";*/
if (scenes.find(s) != scenes.end())
scene = s;
else
std::cout << "Attempted to change to a scene that doesn't exist (`" << s << "`)" << std::endl;
}
void GameEngine::changeScene(std::string s) { std::cout << "Current scene is now '" << s << "'\n"; scene = s; }
void GameEngine::quit() { running = false; }
void GameEngine::setPause(bool p) { paused = p; }
sf::Font & GameEngine::getFont() { /*return font; */ return Resources::font; }
sf::RenderWindow & GameEngine::getWindow() { return window; }
void GameEngine::createScene(std::string s) { scenes[s] = new PyScene(this); }
void GameEngine::setWindowScale(float multiplier)
{
window.setSize(sf::Vector2u(1024 * multiplier, 768 * multiplier)); // 7DRL 2024: window scaling
//window.create(sf::VideoMode(1024 * multiplier, 768 * multiplier), window_title, sf::Style::Titlebar | sf::Style::Close);
}
void GameEngine::run()
{
float fps = 0.0;
@ -54,57 +51,15 @@ void GameEngine::run()
while (running)
{
currentScene()->update();
testTimers();
sUserInput();
if (!paused)
{
}
currentScene()->render();
currentScene()->sRender();
currentFrame++;
frameTime = clock.restart().asSeconds();
fps = 1 / frameTime;
int whole_fps = (int)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");
}
}
void GameEngine::manageTimer(std::string name, PyObject* target, int interval)
{
auto it = timers.find(name);
if (it != timers.end()) // overwrite existing
{
if (target == NULL || target == Py_None)
{
// Delete: Overwrite existing timer with one that calls None. This will be deleted in the next timer check
// see gitea issue #4: this allows for a timer to be deleted during its own call to itself
timers[name] = std::make_shared<PyTimerCallable>(Py_None, 1000, runtime.getElapsedTime().asMilliseconds());
return;
}
}
if (target == NULL || target == Py_None)
{
std::cout << "Refusing to initialize timer to None. It's not an error, it's just pointless." << std::endl;
return;
}
timers[name] = std::make_shared<PyTimerCallable>(target, interval, runtime.getElapsedTime().asMilliseconds());
}
void GameEngine::testTimers()
{
int now = runtime.getElapsedTime().asMilliseconds();
auto it = timers.begin();
while (it != timers.end())
{
it->second->test(now);
if (it->second->isNone())
{
it = timers.erase(it);
}
else
it++;
window.setTitle(window_title + " " + std::to_string(fps) + " FPS");
}
}
@ -117,20 +72,12 @@ void GameEngine::sUserInput()
int actionCode = 0;
if (event.type == sf::Event::Closed) { running = false; continue; }
// TODO: add resize event to Scene to react; call it after constructor too, maybe
else if (event.type == sf::Event::Resized) {
continue; // 7DRL short circuit. Resizing manually disabled
/*
sf::FloatRect area(0.f, 0.f, event.size.width, event.size.height);
//sf::FloatRect area(0.f, 0.f, 1024.f, 768.f); // 7DRL 2024: attempt to set scale appropriately
//sf::FloatRect area(0.f, 0.f, event.size.width, event.size.width * 0.75);
visible = sf::View(area);
window.setView(visible);
//window.setSize(sf::Vector2u(event.size.width, event.size.width * 0.75)); // 7DRL 2024: window scaling
std::cout << "Visible area set to (0, 0, " << event.size.width << ", " << event.size.height <<")"<<std::endl;
//std::cout << "Visible area set to (0, 0, " << event.size.width << ", " << event.size.height <<")"<<std::endl;
actionType = "resize";
//window.setSize(sf::Vector2u(event.size.width, event.size.width * 0.75)); // 7DRL 2024: window scaling
*/
}
else if (event.type == sf::Event::KeyPressed || event.type == sf::Event::MouseButtonPressed || event.type == sf::Event::MouseWheelScrolled) actionType = "start";
@ -167,23 +114,6 @@ void GameEngine::sUserInput()
std::string name = currentScene()->action(actionCode);
currentScene()->doAction(name, actionType);
}
else if (currentScene()->key_callable)
{
currentScene()->key_callable->call(ActionCode::key_str(event.key.code), actionType);
/*
PyObject* args = Py_BuildValue("(ss)", ActionCode::key_str(event.key.code).c_str(), actionType.c_str());
PyObject* retval = PyObject_Call(currentScene()->key_callable, args, NULL);
if (!retval)
{
std::cout << "key_callable has raised an exception. It's going to STDERR and being dropped:" << std::endl;
PyErr_Print();
PyErr_Clear();
} else if (retval != Py_None)
{
std::cout << "key_callable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
}
*/
}
else
{
//std::cout << "[GameEngine] Action not registered for input: " << actionCode << ": " << actionType << std::endl;

View File

@ -1,16 +1,17 @@
#pragma once
#include "Common.h"
#include "Entity.h"
#include "EntityManager.h"
#include "Scene.h"
#include "McRFPy_API.h"
#include "IndexTexture.h"
#include "Timer.h"
#include "PyCallable.h"
class GameEngine
{
sf::RenderWindow window;
sf::Font font;
std::string scene;
std::map<std::string, Scene*> scenes;
bool running = true;
bool paused = false;
@ -20,17 +21,10 @@ class GameEngine
float frameTime;
std::string window_title;
sf::Clock runtime;
//std::map<std::string, Timer> timers;
std::map<std::string, std::shared_ptr<PyTimerCallable>> timers;
void testTimers();
public:
std::string scene;
GameEngine();
Scene* currentScene();
void changeScene(std::string);
void createScene(std::string);
void quit();
void setPause(bool);
sf::Font & getFont();
@ -40,8 +34,6 @@ public:
int getFrame() { return currentFrame; }
float getFrameTime() { return frameTime; }
sf::View getView() { return visible; }
void manageTimer(std::string, PyObject*, int);
void setWindowScale(float);
// global textures for scripts to access
std::vector<IndexTexture> textures;

340
src/Grid.cpp Normal file
View File

@ -0,0 +1,340 @@
#include "Grid.h"
#include <cmath>
#include "Entity.h"
GridPoint::GridPoint():
color(0, 0, 0, 0), walkable(false), tilesprite(-1), transparent(false), visible(false), discovered(false), color_overlay(0,0,0,255), tile_overlay(-1), uisprite(-1)
{};
void Grid::setSprite(int ti)
{
int tx = ti % texture_width, ty = ti / texture_width;
sprite.setTextureRect(sf::IntRect(tx * grid_size, ty * grid_size, grid_size, grid_size));
}
Grid::Grid(int gx, int gy, int gs, int _x, int _y, int _w, int _h):
grid_size(gs),
grid_x(gx), grid_y(gy),
zoom(1.0f), center_x((gx/2) * gs), center_y((gy/2) * gs),
texture_width(12), texture_height(11), visible(false)
{
//grid_size = gs;
//zoom = 1.0f;
//grid_x = gx;
//grid_y = gy;
tcodmap = new TCODMap(gx, gy);
points.resize(gx*gy);
box.setSize(sf::Vector2f(_w, _h));
box.setPosition(sf::Vector2f(_x, _y));
box.setFillColor(sf::Color(0,0,0,0));
renderTexture.create(_w, _h);
texture.loadFromFile("./assets/kenney_tinydungeon.png");
texture.setSmooth(false);
sprite.setTexture(texture);
//output.setSize(box.getSize());
output.setTextureRect(
sf::IntRect(0, 0,
box.getSize().x, box.getSize().y));
output.setPosition(box.getPosition());
// textures are upside-down inside renderTexture
output.setTexture(renderTexture.getTexture());
// Show one texture at a time
sprite.setTexture(texture);
}
void Grid::refreshTCODmap() {
//int total = 0, walkable = 0, transparent = 0;
for (int x = 0; x < grid_x; x++) {
for (int y = 0; y < grid_y; y++) {
auto p = at(x, y);
//total++; if (p.walkable) walkable++; if (p.transparent) transparent++;
tcodmap->setProperties(x, y, p.transparent, p.walkable);
}
}
//std::cout << "Map refreshed: " << total << " squares, " << walkable << "walkable, " << transparent << " transparent" << std::endl;
}
void Grid::refreshTCODsight(int x, int y) {
tcodmap->computeFov(x,y, 0, true, FOV_PERMISSIVE_8);
for (int x = 0; x < grid_x; x++) {
for (int y = 0; y < grid_y; y++) {
auto& p = at(x, y);
if (p.visible && !tcodmap->isInFov(x, y)) {
p.discovered = true;
p.visible = false;
} else if (!p.visible && tcodmap->isInFov(x,y)) {
p.discovered = true;
p.visible = true;
}
}
}
}
bool Grid::inBounds(int x, int y) {
return (x >= 0 && y >= 0 && x < grid_x && y < grid_y);
}
void Grid::screenToGrid(int sx, int sy, int& gx, int& gy) {
float center_x_sq = center_x / grid_size;
float center_y_sq = center_y / grid_size;
float width_sq = box.getSize().x / (grid_size * zoom);
float height_sq = box.getSize().y / (grid_size * zoom);
float left_edge = center_x_sq - (width_sq / 2.0);
float right_edge = center_x_sq + (width_sq / 2.0);
float top_edge = center_y_sq - (height_sq / 2.0);
float bottom_edge = center_y_sq + (height_sq / 2.0);
float grid_px = zoom * grid_size;
//std::cout << "##############################" <<
// "\nscreen coord: (" << sx << ", " << sy << ")" << std::endl;
sx -= box.getPosition().x;
sy -= box.getPosition().y;
//std::cout << "box coord: (" << sx << ", " << sy << ")" << std::endl;
float mouse_x_sq = sx / grid_px;
float mouse_y_sq = sy / grid_px;
float ans_x = mouse_x_sq + left_edge;
float ans_y = mouse_y_sq + top_edge;
// compare integer method with this (mostly working) one
//int diff_realpixel_x = box.getSize().x / 2.0 - sx;
//int diff_realpixel_y = box.getSize().y / 2.0 - sy;
int left_spritepixels = center_x - (box.getSize().x / 2.0 / zoom);
int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom);
std::cout << "Float method got ans (" << ans_x << ", " << ans_y << ")"
<< std::endl << "Int method px (" << left_spritepixels + (sx/zoom) << ", " <<
top_spritepixels + (sy/zoom) << ")" << std::endl <<
"Int grid (" << (left_spritepixels + (sx/zoom) ) / grid_size <<
", " << (top_spritepixels + (sy/zoom)) / grid_size << ")" <<
std::endl;
// casting float turns -0.5 to 0; I want any negative coord to be OOB
if (ans_x < 0) ans_x = -1;
if (ans_y < 0) ans_y = -1;
gx = ans_x;
gy = ans_y;
/*
std::cout <<
"C: (" << center_x << ", " << center_y << ")" << std::endl <<
"W: " << width_sq << " H: " << height_sq << std::endl <<
"L: " << left_edge << " T: " << top_edge << std::endl <<
"R: " << right_edge << " B: " << bottom_edge << std::endl <<
"Grid Px: " << grid_px << "( zoom: " << zoom << ")" << std::endl <<
"answer: G(" << ans_x << ", " << ans_y << ")" << std::endl <<
"##############################" <<
std::endl;
*/
}
void Grid::renderPxToGrid(int sx, int sy, int& gx, int& gy) {
// just like screen px coversion, but no offset by grid's position
float center_x_sq = center_x / grid_size;
float center_y_sq = center_y / grid_size;
float width_sq = box.getSize().x / (grid_size * zoom);
float height_sq = box.getSize().y / (grid_size * zoom);
int width_px = box.getSize().x / 2.0;
int height_px = box.getSize().y / 2.0;
float left_edge = center_x_sq - (width_sq / 2.0);
float top_edge = center_y_sq - (height_sq / 2.0);
float grid_px = zoom * grid_size;
float sx_sq = sx / grid_px;
float sy_sq = sy / grid_px;
float ans_x = sx_sq + left_edge;
float ans_y = sy_sq + top_edge;
if (ans_x < 0) ans_x = -1;
if (ans_y < 0) ans_y = -1;
gx = ans_x;
gy = ans_y;
}
void Grid::integerGrid(float fx, float fy, int& gx, int& gy) {
if (fx < 0) fx -= 0.5;
if (fy < 0) fy -= 0.5;
gx = fx;
gy = fy;
}
void Grid::gridToRenderPx(int gx, int gy, int& sx, int& sy) {
// integer grid square (gx, gy) - what pixel (on rendertexture)
// should it's top left corner be at (the sprite's position)
// eff_gridsize = grid_size * zoom
// if center_x = 161, and grid_size is 16, that's 10 + 1/16 sprites
// center_x - (box.getSize().x / 2 / zoom) = left edge (in px)
// gx * eff_gridsize = pixel position without panning
// pixel_gx - left_edge = grid's render position
sx = (gx * grid_size * zoom) - (center_x - (box.getSize().x / 2.0 / zoom));
sy = (gy * grid_size * zoom) - (center_y - (box.getSize().y / 2.0 / zoom));
}
void Grid::render(sf::RenderWindow & window)
{
renderTexture.clear();
//renderTexture.draw(box);
// sprites that are visible according to zoom, center_x, center_y, and box width
float center_x_sq = center_x / grid_size;
float center_y_sq = center_y / grid_size;
float width_sq = box.getSize().x / (grid_size * zoom);
float height_sq = box.getSize().y / (grid_size * zoom);
float left_edge = center_x_sq - (width_sq / 2.0);
//float right_edge = center_x_sq + (width_sq / 2.0);
float top_edge = center_y_sq - (height_sq / 2.0);
//float bottom_edge = center_y_sq + (height_sq / 2.0);
int left_spritepixels = center_x - (box.getSize().x / 2.0 / zoom);
int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom);
sprite.setScale(sf::Vector2f(zoom, zoom));
sf::RectangleShape r; // for colors and overlays
r.setSize(sf::Vector2f(grid_size * zoom, grid_size * zoom));
r.setOutlineThickness(0);
int x_limit = left_edge + width_sq + 2;
if (x_limit > grid_x) x_limit = grid_x;
int y_limit = top_edge + height_sq + 2;
if (y_limit > grid_y) y_limit = grid_y;
//for (float x = (left_edge >= 0 ? left_edge : 0);
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
x < x_limit; //x < view_width;
x+=1)
{
//for (float y = (top_edge >= 0 ? top_edge : 0);
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
y < y_limit; //y < view_height;
y+=1)
{
// Converting everything to integer pixels to avoid jitter
//auto pixel_pos = sf::Vector2f(
// (x - left_edge) * (zoom * grid_size),
// (y - top_edge) * (zoom * grid_size));
// This failed horribly:
//int gx, gy; integerGrid(x, y, gx, gy);
//int px_x, px_y; gridToRenderPx(gx, gy, px_x, px_y);
//auto pixel_pos = sf::Vector2f(px_x, px_y);
// this draws coherently, but the coordinates
// don't match up with the mouse cursor function
// jitter not eliminated
auto pixel_pos = sf::Vector2f(
(x*grid_size - left_spritepixels) * zoom,
(y*grid_size - top_spritepixels) * zoom );
auto gridpoint = at(std::floor(x), std::floor(y));
sprite.setPosition(pixel_pos);
r.setPosition(pixel_pos);
r.setFillColor(gridpoint.color);
renderTexture.draw(r);
// tilesprite
// if discovered but not visible, set opacity to 90%
// if not discovered... just don't draw it?
if (gridpoint.tilesprite != -1) {
setSprite(gridpoint.tilesprite);
renderTexture.draw(sprite);
}
}
}
for (auto e : entities) {
auto drawent = e->cGrid->indexsprite.drawable();
drawent.setScale(zoom, zoom);
auto pixel_pos = sf::Vector2f(
(drawent.getPosition().x*grid_size - left_spritepixels) * zoom,
(drawent.getPosition().y*grid_size - top_spritepixels) * zoom );
drawent.setPosition(pixel_pos);
renderTexture.draw(drawent);
}
// loop again and draw on top of entities
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
x < x_limit; //x < view_width;
x+=1)
{
//for (float y = (top_edge >= 0 ? top_edge : 0);
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
y < y_limit; //y < view_height;
y+=1)
{
auto pixel_pos = sf::Vector2f(
(x*grid_size - left_spritepixels) * zoom,
(y*grid_size - top_spritepixels) * zoom );
auto gridpoint = at(std::floor(x), std::floor(y));
sprite.setPosition(pixel_pos);
r.setPosition(pixel_pos);
// visible & discovered layers for testing purposes
if (!gridpoint.discovered) {
r.setFillColor(sf::Color(16, 16, 20, 192)); // 255 opacity for actual blackout
renderTexture.draw(r);
} else if (!gridpoint.visible) {
r.setFillColor(sf::Color(32, 32, 40, 128));
renderTexture.draw(r);
}
// overlay
// uisprite
}
}
// grid lines for testing & validation
/*
sf::Vertex line[] =
{
sf::Vertex(sf::Vector2f(0, 0), sf::Color::Red),
sf::Vertex(box.getSize(), sf::Color::Red),
};
renderTexture.draw(line, 2, sf::Lines);
sf::Vertex lineb[] =
{
sf::Vertex(sf::Vector2f(0, box.getSize().y), sf::Color::Blue),
sf::Vertex(sf::Vector2f(box.getSize().x, 0), sf::Color::Blue),
};
renderTexture.draw(lineb, 2, sf::Lines);
*/
// render to window
renderTexture.display();
window.draw(output);
}
GridPoint& Grid::at(int x, int y)
{
return points[y * grid_x + x];
}

56
src/Grid.h Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include "Common.h"
#include "libtcod.h"
//#include "Entity.h"
class Entity; // forward declare
class GridPoint
{
public:
// Layers: color, walkable, tilesprite, transparent, visible, discovered, overlay, uisprite
sf::Color color;
bool walkable;
int tilesprite;
bool transparent, visible, discovered;
sf::Color color_overlay;
int tile_overlay, uisprite;
GridPoint();
};
class Grid
{
private:
public:
Grid();
sf::RectangleShape box; // view on window
bool visible;
sf::Texture texture;
sf::Sprite sprite, output;
sf::RenderTexture renderTexture;
TCODMap* tcodmap;
void setSprite(int);
const int texture_width, texture_height;
auto contains(sf::Vector2i p) { return box.getGlobalBounds().contains(p.x, p.y); }
Grid(int gx, int gy, int gs, int _x, int _y, int _w, int _h);
int grid_x, grid_y; // rectangle map size (integer - sprites)
int grid_size; // pixel size of 1 sprite
float zoom;
int center_x, center_y; // center in 1.0x Pixels
std::vector<GridPoint> points; // grid visible contents
std::vector<std::shared_ptr<Entity>> entities;
void render(sf::RenderWindow&); // draw to screen
GridPoint& at(int, int);
bool inBounds(int, int);
void screenToGrid(int, int, int&, int&);
void renderPxToGrid(int, int, int&, int&);
void gridToRenderPx(int, int, int&, int&);
void integerGrid(float, float, int&, int&);
void refreshTCODmap();
void refreshTCODsight(int, int);
TCODDijkstra *dijkstra; //= new TCODDijkstra(myMap);
};

24
src/IndexSprite.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "IndexSprite.h"
#include "GameEngine.h"
//int texture_index, sprite_index, x, y;
GameEngine* IndexSprite::game;
sf::Sprite IndexSprite::drawable()
{
sf::Sprite s;
auto& tex = IndexSprite::game->textures[texture_index];
s.setTexture(tex.texture);
s.setScale(sf::Vector2f(scale, scale));
s.setPosition(sf::Vector2f(x, y));
//std::cout << "Drawable position: " << x << ", " << y << " -> " << s.getPosition().x << ", " << s.getPosition().y << std::endl;
s.setTextureRect(tex.spriteCoordinates(sprite_index));
return s;
}
IndexSprite::IndexSprite(int _ti, int _si, float _x, float _y, float _s):
texture_index(_ti), sprite_index(_si), x(_x), y(_y), scale(_s) {
//std::cout << "IndexSprite constructed with x, y " << _x << ", " << _y << std::endl;
//std::cout << " * Stored x, y " << x << ", " << y << std::endl;
}

13
src/IndexSprite.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include "Common.h"
class GameEngine; // forward declare
class IndexSprite {
public:
int texture_index, sprite_index;
float x, y;
float scale;
static GameEngine* game;
sf::Sprite drawable();
IndexSprite(int, int, float, float, float);
};

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,19 @@
#pragma once
#include "Common.h"
#include "Entity.h"
//#include "EntityManager.h"
//#include "Scene.h"
//#include "GameEngine.h" // can't - need forward declaration
//#include "ActionCode.h"
#include "Python.h"
#include "UIMenu.h"
#include "Grid.h"
#include "IndexSprite.h"
#include "EntityManager.h"
#include <list>
#include "PyFont.h"
#include "PyTexture.h"
// implementation required to link templates
#include "Animation.h"
class GameEngine; // forward declared (circular members)
@ -15,29 +24,63 @@ private:
texture_width = 12, texture_height = 11, // w & h sprite/frame count
texture_sprite_count = 11 * 12; // t_width * t_height, minus blanks?
// TODO: this is wrong, load resources @ GameEngineSprite sprite;
// sf::Texture texture;
//std::vector<PyMethodDef> mcrfpyMethodsVector;
//static PyObject* PyInit_mcrfpy();
McRFPy_API();
public:
static PyObject* mcrf_module;
static std::shared_ptr<PyFont> default_font;
static std::shared_ptr<PyTexture> default_texture;
//inline static sf::Sprite sprite;
//inline static sf::Texture texture;
//static void setSpriteTexture(int);
inline static sf::Sprite sprite;
inline static sf::Texture texture;
static void setSpriteTexture(int);
inline static GameEngine* game;
static void api_init();
static void api_shutdown();
// Python API functionality - use mcrfpy.* in scripts
//static PyObject* _drawSprite(PyObject*, PyObject*);
static PyObject* _drawSprite(PyObject*, PyObject*);
static void REPL_device(FILE * fp, const char *filename);
static void REPL();
// Jank mode engage: let the API hold data for Python to hack on
static std::map<std::string, UIMenu*> menus;
static EntityManager entities; // this is also kinda good, entities not on the current grid can still act (like monsters following you through doors??)
static std::map<std::string, Grid*> grids;
static std::list<Animation*> animations;
static std::vector<sf::SoundBuffer> soundbuffers;
static sf::Music music;
static sf::Sound sfx;
static std::shared_ptr<Entity> player;
static std::map<std::string, PyObject*> callbacks;
// Jank Python Method Exposures
static PyObject* _createMenu(PyObject*, PyObject*); // creates a new menu object in McRFPy_API::menus
static PyObject* _listMenus(PyObject*, PyObject*);
static PyObject* _modMenu(PyObject*, PyObject*);
static PyObject* _createCaption(PyObject*, PyObject*); // calls menu.add_caption
static PyObject* _createButton(PyObject*, PyObject*);
static PyObject* _createTexture(PyObject*, PyObject*);
static PyObject* _listTextures(PyObject*, PyObject*);
static PyObject* _createSprite(PyObject*, PyObject*);
// use _listMenus, probably will not implement
//static PyObject* _listCaptions(PyObject*, PyObject*);
//static PyObject* _listButtons(PyObject*, PyObject*);
static PyObject* _createEntity(PyObject*, PyObject*);
//static PyObject* _listEntities(PyObject*, PyObject*);
static PyObject* _createGrid(PyObject*, PyObject*);
static PyObject* _listGrids(PyObject*, PyObject*);
static PyObject* _modGrid(PyObject*, PyObject*);
static PyObject* _createAnimation(PyObject*, PyObject*);
static PyObject* _registerPyAction(PyObject*, PyObject*);
static PyObject* _registerInputAction(PyObject*, PyObject*);
@ -49,29 +92,71 @@ public:
static PyObject* _getMusicVolume(PyObject*, PyObject*);
static PyObject* _getSoundVolume(PyObject*, PyObject*);
// allow all player actions (items, menus, movement, combat)
static PyObject* _unlockPlayerInput(PyObject*, PyObject*);
// disallow player actions (animating enemy actions)
static PyObject* _lockPlayerInput(PyObject*, PyObject*);
// prompt C++/Grid Objects to callback with a target Entity or grid space
static PyObject* _requestGridTarget(PyObject*, PyObject*);
// string for labeling the map
static std::string active_grid;
static PyObject* _activeGrid(PyObject*, PyObject*);
static PyObject* _setActiveGrid(PyObject*, PyObject*);
// string for prompting input
static std::string input_mode;
static PyObject* _inputMode(PyObject*, PyObject*);
// turn cycle
static int turn_number;
static PyObject* _turnNumber(PyObject*, PyObject*);
static PyObject* _refreshFov(PyObject*, PyObject*);
static bool do_camfollow;
static void camFollow();
static PyObject* _camFollow(PyObject*, PyObject*);
static PyObject* _sceneUI(PyObject*, PyObject*);
// scene control
static PyObject* _setScene(PyObject*, PyObject*);
static PyObject* _currentScene(PyObject*, PyObject*);
static PyObject* _createScene(PyObject*, PyObject*);
static PyObject* _keypressScene(PyObject*, PyObject*);
// timer control
static PyObject* _setTimer(PyObject*, PyObject*);
static PyObject* _delTimer(PyObject*, PyObject*);
static PyObject* _exit(PyObject*, PyObject*);
static PyObject* _setScale(PyObject*, PyObject*);
// accept keyboard input from scene
static sf::Vector2i cursor_position;
static void player_input(int, int);
static void computerTurn();
static void playerTurn();
// Jank Functionality
static UIMenu* createMenu(int posx, int posy, int sizex, int sizey);
static void createCaption(std::string menukey, std::string text, int fontsize, sf::Color textcolor);
static void createButton(std::string menukey, int x, int y, int w, int h, sf::Color bgcolor, sf::Color textcolor, std::string caption, std::string action);
static void createSprite(std::string menukey, int ti, int si, float x, float y, float scale);
static int createTexture(std::string filename, int grid_size, int grid_width, int grid_height);
//static void playSound(const char * filename);
//static void playMusic(const char * filename);
static void doAction(std::string);
// McRFPy_API(GameEngine*);
// API functionality - use from C++ directly
//void spawnEntity(int tex_index, int grid_x, int grid_y, PyObject* script);
static void executeScript(std::string);
static void executePyString(std::string);
};
/*
static PyMethodDef mcrfpyMethods[] = {
{"drawSprite", McRFPy_API::_drawSprite, METH_VARARGS,
"Draw a sprite (index, x, y)"},
{NULL, NULL, 0, NULL}
};
static PyModuleDef mcrfpyModule = {
PyModuleDef_HEAD_INIT, "mcrfpy", NULL, -1, mcrfpyMethods,
NULL, NULL, NULL, NULL
};
// Module initializer fn, passed to PyImport_AppendInittab
PyObject* PyInit_mcrfpy()
{
return PyModule_Create(&mcrfpyModule);
}
*/

53
src/MenuScene.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "MenuScene.h"
#include "ActionCode.h"
MenuScene::MenuScene(GameEngine* g) : Scene(g)
{
text.setFont(game->getFont());
text.setString("McRogueFace Engine - r/RoguelikeDev Tutorial 2023");
text.setCharacterSize(24);
//std::cout << "MenuScene Initialized. " << game << std::endl;
//std::cout << "Font: " << game->getFont().getInfo().family << std::endl;
text2.setFont(game->getFont());
text2.setString("Press 'Spacebar' to run demo");
text2.setCharacterSize(16);
text2.setPosition(0.0f, 50.0f);
text3.setFont(game->getFont());
text3.setString("use 'W' 'A' 'S' 'D' to move (even when blank; it's a bug)");
text3.setCharacterSize(16);
text3.setPosition(0.0f, 80.0f);
registerAction(ActionCode::KEY + sf::Keyboard::Space, "start_game");
registerAction(ActionCode::KEY + sf::Keyboard::Up, "up");
registerAction(ActionCode::KEY + sf::Keyboard::Down, "down");
}
void MenuScene::update()
{
//std::cout << "MenuScene update" << std::endl;
}
void MenuScene::doAction(std::string name, std::string type)
{
//std::cout << "MenuScene doAction: " << name << ", " << type << std::endl;
//if (name.compare("start_game") == 0 and type.compare("start") == 0)
if(ACTION("start_game", "start"))
game->changeScene("py");
/*
else if(ACTIONONCE("up"))
game->getWindow().setSize(sf::Vector2u(1280, 800));
else if(ACTIONONCE("down"))
game->getWindow().setSize(sf::Vector2u(1024, 768));
*/
}
void MenuScene::sRender()
{
game->getWindow().clear();
game->getWindow().draw(text);
game->getWindow().draw(text2);
game->getWindow().draw(text3);
game->getWindow().display();
}

View File

@ -4,14 +4,15 @@
#include "Scene.h"
#include "GameEngine.h"
class PyScene: public Scene
class MenuScene: public Scene
{
sf::Text text;
sf::Text text2;
sf::Text text3;
public:
PyScene(GameEngine*);
MenuScene(GameEngine*);
void update() override final;
void doAction(std::string, std::string) override final;
void render() override final;
void do_mouse_input(std::string, std::string);
void sRender() override final;
};

View File

@ -1,114 +0,0 @@
#include "PyCallable.h"
PyCallable::PyCallable(PyObject* _target)
{
target = Py_XNewRef(_target);
}
PyCallable::~PyCallable()
{
if (target)
Py_DECREF(target);
}
PyObject* PyCallable::call(PyObject* args, PyObject* kwargs)
{
return PyObject_Call(target, args, kwargs);
}
bool PyCallable::isNone()
{
return (target == Py_None || target == NULL);
}
PyTimerCallable::PyTimerCallable(PyObject* _target, int _interval, int now)
: PyCallable(_target), interval(_interval), last_ran(now)
{}
PyTimerCallable::PyTimerCallable()
: PyCallable(Py_None), interval(0), last_ran(0)
{}
bool PyTimerCallable::hasElapsed(int now)
{
return now >= last_ran + interval;
}
void PyTimerCallable::call(int now)
{
PyObject* args = Py_BuildValue("(i)", now);
PyObject* retval = PyCallable::call(args, NULL);
if (!retval)
{
PyErr_Print();
PyErr_Clear();
} else if (retval != Py_None)
{
std::cout << "timer returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
std::cout << PyUnicode_AsUTF8(PyObject_Repr(retval)) << std::endl;
}
}
bool PyTimerCallable::test(int now)
{
if(hasElapsed(now))
{
call(now);
last_ran = now;
return true;
}
return false;
}
PyClickCallable::PyClickCallable(PyObject* _target)
: PyCallable(_target)
{}
PyClickCallable::PyClickCallable()
: PyCallable(Py_None)
{}
void PyClickCallable::call(sf::Vector2f mousepos, std::string button, std::string action)
{
PyObject* args = Py_BuildValue("(iiss)", (int)mousepos.x, (int)mousepos.y, button.c_str(), action.c_str());
PyObject* retval = PyCallable::call(args, NULL);
if (!retval)
{
std::cout << "ClickCallable has raised an exception. It's going to STDERR and being dropped:" << std::endl;
PyErr_Print();
PyErr_Clear();
} else if (retval != Py_None)
{
std::cout << "ClickCallable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
std::cout << PyUnicode_AsUTF8(PyObject_Repr(retval)) << std::endl;
}
}
PyObject* PyClickCallable::borrow()
{
return target;
}
PyKeyCallable::PyKeyCallable(PyObject* _target)
: PyCallable(_target)
{}
PyKeyCallable::PyKeyCallable()
: PyCallable(Py_None)
{}
void PyKeyCallable::call(std::string key, std::string action)
{
if (target == Py_None || target == NULL) return;
PyObject* args = Py_BuildValue("(ss)", key.c_str(), action.c_str());
PyObject* retval = PyCallable::call(args, NULL);
if (!retval)
{
std::cout << "KeyCallable has raised an exception. It's going to STDERR and being dropped:" << std::endl;
PyErr_Print();
PyErr_Clear();
} else if (retval != Py_None)
{
std::cout << "KeyCallable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
}
}

View File

@ -1,45 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
class PyCallable
{
protected:
PyObject* target;
PyCallable(PyObject*);
~PyCallable();
PyObject* call(PyObject*, PyObject*);
public:
bool isNone();
};
class PyTimerCallable: public PyCallable
{
private:
int interval;
int last_ran;
void call(int);
public:
bool hasElapsed(int);
bool test(int);
PyTimerCallable(PyObject*, int, int);
PyTimerCallable();
};
class PyClickCallable: public PyCallable
{
public:
void call(sf::Vector2f, std::string, std::string);
PyObject* borrow();
PyClickCallable(PyObject*);
PyClickCallable();
};
class PyKeyCallable: public PyCallable
{
public:
void call(std::string, std::string);
//PyObject* borrow(); // not yet implemented
PyKeyCallable(PyObject*);
PyKeyCallable();
};

View File

@ -1,174 +0,0 @@
#include "PyColor.h"
#include "McRFPy_API.h"
#include "PyObjectUtils.h"
#include "PyRAII.h"
PyGetSetDef PyColor::getsetters[] = {
{"r", (getter)PyColor::get_member, (setter)PyColor::set_member, "Red component", (void*)0},
{"g", (getter)PyColor::get_member, (setter)PyColor::set_member, "Green component", (void*)1},
{"b", (getter)PyColor::get_member, (setter)PyColor::set_member, "Blue component", (void*)2},
{"a", (getter)PyColor::get_member, (setter)PyColor::set_member, "Alpha component", (void*)3},
{NULL}
};
PyColor::PyColor(sf::Color target)
:data(target) {}
PyObject* PyColor::pyObject()
{
PyTypeObject* type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color");
if (!type) return nullptr;
PyColorObject* obj = (PyColorObject*)type->tp_alloc(type, 0);
Py_DECREF(type);
if (obj) {
obj->data = data;
}
return (PyObject*)obj;
}
sf::Color PyColor::fromPy(PyObject* obj)
{
PyColorObject* self = (PyColorObject*)obj;
return self->data;
}
sf::Color PyColor::fromPy(PyColorObject* self)
{
return self->data;
}
void PyColor::set(sf::Color color)
{
data = color;
}
sf::Color PyColor::get()
{
return data;
}
Py_hash_t PyColor::hash(PyObject* obj)
{
auto self = (PyColorObject*)obj;
Py_hash_t value = 0;
value += self->data.r;
value << 8; value += self->data.g;
value << 8; value += self->data.b;
value << 8; value += self->data.a;
return value;
}
PyObject* PyColor::repr(PyObject* obj)
{
PyColorObject* self = (PyColorObject*)obj;
std::ostringstream ss;
sf::Color c = self->data;
ss << "<Color (" << int(c.r) << ", " << int(c.g) << ", " << int(c.b) << ", " << int(c.a) << ")>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int PyColor::init(PyColorObject* self, PyObject* args, PyObject* kwds) {
//using namespace mcrfpydef;
static const char* keywords[] = { "r", "g", "b", "a", nullptr };
PyObject* leader;
int r = -1, g = -1, b = -1, a = 255;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iii", const_cast<char**>(keywords), &leader, &g, &b, &a)) {
PyErr_SetString(PyExc_TypeError, "mcrfpy.Color requires a 3-tuple, 4-tuple, color name, or integer values within 0-255 (r, g, b, optionally a)");
return -1;
}
//std::cout << "Arg parsing succeeded. Values: " << r << " " << g << " " << b << " " << a <<std::endl;
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(leader)) << std::endl;
// Tuple cases
if (PyTuple_Check(leader)) {
Py_ssize_t tupleSize = PyTuple_Size(leader);
if (tupleSize < 3 || tupleSize > 4) {
PyErr_SetString(PyExc_TypeError, "Invalid tuple length: mcrfpy.Color requires a 3-tuple, 4-tuple, color name, or integer values within 0-255 (r, g, b, optionally a)");
return -1;
}
r = PyLong_AsLong(PyTuple_GetItem(leader, 0));
g = PyLong_AsLong(PyTuple_GetItem(leader, 1));
b = PyLong_AsLong(PyTuple_GetItem(leader, 2));
if (tupleSize == 4) {
a = PyLong_AsLong(PyTuple_GetItem(leader, 3));
}
}
// Color name (not implemented yet)
else if (PyUnicode_Check(leader)) {
PyErr_SetString(PyExc_NotImplementedError, "Color names aren't ready yet");
return -1;
}
// Check if the leader is actually an integer for the r value
else if (PyLong_Check(leader)) {
r = PyLong_AsLong(leader);
// Additional validation not shown; g, b are required to be parsed
} else {
PyErr_SetString(PyExc_TypeError, "mcrfpy.Color requires a 3-tuple, 4-tuple, color name, or integer values within 0-255 (r, g, b, optionally a)");
return -1;
}
// Validate color values
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255) {
PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255.");
return -1;
}
self->data = sf::Color(r, g, b, a);
return 0;
}
PyObject* PyColor::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
auto obj = (PyObject*)type->tp_alloc(type, 0);
//Py_INCREF(obj);
return obj;
}
PyObject* PyColor::get_member(PyObject* obj, void* closure)
{
// TODO
return Py_None;
}
int PyColor::set_member(PyObject* obj, PyObject* value, void* closure)
{
// TODO
return 0;
}
PyColorObject* PyColor::from_arg(PyObject* args)
{
// Use RAII for type reference management
PyRAII::PyTypeRef type("Color", McRFPy_API::mcrf_module);
if (!type) {
return 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) {
// obj will be automatically cleaned up when it goes out of scope
return NULL;
}
// Release ownership and return
return (PyColorObject*)obj.release();
}

View File

@ -1,49 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
class PyColor;
class UIDrawable; // forward declare for pointer
typedef struct {
PyObject_HEAD
sf::Color data;
} PyColorObject;
class PyColor
{
private:
public:
sf::Color data;
PyColor(sf::Color);
void set(sf::Color);
sf::Color get();
PyObject* pyObject();
static sf::Color fromPy(PyObject*);
static sf::Color fromPy(PyColorObject*);
static PyObject* repr(PyObject*);
static Py_hash_t hash(PyObject*);
static int init(PyColorObject*, PyObject*, PyObject*);
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
static PyObject* get_member(PyObject*, void*);
static int set_member(PyObject*, PyObject*, void*);
static PyGetSetDef getsetters[];
static PyColorObject* from_arg(PyObject*);
};
namespace mcrfpydef {
static PyTypeObject PyColorType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Color",
.tp_basicsize = sizeof(PyColorObject),
.tp_itemsize = 0,
.tp_repr = PyColor::repr,
.tp_hash = PyColor::hash,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("SFML Color Object"),
.tp_getset = PyColor::getsetters,
.tp_init = (initproc)PyColor::init,
.tp_new = PyColor::pynew,
};
}

View File

@ -1,63 +0,0 @@
#include "PyFont.h"
#include "McRFPy_API.h"
PyFont::PyFont(std::string filename)
: source(filename)
{
font = sf::Font();
font.loadFromFile(source);
}
PyObject* PyFont::pyObject()
{
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font");
//PyObject* obj = PyType_GenericAlloc(&mcrfpydef::PyFontType, 0);
PyObject* obj = PyFont::pynew(type, Py_None, Py_None);
try {
((PyFontObject*)obj)->data = shared_from_this();
}
catch (std::bad_weak_ptr& e)
{
std::cout << "Bad weak ptr: shared_from_this() failed in PyFont::pyObject(); did you create a PyFont outside of std::make_shared? enjoy your segfault, soon!" << std::endl;
}
// TODO - shared_from_this will raise an exception if the object does not have a shared pointer. Constructor should be made private; write a factory function
return obj;
}
PyObject* PyFont::repr(PyObject* obj)
{
PyFontObject* self = (PyFontObject*)obj;
std::ostringstream ss;
if (!self->data)
{
ss << "<Font [invalid internal object]>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
auto& pfont = *(self->data);
ss << "<Font (family=" << pfont.font.getInfo().family << ") source=`" << pfont.source << "`>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
Py_hash_t PyFont::hash(PyObject* obj)
{
auto self = (PyFontObject*)obj;
return reinterpret_cast<Py_hash_t>(self->data.get());
}
int PyFont::init(PyFontObject* self, PyObject* args, PyObject* kwds)
{
static const char* keywords[] = { "filename", nullptr };
char* filename;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", const_cast<char**>(keywords), &filename))
return -1;
self->data = std::make_shared<PyFont>(filename);
return 0;
}
PyObject* PyFont::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
return (PyObject*)type->tp_alloc(type, 0);
}

View File

@ -1,40 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
class PyFont;
typedef struct {
PyObject_HEAD
std::shared_ptr<PyFont> data;
} PyFontObject;
class PyFont : public std::enable_shared_from_this<PyFont>
{
private:
std::string source;
public:
PyFont(std::string filename);
sf::Font font;
PyObject* pyObject();
static PyObject* repr(PyObject*);
static Py_hash_t hash(PyObject*);
static int init(PyFontObject*, PyObject*, PyObject*);
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
};
namespace mcrfpydef {
static PyTypeObject PyFontType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Font",
.tp_basicsize = sizeof(PyFontObject),
.tp_itemsize = 0,
.tp_repr = PyFont::repr,
//.tp_hash = PyFont::hash,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("SFML Font Object"),
//.tp_base = &PyBaseObject_Type,
.tp_init = (initproc)PyFont::init,
.tp_new = PyType_GenericNew, //PyFont::pynew,
};
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -1,75 +0,0 @@
#include "PyScene.h"
#include "ActionCode.h"
#include "Resources.h"
#include "PyCallable.h"
PyScene::PyScene(GameEngine* g) : Scene(g)
{
// mouse events
registerAction(ActionCode::MOUSEBUTTON + sf::Mouse::Left, "left");
registerAction(ActionCode::MOUSEBUTTON + sf::Mouse::Right, "right");
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_DEL, "wheel_up");
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");
}
void PyScene::update()
{
}
void PyScene::do_mouse_input(std::string button, std::string type)
{
auto unscaledmousepos = sf::Mouse::getPosition(game->getWindow());
auto mousepos = game->getWindow().mapPixelToCoords(unscaledmousepos);
UIDrawable* target;
for (auto d: *ui_elements)
{
target = d->click_at(sf::Vector2f(mousepos));
if (target)
{
/*
PyObject* args = Py_BuildValue("(iiss)", (int)mousepos.x, (int)mousepos.y, button.c_str(), type.c_str());
PyObject* retval = PyObject_Call(target->click_callable, args, NULL);
if (!retval)
{
std::cout << "click_callable has raised an exception. It's going to STDERR and being dropped:" << std::endl;
PyErr_Print();
PyErr_Clear();
} else if (retval != Py_None)
{
std::cout << "click_callable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
}
*/
target->click_callable->call(mousepos, button, type);
}
}
}
void PyScene::doAction(std::string name, std::string type)
{
if (ACTIONPY) {
McRFPy_API::doAction(name.substr(0, name.size() - 3));
}
else if (name.compare("left") == 0 || name.compare("rclick") == 0 || name.compare("wheel_up") == 0 || name.compare("wheel_down") == 0) {
do_mouse_input(name, type);
}
else if ACTIONONCE("debug_menu") {
McRFPy_API::REPL();
}
}
void PyScene::render()
{
game->getWindow().clear();
auto vec = *ui_elements;
for (auto e: vec)
{
if (e)
e->render();
}
game->getWindow().display();
}

View File

@ -1,81 +0,0 @@
#include "PyTexture.h"
#include "McRFPy_API.h"
PyTexture::PyTexture(std::string filename, int sprite_w, int sprite_h)
: source(filename), sprite_width(sprite_w), sprite_height(sprite_h)
{
texture = sf::Texture();
texture.loadFromFile(source);
auto size = texture.getSize();
sheet_width = (size.x / sprite_width);
sheet_height = (size.y / sprite_height);
if (size.x % sprite_width != 0 || size.y % sprite_height != 0)
{
std::cout << "Warning: Texture `" << source << "` is not an even number of sprite widths or heights across." << std::endl
<< "Sprite size given was " << sprite_w << "x" << sprite_h << "px but the file has a resolution of " << sheet_width << "x" << sheet_height << "px." << std::endl;
}
}
sf::Sprite PyTexture::sprite(int index, sf::Vector2f pos, sf::Vector2f s)
{
int tx = index % sheet_width, ty = index / sheet_width;
auto ir = sf::IntRect(tx * sprite_width, ty * sprite_height, sprite_width, sprite_height);
auto sprite = sf::Sprite(texture, ir);
sprite.setPosition(pos);
sprite.setScale(s);
return sprite;
}
PyObject* PyTexture::pyObject()
{
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
PyObject* obj = PyTexture::pynew(type, Py_None, Py_None);
try {
((PyTextureObject*)obj)->data = shared_from_this();
}
catch (std::bad_weak_ptr& e)
{
std::cout << "Bad weak ptr: shared_from_this() failed in PyTexture::pyObject(); did you create a PyTexture outside of std::make_shared? enjoy your segfault, soon!" << std::endl;
}
// TODO - shared_from_this will raise an exception if the object does not have a shared pointer. Constructor should be made private; write a factory function
return obj;
}
PyObject* PyTexture::repr(PyObject* obj)
{
PyTextureObject* self = (PyTextureObject*)obj;
std::ostringstream ss;
if (!self->data)
{
ss << "<Texture [invalid internal object]>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
auto& ptex = *(self->data);
ss << "<Texture " << ptex.sheet_height << " rows, " << ptex.sheet_width << " columns; " << ptex.sprite_width << "x" << ptex.sprite_height << "px sprites. source='" << ptex.source << "'>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
Py_hash_t PyTexture::hash(PyObject* obj)
{
auto self = (PyTextureObject*)obj;
return reinterpret_cast<Py_hash_t>(self->data.get());
}
int PyTexture::init(PyTextureObject* self, PyObject* args, PyObject* kwds)
{
static const char* keywords[] = { "filename", "sprite_width", "sprite_height", nullptr };
char* filename;
int sprite_width, sprite_height;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sii", const_cast<char**>(keywords), &filename, &sprite_width, &sprite_height))
return -1;
self->data = std::make_shared<PyTexture>(filename, sprite_width, sprite_height);
return 0;
}
PyObject* PyTexture::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
return (PyObject*)type->tp_alloc(type, 0);
}

View File

@ -1,44 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
class PyTexture;
typedef struct {
PyObject_HEAD
std::shared_ptr<PyTexture> data;
} PyTextureObject;
class PyTexture : public std::enable_shared_from_this<PyTexture>
{
private:
sf::Texture texture;
std::string source;
int sheet_width, sheet_height;
public:
int sprite_width, sprite_height; // just use them read only, OK?
PyTexture(std::string filename, int sprite_w, int sprite_h);
sf::Sprite sprite(int index, sf::Vector2f pos = sf::Vector2f(0, 0), sf::Vector2f s = sf::Vector2f(1.0, 1.0));
PyObject* pyObject();
static PyObject* repr(PyObject*);
static Py_hash_t hash(PyObject*);
static int init(PyTextureObject*, PyObject*, PyObject*);
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
};
namespace mcrfpydef {
static PyTypeObject PyTextureType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Texture",
.tp_basicsize = sizeof(PyTextureObject),
.tp_itemsize = 0,
.tp_repr = PyTexture::repr,
.tp_hash = PyTexture::hash,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("SFML Texture Object"),
//.tp_base = &PyBaseObject_Type,
.tp_init = (initproc)PyTexture::init,
.tp_new = PyType_GenericNew, //PyTexture::pynew,
};
}

View File

@ -1,130 +0,0 @@
#include "PyVector.h"
#include "PyObjectUtils.h"
PyGetSetDef PyVector::getsetters[] = {
{"x", (getter)PyVector::get_member, (setter)PyVector::set_member, "X/horizontal component", (void*)0},
{"y", (getter)PyVector::get_member, (setter)PyVector::set_member, "Y/vertical component", (void*)1},
{NULL}
};
PyVector::PyVector(sf::Vector2f target)
:data(target) {}
PyObject* PyVector::pyObject()
{
PyTypeObject* type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
if (!type) return nullptr;
PyVectorObject* obj = (PyVectorObject*)type->tp_alloc(type, 0);
Py_DECREF(type);
if (obj) {
obj->data = data;
}
return (PyObject*)obj;
}
sf::Vector2f PyVector::fromPy(PyObject* obj)
{
PyVectorObject* self = (PyVectorObject*)obj;
return self->data;
}
sf::Vector2f PyVector::fromPy(PyVectorObject* self)
{
return self->data;
}
Py_hash_t PyVector::hash(PyObject* obj)
{
auto self = (PyVectorObject*)obj;
Py_hash_t value = 0;
value += self->data.x;
value << 8; value += self->data.y;
return value;
}
PyObject* PyVector::repr(PyObject* obj)
{
PyVectorObject* self = (PyVectorObject*)obj;
std::ostringstream ss;
sf::Vector2f v = self->data;
ss << "<Vector (" << v.x << ", " << v.y << ")>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int PyVector::init(PyVectorObject* self, PyObject* args, PyObject* kwds)
{
using namespace mcrfpydef;
static const char* keywords[] = { "x", "y", nullptr };
PyObject* leader = NULL;
float x=0, y=0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Of", const_cast<char**>(keywords), &leader, &y))
{
//PyErr_SetString(PyExc_TypeError, "mcrfpy.Vector requires a 2-tuple or two numeric values");
return -1;
}
if (leader == NULL || leader == Py_None)
{
self->data = sf::Vector2f();
return 0;
}
if (PyTuple_Check(leader))
{
if (PyTuple_Size(leader) != 2)
{
PyErr_SetString(PyExc_TypeError, "Invalid tuple length: mcrfpy.Vector requires a 2-tuple");
return -1;
}
x = PyFloat_AsDouble(PyTuple_GetItem(leader, 0));
y = PyFloat_AsDouble(PyTuple_GetItem(leader, 1));
self->data = sf::Vector2f(x, y);
return 0;
}
// else -
else if (!PyFloat_Check(leader) && !(PyLong_Check(leader)))
{
PyErr_SetString(PyExc_TypeError, "mcrfpy.Vector requires a 2-tuple or two numeric values");
return -1;
}
if (PyFloat_Check(leader)) x = PyFloat_AsDouble(leader);
else x = PyLong_AsDouble(leader);
self->data = sf::Vector2f(x, y);
return 0;
}
PyObject* PyVector::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
return (PyObject*)type->tp_alloc(type, 0);
}
PyObject* PyVector::get_member(PyObject* obj, void* closure)
{
// TODO
return Py_None;
}
int PyVector::set_member(PyObject* obj, PyObject* value, void* closure)
{
// TODO
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;
}

View File

@ -1,45 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "McRFPy_API.h"
typedef struct {
PyObject_HEAD
sf::Vector2f data;
} PyVectorObject;
class PyVector
{
public:
sf::Vector2f data;
PyVector(sf::Vector2f);
PyVector();
PyObject* pyObject();
static sf::Vector2f fromPy(PyObject*);
static sf::Vector2f fromPy(PyVectorObject*);
static PyObject* repr(PyObject*);
static Py_hash_t hash(PyObject*);
static int init(PyVectorObject*, PyObject*, PyObject*);
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
static PyObject* get_member(PyObject*, void*);
static int set_member(PyObject*, PyObject*, void*);
static PyVectorObject* from_arg(PyObject*);
static PyGetSetDef getsetters[];
};
namespace mcrfpydef {
static PyTypeObject PyVectorType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Vector",
.tp_basicsize = sizeof(PyVectorObject),
.tp_itemsize = 0,
.tp_repr = PyVector::repr,
.tp_hash = PyVector::hash,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("SFML Vector Object"),
.tp_getset = PyVector::getsetters,
.tp_init = (initproc)PyVector::init,
.tp_new = PyVector::pynew,
};
}

269
src/PythonScene.cpp Normal file
View File

@ -0,0 +1,269 @@
#include "PythonScene.h"
#include "ActionCode.h"
#include "McRFPy_API.h"
//#include "Animation.h"
PythonScene::PythonScene(GameEngine* g, std::string pymodule)
: Scene(g) {
// mouse events
registerAction(ActionCode::MOUSEBUTTON + sf::Mouse::Left, "click");
registerAction(ActionCode::MOUSEBUTTON + sf::Mouse::Right, "rclick");
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_DEL, "wheel_up");
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_NEG + ActionCode::WHEEL_DEL, "wheel_down");
// keyboard events
/*
registerAction(ActionCode::KEY + sf::Keyboard::Q, "upleft");
registerAction(ActionCode::KEY + sf::Keyboard::W, "up");
registerAction(ActionCode::KEY + sf::Keyboard::E, "upright");
registerAction(ActionCode::KEY + sf::Keyboard::A, "left");
registerAction(ActionCode::KEY + sf::Keyboard::S, "down");
registerAction(ActionCode::KEY + sf::Keyboard::D, "right");
registerAction(ActionCode::KEY + sf::Keyboard::Z, "downleft");
registerAction(ActionCode::KEY + sf::Keyboard::X, "wait");
registerAction(ActionCode::KEY + sf::Keyboard::C, "downright");
registerAction(ActionCode::KEY + sf::Keyboard::Numpad7, "upleft");
registerAction(ActionCode::KEY + sf::Keyboard::Numpad8, "up");
registerAction(ActionCode::KEY + sf::Keyboard::Numpad9, "upright");
registerAction(ActionCode::KEY + sf::Keyboard::Numpad4, "left");
registerAction(ActionCode::KEY + sf::Keyboard::Numpad5, "wait");
registerAction(ActionCode::KEY + sf::Keyboard::Numpad6, "right");
registerAction(ActionCode::KEY + sf::Keyboard::Numpad1, "downleft");
registerAction(ActionCode::KEY + sf::Keyboard::Numpad2, "down");
registerAction(ActionCode::KEY + sf::Keyboard::Numpad3, "downright");
*/
// window resize
registerAction(0, "event");
dragging = false;
drag_grid = NULL;
// import pymodule and call start()
McRFPy_API::executePyString("import " + pymodule);
McRFPy_API::executePyString(pymodule + ".start()");
}
void PythonScene::animate() {
//std::cout << "Number of animations: " << McRFPy_API::animations.size() << std::endl;
auto frametime = game->getFrameTime();
auto it = McRFPy_API::animations.begin();
while (it != McRFPy_API::animations.end()) {
//std::cout << "Iterating" << std::endl;
(*it)->step(frametime);
//std::cout << "Step complete" << std::endl;
if ((*it)->isDone()) {
//std::cout << "Cleaning up Animation" << std::endl;
auto prev = it;
it++;
McRFPy_API::animations.erase(prev);
} else it++;
}
/* // workin on it
for (auto p : animations) {
if (p.first == "int") {
((Animation<int>)p.second).step(frametime);
} else if (p.first == "string") {
((Animation<std::string>)p.second).step(frametime);
} else if (p.first == "float") {
((Animation<float>)p.second).step(frametime);
} else if (p.first == "vector2f") {
((Animation<sf::Vector2f>)p.second).step(frametime);
} else if (p.first == "vector2i") {
((Animation<sf::Vector2i>)p.second).step(frametime);
} else if (p.first == "color") {
((Animation<int>)p.second).step(frametime); // TODO
} else {
std::cout << "Animation has label " << p.first << "; no type found" << std::endl;
}
}
auto it = animations.begin();
while (it != animations.end()) {
bool done = false;
if (p.first == "int") {
((Animation<int>)p.second).step(frametime);
} else if (p.first == "string") {
if ((Animation<std::string>)p.second).isDone()
delete (Animation<std::string>)p.second
} else if (p.first == "float") {
((Animation<float>)p.second).step(frametime);
} else if (p.first == "vector2f") {
((Animation<sf::Vector2f>)p.second).step(frametime);
} else if (p.first == "vector2i") {
((Animation<sf::Vector2i>)p.second).step(frametime);
} else if (p.first == "color") {
((Animation<int>)p.second).step(frametime); // TODO
if ((*it).second.isDone()) {
animations.erase(it++);
} else { it++; }
}
*/
}
void PythonScene::update() {
// turn cycle: If player's input made the state "computerturnwait", finish
// all animations and then let the NPCs act
if (McRFPy_API::animations.size() == 0 && McRFPy_API::input_mode.compare("computerturnwait") == 0) {
McRFPy_API::input_mode = "computerturn";
}
else if (McRFPy_API::animations.size() == 0 && McRFPy_API::input_mode.compare("computerturnrunning") == 0) {
McRFPy_API::input_mode = "playerturnstart";
}
McRFPy_API::entities.update();
// check if left click is still down & mouse has moved
// continue the drag motion
if (dragging && drag_grid) {
//std::cout << "Compute dragging" << std::endl;
auto mousepos = sf::Mouse::getPosition(game->getWindow());
auto dx = mouseprev.x - mousepos.x,
dy = mouseprev.y - mousepos.y;
if (dx != 0 || dy != 0) { McRFPy_API::do_camfollow = false; }
drag_grid->center_x += (dx / drag_grid->zoom);
drag_grid->center_y += (dy / drag_grid->zoom);
mouseprev = mousepos;
}
animate();
McRFPy_API::camFollow();
if (McRFPy_API::input_mode.compare(std::string("computerturn")) == 0) McRFPy_API::computerTurn();
if (McRFPy_API::input_mode.compare(std::string("playerturnstart")) == 0) McRFPy_API::playerTurn();
}
void PythonScene::doLClick(sf::Vector2i mousepos) {
// UI buttons get first chance
for (auto pair : McRFPy_API::menus) {
if (!pair.second->visible) continue;
for (auto b : pair.second->buttons) {
//std::cout << "Box: " << pair.second->box.getPosition().x << ", "
//<< pair.second->box.getPosition().y << "; Button:" << b.rect.getPosition().x <<
//", " << b.rect.getPosition().y << "; Mouse: " << mousepos.x << ", " <<
//mousepos.y << std::endl;
// JANK: provide the button a relative coordinate.
if (b.contains(pair.second->box.getPosition(), mousepos)) {
McRFPy_API::doAction(b.getAction());
return;
}
}
}
// left clicking a grid to select a square
for (auto pair : McRFPy_API::grids) {
if (!pair.second->visible) continue;
if (pair.second->contains(mousepos)) {
// grid was clicked
return;
}
}
}
void PythonScene::doRClick(sf::Vector2i mousepos) {
// just grids for right click
for (auto pair : McRFPy_API::grids) {
if (!pair.second->visible) continue;
if (pair.second->contains(mousepos)) {
// grid was clicked
return;
}
}
}
void PythonScene::doZoom(sf::Vector2i mousepos, int value) {
// just grids for right click
for (auto pair : McRFPy_API::grids) {
if (!pair.second->visible) continue;
if (pair.second->contains(mousepos)) {
// grid was zoomed
float new_zoom = pair.second->zoom + (value * 0.25);
if (new_zoom >= 0.5 && new_zoom <= 5.0) {
pair.second->zoom = new_zoom;
}
}
}
}
void PythonScene::doAction(std::string name, std::string type) {
auto mousepos = sf::Mouse::getPosition(game->getWindow());
//std::cout << "name: " << name << ", type: " << type << std::endl;
if (ACTIONPY) {
McRFPy_API::doAction(name.substr(0, name.size() - 3));
}
else if (ACTIONONCE("click")) {
// left click start
//std::cout << "LClick started at (" << mousepos.x << ", " << mousepos.y << ")" << std::endl;
dragstart = mousepos;
mouseprev = mousepos;
dragging = true;
// determine the grid that contains the cursor
for (auto pair : McRFPy_API::grids) {
if (!pair.second->visible) continue;
if (pair.second->contains(mousepos)) {
// grid was clicked
drag_grid = pair.second;
}
}
}
else if (ACTIONAFTER("click")) {
// left click end
//std::cout << "LClick ended at (" << mousepos.x << ", " << mousepos.y << ")" << std::endl;
// if click ended without starting a drag event, try lclick?
if (dragstart == mousepos) {
// mouse did not move, do click
//std::cout << "(did not move)" << std::endl;
doLClick(mousepos);
}
dragging = false;
drag_grid = NULL;
}
else if (ACTIONONCE("rclick")) {
// not going to test for right click drag - just rclick
doRClick(mousepos);
}
else if (ACTIONONCE("wheel_up")) {
// try zoom in
doZoom(mousepos, 1);
}
else if (ACTIONONCE("wheel_down")) {
// try zoom out
doZoom(mousepos, -1);
}
else if (ACTIONONCE("up")) { McRFPy_API::player_input(+0, -1); }
else if (ACTIONONCE("upright")) { McRFPy_API::player_input(+1, -1); }
else if (ACTIONONCE("right")) { McRFPy_API::player_input(+1, +0); }
else if (ACTIONONCE("downright")) { McRFPy_API::player_input(+1, +1); }
else if (ACTIONONCE("down")) { McRFPy_API::player_input(+0, +1); }
else if (ACTIONONCE("downleft")) { McRFPy_API::player_input(-1, +1); }
else if (ACTIONONCE("left")) { McRFPy_API::player_input(-1, +0); }
else if (ACTIONONCE("upleft")) { McRFPy_API::player_input(-1, -1); }
else if (ACTIONONCE("wait")) { McRFPy_API::player_input(+0, +0); }
}
bool PythonScene::registerActionInjected(int code, std::string name) {
std::cout << "Registering injected action (PythonScene): " << code << " (" << ActionCode::KEY + code << ")\n";
registerAction(ActionCode::KEY + code, name);
//return false;
return true;
}
bool PythonScene::unregisterActionInjected(int code, std::string name) {
return false;
}
void PythonScene::sRender() {
game->getWindow().clear();
for (auto pair: McRFPy_API::grids) {
if (!pair.second->visible) continue;
pair.second->render(game->getWindow());
}
for (auto pair: McRFPy_API::menus) {
if (!pair.second->visible) continue;
pair.second->render(game->getWindow());
}
game->getWindow().display();
}

29
src/PythonScene.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include "Common.h"
#include "Scene.h"
#include "GameEngine.h"
#include "Grid.h"
//#include "Animation.h"
//#include <list>
class PythonScene: public Scene
{
sf::Vector2i dragstart, mouseprev;
bool dragging;
Grid* drag_grid;
void doLClick(sf::Vector2i);
void doRClick(sf::Vector2i);
void doZoom(sf::Vector2i, int);
//std::list<Animation*> animations;
void animate();
std::map<std::string, bool> actionInjected;
public:
PythonScene(GameEngine*, std::string);
void update() override final;
void doAction(std::string, std::string) override final;
void sRender() override final;
bool registerActionInjected(int, std::string) override;
bool unregisterActionInjected(int, std::string) override;
};

View File

@ -1,8 +1,7 @@
#include "Resources.h"
//#include <list>
//#include "UI.h"
#include <list>
#include "UI.h"
// Resources class members memory allocation
sf::Font Resources::font;
GameEngine* Resources::game;
std::string Resources::caption_buffer;
GameEngine* Resources::game;

View File

@ -1,7 +1,7 @@
#pragma once
#include "Common.h"
//#include <list>
//#include "UI.h"
#include <list>
#include "UI.h"
class GameEngine; // forward declared
@ -10,5 +10,4 @@ class Resources
public:
static sf::Font font;
static GameEngine* game;
static std::string caption_buffer;
};

View File

@ -4,7 +4,6 @@
//Scene::Scene() { game = 0; std::cout << "WARN: default Scene constructor called. (game = " << game << ")" << std::endl;};
Scene::Scene(GameEngine* g)
{
key_callable = std::make_unique<PyKeyCallable>();
game = g;
ui_elements = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
}
@ -40,27 +39,3 @@ bool Scene::unregisterActionInjected(int code, std::string name)
{
return false;
}
void Scene::key_register(PyObject* callable)
{
/*
if (key_callable)
{
// decrement reference before overwriting
Py_DECREF(key_callable);
}
key_callable = callable;
Py_INCREF(key_callable);
*/
key_callable = std::make_unique<PyKeyCallable>(callable);
}
void Scene::key_unregister()
{
/*
if (key_callable == NULL) return;
Py_DECREF(key_callable);
key_callable = NULL;
*/
key_callable.reset();
}

View File

@ -9,7 +9,6 @@
#include "Common.h"
#include <list>
#include "UI.h"
#include "PyCallable.h"
//#include "GameEngine.h"
class GameEngine; // forward declare
@ -31,7 +30,7 @@ public:
//Scene();
Scene(GameEngine*);
virtual void update() = 0;
virtual void render() = 0;
virtual void sRender() = 0;
virtual void doAction(std::string, std::string) = 0;
bool hasAction(std::string);
bool hasAction(int);
@ -42,8 +41,4 @@ public:
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> ui_elements;
//PyObject* key_callable;
std::unique_ptr<PyKeyCallable> key_callable;
void key_register(PyObject*);
void key_unregister();
};

View File

@ -1,31 +0,0 @@
#include "Timer.h"
Timer::Timer(PyObject* _target, int _interval, int now)
: target(_target), interval(_interval), last_ran(now)
{}
Timer::Timer()
: target(Py_None), interval(0), last_ran(0)
{}
bool Timer::test(int now)
{
if (!target || target == Py_None) return false;
if (now > last_ran + interval)
{
last_ran = now;
PyObject* args = Py_BuildValue("(i)", now);
PyObject* retval = PyObject_Call(target, args, NULL);
if (!retval)
{
std::cout << "timer has raised an exception. It's going to STDERR and being dropped:" << std::endl;
PyErr_Print();
PyErr_Clear();
} else if (retval != Py_None)
{
std::cout << "timer returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
}
return true;
}
return false;
}

View File

@ -1,15 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
class GameEngine; // forward declare
class Timer
{
public:
PyObject* target;
int interval;
int last_ran;
Timer(); // for map to build
Timer(PyObject*, int, int);
bool test(int);
};

237
src/UI.cpp Normal file
View File

@ -0,0 +1,237 @@
#include "UI.h"
#include "Resources.h"
#include "GameEngine.h"
void UIDrawable::render()
{
//std::cout << "Rendering base UIDrawable\n";
render(sf::Vector2f());
}
UIFrame::UIFrame():
x(0), y(0), w(0), h(0), outline(0)
{
children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
/*
pyOutlineColor = NULL;
pyFillColor = NULL;
_outlineColor = NULL;
_fillColor = NULL;
*/
}
UIFrame::UIFrame(float _x, float _y, float _w, float _h):
x(_x), y(_y), w(_w), h(_h), outline(0)
{
children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
/*
pyOutlineColor = NULL;
pyFillColor = NULL;
_outlineColor = NULL;
_fillColor = NULL;
*/
}
UIFrame::~UIFrame()
{
children.reset();
/*
if (pyOutlineColor) Py_DECREF(pyOutlineColor);
else if (_outlineColor) delete _outlineColor;
if (pyFillColor) Py_DECREF(pyFillColor);
else if (_fillColor) delete _fillColor;
*/
}
/*
sf::Color& fillColor(); // getter
void fillColor(sf::Color c); // C++ setter
void fillColor(PyObject* pyColor); // Python setter
sf::Color& outlineColor(); // getter
void outlineColor(sf::Color c); // C++ setter
void outlineColor(PyObject* pyColor); // Python setter
*/
PyObjectsEnum UIFrame::derived_type()
{
return PyObjectsEnum::UIFRAME;
}
void UIFrame::render(sf::Vector2f offset)
{
//std::cout << "Rendering UIFrame w/ offset " << offset.x << ", " << offset.y << "\n";
//std::cout << "position = " << x << ", " << y << "\n";
box.move(offset);
Resources::game->getWindow().draw(box);
box.move(-offset);
//sf::RectangleShape box = sf::RectangleShape(sf::Vector2f(w,h));
//sf::Vector2f pos = sf::Vector2f(x, y);
//box.setPosition(offset + pos);
//if (_fillColor) { box.setFillColor(fillColor()); }
//if (_outlineColor) { box.setOutlineColor(outlineColor()); }
//box.setOutlineThickness(outline);
//Resources::game->getWindow().draw(box);
for (auto drawable : *children) {
drawable->render(offset + box.getPosition());
}
}
void UICaption::render(sf::Vector2f offset)
{
//std::cout << "Rendering Caption with offset\n";
text.move(offset);
Resources::game->getWindow().draw(text);
text.move(-offset);
}
UISprite::UISprite() {}
UISprite::UISprite(IndexTexture* _itex, int _sprite_index, float x = 0.0, float y = 0.0, float s = 1.0)
: itex(_itex), sprite_index(_sprite_index)
{
sprite.setTexture(_itex->texture);
sprite.setTextureRect(_itex->spriteCoordinates(_sprite_index));
sprite.setPosition(sf::Vector2f(x, y));
sprite.setScale(sf::Vector2f(s, s));
}
UISprite::UISprite(IndexTexture* _itex, int _sprite_index, sf::Vector2f pos, float s = 1.0)
: itex(_itex), sprite_index(_sprite_index)
{
sprite.setTexture(_itex->texture);
sprite.setTextureRect(_itex->spriteCoordinates(_sprite_index));
sprite.setPosition(pos);
sprite.setScale(sf::Vector2f(s, s));
}
//void UISprite::update()
//{
//auto& tex = Resources::game->textures[texture_index];
//sprite.setTexture(tex.texture);
//sprite.setScale(sf::Vector2f(scale, scale));
//sprite.setPosition(sf::Vector2f(x, y));
//std::cout << "Drawable position: " << x << ", " << y << " -> " << s.getPosition().x << ", " << s.getPosition().y << std::endl;
//sprite.setTextureRect(tex.spriteCoordinates(sprite_index));
//}
void UISprite::render(sf::Vector2f offset)
{
sprite.move(offset);
Resources::game->getWindow().draw(sprite);
sprite.move(-offset);
}
void UISprite::setPosition(float x, float y)
{
setPosition(sf::Vector2f(x, y));
}
void UISprite::setPosition(sf::Vector2f pos)
{
sprite.setPosition(pos);
}
void UISprite::setScale(float s)
{
sprite.setScale(sf::Vector2f(s, s));
}
PyObjectsEnum UICaption::derived_type()
{
return PyObjectsEnum::UICAPTION;
}
PyObjectsEnum UISprite::derived_type()
{
return PyObjectsEnum::UISPRITE;
}
PyObject* DEFUNCT_py_instance(std::shared_ptr<UIDrawable> source)
{
// takes a UI drawable, calls its derived_type virtual function, and builds a Python object based on the return value.
using namespace mcrfpydef;
PyObject* newobj = NULL;
std::cout << "py_instance called\n";
switch (source->derived_type())
{
case PyObjectsEnum::UIFRAME:
{
std::cout << "UIFRAME case\n" << std::flush;
PyTypeObject* UIFrameType = &PyUIFrameType;
//std::cout << "tp_alloc\n" << std::flush;
//PyObject* _o = UIFrameType->tp_alloc(UIFrameType, 0);
//std::cout << "reinterpret_cast\n" << std::flush;
//auto o = reinterpret_cast<PyUICollectionObject*>(_o);
//PyUIFrameObject* o = (PyUIFrameObject*)PyObject_New(PyUIFrameObject, UIFrameType);
PyUIFrameObject* o = (PyUIFrameObject*)(UIFrameType->tp_alloc(UIFrameType, 0));
//PyUIFrameObject* o = PyObject_New(PyUIFrameObject, UIFrameType);
/*
// backtracking the problem: instantiate a PyColor of (255, 0, 0) for testing
PyTypeObject* colorType = &PyColorType;
PyObject* pyColor = colorType->tp_alloc(colorType, 0);
if (pyColor == NULL)
{
std::cout << "failure to allocate mcrfpy.Color / PyColorType" << std::endl;
return NULL;
}
PyColorObject* pyColorObj = reinterpret_cast<PyColorObject*>(pyColor);
pyColorObj->data = std::make_shared<sf::Color>();
pyColorObj->data-> r = 255;
return (PyObject*)pyColorObj;
*/
std::cout << "pointer check: " << o << "\n" << std::flush;
if (o)
{
std::cout << "Casting data...\n" << std::flush;
auto p = std::static_pointer_cast<UIFrame>(source);
std::cout << "casted. Assigning...\n" << std::flush;
//o->data = std::make_shared<UIFrame>();
o->data = p;
//std::cout << "assigned.\n" << std::flush;
auto usource = o->data; //(UIFrame*)source.get();
std::cout << "Loaded data into object. " << usource->box.getPosition().x << " " << usource->box.getPosition().y << " " <<
usource->box.getSize().x << " " << usource->box.getSize().y << std::endl;
}
else
{
std::cout << "Allocation failed.\n" << std::flush;
}
newobj = (PyObject*)o;
break;
}
case PyObjectsEnum::UICAPTION:
{
std::cout << "UICAPTION case\n";
PyErr_SetString(PyExc_NotImplementedError, "UICaption class not implemented in Python yet.");
/* not yet implemented
PyUICaptionObject* o = (PyUICaptionObject*)PyUICaptionType.tp_alloc(&PyUICaptionType, 0);
if (o)
o->data = std::static_pointer_cast<UICaption>(source);
*/
break;
}
case PyObjectsEnum::UISPRITE:
{
std::cout << "UISPRITE case\n";
PyErr_SetString(PyExc_NotImplementedError, "UISprite class not implemented in Python yet.");
/*
PyUISpriteObject* o = (PyUISpriteObject*)PyUISpriteType.tp_alloc(&PyUISpriteType, 0);
if (o)
o->data = std::static_pointer_cast<UISprite>(source);
*/
break;
}
default:
return NULL;
break;
}
return newobj;
}

1436
src/UI.h

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +0,0 @@
#pragma once
class UIEntity;
typedef struct {
PyObject_HEAD
std::shared_ptr<UIEntity> data;
} PyUIEntityObject;
class UIFrame;
typedef struct {
PyObject_HEAD
std::shared_ptr<UIFrame> data;
} PyUIFrameObject;
class UICaption;
typedef struct {
PyObject_HEAD
std::shared_ptr<UICaption> data;
PyObject* font;
} PyUICaptionObject;
class UIGrid;
typedef struct {
PyObject_HEAD
std::shared_ptr<UIGrid> data;
} PyUIGridObject;
class UISprite;
typedef struct {
PyObject_HEAD
std::shared_ptr<UISprite> data;
} PyUISpriteObject;

View File

@ -1,296 +0,0 @@
#include "UICaption.h"
#include "GameEngine.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
UIDrawable* UICaption::click_at(sf::Vector2f point)
{
if (click_callable)
{
if (text.getGlobalBounds().contains(point)) return this;
}
return NULL;
}
void UICaption::render(sf::Vector2f offset, sf::RenderTarget& target)
{
text.move(offset);
//Resources::game->getWindow().draw(text);
target.draw(text);
text.move(-offset);
}
PyObjectsEnum UICaption::derived_type()
{
return PyObjectsEnum::UICAPTION;
}
PyObject* UICaption::get_float_member(PyUICaptionObject* self, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr == 0)
return PyFloat_FromDouble(self->data->text.getPosition().x);
else if (member_ptr == 1)
return PyFloat_FromDouble(self->data->text.getPosition().y);
else if (member_ptr == 4)
return PyFloat_FromDouble(self->data->text.getOutlineThickness());
else if (member_ptr == 5)
return PyLong_FromLong(self->data->text.getCharacterSize());
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
}
int UICaption::set_float_member(PyUICaptionObject* self, PyObject* value, void* closure)
{
float val;
auto member_ptr = reinterpret_cast<long>(closure);
if (PyFloat_Check(value))
{
val = PyFloat_AsDouble(value);
}
else if (PyLong_Check(value))
{
val = PyLong_AsLong(value);
}
else
{
PyErr_SetString(PyExc_TypeError, "Value must be an integer.");
return -1;
}
if (member_ptr == 0) //x
self->data->text.setPosition(val, self->data->text.getPosition().y);
else if (member_ptr == 1) //y
self->data->text.setPosition(self->data->text.getPosition().x, val);
else if (member_ptr == 4) //outline
self->data->text.setOutlineThickness(val);
else if (member_ptr == 5) // character size
self->data->text.setCharacterSize(val);
return 0;
}
PyObject* UICaption::get_vec_member(PyUICaptionObject* self, void* closure)
{
return PyVector(self->data->text.getPosition()).pyObject();
}
int UICaption::set_vec_member(PyUICaptionObject* self, PyObject* value, void* closure)
{
self->data->text.setPosition(PyVector::fromPy(value));
return 0;
}
PyObject* UICaption::get_color_member(PyUICaptionObject* self, void* closure)
{
// TODO: migrate this code to a switch statement - validate closure & return values in one tighter, more extensible structure
// validate closure (should be impossible to be wrong, but it's thorough)
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr != 0 && member_ptr != 1)
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
// TODO: manually calling tp_alloc to create a PyColorObject seems like an antipattern
// fetch correct member data
sf::Color color;
if (member_ptr == 0)
{
color = self->data->text.getFillColor();
}
else if (member_ptr == 1)
{
color = self->data->text.getOutlineColor();
}
return PyColor(color).pyObject();
}
int UICaption::set_color_member(PyUICaptionObject* self, PyObject* value, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
//TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse
int r, g, b, a;
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color") /*(PyObject*)&mcrfpydef::PyColorType)*/))
{
// get value from mcrfpy.Color instance
auto c = ((PyColorObject*)value)->data;
r = c.r; g = c.g; b = c.b; a = c.a;
std::cout << "got " << int(r) << ", " << int(g) << ", " << int(b) << ", " << int(a) << std::endl;
}
else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4)
{
// reject non-Color, non-tuple value
PyErr_SetString(PyExc_TypeError, "Value must be a tuple of 3 or 4 integers or an mcrfpy.Color object.");
return -1;
}
else // get value from tuples
{
r = PyLong_AsLong(PyTuple_GetItem(value, 0));
g = PyLong_AsLong(PyTuple_GetItem(value, 1));
b = PyLong_AsLong(PyTuple_GetItem(value, 2));
a = 255;
if (PyTuple_Size(value) == 4)
{
a = PyLong_AsLong(PyTuple_GetItem(value, 3));
}
}
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255)
{
PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255.");
return -1;
}
if (member_ptr == 0)
{
self->data->text.setFillColor(sf::Color(r, g, b, a));
}
else if (member_ptr == 1)
{
self->data->text.setOutlineColor(sf::Color(r, g, b, a));
}
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return -1;
}
return 0;
}
//TODO: evaluate use of Resources::caption_buffer... can't I do this with a std::string?
PyObject* UICaption::get_text(PyUICaptionObject* self, void* closure)
{
Resources::caption_buffer = self->data->text.getString();
return PyUnicode_FromString(Resources::caption_buffer.c_str());
}
int UICaption::set_text(PyUICaptionObject* self, PyObject* value, void* closure)
{
PyObject* s = PyObject_Str(value);
PyObject * temp_bytes = PyUnicode_AsEncodedString(s, "UTF-8", "strict"); // Owned reference
if (temp_bytes != NULL) {
Resources::caption_buffer = PyBytes_AS_STRING(temp_bytes); // Borrowed pointer
Py_DECREF(temp_bytes);
}
self->data->text.setString(Resources::caption_buffer);
return 0;
}
PyGetSetDef UICaption::getsetters[] = {
{"x", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "X coordinate of top-left corner", (void*)0},
{"y", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Y coordinate of top-left corner", (void*)1},
{"pos", (getter)UICaption::get_vec_member, (setter)UICaption::set_vec_member, "(x, y) vector", (void*)0},
//{"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2},
//{"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3},
{"outline", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Thickness of the border", (void*)4},
{"fill_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Fill color of the text", (void*)0},
{"outline_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Outline color of the text", (void*)1},
//{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL},
{"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL},
{"size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Text size (integer) in points", (void*)5},
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UICAPTION},
{NULL}
};
PyObject* UICaption::repr(PyUICaptionObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<Caption (invalid internal object)>";
else {
auto text = self->data->text;
auto fc = text.getFillColor();
auto oc = text.getOutlineColor();
ss << "<Caption (x=" << text.getPosition().x << ", y=" << text.getPosition().y << ", " <<
"text='" << (std::string)text.getString() << "', " <<
"outline=" << text.getOutlineThickness() << ", " <<
"fill_color=(" << (int)fc.r << ", " << (int)fc.g << ", " << (int)fc.b << ", " << (int)fc.a <<"), " <<
"outline_color=(" << (int)oc.r << ", " << (int)oc.g << ", " << (int)oc.b << ", " << (int)oc.a <<"), " <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
{
using namespace mcrfpydef;
// Constructor switch to Vector position
//static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", nullptr };
//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;
PyObject* font=NULL, *fill_color=NULL, *outline_color=NULL;
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
// 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;
}
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
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
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");
return -1;
} else if (font != NULL)
{
auto font_obj = (PyFontObject*)font;
self->data->text.setFont(font_obj->data->font);
self->font = font;
Py_INCREF(font);
} else
{
// default font
//self->data->text.setFont(Resources::game->getFont());
}
self->data->text.setString((std::string)text);
self->data->text.setOutlineThickness(outline);
if (fill_color) {
auto fc = PyColor::from_arg(fill_color);
if (!fc) {
PyErr_SetString(PyExc_TypeError, "fill_color must be mcrfpy.Color or arguments to mcrfpy.Color.__init__");
return -1;
}
self->data->text.setFillColor(PyColor::fromPy(fc));
//Py_DECREF(fc);
} else {
self->data->text.setFillColor(sf::Color(0,0,0,255));
}
if (outline_color) {
auto oc = PyColor::from_arg(outline_color);
if (!oc) {
PyErr_SetString(PyExc_TypeError, "outline_color must be mcrfpy.Color or arguments to mcrfpy.Color.__init__");
return -1;
}
self->data->text.setOutlineColor(PyColor::fromPy(oc));
//Py_DECREF(oc);
} else {
self->data->text.setOutlineColor(sf::Color(128,128,128,255));
}
return 0;
}

View File

@ -1,63 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "UIDrawable.h"
class UICaption: public UIDrawable
{
public:
sf::Text text;
void render(sf::Vector2f, sf::RenderTarget&) override final;
PyObjectsEnum derived_type() override final;
virtual UIDrawable* click_at(sf::Vector2f point) override final;
static PyObject* get_float_member(PyUICaptionObject* self, void* closure);
static int set_float_member(PyUICaptionObject* self, PyObject* value, void* closure);
static PyObject* get_vec_member(PyUICaptionObject* self, void* closure);
static int set_vec_member(PyUICaptionObject* self, PyObject* value, void* closure);
static PyObject* get_color_member(PyUICaptionObject* self, void* closure);
static int set_color_member(PyUICaptionObject* self, PyObject* value, void* closure);
static PyObject* get_text(PyUICaptionObject* self, void* closure);
static int set_text(PyUICaptionObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* repr(PyUICaptionObject* self);
static int init(PyUICaptionObject* self, PyObject* args, PyObject* kwds);
};
namespace mcrfpydef {
static PyTypeObject PyUICaptionType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Caption",
.tp_basicsize = sizeof(PyUICaptionObject),
.tp_itemsize = 0,
// TODO - move tp_dealloc to .cpp file as static function (UICaption::dealloc)
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUICaptionObject* obj = (PyUICaptionObject*)self;
// TODO - reevaluate with PyFont usage; UICaption does not own the font
// release reference to font object
if (obj->font) Py_DECREF(obj->font);
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UICaption::repr,
//.tp_hash = NULL,
//.tp_iter
//.tp_iternext
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("docstring"),
//.tp_methods = PyUIFrame_methods,
//.tp_members = PyUIFrame_members,
.tp_getset = UICaption::getsetters,
//.tp_base = NULL,
.tp_init = (initproc)UICaption::init,
// TODO - move tp_new to .cpp file as a static function (UICaption::new)
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyUICaptionObject* self = (PyUICaptionObject*)type->tp_alloc(type, 0);
if (self) self->data = std::make_shared<UICaption>();
return (PyObject*)self;
}
};
}

View File

@ -1,284 +0,0 @@
#include "UICollection.h"
#include "UIFrame.h"
#include "UICaption.h"
#include "UISprite.h"
#include "UIGrid.h"
#include "McRFPy_API.h"
#include "PyObjectUtils.h"
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)
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return -1;
}
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)
{
PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration");
return NULL;
}
if (self->index > self->start_size - 1)
{
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
self->index++;
auto vec = self->data.get();
if (!vec)
{
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return NULL;
}
auto target = (*vec)[self->index-1];
// Return the proper Python object for this UIDrawable
return convertDrawableToPython(target);
}
PyObject* UICollectionIter::repr(PyUICollectionIterObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<UICollectionIter (invalid internal object)>";
else {
ss << "<UICollectionIter (" << self->data->size() << " child objects, @ index " << self->index << ")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
Py_ssize_t UICollection::len(PyUICollectionObject* self) {
return self->data->size();
}
PyObject* UICollection::getitem(PyUICollectionObject* self, Py_ssize_t index) {
// build a Python version of item at self->data[index]
// Copy pasted::
auto vec = self->data.get();
if (!vec)
{
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return NULL;
}
while (index < 0) index += self->data->size();
if (index > self->data->size() - 1)
{
PyErr_SetString(PyExc_IndexError, "UICollection index out of range");
return NULL;
}
auto target = (*vec)[index];
return convertDrawableToPython(target);
}
PySequenceMethods UICollection::sqmethods = {
.sq_length = (lenfunc)UICollection::len,
.sq_item = (ssizeargfunc)UICollection::getitem,
//.sq_item_by_index = PyUICollection_getitem
//.sq_slice - return a subset of the iterable
//.sq_ass_item - called when `o[x] = y` is executed (x is any object type)
//.sq_ass_slice - cool; no thanks, for now
//.sq_contains - called when `x in o` is executed
//.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer)
};
/* Idiomatic way to fetch complete types from the API rather than referencing their PyTypeObject struct
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
I never identified why `using namespace mcrfpydef;` doesn't solve the segfault issue.
The horrible macro in UIDrawable was originally a workaround for this, but as I interact with the types outside of the monster UI.h, a more general (and less icky) solution is required.
*/
PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o)
{
// if not UIDrawable subclass, reject it
// self->data->push_back( c++ object inside o );
// this would be a great use case for .tp_base
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))
)
{
PyErr_SetString(PyExc_TypeError, "Only Frame, Caption, Sprite, and Grid objects can be added to UICollection");
return NULL;
}
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")))
{
PyUIFrameObject* frame = (PyUIFrameObject*)o;
self->data->push_back(frame->data);
}
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")))
{
PyUICaptionObject* caption = (PyUICaptionObject*)o;
self->data->push_back(caption->data);
}
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")))
{
PyUISpriteObject* sprite = (PyUISpriteObject*)o;
self->data->push_back(sprite->data);
}
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")))
{
PyUIGridObject* grid = (PyUIGridObject*)o;
self->data->push_back(grid->data);
}
Py_INCREF(Py_None);
return Py_None;
}
PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o)
{
if (!PyLong_Check(o))
{
PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove");
return NULL;
}
long index = PyLong_AsLong(o);
if (index >= self->data->size())
{
PyErr_SetString(PyExc_ValueError, "Index out of range");
return NULL;
}
else if (index < 0)
{
PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented.");
return NULL;
}
// release the shared pointer at self->data[index];
self->data->erase(self->data->begin() + index);
Py_INCREF(Py_None);
return Py_None;
}
PyMethodDef UICollection::methods[] = {
{"append", (PyCFunction)UICollection::append, METH_O},
//{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO
{"remove", (PyCFunction)UICollection::remove, METH_O},
{NULL, NULL, 0, NULL}
};
PyObject* UICollection::repr(PyUICollectionObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<UICollection (invalid internal object)>";
else {
ss << "<UICollection (" << self->data->size() << " child objects)>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int UICollection::init(PyUICollectionObject* self, PyObject* args, PyObject* kwds)
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return -1;
}
PyObject* UICollection::iter(PyUICollectionObject* self)
{
// Get the iterator type from the module to ensure we have the registered version
PyTypeObject* iterType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollectionIter");
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) {
Py_DECREF(iterType);
return NULL; // Failed to allocate memory for the iterator object
}
iterObj->data = self->data;
iterObj->index = 0;
iterObj->start_size = self->data->size();
Py_DECREF(iterType);
return (PyObject*)iterObj;
}

View File

@ -1,90 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "UIDrawable.h"
class UICollectionIter
{
// really more of a namespace: all the members are public and static. But being consistent with other UI objects
public:
static int init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds);
static PyObject* next(PyUICollectionIterObject* self);
static PyObject* repr(PyUICollectionIterObject* self);
};
class UICollection
{
// really more of a namespace: all the members are public and static. But being consistent with other UI objects
public:
static Py_ssize_t len(PyUICollectionObject* self);
static PyObject* getitem(PyUICollectionObject* self, Py_ssize_t index);
static PySequenceMethods sqmethods;
static PyObject* append(PyUICollectionObject* self, PyObject* o);
static PyObject* remove(PyUICollectionObject* self, PyObject* o);
static PyMethodDef methods[];
static PyObject* repr(PyUICollectionObject* self);
static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds);
static PyObject* iter(PyUICollectionObject* self);
};
namespace mcrfpydef {
static PyTypeObject PyUICollectionIterType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.UICollectionIter",
.tp_basicsize = sizeof(PyUICollectionIterObject),
.tp_itemsize = 0,
//TODO - as static method, not inline lambda def, please
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUICollectionIterObject* obj = (PyUICollectionIterObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UICollectionIter::repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterator for a collection of UI objects"),
.tp_iter = PyObject_SelfIter,
.tp_iternext = (iternextfunc)UICollectionIter::next,
//.tp_getset = PyUICollection_getset,
.tp_init = (initproc)UICollectionIter::init, // just raise an exception
.tp_alloc = PyType_GenericAlloc,
//TODO - as static method, not inline lambda def, please
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
static PyTypeObject PyUICollectionType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.UICollection",
.tp_basicsize = sizeof(PyUICollectionObject),
.tp_itemsize = 0,
//TODO - as static method, not inline lambda def, please
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUICollectionObject* obj = (PyUICollectionObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UICollection::repr,
.tp_as_sequence = &UICollection::sqmethods,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"),
.tp_iter = (getiterfunc)UICollection::iter,
.tp_methods = UICollection::methods, // append, remove
//.tp_getset = PyUICollection_getset,
.tp_init = (initproc)UICollection::init, // just raise an exception
//TODO - as static method, not inline lambda def, please
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
// Does PyUICollectionType need __new__ if it's not supposed to be instantiable by the user?
// Should I just raise an exception? Or is the uninitialized shared_ptr enough of a blocker?
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
}

View File

@ -1,82 +0,0 @@
#include "UIDrawable.h"
#include "UIFrame.h"
#include "UICaption.h"
#include "UISprite.h"
#include "UIGrid.h"
#include "GameEngine.h"
UIDrawable::UIDrawable() { click_callable = NULL; }
void UIDrawable::click_unregister()
{
click_callable.reset();
}
void UIDrawable::render()
{
render(sf::Vector2f(), Resources::game->getWindow());
}
PyObject* UIDrawable::get_click(PyObject* self, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
PyObject* ptr;
switch (objtype)
{
case PyObjectsEnum::UIFRAME:
ptr = ((PyUIFrameObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UICAPTION:
ptr = ((PyUICaptionObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UISPRITE:
ptr = ((PyUISpriteObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UIGRID:
ptr = ((PyUIGridObject*)self)->data->click_callable->borrow();
break;
default:
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _get_click");
return NULL;
}
if (ptr && ptr != Py_None)
return ptr;
else
return Py_None;
}
int UIDrawable::set_click(PyObject* self, PyObject* value, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
UIDrawable* target;
switch (objtype)
{
case PyObjectsEnum::UIFRAME:
target = (((PyUIFrameObject*)self)->data.get());
break;
case PyObjectsEnum::UICAPTION:
target = (((PyUICaptionObject*)self)->data.get());
break;
case PyObjectsEnum::UISPRITE:
target = (((PyUISpriteObject*)self)->data.get());
break;
case PyObjectsEnum::UIGRID:
target = (((PyUIGridObject*)self)->data.get());
break;
default:
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _set_click");
return -1;
}
if (value == Py_None)
{
target->click_unregister();
} else {
target->click_register(value);
}
return 0;
}
void UIDrawable::click_register(PyObject* callable)
{
click_callable = std::make_unique<PyClickCallable>(callable);
}

View File

@ -1,127 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
#include "Resources.h"
#include "UIBase.h"
class UIFrame; class UICaption; class UISprite; class UIEntity; class UIGrid;
enum PyObjectsEnum : int
{
UIFRAME = 1,
UICAPTION,
UISPRITE,
UIGRID
};
class UIDrawable
{
public:
void render();
//virtual void render(sf::Vector2f) = 0;
virtual void render(sf::Vector2f, sf::RenderTarget&) = 0;
virtual PyObjectsEnum derived_type() = 0;
// Mouse input handling - callable object, methods to find event's destination
std::unique_ptr<PyClickCallable> click_callable;
virtual UIDrawable* click_at(sf::Vector2f point) = 0;
void click_register(PyObject*);
void click_unregister();
UIDrawable();
static PyObject* get_click(PyObject* self, void* closure);
static int set_click(PyObject* self, PyObject* value, void* closure);
};
typedef struct {
PyObject_HEAD
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> data;
} PyUICollectionObject;
typedef struct {
PyObject_HEAD
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> data;
int index;
int start_size;
} PyUICollectionIterObject;
namespace mcrfpydef {
// DEPRECATED: RET_PY_INSTANCE macro has been replaced with template functions in PyObjectUtils.h
// The macro was difficult to debug and used static type references that could cause initialization order issues.
// Use PyObjectUtils::convertDrawableToPython() or PyObjectUtils::createPyObject<T>() instead.
//TODO: add this method to class scope; move implementation to .cpp file
/*
static PyObject* PyUIDrawable_get_click(PyObject* self, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
PyObject* ptr;
switch (objtype)
{
case PyObjectsEnum::UIFRAME:
ptr = ((PyUIFrameObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UICAPTION:
ptr = ((PyUICaptionObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UISPRITE:
ptr = ((PyUISpriteObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UIGRID:
ptr = ((PyUIGridObject*)self)->data->click_callable->borrow();
break;
default:
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _get_click");
return NULL;
}
if (ptr && ptr != Py_None)
return ptr;
else
return Py_None;
}*/
//TODO: add this method to class scope; move implementation to .cpp file
/*
static int PyUIDrawable_set_click(PyObject* self, PyObject* value, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
UIDrawable* target;
switch (objtype)
{
case PyObjectsEnum::UIFRAME:
target = (((PyUIFrameObject*)self)->data.get());
break;
case PyObjectsEnum::UICAPTION:
target = (((PyUICaptionObject*)self)->data.get());
break;
case PyObjectsEnum::UISPRITE:
target = (((PyUISpriteObject*)self)->data.get());
break;
case PyObjectsEnum::UIGRID:
target = (((PyUIGridObject*)self)->data.get());
break;
default:
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _set_click");
return -1;
}
if (value == Py_None)
{
target->click_unregister();
} else {
target->click_register(value);
}
return 0;
}
*/
}

View File

@ -1,213 +0,0 @@
#include "UIEntity.h"
#include "UIGrid.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(UIGrid& grid)
: gridstate(grid.grid_x * grid.grid_y)
{
}
PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
int x, y;
if (!PyArg_ParseTuple(o, "ii", &x, &y)) {
PyErr_SetString(PyExc_TypeError, "UIEntity.at requires two integer arguments: (x, y)");
return NULL;
}
if (self->data->grid == NULL) {
PyErr_SetString(PyExc_ValueError, "Entity cannot access surroundings because it is not associated with a grid");
return NULL;
}
/*
PyUIGridPointStateObject* obj = (PyUIGridPointStateObject*)((&mcrfpydef::PyUIGridPointStateType)->tp_alloc(&mcrfpydef::PyUIGridPointStateType, 0));
*/
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0);
//auto target = std::static_pointer_cast<UIEntity>(target);
obj->data = &(self->data->gridstate[y + self->data->grid->grid_x * x]);
obj->grid = self->data->grid;
obj->entity = self->data;
return (PyObject*)obj;
}
int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
//static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr };
//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;
PyObject* texture = NULL;
PyObject* grid = NULL;
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
// 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;
}
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
//
// Set Texture
//
if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
return -1;
} /*else if (texture != NULL) // this section needs to go; texture isn't optional and isn't managed by the UI objects anymore
{
self->texture = texture;
Py_INCREF(texture);
} else
{
// default tex?
}*/
if (grid != NULL && !PyObject_IsInstance(grid, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance");
return -1;
}
auto pytexture = (PyTextureObject*)texture;
if (grid == NULL)
self->data = std::make_shared<UIEntity>();
else
self->data = std::make_shared<UIEntity>(*((PyUIGridObject*)grid)->data);
// 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->position = pos_result->data;
if (grid != NULL) {
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
self->data->grid = pygrid->data;
// todone - on creation of Entity with Grid assignment, also append it to the entity list
pygrid->data->entities->push_back(self->data);
}
return 0;
}
PyObject* UIEntity::get_spritenumber(PyUIEntityObject* self, void* closure) {
return PyLong_FromDouble(self->data->sprite.getSpriteIndex());
}
PyObject* sfVector2f_to_PyObject(sf::Vector2f vector) {
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) {
float x, y;
if (!PyArg_ParseTuple(obj, "ff", &x, &y)) {
return sf::Vector2f(); // TODO / reconsider this default: Return default vector on parse error
}
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
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
// This function is incomplete - it creates an empty object without setting state data
// Should use PyObjectUtils::createGridPointState() instead
return PyObjectUtils::createPyObjectGeneric("GridPointState");
}
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec) {
PyObject* list = PyList_New(vec.size());
if (!list) return PyErr_NoMemory();
for (size_t i = 0; i < vec.size(); ++i) {
PyObject* obj = UIGridPointState_to_PyObject(vec[i]);
if (!obj) { // Cleanup on failure
Py_DECREF(list);
return NULL;
}
PyList_SET_ITEM(list, i, obj); // This steals a reference to obj
}
return list;
}
PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) {
if (reinterpret_cast<long>(closure) == 0) {
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) {
if (reinterpret_cast<long>(closure) == 0) {
self->data->position = PyObject_to_sfVector2f(value);
} else {
self->data->collision_pos = PyObject_to_sfVector2i(value);
}
return 0;
}
PyObject* UIEntity::get_gridstate(PyUIEntityObject* self, void* closure) {
// Assuming a function to convert std::vector<UIGridPointState> to PyObject* list
return UIGridPointStateVector_to_PyList(self->data->gridstate);
}
int UIEntity::set_spritenumber(PyUIEntityObject* self, PyObject* value, void* closure) {
int val;
if (PyLong_Check(value))
val = PyLong_AsLong(value);
else
{
PyErr_SetString(PyExc_TypeError, "Value must be an integer.");
return -1;
}
//self->data->sprite.sprite_index = val;
self->data->sprite.setSpriteIndex(val); // todone - I don't like ".sprite.sprite" in this stack of UIEntity.UISprite.sf::Sprite
return 0;
}
PyMethodDef UIEntity::methods[] = {
{"at", (PyCFunction)UIEntity::at, METH_O},
{NULL, NULL, 0, NULL}
};
PyGetSetDef UIEntity::getsetters[] = {
{"draw_pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (graphically)", (void*)0},
{"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},
{"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite number (index) on the texture on the display", NULL},
{NULL} /* Sentinel */
};
PyObject* UIEntity::repr(PyUIEntityObject* self) {
std::ostringstream ss;
if (!self->data) ss << "<Entity (invalid internal object)>";
else {
auto ent = self->data;
ss << "<Entity (x=" << self->data->position.x << ", y=" << self->data->position.y << ", sprite_number=" << self->data->sprite.getSpriteIndex() <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}

View File

@ -1,76 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
#include "UIGridPoint.h"
#include "UIDrawable.h"
#include "UIBase.h"
#include "UISprite.h"
class UIGrid;
//class UIEntity;
//typedef struct {
// PyObject_HEAD
// std::shared_ptr<UIEntity> data;
//} PyUIEntityObject;
// helper methods with no namespace requirement
static PyObject* sfVector2f_to_PyObject(sf::Vector2f vector);
static sf::Vector2f PyObject_to_sfVector2f(PyObject* obj);
static PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state);
static PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec);
// TODO: make UIEntity a drawable
class UIEntity//: public UIDrawable
{
public:
//PyObject* self;
std::shared_ptr<UIGrid> grid;
std::vector<UIGridPointState> gridstate;
UISprite sprite;
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;
UIEntity();
UIEntity(UIGrid&);
static PyObject* at(PyUIEntityObject* self, PyObject* o);
static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
static PyObject* get_position(PyUIEntityObject* self, void* closure);
static int set_position(PyUIEntityObject* self, PyObject* value, void* closure);
static PyObject* get_gridstate(PyUIEntityObject* self, void* closure);
static PyObject* get_spritenumber(PyUIEntityObject* self, void* closure);
static int set_spritenumber(PyUIEntityObject* self, PyObject* value, void* closure);
static PyMethodDef methods[];
static PyGetSetDef getsetters[];
static PyObject* repr(PyUIEntityObject* self);
};
namespace mcrfpydef {
static PyTypeObject PyUIEntityType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Entity",
.tp_basicsize = sizeof(PyUIEntityObject),
.tp_itemsize = 0,
.tp_repr = (reprfunc)UIEntity::repr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_doc = "UIEntity objects",
.tp_methods = UIEntity::methods,
.tp_getset = UIEntity::getsetters,
.tp_init = (initproc)UIEntity::init,
.tp_new = PyType_GenericNew,
};
}

View File

@ -1,266 +0,0 @@
#include "UIFrame.h"
#include "UICollection.h"
#include "GameEngine.h"
UIDrawable* UIFrame::click_at(sf::Vector2f point)
{
for (auto e: *children)
{
auto p = e->click_at(point + box.getPosition());
if (p)
return p;
}
if (click_callable)
{
float x = box.getPosition().x, y = box.getPosition().y, w = box.getSize().x, h = box.getSize().y;
if (point.x > x && point.y > y && point.x < x+w && point.y < y+h) return this;
}
return NULL;
}
UIFrame::UIFrame()
: outline(0)
{
children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
box.setPosition(0, 0);
box.setSize(sf::Vector2f(0, 0));
}
UIFrame::UIFrame(float _x, float _y, float _w, float _h)
: outline(0)
{
box.setPosition(_x, _y);
box.setSize(sf::Vector2f(_w, _h));
children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
}
UIFrame::~UIFrame()
{
children.reset();
}
PyObjectsEnum UIFrame::derived_type()
{
return PyObjectsEnum::UIFRAME;
}
void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target)
{
box.move(offset);
//Resources::game->getWindow().draw(box);
target.draw(box);
box.move(-offset);
for (auto drawable : *children) {
drawable->render(offset + box.getPosition(), target);
}
}
PyObject* UIFrame::get_children(PyUIFrameObject* self, void* closure)
{
// create PyUICollection instance pointing to self->data->children
//PyUICollectionObject* o = (PyUICollectionObject*)mcrfpydef::PyUICollectionType.tp_alloc(&mcrfpydef::PyUICollectionType, 0);
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollection");
auto o = (PyUICollectionObject*)type->tp_alloc(type, 0);
if (o)
o->data = self->data->children;
return (PyObject*)o;
}
PyObject* UIFrame::get_float_member(PyUIFrameObject* self, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr == 0)
return PyFloat_FromDouble(self->data->box.getPosition().x);
else if (member_ptr == 1)
return PyFloat_FromDouble(self->data->box.getPosition().y);
else if (member_ptr == 2)
return PyFloat_FromDouble(self->data->box.getSize().x);
else if (member_ptr == 3)
return PyFloat_FromDouble(self->data->box.getSize().y);
else if (member_ptr == 4)
return PyFloat_FromDouble(self->data->box.getOutlineThickness());
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
}
int UIFrame::set_float_member(PyUIFrameObject* self, PyObject* value, void* closure)
{
float val;
auto member_ptr = reinterpret_cast<long>(closure);
if (PyFloat_Check(value))
{
val = PyFloat_AsDouble(value);
}
else if (PyLong_Check(value))
{
val = PyLong_AsLong(value);
}
else
{
PyErr_SetString(PyExc_TypeError, "Value must be an integer.");
return -1;
}
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) //outline
self->data->box.setOutlineThickness(val);
return 0;
}
PyObject* UIFrame::get_color_member(PyUIFrameObject* self, void* closure)
{
// validate closure (should be impossible to be wrong, but it's thorough)
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr != 0 && member_ptr != 1)
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
//PyTypeObject* colorType = &PyColorType;
auto colorType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color");
PyObject* pyColor = colorType->tp_alloc(colorType, 0);
if (pyColor == NULL)
{
std::cout << "failure to allocate mcrfpy.Color / PyColorType" << std::endl;
return NULL;
}
PyColorObject* pyColorObj = reinterpret_cast<PyColorObject*>(pyColor);
// fetch correct member data
sf::Color color;
if (member_ptr == 0)
{
color = self->data->box.getFillColor();
//return Py_BuildValue("(iii)", color.r, color.g, color.b);
}
else if (member_ptr == 1)
{
color = self->data->box.getOutlineColor();
//return Py_BuildValue("(iii)", color.r, color.g, color.b);
}
return PyColor(color).pyObject();
}
int UIFrame::set_color_member(PyUIFrameObject* self, PyObject* value, void* closure)
{
//TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse
auto member_ptr = reinterpret_cast<long>(closure);
int r, g, b, a;
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color")))
{
sf::Color c = ((PyColorObject*)value)->data;
r = c.r; g = c.g; b = c.b; a = c.a;
}
else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4)
{
// reject non-Color, non-tuple value
PyErr_SetString(PyExc_TypeError, "Value must be a tuple of 3 or 4 integers or an mcrfpy.Color object.");
return -1;
}
else // get value from tuples
{
r = PyLong_AsLong(PyTuple_GetItem(value, 0));
g = PyLong_AsLong(PyTuple_GetItem(value, 1));
b = PyLong_AsLong(PyTuple_GetItem(value, 2));
a = 255;
if (PyTuple_Size(value) == 4)
{
a = PyLong_AsLong(PyTuple_GetItem(value, 3));
}
}
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255)
{
PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255.");
return -1;
}
if (member_ptr == 0)
{
self->data->box.setFillColor(sf::Color(r, g, b, a));
}
else if (member_ptr == 1)
{
self->data->box.setOutlineColor(sf::Color(r, g, b, a));
}
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return -1;
}
return 0;
}
PyGetSetDef UIFrame::getsetters[] = {
{"x", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "X coordinate of top-left corner", (void*)0},
{"y", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Y coordinate of top-left corner", (void*)1},
{"w", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "width of the rectangle", (void*)2},
{"h", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "height of the rectangle", (void*)3},
{"outline", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Thickness of the border", (void*)4},
{"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Fill color of the rectangle", (void*)0},
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1},
{"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL},
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME},
{NULL}
};
PyObject* UIFrame::repr(PyUIFrameObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<Frame (invalid internal object)>";
else {
auto box = self->data->box;
auto fc = box.getFillColor();
auto oc = box.getOutlineColor();
ss << "<Frame (x=" << box.getPosition().x << ", y=" << box.getPosition().y << ", w=" <<
box.getSize().x << ", w=" << box.getSize().y << ", " <<
"outline=" << box.getOutlineThickness() << ", " <<
"fill_color=(" << (int)fc.r << ", " << (int)fc.g << ", " << (int)fc.b << ", " << (int)fc.a <<"), " <<
"outline_color=(" << (int)oc.r << ", " << (int)oc.g << ", " << (int)oc.b << ", " << (int)oc.a <<"), " <<
self->data->children->size() << " child objects" <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
{
//std::cout << "Init called\n";
const char* keywords[] = { "x", "y", "w", "h", "fill_color", "outline_color", "outline", nullptr };
float x = 0.0f, y = 0.0f, w = 0.0f, h=0.0f, outline=0.0f;
PyObject* fill_color = 0;
PyObject* outline_color = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffff|OOf", const_cast<char**>(keywords), &x, &y, &w, &h, &fill_color, &outline_color, &outline))
{
return -1;
}
self->data->box.setPosition(sf::Vector2f(x, y));
self->data->box.setSize(sf::Vector2f(w, h));
self->data->box.setOutlineThickness(outline);
// getsetter abuse because I haven't standardized Color object parsing (TODO)
int err_val = 0;
if (fill_color && fill_color != Py_None) err_val = UIFrame::set_color_member(self, fill_color, (void*)0);
else self->data->box.setFillColor(sf::Color(0,0,0,255));
if (err_val) return err_val;
if (outline_color && outline_color != Py_None) err_val = UIFrame::set_color_member(self, outline_color, (void*)1);
else self->data->box.setOutlineColor(sf::Color(128,128,128,255));
if (err_val) return err_val;
return 0;
}

View File

@ -1,77 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyColor.h"
#include "PyVector.h"
#include "UIDrawable.h"
#include "UIBase.h"
//class UIFrame;
//
//typedef struct {
// PyObject_HEAD
// std::shared_ptr<UIFrame> data;
//} PyUIFrameObject;
class UIFrame: public UIDrawable
{
public:
UIFrame(float, float, float, float);
UIFrame();
~UIFrame();
sf::RectangleShape box;
float outline;
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> children;
void render(sf::Vector2f, sf::RenderTarget&) override final;
void move(sf::Vector2f);
PyObjectsEnum derived_type() override final;
virtual UIDrawable* click_at(sf::Vector2f point) override final;
static PyObject* get_children(PyUIFrameObject* self, void* closure);
static PyObject* get_float_member(PyUIFrameObject* self, void* closure);
static int set_float_member(PyUIFrameObject* self, PyObject* value, void* closure);
static PyObject* get_color_member(PyUIFrameObject* self, void* closure);
static int set_color_member(PyUIFrameObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* repr(PyUIFrameObject* self);
static int init(PyUIFrameObject* self, PyObject* args, PyObject* kwds);
};
namespace mcrfpydef {
static PyTypeObject PyUIFrameType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Frame",
.tp_basicsize = sizeof(PyUIFrameObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUIFrameObject* obj = (PyUIFrameObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UIFrame::repr,
//.tp_hash = NULL,
//.tp_iter
//.tp_iternext
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("docstring"),
//.tp_methods = PyUIFrame_methods,
//.tp_members = PyUIFrame_members,
.tp_getset = UIFrame::getsetters,
//.tp_base = NULL,
.tp_init = (initproc)UIFrame::init,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyUIFrameObject* self = (PyUIFrameObject*)type->tp_alloc(type, 0);
if (self) self->data = std::make_shared<UIFrame>();
return (PyObject*)self;
}
};
}

View File

@ -1,652 +0,0 @@
#include "UIGrid.h"
#include "GameEngine.h"
#include "McRFPy_API.h"
UIGrid::UIGrid() {}
UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _xy, sf::Vector2f _wh)
: grid_x(gx), grid_y(gy),
zoom(1.0f), center_x((gx/2) * _ptex->sprite_width), center_y((gy/2) * _ptex->sprite_height),
ptex(_ptex), points(gx * gy)
{
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
box.setSize(_wh);
box.setPosition(_xy);
box.setFillColor(sf::Color(0,0,0,0));
// create renderTexture with maximum theoretical size; sprite can resize to show whatever amount needs to be rendered
renderTexture.create(1920, 1080); // TODO - renderTexture should be window size; above 1080p this will cause rendering errors
sprite = ptex->sprite(0);
output.setTextureRect(
sf::IntRect(0, 0,
box.getSize().x, box.getSize().y));
output.setPosition(box.getPosition());
// textures are upside-down inside renderTexture
output.setTexture(renderTexture.getTexture());
}
void UIGrid::update() {}
void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
{
output.setPosition(box.getPosition() + offset); // output sprite can move; update position when drawing
// output size can change; update size when drawing
output.setTextureRect(
sf::IntRect(0, 0,
box.getSize().x, box.getSize().y));
renderTexture.clear(sf::Color(8, 8, 8, 255)); // TODO - UIGrid needs a "background color" field
// sprites that are visible according to zoom, center_x, center_y, and box width
float center_x_sq = center_x / ptex->sprite_width;
float center_y_sq = center_y / ptex->sprite_height;
float width_sq = box.getSize().x / (ptex->sprite_width * zoom);
float height_sq = box.getSize().y / (ptex->sprite_height * zoom);
float left_edge = center_x_sq - (width_sq / 2.0);
float top_edge = center_y_sq - (height_sq / 2.0);
int left_spritepixels = center_x - (box.getSize().x / 2.0 / zoom);
int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom);
//sprite.setScale(sf::Vector2f(zoom, zoom));
sf::RectangleShape r; // for colors and overlays
r.setSize(sf::Vector2f(ptex->sprite_width * zoom, ptex->sprite_height * zoom));
r.setOutlineThickness(0);
int x_limit = left_edge + width_sq + 2;
if (x_limit > grid_x) x_limit = grid_x;
int y_limit = top_edge + height_sq + 2;
if (y_limit > grid_y) y_limit = grid_y;
// base layer - bottom color, tile sprite ("ground")
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
x < x_limit; //x < view_width;
x+=1)
{
//for (float y = (top_edge >= 0 ? top_edge : 0);
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
y < y_limit; //y < view_height;
y+=1)
{
auto pixel_pos = sf::Vector2f(
(x*ptex->sprite_width - left_spritepixels) * zoom,
(y*ptex->sprite_height - top_spritepixels) * zoom );
auto gridpoint = at(std::floor(x), std::floor(y));
//sprite.setPosition(pixel_pos);
r.setPosition(pixel_pos);
r.setFillColor(gridpoint.color);
renderTexture.draw(r);
// tilesprite
// if discovered but not visible, set opacity to 90%
// if not discovered... just don't draw it?
if (gridpoint.tilesprite != -1) {
sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);;
renderTexture.draw(sprite);
}
}
}
// middle layer - entities
// disabling entity rendering until I can render their UISprite inside the rendertexture (not directly to window)
for (auto e : *entities) {
// TODO skip out-of-bounds entities (grid square not visible at all, check for partially on visible grid squares / floating point grid position)
//auto drawent = e->cGrid->indexsprite.drawable();
auto& drawent = e->sprite;
//drawent.setScale(zoom, zoom);
drawent.setScale(sf::Vector2f(zoom, zoom));
auto pixel_pos = sf::Vector2f(
(e->position.x*ptex->sprite_width - left_spritepixels) * zoom,
(e->position.y*ptex->sprite_height - top_spritepixels) * zoom );
//drawent.setPosition(pixel_pos);
//renderTexture.draw(drawent);
drawent.render(pixel_pos, renderTexture);
}
// top layer - opacity for discovered / visible status (debug, basically)
/* // Disabled until I attach a "perspective"
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
x < x_limit; //x < view_width;
x+=1)
{
//for (float y = (top_edge >= 0 ? top_edge : 0);
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
y < y_limit; //y < view_height;
y+=1)
{
auto pixel_pos = sf::Vector2f(
(x*itex->grid_size - left_spritepixels) * zoom,
(y*itex->grid_size - top_spritepixels) * zoom );
auto gridpoint = at(std::floor(x), std::floor(y));
sprite.setPosition(pixel_pos);
r.setPosition(pixel_pos);
// visible & discovered layers for testing purposes
if (!gridpoint.discovered) {
r.setFillColor(sf::Color(16, 16, 20, 192)); // 255 opacity for actual blackout
renderTexture.draw(r);
} else if (!gridpoint.visible) {
r.setFillColor(sf::Color(32, 32, 40, 128));
renderTexture.draw(r);
}
// overlay
// uisprite
}
}
*/
// grid lines for testing & validation
/*
sf::Vertex line[] =
{
sf::Vertex(sf::Vector2f(0, 0), sf::Color::Red),
sf::Vertex(box.getSize(), sf::Color::Red),
};
renderTexture.draw(line, 2, sf::Lines);
sf::Vertex lineb[] =
{
sf::Vertex(sf::Vector2f(0, box.getSize().y), sf::Color::Blue),
sf::Vertex(sf::Vector2f(box.getSize().x, 0), sf::Color::Blue),
};
renderTexture.draw(lineb, 2, sf::Lines);
*/
// render to window
renderTexture.display();
//Resources::game->getWindow().draw(output);
target.draw(output);
}
UIGridPoint& UIGrid::at(int x, int y)
{
return points[y * grid_x + x];
}
PyObjectsEnum UIGrid::derived_type()
{
return PyObjectsEnum::UIGRID;
}
std::shared_ptr<PyTexture> UIGrid::getTexture()
{
return ptex;
}
UIDrawable* UIGrid::click_at(sf::Vector2f point)
{
if (click_callable)
{
if(box.getGlobalBounds().contains(point)) return this;
}
return NULL;
}
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
int grid_x, grid_y;
PyObject* textureObj;
//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, "iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
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*
// This requires the texture object to have been initialized similar to UISprite's texture handling
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
return -1;
}
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
// TODO (7DRL day 2, item 4.) use shared_ptr / PyTextureObject on UIGrid
//IndexTexture* texture = pyTexture->data.get();
// 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 = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data,
// 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
}
PyObject* UIGrid::get_grid_size(PyUIGridObject* self, void* closure) {
return Py_BuildValue("(ii)", self->data->grid_x, self->data->grid_y);
}
PyObject* UIGrid::get_position(PyUIGridObject* self, void* closure) {
auto& box = self->data->box;
return Py_BuildValue("(ff)", box.getPosition().x, box.getPosition().y);
}
int UIGrid::set_position(PyUIGridObject* self, PyObject* value, void* closure) {
float x, y;
if (!PyArg_ParseTuple(value, "ff", &x, &y)) {
PyErr_SetString(PyExc_ValueError, "Position must be a tuple of two floats");
return -1;
}
self->data->box.setPosition(x, y);
return 0;
}
PyObject* UIGrid::get_size(PyUIGridObject* self, void* closure) {
auto& box = self->data->box;
return Py_BuildValue("(ff)", box.getSize().x, box.getSize().y);
}
int UIGrid::set_size(PyUIGridObject* self, PyObject* value, void* closure) {
float w, h;
if (!PyArg_ParseTuple(value, "ff", &w, &h)) {
PyErr_SetString(PyExc_ValueError, "Size must be a tuple of two floats");
return -1;
}
self->data->box.setSize(sf::Vector2f(w, h));
return 0;
}
PyObject* UIGrid::get_center(PyUIGridObject* self, void* closure) {
return Py_BuildValue("(ff)", self->data->center_x, self->data->center_y);
}
int UIGrid::set_center(PyUIGridObject* self, PyObject* value, void* closure) {
float x, y;
if (!PyArg_ParseTuple(value, "ff", &x, &y)) {
PyErr_SetString(PyExc_ValueError, "Size must be a tuple of two floats");
return -1;
}
self->data->center_x = x;
self->data->center_y = y;
return 0;
}
PyObject* UIGrid::get_float_member(PyUIGridObject* self, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr == 0) // x
return PyFloat_FromDouble(self->data->box.getPosition().x);
else if (member_ptr == 1) // y
return PyFloat_FromDouble(self->data->box.getPosition().y);
else if (member_ptr == 2) // w
return PyFloat_FromDouble(self->data->box.getSize().x);
else if (member_ptr == 3) // h
return PyFloat_FromDouble(self->data->box.getSize().y);
else if (member_ptr == 4) // center_x
return PyFloat_FromDouble(self->data->center_x);
else if (member_ptr == 5) // center_y
return PyFloat_FromDouble(self->data->center_y);
else if (member_ptr == 6) // zoom
return PyFloat_FromDouble(self->data->zoom);
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
}
int UIGrid::set_float_member(PyUIGridObject* self, PyObject* value, void* closure)
{
float val;
auto member_ptr = reinterpret_cast<long>(closure);
if (PyFloat_Check(value))
{
val = PyFloat_AsDouble(value);
}
else if (PyLong_Check(value))
{
val = PyLong_AsLong(value);
}
else
{
PyErr_SetString(PyExc_TypeError, "Value must be a floating point number.");
return -1;
}
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;
return 0;
}
// TODO (7DRL Day 2, item 5.) return Texture object
/*
PyObject* UIGrid::get_texture(PyUIGridObject* self, void* closure) {
Py_INCREF(self->texture);
return self->texture;
}
*/
PyObject* UIGrid::get_texture(PyUIGridObject* self, void* closure) {
//return self->data->getTexture()->pyObject();
// PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState")
//PyTextureObject* obj = (PyTextureObject*)((&PyTextureType)->tp_alloc(&PyTextureType, 0));
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
auto obj = (PyTextureObject*)type->tp_alloc(type, 0);
obj->data = self->data->getTexture();
return (PyObject*)obj;
}
PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* o)
{
int x, y;
if (!PyArg_ParseTuple(o, "ii", &x, &y)) {
PyErr_SetString(PyExc_TypeError, "UIGrid.at requires two integer arguments: (x, y)");
return NULL;
}
if (x < 0 || x >= self->data->grid_x) {
PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_y)");
return NULL;
}
if (y < 0 || y >= self->data->grid_y) {
PyErr_SetString(PyExc_ValueError, "y value out of range (0, Grid.grid_y)");
return NULL;
}
//PyUIGridPointObject* obj = (PyUIGridPointObject*)((&PyUIGridPointType)->tp_alloc(&PyUIGridPointType, 0));
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPoint");
auto obj = (PyUIGridPointObject*)type->tp_alloc(type, 0);
//auto target = std::static_pointer_cast<UIEntity>(target);
obj->data = &(self->data->points[x + self->data->grid_x * y]);
obj->grid = self->data;
return (PyObject*)obj;
}
PyMethodDef UIGrid::methods[] = {
{"at", (PyCFunction)UIGrid::py_at, METH_O},
{NULL, NULL, 0, NULL}
};
PyGetSetDef UIGrid::getsetters[] = {
// TODO - refactor into get_vector_member with field identifier values `(void*)n`
{"grid_size", (getter)UIGrid::get_grid_size, NULL, "Grid dimensions (grid_x, grid_y)", NULL},
{"position", (getter)UIGrid::get_position, (setter)UIGrid::set_position, "Position of the grid (x, y)", NULL},
{"size", (getter)UIGrid::get_size, (setter)UIGrid::set_size, "Size of the grid (width, height)", NULL},
{"center", (getter)UIGrid::get_center, (setter)UIGrid::set_center, "Grid coordinate at the center of the Grid's view (pan)", NULL},
{"entities", (getter)UIGrid::get_children, NULL, "EntityCollection of entities on this grid", NULL},
{"x", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "top-left corner X-coordinate", (void*)0},
{"y", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "top-left corner Y-coordinate", (void*)1},
{"w", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "visible widget width", (void*)2},
{"h", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "visible widget height", (void*)3},
{"center_x", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "center of the view X-coordinate", (void*)4},
{"center_y", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "center of the view Y-coordinate", (void*)5},
{"zoom", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "zoom factor for displaying the Grid", (void*)6},
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIGRID},
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
{NULL} /* Sentinel */
};
PyObject* UIGrid::get_children(PyUIGridObject* self, void* closure)
{
// create PyUICollection instance pointing to self->data->children
//PyUIEntityCollectionObject* o = (PyUIEntityCollectionObject*)PyUIEntityCollectionType.tp_alloc(&PyUIEntityCollectionType, 0);
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "EntityCollection");
auto o = (PyUIEntityCollectionObject*)type->tp_alloc(type, 0);
if (o) {
o->data = self->data->entities; // todone. / BUGFIX - entities isn't a shared pointer on UIGrid, what to do? -- I made it a sp<list<sp<UIEntity>>>
o->grid = self->data;
}
return (PyObject*)o;
}
PyObject* UIGrid::repr(PyUIGridObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<Grid (invalid internal object)>";
else {
auto grid = self->data;
auto box = grid->box;
ss << "<Grid (x=" << box.getPosition().x << ", y=" << box.getPosition().y << ", w=" << box.getSize().x << ", h=" << box.getSize().y << ", " <<
"center=(" << grid->center_x << ", " << grid->center_y << "), zoom=" << grid->zoom <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
/* // TODO standard pointer would need deleted, but I opted for a shared pointer. tp_dealloc currently not even defined in the PyTypeObject
void PyUIGrid_dealloc(PyUIGridObject* self) {
delete self->data; // Clean up the allocated UIGrid object
Py_TYPE(self)->tp_free((PyObject*)self);
}
*/
int UIEntityCollectionIter::init(PyUIEntityCollectionIterObject* self, PyObject* args, PyObject* kwds)
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return -1;
}
PyObject* UIEntityCollectionIter::next(PyUIEntityCollectionIterObject* self)
{
if (self->data->size() != self->start_size)
{
PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration");
return NULL;
}
if (self->index > self->start_size - 1)
{
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
self->index++;
auto vec = self->data.get();
if (!vec)
{
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return NULL;
}
// Advance list iterator since Entities are stored in a list, not a vector
auto l_begin = (*vec).begin();
std::advance(l_begin, self->index-1);
auto target = *l_begin;
// Create and return a Python Entity object
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
auto o = (PyUIEntityObject*)type->tp_alloc(type, 0);
auto p = std::static_pointer_cast<UIEntity>(target);
o->data = p;
return (PyObject*)o;
}
PyObject* UIEntityCollectionIter::repr(PyUIEntityCollectionIterObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<UICollectionIter (invalid internal object)>";
else {
ss << "<UICollectionIter (" << self->data->size() << " child objects, @ index " << self->index << ")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
Py_ssize_t UIEntityCollection::len(PyUIEntityCollectionObject* self) {
return self->data->size();
}
PyObject* UIEntityCollection::getitem(PyUIEntityCollectionObject* self, Py_ssize_t index) {
// build a Python version of item at self->data[index]
// Copy pasted::
auto vec = self->data.get();
if (!vec)
{
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return NULL;
}
while (index < 0) index += self->data->size();
if (index > self->data->size() - 1)
{
PyErr_SetString(PyExc_IndexError, "EntityCollection index out of range");
return NULL;
}
auto l_begin = (*vec).begin();
std::advance(l_begin, index);
auto target = *l_begin; //auto target = (*vec)[index];
//RET_PY_INSTANCE(target);
// construct and return an entity object that points directly into the UIGrid's entity vector
//PyUIEntityObject* o = (PyUIEntityObject*)((&PyUIEntityType)->tp_alloc(&PyUIEntityType, 0));
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
auto o = (PyUIEntityObject*)type->tp_alloc(type, 0);
auto p = std::static_pointer_cast<UIEntity>(target);
o->data = p;
return (PyObject*)o;
return NULL;
}
PySequenceMethods UIEntityCollection::sqmethods = {
.sq_length = (lenfunc)UIEntityCollection::len,
.sq_item = (ssizeargfunc)UIEntityCollection::getitem,
//.sq_item_by_index = UIEntityCollection::getitem
//.sq_slice - return a subset of the iterable
//.sq_ass_item - called when `o[x] = y` is executed (x is any object type)
//.sq_ass_slice - cool; no thanks, for now
//.sq_contains - called when `x in o` is executed
//.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer)
};
PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o)
{
// if not UIDrawable subclass, reject it
// self->data->push_back( c++ object inside o );
// this would be a great use case for .tp_base
//if (!PyObject_IsInstance(o, (PyObject*)&PyUIEntityType))
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity")))
{
PyErr_SetString(PyExc_TypeError, "Only Entity objects can be added to EntityCollection");
return NULL;
}
PyUIEntityObject* entity = (PyUIEntityObject*)o;
self->data->push_back(entity->data);
entity->data->grid = self->grid;
Py_INCREF(Py_None);
return Py_None;
}
PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject* o)
{
if (!PyLong_Check(o))
{
PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove");
return NULL;
}
long index = PyLong_AsLong(o);
if (index >= self->data->size())
{
PyErr_SetString(PyExc_ValueError, "Index out of range");
return NULL;
}
else if (index < 0)
{
PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented.");
return NULL;
}
// release the shared pointer at correct part of the list
self->data->erase(std::next(self->data->begin(), index));
Py_INCREF(Py_None);
return Py_None;
}
PyMethodDef UIEntityCollection::methods[] = {
{"append", (PyCFunction)UIEntityCollection::append, METH_O},
//{"extend", (PyCFunction)UIEntityCollection::extend, METH_O}, // TODO
{"remove", (PyCFunction)UIEntityCollection::remove, METH_O},
{NULL, NULL, 0, NULL}
};
PyObject* UIEntityCollection::repr(PyUIEntityCollectionObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<UICollection (invalid internal object)>";
else {
ss << "<UICollection (" << self->data->size() << " child objects)>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int UIEntityCollection::init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds)
{
PyErr_SetString(PyExc_TypeError, "EntityCollection cannot be instantiated: a C++ data source is required.");
return -1;
}
PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
{
// Get the iterator type from the module to ensure we have the registered version
PyTypeObject* iterType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UIEntityCollectionIter");
if (!iterType) {
PyErr_SetString(PyExc_RuntimeError, "Could not find UIEntityCollectionIter type in module");
return NULL;
}
// Allocate new iterator instance
PyUIEntityCollectionIterObject* iterObj = (PyUIEntityCollectionIterObject*)iterType->tp_alloc(iterType, 0);
if (iterObj == NULL) {
Py_DECREF(iterType);
return NULL; // Failed to allocate memory for the iterator object
}
iterObj->data = self->data;
iterObj->index = 0;
iterObj->start_size = self->data->size();
Py_DECREF(iterType);
return (PyObject*)iterObj;
}

View File

@ -1,186 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
#include "UIGridPoint.h"
#include "UIEntity.h"
#include "UIDrawable.h"
#include "UIBase.h"
class UIGrid: public UIDrawable
{
private:
std::shared_ptr<PyTexture> ptex;
public:
UIGrid();
//UIGrid(int, int, IndexTexture*, float, float, float, float);
UIGrid(int, int, std::shared_ptr<PyTexture>, sf::Vector2f, sf::Vector2f);
void update();
void render(sf::Vector2f, sf::RenderTarget&) override final;
UIGridPoint& at(int, int);
PyObjectsEnum derived_type() override final;
//void setSprite(int);
virtual UIDrawable* click_at(sf::Vector2f point) override final;
int grid_x, grid_y;
//int grid_size; // grid sizes are implied by IndexTexture now
sf::RectangleShape box;
float center_x, center_y, zoom;
//IndexTexture* itex;
std::shared_ptr<PyTexture> getTexture();
sf::Sprite sprite, output;
sf::RenderTexture renderTexture;
std::vector<UIGridPoint> points;
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> entities;
static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds);
static PyObject* get_grid_size(PyUIGridObject* self, void* closure);
static PyObject* get_position(PyUIGridObject* self, void* closure);
static int set_position(PyUIGridObject* self, PyObject* value, void* closure);
static PyObject* get_size(PyUIGridObject* self, void* closure);
static int set_size(PyUIGridObject* self, PyObject* value, void* closure);
static PyObject* get_center(PyUIGridObject* self, void* closure);
static int set_center(PyUIGridObject* self, PyObject* value, void* closure);
static PyObject* get_float_member(PyUIGridObject* self, void* closure);
static int set_float_member(PyUIGridObject* self, PyObject* value, void* closure);
static PyObject* get_texture(PyUIGridObject* self, void* closure);
static PyObject* py_at(PyUIGridObject* self, PyObject* o);
static PyMethodDef methods[];
static PyGetSetDef getsetters[];
static PyObject* get_children(PyUIGridObject* self, void* closure);
static PyObject* repr(PyUIGridObject* self);
};
typedef struct {
PyObject_HEAD
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> data;
std::shared_ptr<UIGrid> grid;
} PyUIEntityCollectionObject;
class UIEntityCollection {
public:
static PySequenceMethods sqmethods;
static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o);
static PyMethodDef methods[];
static PyObject* repr(PyUIEntityCollectionObject* self);
static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);
static PyObject* iter(PyUIEntityCollectionObject* self);
static Py_ssize_t len(PyUIEntityCollectionObject* self);
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
};
typedef struct {
PyObject_HEAD
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> data;
int index;
int start_size;
} PyUIEntityCollectionIterObject;
class UIEntityCollectionIter {
public:
static int init(PyUIEntityCollectionIterObject* self, PyObject* args, PyObject* kwds);
static PyObject* next(PyUIEntityCollectionIterObject* self);
static PyObject* repr(PyUIEntityCollectionIterObject* self);
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
};
namespace mcrfpydef {
static PyTypeObject PyUIGridType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Grid",
.tp_basicsize = sizeof(PyUIGridObject),
.tp_itemsize = 0,
//.tp_dealloc = (destructor)[](PyObject* self)
//{
// PyUIGridObject* obj = (PyUIGridObject*)self;
// obj->data.reset();
// Py_TYPE(self)->tp_free(self);
//},
//TODO - PyUIGrid REPR def:
.tp_repr = (reprfunc)UIGrid::repr,
//.tp_hash = NULL,
//.tp_iter
//.tp_iternext
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("docstring"),
.tp_methods = UIGrid::methods,
//.tp_members = UIGrid::members,
.tp_getset = UIGrid::getsetters,
//.tp_base = NULL,
.tp_init = (initproc)UIGrid::init,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyUIGridObject* self = (PyUIGridObject*)type->tp_alloc(type, 0);
if (self) self->data = std::make_shared<UIGrid>();
return (PyObject*)self;
}
};
static PyTypeObject PyUIEntityCollectionIterType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.UIEntityCollectionIter",
.tp_basicsize = sizeof(PyUIEntityCollectionIterObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUIEntityCollectionIterObject* obj = (PyUIEntityCollectionIterObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UIEntityCollectionIter::repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterator for a collection of UI objects"),
.tp_iter = PyObject_SelfIter,
.tp_iternext = (iternextfunc)UIEntityCollectionIter::next,
//.tp_getset = UIEntityCollection::getset,
.tp_init = (initproc)UIEntityCollectionIter::init, // just raise an exception
.tp_alloc = PyType_GenericAlloc,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
static PyTypeObject PyUIEntityCollectionType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.EntityCollection",
.tp_basicsize = sizeof(PyUIEntityCollectionObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUIEntityCollectionObject* obj = (PyUIEntityCollectionObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UIEntityCollection::repr,
.tp_as_sequence = &UIEntityCollection::sqmethods,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"),
.tp_iter = (getiterfunc)UIEntityCollection::iter,
.tp_methods = UIEntityCollection::methods, // append, remove
//.tp_getset = UIEntityCollection::getset,
.tp_init = (initproc)UIEntityCollection::init, // just raise an exception
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
// Does PyUIEntityCollectionType need __new__ if it's not supposed to be instantiable by the user?
// Should I just raise an exception? Or is the uninitialized shared_ptr enough of a blocker?
PyErr_SetString(PyExc_TypeError, "EntityCollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
}

View File

@ -1,158 +0,0 @@
#include "UIGridPoint.h"
UIGridPoint::UIGridPoint()
: color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false),
tilesprite(-1), tile_overlay(-1), uisprite(-1)
{}
// Utility function to convert sf::Color to PyObject*
PyObject* sfColor_to_PyObject(sf::Color color) {
return Py_BuildValue("(iiii)", color.r, color.g, color.b, color.a);
}
// Utility function to convert PyObject* to sf::Color
sf::Color PyObject_to_sfColor(PyObject* obj) {
int r, g, b, a = 255; // Default alpha to fully opaque if not specified
if (!PyArg_ParseTuple(obj, "iii|i", &r, &g, &b, &a)) {
return sf::Color(); // Return default color on parse error
}
return sf::Color(r, g, b, a);
}
PyObject* UIGridPoint::get_color(PyUIGridPointObject* self, void* closure) {
if (reinterpret_cast<long>(closure) == 0) { // color
return sfColor_to_PyObject(self->data->color);
} else { // color_overlay
return sfColor_to_PyObject(self->data->color_overlay);
}
}
int UIGridPoint::set_color(PyUIGridPointObject* self, PyObject* value, void* closure) {
sf::Color color = PyObject_to_sfColor(value);
if (reinterpret_cast<long>(closure) == 0) { // color
self->data->color = color;
} else { // color_overlay
self->data->color_overlay = color;
}
return 0;
}
PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure) {
if (reinterpret_cast<long>(closure) == 0) { // walkable
return PyBool_FromLong(self->data->walkable);
} else { // transparent
return PyBool_FromLong(self->data->transparent);
}
}
int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure) {
if (value == Py_True) {
if (reinterpret_cast<long>(closure) == 0) { // walkable
self->data->walkable = true;
} else { // transparent
self->data->transparent = true;
}
} else if (value == Py_False) {
if (reinterpret_cast<long>(closure) == 0) { // walkable
self->data->walkable = false;
} else { // transparent
self->data->transparent = false;
}
} else {
PyErr_SetString(PyExc_ValueError, "Expected a boolean value");
return -1;
}
return 0;
}
PyObject* UIGridPoint::get_int_member(PyUIGridPointObject* self, void* closure) {
switch(reinterpret_cast<long>(closure)) {
case 0: return PyLong_FromLong(self->data->tilesprite);
case 1: return PyLong_FromLong(self->data->tile_overlay);
case 2: return PyLong_FromLong(self->data->uisprite);
default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return nullptr;
}
}
int UIGridPoint::set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure) {
long val = PyLong_AsLong(value);
if (PyErr_Occurred()) return -1;
switch(reinterpret_cast<long>(closure)) {
case 0: self->data->tilesprite = val; break;
case 1: self->data->tile_overlay = val; break;
case 2: self->data->uisprite = val; break;
default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return -1;
}
return 0;
}
PyGetSetDef UIGridPoint::getsetters[] = {
{"color", (getter)UIGridPoint::get_color, (setter)UIGridPoint::set_color, "GridPoint color", (void*)0},
{"color_overlay", (getter)UIGridPoint::get_color, (setter)UIGridPoint::set_color, "GridPoint color overlay", (void*)1},
{"walkable", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint walkable", (void*)0},
{"transparent", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint transparent", (void*)1},
{"tilesprite", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "Tile sprite index", (void*)0},
{"tile_overlay", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "Tile overlay sprite index", (void*)1},
{"uisprite", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "UI sprite index", (void*)2},
{NULL} /* Sentinel */
};
PyObject* UIGridPoint::repr(PyUIGridPointObject* self) {
std::ostringstream ss;
if (!self->data) ss << "<GridPoint (invalid internal object)>";
else {
auto gp = self->data;
ss << "<GridPoint (walkable=" << (gp->walkable ? "True" : "False") << ", transparent=" << (gp->transparent ? "True" : "False") <<
", tilesprite=" << gp->tilesprite << ", tile_overlay=" << gp->tile_overlay << ", uisprite=" << gp->uisprite <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
PyObject* UIGridPointState::get_bool_member(PyUIGridPointStateObject* self, void* closure) {
if (reinterpret_cast<long>(closure) == 0) { // visible
return PyBool_FromLong(self->data->visible);
} else { // discovered
return PyBool_FromLong(self->data->discovered);
}
}
int UIGridPointState::set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure) {
if (!PyBool_Check(value)) {
PyErr_SetString(PyExc_TypeError, "Value must be a boolean");
return -1;
}
int truthValue = PyObject_IsTrue(value);
if (truthValue < 0) {
return -1; // PyObject_IsTrue returns -1 on error
}
if (reinterpret_cast<long>(closure) == 0) { // visible
self->data->visible = truthValue;
} else { // discovered
self->data->discovered = truthValue;
}
return 0;
}
PyGetSetDef UIGridPointState::getsetters[] = {
{"visible", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Is the GridPointState visible", (void*)0},
{"discovered", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Has the GridPointState been discovered", (void*)1},
{NULL} /* Sentinel */
};
PyObject* UIGridPointState::repr(PyUIGridPointStateObject* self) {
std::ostringstream ss;
if (!self->data) ss << "<GridPointState (invalid internal object)>";
else {
auto gps = self->data;
ss << "<GridPointState (visible=" << (gps->visible ? "True" : "False") << ", discovered=" << (gps->discovered ? "True" : "False") <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}

View File

@ -1,92 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
static PyObject* sfColor_to_PyObject(sf::Color color);
static sf::Color PyObject_to_sfColor(PyObject* obj);
class UIGrid;
class UIEntity;
class UIGridPoint;
class UIGridPointState;
typedef struct {
PyObject_HEAD
UIGridPoint* data;
std::shared_ptr<UIGrid> grid;
} PyUIGridPointObject;
typedef struct {
PyObject_HEAD
UIGridPointState* data;
std::shared_ptr<UIGrid> grid;
std::shared_ptr<UIEntity> entity;
} PyUIGridPointStateObject;
// UIGridPoint - revised grid data for each point
class UIGridPoint
{
public:
sf::Color color, color_overlay;
bool walkable, transparent;
int tilesprite, tile_overlay, uisprite;
UIGridPoint();
static int set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* get_color(PyUIGridPointObject* self, void* closure);
static PyObject* get_int_member(PyUIGridPointObject* self, void* closure);
static int set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure);
static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure);
static int set_color(PyUIGridPointObject* self, PyObject* value, void* closure);
static PyObject* repr(PyUIGridPointObject* self);
};
// UIGridPointState - entity-specific info for each cell
class UIGridPointState
{
public:
bool visible, discovered;
static PyObject* get_bool_member(PyUIGridPointStateObject* self, void* closure);
static int set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* repr(PyUIGridPointStateObject* self);
};
namespace mcrfpydef {
static PyTypeObject PyUIGridPointType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.GridPoint",
.tp_basicsize = sizeof(PyUIGridPointObject),
.tp_itemsize = 0,
.tp_repr = (reprfunc)UIGridPoint::repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "UIGridPoint object",
.tp_getset = UIGridPoint::getsetters,
//.tp_init = (initproc)PyUIGridPoint_init, // TODO Define the init function
.tp_new = PyType_GenericNew,
};
static PyTypeObject PyUIGridPointStateType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.GridPointState",
.tp_basicsize = sizeof(PyUIGridPointStateObject),
.tp_itemsize = 0,
.tp_repr = (reprfunc)UIGridPointState::repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "UIGridPointState object", // TODO: Add PyUIGridPointState tp_init
.tp_getset = UIGridPointState::getsetters,
.tp_new = PyType_GenericNew,
};
}

83
src/UIMenu.cpp Normal file
View File

@ -0,0 +1,83 @@
#include "UIMenu.h"
#include "Common.h"
#include "Resources.h"
UIMenu::UIMenu(sf::Font & _font)
: font(_font)
{
//font = _font;
box.setSize(sf::Vector2f(300, 400));
box.setPosition(sf::Vector2f(300, 250));
box.setFillColor(sf::Color(0,0,255));
}
UIMenu::UIMenu()
: font(Resources::font)
{
box.setSize(sf::Vector2f(300, 400));
box.setPosition(sf::Vector2f(300, 250));
box.setFillColor(sf::Color(0,0,255));
}
void UIMenu::render(sf::RenderWindow & window)
{
window.draw(box);
for (auto& s: sprites) {
auto _s = s.drawable();
//std::cout << "Sprite has values " << s.x << ", " << s.y << std::endl;
//std::cout << "Drawable generated @ " << _s.getPosition().x << ", " << _s.getPosition().y << std::endl;
_s.move(box.getPosition());
//std::cout << "Moved by " << box.getPosition().x << ", " << box.getPosition().y << std::endl;
//std::cout << "Render UIMenu Sprite @ " << _s.getPosition().x << ", " << _s.getPosition().y << std::endl;
window.draw(_s);
}
for (auto& c : captions) {
//auto s = std::string(c.getString());
//std::cout << s << std::flush << std::endl;
c.move(box.getPosition());
window.draw(c);
c.move(-box.getPosition());
}
for (auto& b : buttons) {
//b.render(window);
b.setPosition(box.getPosition() + b.rect.getPosition());
//b.caption.setPosition(box.getPosition() + b.caption.getPosition());
b.render(window);
b.setPosition(b.rect.getPosition() - box.getPosition());
//b.caption.setPosition(b.caption.getPosition() - box.getPosition());
}
}
void UIMenu::refresh()
{
}
void UIMenu::add_caption(const char* text, int tsize, sf::Color color)
{
auto c = sf::Text();
//auto bpos = box.getPosition();
c.setFillColor(color);
c.setPosition(10, next_text);
next_text += 50;
c.setCharacterSize(tsize);
c.setString(text);
c.setFont(font);
captions.push_back(c);
}
void UIMenu::add_button(Button b)
{
b.setPosition(sf::Vector2f(box.getSize().x / 2.0f, next_button));
next_button += 50;
buttons.push_back(b);
}
void UIMenu::add_sprite(IndexSprite s)
{
//std::cout << "Adding sprite to UIMenu x,y " << s.x << ", " << s.y << std::endl;
sprites.push_back(s);
}

38
src/UIMenu.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include "Common.h"
#include "Button.h"
#include "IndexSprite.h"
class UIMenu
{
public:
//UIMenu() {};
sf::Font & font;
UIMenu(sf::Font & _font);
UIMenu();
std::vector<sf::Text> captions;
std::vector<Button> buttons;
std::vector<IndexSprite> sprites;
/* idea: */ //std::vector<UIDrawable> children; // on the UIBox class?
sf::RectangleShape box;
bool visible = false;
int next_text = 10;
int next_button = 10;
void render(sf::RenderWindow & window);
void refresh();
void add_caption(const char* text, int size, sf::Color color);
void add_button(Button b);
void add_sprite(IndexSprite s);
protected:
private:
};

View File

@ -1,217 +0,0 @@
#include "UISprite.h"
#include "GameEngine.h"
UIDrawable* UISprite::click_at(sf::Vector2f point)
{
if (click_callable)
{
if(sprite.getGlobalBounds().contains(point)) return this;
}
return NULL;
}
UISprite::UISprite() {}
UISprite::UISprite(std::shared_ptr<PyTexture> _ptex, int _sprite_index, sf::Vector2f _pos, float _scale)
: ptex(_ptex), sprite_index(_sprite_index)
{
sprite = ptex->sprite(sprite_index, _pos, sf::Vector2f(_scale, _scale));
}
/*
void UISprite::render(sf::Vector2f offset)
{
sprite.move(offset);
Resources::game->getWindow().draw(sprite);
sprite.move(-offset);
}
*/
void UISprite::render(sf::Vector2f offset, sf::RenderTarget& target)
{
sprite.move(offset);
target.draw(sprite);
sprite.move(-offset);
}
void UISprite::setPosition(sf::Vector2f pos)
{
sprite.setPosition(pos);
}
void UISprite::setScale(sf::Vector2f s)
{
sprite.setScale(s);
}
void UISprite::setTexture(std::shared_ptr<PyTexture> _ptex, int _sprite_index)
{
ptex = _ptex;
if (_sprite_index != -1) // if you are changing textures, there's a good chance you need a new index too
sprite_index = _sprite_index;
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
}
void UISprite::setSpriteIndex(int _sprite_index)
{
sprite_index = _sprite_index;
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
}
sf::Vector2f UISprite::getScale()
{
return sprite.getScale();
}
sf::Vector2f UISprite::getPosition()
{
return sprite.getPosition();
}
std::shared_ptr<PyTexture> UISprite::getTexture()
{
return ptex;
}
int UISprite::getSpriteIndex()
{
return sprite_index;
}
PyObjectsEnum UISprite::derived_type()
{
return PyObjectsEnum::UISPRITE;
}
PyObject* UISprite::get_float_member(PyUISpriteObject* self, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr == 0)
return PyFloat_FromDouble(self->data->getPosition().x);
else if (member_ptr == 1)
return PyFloat_FromDouble(self->data->getPosition().y);
else if (member_ptr == 2)
return PyFloat_FromDouble(self->data->getScale().x); // scale X and Y are identical, presently
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
}
int UISprite::set_float_member(PyUISpriteObject* self, PyObject* value, void* closure)
{
float val;
auto member_ptr = reinterpret_cast<long>(closure);
if (PyFloat_Check(value))
{
val = PyFloat_AsDouble(value);
}
else if (PyLong_Check(value))
{
val = PyLong_AsLong(value);
}
else
{
PyErr_SetString(PyExc_TypeError, "Value must be a floating point number.");
return -1;
}
if (member_ptr == 0) //x
self->data->setPosition(sf::Vector2f(val, self->data->getPosition().y));
else if (member_ptr == 1) //y
self->data->setPosition(sf::Vector2f(self->data->getPosition().x, val));
else if (member_ptr == 2) // scale
self->data->setScale(sf::Vector2f(val, val));
return 0;
}
PyObject* UISprite::get_int_member(PyUISpriteObject* self, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
if (true) {}
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
return PyLong_FromDouble(self->data->getSpriteIndex());
}
int UISprite::set_int_member(PyUISpriteObject* self, PyObject* value, void* closure)
{
int val;
auto member_ptr = reinterpret_cast<long>(closure);
if (PyLong_Check(value))
{
val = PyLong_AsLong(value);
}
else
{
PyErr_SetString(PyExc_TypeError, "Value must be an integer.");
return -1;
}
self->data->setSpriteIndex(val);
return 0;
}
PyObject* UISprite::get_texture(PyUISpriteObject* self, void* closure)
{
return self->data->getTexture()->pyObject();
}
int UISprite::set_texture(PyUISpriteObject* self, PyObject* value, void* closure)
{
return -1;
}
PyGetSetDef UISprite::getsetters[] = {
{"x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "X coordinate of top-left corner", (void*)0},
{"y", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Y coordinate of top-left corner", (void*)1},
{"scale", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Size factor", (void*)2},
{"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL},
{"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture, "Texture object", NULL},
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE},
{NULL}
};
PyObject* UISprite::repr(PyUISpriteObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<Sprite (invalid internal object)>";
else {
//auto sprite = self->data->sprite;
ss << "<Sprite (x=" << self->data->getPosition().x << ", y=" << self->data->getPosition().y << ", " <<
"scale=" << self->data->getScale().x << ", " <<
"sprite_number=" << self->data->getSpriteIndex() << ")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
{
//std::cout << "Init called\n";
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr };
float x = 0.0f, y = 0.0f, scale = 1.0f;
int sprite_index;
PyObject* texture;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif",
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale))
{
return -1;
}
// check types for texture
//if (texture != NULL && !PyObject_IsInstance(texture, (PyObject*)&PyTextureType)){
if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
return -1;
}
auto pytexture = (PyTextureObject*)texture;
self->data = std::make_shared<UISprite>(pytexture->data, sprite_index, sf::Vector2f(x, y), scale);
self->data->setPosition(sf::Vector2f(x, y));
return 0;
}

View File

@ -1,90 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
#include "UIDrawable.h"
#include "UIBase.h"
class UISprite: public UIDrawable
{
private:
int sprite_index;
sf::Sprite sprite;
protected:
std::shared_ptr<PyTexture> ptex;
public:
UISprite();
UISprite(std::shared_ptr<PyTexture>, int, sf::Vector2f, float);
void update();
void render(sf::Vector2f, sf::RenderTarget&) override final;
virtual UIDrawable* click_at(sf::Vector2f point) override final;
//void render(sf::Vector2f, sf::RenderTexture&);
void setPosition(sf::Vector2f);
sf::Vector2f getPosition();
void setScale(sf::Vector2f);
sf::Vector2f getScale();
void setSpriteIndex(int);
int getSpriteIndex();
void setTexture(std::shared_ptr<PyTexture> _ptex, int _sprite_index=-1);
std::shared_ptr<PyTexture> getTexture();
PyObjectsEnum derived_type() override final;
static PyObject* get_float_member(PyUISpriteObject* self, void* closure);
static int set_float_member(PyUISpriteObject* self, PyObject* value, void* closure);
static PyObject* get_int_member(PyUISpriteObject* self, void* closure);
static int set_int_member(PyUISpriteObject* self, PyObject* value, void* closure);
static PyObject* get_texture(PyUISpriteObject* self, void* closure);
static int set_texture(PyUISpriteObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* repr(PyUISpriteObject* self);
static int init(PyUISpriteObject* self, PyObject* args, PyObject* kwds);
};
namespace mcrfpydef {
static PyTypeObject PyUISpriteType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Sprite",
.tp_basicsize = sizeof(PyUISpriteObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUISpriteObject* obj = (PyUISpriteObject*)self;
// release reference to font object
//if (obj->texture) Py_DECREF(obj->texture);
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UISprite::repr,
//.tp_hash = NULL,
//.tp_iter
//.tp_iternext
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("docstring"),
//.tp_methods = PyUIFrame_methods,
//.tp_members = PyUIFrame_members,
.tp_getset = UISprite::getsetters,
//.tp_base = NULL,
.tp_init = (initproc)UISprite::init,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyUISpriteObject* self = (PyUISpriteObject*)type->tp_alloc(type, 0);
//if (self) self->data = std::make_shared<UICaption>();
return (PyObject*)self;
}
};
}

View File

@ -4,10 +4,8 @@
UITestScene::UITestScene(GameEngine* g) : Scene(g)
{
registerAction(ActionCode::KEY + sf::Keyboard::Grave, "debug_menu");
text.setFont(game->getFont());
text.setString("UITest: surprised to be here? game.py raised an exception.");
text.setString("Test Scene for UI elements");
text.setCharacterSize(24);
@ -61,15 +59,15 @@ UITestScene::UITestScene(GameEngine* g) : Scene(g)
//ui_elements.push_back(&e1);
//ui_elements.push_back(&e2);
//t.loadFromFile("./assets/kenney_tinydungeon.png");
//t.setSmooth(false);
//auto* indextex = new IndexTexture(t, 16, 12, 11);
//Resources::game->textures.push_back(*indextex);
t.loadFromFile("./assets/kenney_tinydungeon.png");
t.setSmooth(false);
auto* indextex = new IndexTexture(t, 16, 12, 11);
Resources::game->textures.push_back(*indextex);
auto ptex = std::make_shared<PyTexture>("./assets/kenney_tinydungeon.png", 16, 16);
//std::cout << Resources::game->textures.size() << " textures loaded.\n";
auto e3 = std::make_shared<UISprite>(ptex, 84, sf::Vector2f(10, 10), 4.0);
auto e3 = std::make_shared<UISprite>();
// Make UISprite more like IndexSprite: this is bad
//e3->x = 10; e3->y = 10;
@ -79,19 +77,19 @@ UITestScene::UITestScene(GameEngine* g) : Scene(g)
//e3->update();
// This goes to show how inconvenient the default constructor is. It should be removed
//e3->itex = &Resources::game->textures[0];
//e3->sprite.setTexture(e3->itex->texture);
//e3->sprite_index = 84;
//e3->sprite.setTextureRect(e3->itex->spriteCoordinates(e3->sprite_index));
//e3->setPosition(10, 10);
//e3->setScale(4.0f);
e3->itex = &Resources::game->textures[0];
e3->sprite.setTexture(e3->itex->texture);
e3->sprite_index = 84;
e3->sprite.setTextureRect(e3->itex->spriteCoordinates(e3->sprite_index));
e3->setPosition(10, 10);
e3->setScale(4.0f);
e1aa->children->push_back(e3);
auto e4 = std::make_shared<UISprite>(
ptex, //indextex, //&Resources::game->textures[0],
indextex, //&Resources::game->textures[0],
85, sf::Vector2f(90, 10), 4.0);
e1aa->children->push_back(e4);
//std::cout << "UISprite built: " << e4->sprite.getPosition().x << " " << e4->sprite.getPosition().y << " " << e4->sprite.getScale().x << " " <<
@ -103,33 +101,6 @@ UITestScene::UITestScene(GameEngine* g) : Scene(g)
if (ui)
std::cout << "pointer to ui_elements now shows size=" << ui->size() << std::endl;
*/
// UIGrid test: (in grid cells) ( in screen pixels )
// constructor args: w h texture x y w h
auto e5 = std::make_shared<UIGrid>(4, 4, ptex, sf::Vector2f(550, 150), sf::Vector2f(200, 200));
e5->zoom=2.0;
e5->points[0].color = sf::Color(255, 0, 0);
e5->points[1].tilesprite = 1;
e5->points[5].color = sf::Color(0, 255, 0);
e5->points[6].tilesprite = 2;
e5->points[10].color = sf::Color(0, 0, 255);
e5->points[11].tilesprite = 3;
e5->points[15].color = sf::Color(255, 255, 255);
ui_elements->push_back(e5);
//UIEntity test:
// asdf
// TODO - reimplement UISprite style rendering within UIEntity class. Entities don't have a screen pixel position, they have a grid position, and grid sets zoom when rendering them.
auto e5a = std::make_shared<UIEntity>(*e5); // this basic constructor sucks: sprite position + zoom are irrelevant for UIEntity.
e5a->grid = e5;
//auto e5as = UISprite(indextex, 85, sf::Vector2f(0, 0), 1.0);
//e5a->sprite = e5as; // will copy constructor even exist for UISprite...?
e5a->sprite = UISprite(ptex, 85, sf::Vector2f(0, 0), 1.0);
e5a->position = sf::Vector2f(1, 0);
e5->entities->push_back(e5a);
}
void UITestScene::update()
@ -143,9 +114,6 @@ void UITestScene::doAction(std::string name, std::string type)
//if (name.compare("start_game") == 0 and type.compare("start") == 0)
if(ACTION("start_game", "start"))
game->changeScene("py");
else if ACTIONONCE("debug_menu") {
McRFPy_API::REPL();
}
/*
else if(ACTIONONCE("up"))
game->getWindow().setSize(sf::Vector2u(1280, 800));
@ -154,7 +122,7 @@ void UITestScene::doAction(std::string name, std::string type)
*/
}
void UITestScene::render()
void UITestScene::sRender()
{
game->getWindow().clear();
game->getWindow().draw(text);
@ -177,5 +145,5 @@ void UITestScene::render()
game->getWindow().display();
//McRFPy_API::REPL();
McRFPy_API::REPL();
}

View File

@ -18,5 +18,5 @@ public:
UITestScene(GameEngine*);
void update() override final;
void doAction(std::string, std::string) override final;
void render() override final;
void sRender() override final;
};

50
src/scripts/Grid.py Normal file
View File

@ -0,0 +1,50 @@
class GridPoint:
def __init__(self, color, walkable, tilesprite, transparent, visible, discovered, color_overlay, tile_overlay, uisprite):
self.color = color
self.walkable = walkable
self.tilesprite = tilesprite
self.transparent = transparent
self.visible = visible
self.discovered = discovered
self.color_overlay = color_overlay
self.tile_overlay = tile_overlay
self.uisprite = uisprite
def __repr__(self):
return f"<GridPoint {self.color}, {self.tilesprite}/{self.uisprite} {'W' if self.walkable else '-'}{'T' if self.transparent else '-'}{'V' if self.visible else '-'}{'D' if self.discovered else '-'} {self.color_overlay}/{self.tile_overlay}>"
class Grid:
def __init__(self, title, gx, gy, gs, x, y, w, h, visible=False):
self.title = title
self.grid_x = gx
self.grid_y = gy
self.grid_size = gs
self.x = x
self.y = y
self.w = w
self.h = h
self.visible = visible
self.points = []
self.entities = []
def at(self, x, y):
if not (x > 0 and y > 0 and x < self.grid_x and y < self.grid_y): return None
return self.points[y * self.grid_y + x]
def __repr__(self):
return f"<Grid {self.grid_x}x{self.grid_y}, grid_size={self.grid_size}, (({self.x},{self.y}), ({self.w}, {self.h})), visible={self.visible}>"
# CGrid(Grid* _g, int _ti, int _si, int _x, int _y, bool _v)
class Entity:
def __init__(self, parent, tex_index, sprite_index, x, y, visible=True):
self.parent = parent
self.tex_index = tex_index
self.sprite_index = sprite_index
self.x = x
self.y = y
self.visible = visible
def __repr__(self):
return f"<Entity on grid {repr(self.parent)}@({self.x},{self.y}), TI={self.tex_index}, SI={self.sprite_index}, visible={self.visible}>"

79
src/scripts/MusicScene.py Normal file
View File

@ -0,0 +1,79 @@
import mcrfpy
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED, GREEN, BLUE = (255, 0, 0), (0, 255, 0), (0, 0, 255)
DARKRED, DARKGREEN, DARKBLUE = (192, 0, 0), (0, 192, 0), (0, 0, 192)
class MusicScene:
def __init__(self, ui_name = "demobox1", grid_name = "demogrid"):
# Texture & Sound Loading
print("Load textures")
mcrfpy.createTexture("./assets/test_portraits.png", 32, 8, 8) #0 - portraits
mcrfpy.createTexture("./assets/alives_other.png", 16, 64, 64) #1 - TinyWorld NPCs
mcrfpy.createTexture("./assets/alives_other.png", 32, 32, 32) #2 - TinyWorld NPCs - 2x2 sprite
# {"createSoundBuffer", McRFPy_API::_createSoundBuffer, METH_VARARGS, "(filename)"},
#{"loadMusic", McRFPy_API::_loadMusic, METH_VARARGS, "(filename)"},
#{"setMusicVolume", McRFPy_API::_setMusicVolume, METH_VARARGS, "(int)"},
#{"setSoundVolume", McRFPy_API::_setSoundVolume, METH_VARARGS, "(int)"},
#{"playSound", McRFPy_API::_playSound, METH_VARARGS, "(int)"},
#{"getMusicVolume", McRFPy_API::_getMusicVolume, METH_VARARGS, ""},
#{"getSoundVolume", McRFPy_API::_getSoundVolume, METH_VARARGS, ""},
mcrfpy.loadMusic("./assets/ultima.ogg")
mcrfpy.createSoundBuffer("./assets/boom.wav")
self.ui_name = ui_name
self.grid_name = grid_name
print("Create UI")
# Create dialog UI
mcrfpy.createMenu(ui_name, 20, 540, 500, 200)
mcrfpy.createCaption(ui_name, f"Music Volume: {mcrfpy.getMusicVolume()}", 24, RED)
mcrfpy.createCaption(ui_name, f"SFX Volume: {mcrfpy.getSoundVolume()}", 24, RED)
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (0, 0, 0), "clicky", "testaction")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKRED, (0, 0, 0), "Music+", "mvol+")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKGREEN, (0, 0, 0), "Music-", "mvol-")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, GREEN, "SFX+", "svol+")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, RED, "SFX-", "svol-")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKRED, (0, 0, 0), "REPL", "startrepl")
mcrfpy.createSprite(ui_name, 1, 0, 20, 40, 3.0)
print("Create UI 2")
entitymenu = "entitytestmenu"
mcrfpy.createMenu(entitymenu, 840, 20, 20, 500)
mcrfpy.createButton(entitymenu, 0, 10, 150, 40, DARKBLUE, BLACK, "PlayM", "playm")
mcrfpy.createButton(entitymenu, 0, 60, 150, 40, DARKBLUE, BLACK, "StopM", "stopm")
mcrfpy.createButton(entitymenu, 0, 110, 150, 40, DARKBLUE, BLACK, "SFX", "boom")
print("Make UIs visible")
self.menus = mcrfpy.listMenus()
self.menus[0].visible = True
self.menus[1].w = 170
self.menus[1].visible = True
mcrfpy.modMenu(self.menus[0])
mcrfpy.modMenu(self.menus[1])
self.mvol = mcrfpy.getMusicVolume()
self.svol = mcrfpy.getSoundVolume()
mcrfpy.registerPyAction("mvol+", lambda: self.setmvol(self.mvol+10))
mcrfpy.registerPyAction("mvol-", lambda: self.setmvol(self.mvol-10))
mcrfpy.registerPyAction("svol+", lambda: self.setsvol(self.svol+10))
mcrfpy.registerPyAction("svol-", lambda: self.setsvol(self.svol-10))
mcrfpy.registerPyAction("playm", lambda: None)
mcrfpy.registerPyAction("stopm", lambda: None)
mcrfpy.registerPyAction("boom", lambda: mcrfpy.playSound(0))
def setmvol(self, v):
mcrfpy.setMusicVolume(int(v))
self.menus[0].captions[0].text = f"Music Volume: {mcrfpy.getMusicVolume():.1f}"
mcrfpy.modMenu(self.menus[0])
self.mvol = mcrfpy.getMusicVolume()
def setsvol(self, v):
mcrfpy.setSoundVolume(int(v))
self.menus[0].captions[1].text = f"Sound Volume: {mcrfpy.getSoundVolume():.1f}"
mcrfpy.modMenu(self.menus[0])
self.svol = mcrfpy.getSoundVolume()
scene = None
def start():
global scene
scene = MusicScene()

575
src/scripts/TestScene.py Normal file
View File

@ -0,0 +1,575 @@
import UIMenu
import Grid
import mcrfpy
from random import randint, choice
from pprint import pprint
#print("TestScene imported")
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED, GREEN, BLUE = (255, 0, 0), (0, 255, 0), (0, 0, 255)
DARKRED, DARKGREEN, DARKBLUE = (192, 0, 0), (0, 192, 0), (0, 0, 192)
animations_in_progress = 0
# don't load grid over and over, use the global scene
scene = None
class TestEntity:
def __init__(self, grid, label, tex_index, basesprite, x, y, texture_width=64, walk_frames=5, attack_frames=6, do_fov=False):
self.grid = grid
self.basesprite = basesprite
self.texture_width = texture_width
self.walk_frames = walk_frames
self.attack_frames = attack_frames
self.x = x
self.y = y
self.facing_direction = 0
self.do_fov = do_fov
self.label = label
self.inventory = []
#print(f"Calling C++ with: {repr((self.grid, label, tex_index, self.basesprite, x, y, self))}")
grids = mcrfpy.listGrids()
for g in grids:
if g.title == self.grid:
self.entity_index = len(g.entities)
mcrfpy.createEntity(self.grid, label, tex_index, self.basesprite, x, y, self)
def ai_act(self):
return # no AI motion
#if self.label == "player": return
self.move(randint(-1, 1), randint(-1, 1))
scene.actors += 1
def player_act(self):
#print("I'M INTERVENING")
mcrfpy.unlockPlayerInput()
scene.updatehints()
def die(self):
#self.x = -2
#self.y = -2
self.move(-1000,-1000)
self.animate(0,animove=(-1000,-1000))
self.x = -1000
self.y = -1000
self.label = "dead"
def interact(self, initiator, callback):
print(f"Interacted with {self.label}. ", end='')
if self.label == 'item':
print("'taking' item.")
callback()
self.die()
else:
print("blocking movement.")
def move(self, dx, dy, force=False):
# select animation direction
# prefer left or right for diagonals.
#grids = mcrfpy.listGrids()
for g in scene.grids:
if g.title == self.grid:
if not force and g.at(self.x + dx, self.y + dy) is None or not g.at(self.x + dx, self.y + dy).walkable:
#print("Blocked at target location.")
return
if not force: # entities can be stepped on when force=True (like collecting items!)
for entity in scene.tes:
if (entity.x, entity.y) == (self.x + dx, self.y + dy):
print(f"Blocked by entity {entity.label} at ({entity.x}, {entity.y})")
return entity.interact(self, lambda: self.move(dx, dy, force=True))
if self.label == "player":
mcrfpy.lockPlayerInput()
scene.updatehints()
if (dx == 0 and dy == 0):
direction = self.facing_direction # TODO, jump straight to computer turn
elif (dx):
direction = 2 if dx == +1 else 3
else:
direction = 0 if dy == +1 else 1
self.animate(direction, move=True, animove=(self.x + dx, self.y+dy))
self.facing_direction = direction
if (self.do_fov): mcrfpy.refreshFov()
def animate(self, direction, attacking=False, move=False, animove=None):
start_sprite = self.basesprite + (self.texture_width * (direction + (4 if attacking else 0)))
animation_frames = [start_sprite + i for i in range((self.attack_frames if attacking else self.walk_frames))]
mcrfpy.createAnimation(
0.15, # duration, seconds
self.grid, # parent: a UIMenu or Grid key
"entity", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
self.entity_index, # target id: integer index for menu or grid objs; None for grid/menu
"sprite", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
self.animation_done, #callback: callable once animation is complete
False, #loop: repeat indefinitely
animation_frames # values: iterable of frames for 'sprite', lerp target for others
)
#global animations_in_progress
#animations_in_progress += 1
if move:
pos = [self.x, self.y]
if (direction == 0): pos[1] += 1
elif (direction == 1): pos[1] -= 1
elif (direction == 2): pos[0] += 1
elif (direction == 3): pos[0] -= 1
if not animove:
self.x, self.y = pos
animove = pos
else:
pos = animove
self.x, self.y = animove
#scene.move_entity(self.grid, self.entity_index, pos)
#for g in mcrfpy.listGrids():
for g in scene.grids:
if g.title == self.grid:
g.entities[self.entity_index].x = pos[0]
g.entities[self.entity_index].y = pos[1]
mcrfpy.modGrid(g, True)
if animove:
mcrfpy.createAnimation(
0.25, # duration, seconds
self.grid, # parent: a UIMenu or Grid key
"entity", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
self.entity_index, # target id: integer index for menu or grid objs; None for grid/menu
"position", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
self.animation_done, #callback: callable once animation is complete
False, #loop: repeat indefinitely
animove # values: iterable of frames for 'sprite', lerp target for others
)
#animations_in_progress += 1
def animation_done(self):
#global animations_in_progress
#animations_in_progress -= 1
scene.actors -= 1
#print(f"{self} done animating - {scene.actors} remaining")
if scene.actors <= 0:
scene.actors = 0
mcrfpy.unlockPlayerInput()
scene.updatehints()
class TestItemEntity(TestEntity):
def __init__(self, grid, label, tex_index, basesprite, x, y, texture_width=64, walk_frames=5, attack_frames=6, do_fov=False, item="Nothing"):
super().__init__(grid, label, tex_index, basesprite, x, y, texture_width, walk_frames, attack_frames, do_fov)
self.item = item
def interact(self, initiator, callback):
if self.label == 'dead': return super().interact(initiator, callback)
print(f"Interacted with {self.label}, an item. Adding {self.item} to {initiator.label}'s inventory")
initiator.inventory.append(self.item)
callback()
scene.itemguis()
self.die()
class TestDoorEntity(TestEntity):
def __init__(self, grid, label, tex_index, basesprite, x, y, texture_width=64, walk_frames=5, attack_frames=6, do_fov=False, key="Nothing"):
super().__init__(grid, label, tex_index, basesprite, x, y, texture_width, walk_frames, attack_frames, do_fov)
self.key = key
def interact(self, initiator, callback):
if self.label == 'dead': return super().interact(initiator, callback)
print(f"Interacted with {self.label}, a Door. ", end='')
if self.key in initiator.inventory:
initiator.inventory.remove(self.key)
print("Taking key & passing.")
callback()
scene.itemguis()
self.die()
else:
print("The door is locked.")
class TestScene:
def __init__(self, ui_name = "demobox1", grid_name = "demogrid"):
# Texture & Sound Loading
self.actors = 0
#print("Load textures")
mcrfpy.createTexture("./assets/test_portraits.png", 32, 8, 8) #0 - portraits
mcrfpy.createTexture("./assets/alives_other.png", 16, 64, 64) #1 - TinyWorld NPCs
mcrfpy.createTexture("./assets/alives_other.png", 32, 32, 32) #2 - TinyWorld NPCs - 2x2 sprite
mcrfpy.createTexture("./assets/custom_player.png", 16, 5, 13) #3 - player
mcrfpy.createTexture("./assets/Sprite-0001.png", 80, 10, 10) #4 - LGJ2023 keycard + other icons
mcrfpy.createTexture("./assets/tiny_keycards.png", 16, 8, 8) #5 - tiny keycards (ground items)
self.ui_name = ui_name
self.grid_name = grid_name
# Menu index = 0
#print("Create UI")
# Create dialog UI
mcrfpy.createMenu(ui_name, 20, 540, 500, 200)
#mcrfpy.createCaption(ui_name, "Hello There", 18, BLACK)
mcrfpy.createCaption(ui_name, "", 24, RED)
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (0, 0, 0), "clicky", "testaction")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKRED, (0, 0, 0), "REPL", "startrepl")
##mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKGREEN, (0, 0, 0), "map gen", "gridgen")
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKGREEN, (0, 0, 0), "mapL", "gridgen2")
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKBLUE, (192, 0, 0), "a_menu", "animtest")
#mcrfpy.createButton(ui_name, 250, 20, 100, 50, DARKRED, GREEN, "a_spr", "animtest2")
#mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, GREEN, "Next sp", "nextsp")
#mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, RED, "Prev sp", "prevsp")
#mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, DARKGREEN, "+16 sp", "skipsp")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKGREEN, (0, 0, 0), "Next", "nextsp")
mcrfpy.createButton(ui_name, 250, 0, 130, 40, DARKBLUE, (0, 0, 0), "Prev", "prevsp")
mcrfpy.createSprite(ui_name, 4, 1, 10, 10, 2.0)
# Menu index = 1
#print("Create UI 2")
entitymenu = "entitytestmenu"
mcrfpy.createMenu(entitymenu, 840, 20, 20, 500)
mcrfpy.createButton(entitymenu, 0, 10, 150, 40, DARKBLUE, BLACK, "Up", "test_up")
mcrfpy.createButton(entitymenu, 0, 60, 150, 40, DARKBLUE, BLACK, "Down", "test_down")
mcrfpy.createButton(entitymenu, 0, 110, 150, 40, DARKBLUE, BLACK, "Left", "test_left")
mcrfpy.createButton(entitymenu, 0, 160, 150, 40, DARKBLUE, BLACK, "Right", "test_right")
mcrfpy.createButton(entitymenu, 0, 210, 150, 40, DARKBLUE, BLACK, "Attack", "test_attack")
mcrfpy.createButton(entitymenu, 0, 210, 150, 40, DARKBLUE, RED, "TE", "testent")
# Menu index = 2
mcrfpy.createMenu( "gridtitlemenu", 0, -10, 0, 0)
mcrfpy.createCaption("gridtitlemenu", "<grid name>", 18, WHITE)
#mcrfpy.createCaption("gridtitlemenu", "<camstate>", 16, WHITE)
# Menu index = 3
mcrfpy.createMenu( "hintsmenu", 0, 505, 0, 0)
mcrfpy.createCaption("hintsmenu", "<hintline>", 16, WHITE)
# Menu index = 4
# menu names must be created in alphabetical order (?!) - thanks, C++ hash map
mcrfpy.createMenu( "i", 600, 20, 0, 0)
#mcrfpy.createMenu( "camstatemenu", 600, 20, 0, 0)
mcrfpy.createCaption("i", "<camstate>", 16, WHITE)
# Menu index = 5
mcrfpy.createMenu( "j", 600, 500, 0, 0)
mcrfpy.createButton( "j", 0, 0, 80, 40, DARKBLUE, WHITE, "Recenter", "activatecamfollow")
# Menu index = 6, 7, 8, 9, 10: keycard sprites
mcrfpy.createMenu("k", 540, 540, 80, 80) #6
mcrfpy.createSprite("k", 4, 0, 10, 10, 1.0)
mcrfpy.createMenu("l", 540 + (80 * 1), 540, 80, 80) #7
mcrfpy.createSprite("l", 4, 1, 10, 10, 1.0)
mcrfpy.createMenu("m", 540 + (80 * 2), 540, 80, 80) #8
mcrfpy.createSprite("m", 4, 2, 10, 10, 1.0)
mcrfpy.createMenu("n", 540 + (80 * 3), 540, 80, 80) #9
mcrfpy.createSprite("n", 4, 3, 10, 10, 1.0)
mcrfpy.createMenu("o", 540 + (80 * 4), 540, 80, 80) #10
mcrfpy.createSprite("o", 4, 4, 10, 10, 1.0)
mcrfpy.createMenu("p", 20, 20, 40, 40) #11
#mcrfpy.createButton("p", 0, 0, 130, 40, DARKGREEN, (0, 0, 0), "Register", "keyregistration")
mcrfpy.createButton("p", 0, 0, 130, 40, DARKGREEN, (0, 0, 0), "Register", "startrepl")
mcrfpy.registerPyAction("keyregistration", keyregistration)
#print("Make UIs visible")
self.menus = mcrfpy.listMenus()
self.menus[0].visible = True
self.menus[1].w = 170
self.menus[1].visible = True
self.menus[2].visible = True
for mn in range(2, 6):
self.menus[mn].bgcolor = BLACK
self.menus[mn].visible = True
mcrfpy.modMenu(self.menus[mn])
for mn in range(6, 11):
self.menus[mn].bgcolor = BLACK
self.menus[mn].visible = False
mcrfpy.modMenu(self.menus[mn])
self.menus[11].visible=True
mcrfpy.modMenu(self.menus[11])
#self.menus[2].bgcolor = BLACK
#self.menus[3].visible = True
#self.menus[3].bgcolor = BLACK
#self.menus[4].visible = True
#self.menus[4].bgcolor = BLACK
#self.menus[5].visible = True
#mcrfpy.modMenu(self.menus[0])
#mcrfpy.modMenu(self.menus[1])
#mcrfpy.modMenu(self.menus[2])
#mcrfpy.modMenu(self.menus[3])
#mcrfpy.modMenu(self.menus[4])
#mcrfpy.modMenu(self.menus[5])
#pprint(mcrfpy.listMenus())
#print(f"UI 1 gave back this sprite: {self.menus[0].sprites}")
#print("Create grid")
# create grid (title, gx, gy, gs, x, y, w, h)
mcrfpy.createGrid(grid_name, 100, 100, 16, 20, 20, 800, 500)
self.grids = mcrfpy.listGrids()
#print(self.grids)
#print("Create entities")
#mcrfpy.createEntity("demogrid", "dragon", 2, 545, 10, 10, lambda: None)
#mcrfpy.createEntity("demogrid", "tinyenemy", 1, 1538, 3, 6, lambda: None)
#print("Create fancy entity")
self.player = TestEntity("demogrid", "player", 3, 20, 17, 3, 5, walk_frames=4, attack_frames=5, do_fov=True)
self.tes = [
TestEntity("demogrid", "classtest", 1, 1538, 5, 7, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 1545, 7, 9, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 1552, 9, 11, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 1566, 11, 13, 64, walk_frames=4, attack_frames=6),
#TestEntity("demogrid", "item", 1, 1573, 13, 15, 64, walk_frames=4, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 1583, 15, 17, 64, walk_frames=4, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 130, 9, 7, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 136, 11, 9, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 143, 13, 11, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 158, 15, 13, 64, walk_frames=5, attack_frames=6),
TestEntity("demogrid", "classtest", 1, 165, 17, 15, 64, walk_frames=5, attack_frames=6),
self.player,
TestItemEntity("demogrid", "GreenKeyCard", 5, 0, 19, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Green Keycard"),
TestItemEntity("demogrid", "BlueKeyCard", 5, 1, 21, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Blue Keycard"),
TestItemEntity("demogrid", "RedKeyCard", 5, 2, 23, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Red Keycard"),
TestItemEntity("demogrid", "OrangeKeyCard", 5, 3, 25, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Orange Keycard"),
TestItemEntity("demogrid", "DevilKeyCard", 5, 4, 27, 3, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, item="Devil Keycard"),
TestDoorEntity("demogrid", "GreenKeyDoor", 5, 8, 19, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Green Keycard"),
TestDoorEntity("demogrid", "BlueKeyDoor", 5, 9, 21, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Blue Keycard"),
TestDoorEntity("demogrid", "RedKeyDoor", 5, 10, 23, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Red Keycard"),
TestDoorEntity("demogrid", "OrangeKeyDoor", 5, 11, 25, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Orange Keycard"),
TestDoorEntity("demogrid", "DevilKeyDoor", 5, 12, 27, 5, texture_width=64,
walk_frames=5, attack_frames=6, do_fov=False, key="Devil Keycard")
]
self.grids = mcrfpy.listGrids()
self.entity_direction = 0
mcrfpy.registerPyAction("test_down", lambda: [te.animate(0, False, True) for te in self.tes])
mcrfpy.registerPyAction("test_up", lambda: [te.animate(1, False, True) for te in self.tes])
mcrfpy.registerPyAction("test_right", lambda: [te.animate(2, False, True) for te in self.tes])
mcrfpy.registerPyAction("test_left", lambda: [te.animate(3, False, True) for te in self.tes])
mcrfpy.registerPyAction("test_attack", lambda: [te.animate(0, True) for te in self.tes])
mcrfpy.registerPyAction("testent", lambda: [te.animate(2, True) for te in self.tes])
mcrfpy.registerPyAction("activatecamfollow", lambda: mcrfpy.camFollow(True))
# Button behavior
self.clicks = 0
self.sprite_index = 0
#mcrfpy.registerPyAction("testaction", self.click)
mcrfpy.registerPyAction("gridgen", self.gridgen)
#mcrfpy.registerPyAction("gridgen2", lambda: self.gridgen())
#mcrfpy.registerPyAction("animtest", lambda: self.createAnimation())
#mcrfpy.registerPyAction("animtest2", lambda: self.createAnimation2())
mcrfpy.registerPyAction("nextsp", lambda: self.changesprite(1))
mcrfpy.registerPyAction("prevsp", lambda: self.changesprite(-1))
mcrfpy.registerPyAction("skipsp", lambda: self.changesprite(16))
mcrfpy.unlockPlayerInput()
mcrfpy.setActiveGrid("demogrid")
self.gridgen()
self.updatehints()
mcrfpy.camFollow(True)
def itemguis(self):
print(self.player.inventory)
items = ["Green Keycard", "Blue Keycard", "Red Keycard", "Orange Keycard", "Devil Keycard"]
for mn in range(6, 11):
self.menus[mn].visible = items[mn-6] in self.player.inventory
mcrfpy.modMenu(self.menus[mn])
def animate_entity(self, direction, attacking=False):
if direction is None:
direction = self.entity_direction
else:
self.entity_direction = direction
dragon_sprite = 545 + (32 * (direction + (4 if attacking else 0)))
dragon_animation = [dragon_sprite + i for i in range((5 if attacking else 4))]
mcrfpy.createAnimation(
1.0, # duration, seconds
"demogrid", # parent: a UIMenu or Grid key
"entity", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
0, # target id: integer index for menu or grid objs; None for grid/menu
"sprite", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
lambda: self.animation_done("demobox1"), #callback: callable once animation is complete
False, #loop: repeat indefinitely
dragon_animation # values: iterable of frames for 'sprite', lerp target for others
)
orc_sprite = 1538 + (64 * (direction + (4 if attacking else 0)))
orc_animation = [orc_sprite + i for i in range((5 if attacking else 4))]
mcrfpy.createAnimation(
1.0, # duration, seconds
"demogrid", # parent: a UIMenu or Grid key
"entity", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
1, # target id: integer index for menu or grid objs; None for grid/menu
"sprite", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
lambda: self.animation_done("demobox1"), #callback: callable once animation is complete
False, #loop: repeat indefinitely
orc_animation # values: iterable of frames for 'sprite', lerp target for others
)
#def move_entity(self, targetgrid, entity_index, position):
# for i, g in enumerate(self.grids):
# if g.title == targetgrid:
# g.entities[entity_index].x = position[0]
# g.entities[entity_index].y = position[1]
# #pts = g.points
# g.visible = True
# mcrfpy.modGrid(g)
# self.grids = mcrfpy.listGrids()
# #self.grids[i].points = pts
# return
def changesprite(self, n):
self.sprite_index += n
self.menus[0].captions[0].text = f"Sprite #{self.sprite_index}"
self.menus[0].sprites[0].sprite_index = self.sprite_index
mcrfpy.modMenu(self.menus[0])
def click(self):
self.clicks += 1
self.menus[0].captions[0].text = f"Clicks: {self.clicks}"
self.menus[0].sprites[0].sprite_index = randint(0, 3)
mcrfpy.modMenu(self.menus[0])
def updatehints(self):
self.menus[2].captions[0].text=mcrfpy.activeGrid()
mcrfpy.modMenu(self.menus[2])
self.menus[3].captions[0].text=mcrfpy.inputMode()
mcrfpy.modMenu(self.menus[3])
#self.menus[4].captions[0].text=f"follow: {mcrfpy.camFollow()}"
self.menus[4].captions[0].text="following" if mcrfpy.camFollow() else "free"
mcrfpy.modMenu(self.menus[4])
self.menus[5].visible = not mcrfpy.camFollow()
mcrfpy.modMenu(self.menus[5])
def gridgen(self):
#print(f"[Python] modifying {len(self.grids[0].points)} grid points")
for p in self.grids[0].points:
#p.color = (randint(0, 255), randint(64, 192), 0)
p.color = (0,0,0)
p.walkable = False
p.transparent = False
room_centers = [(randint(0, self.grids[0].grid_x-1), randint(0, self.grids[0].grid_y-1)) for i in range(20)] + [(17, 3), (20,10) + (20,5)]
#room_centers.append((3, 5))
for r in room_centers:
# random hallway
target = choice(room_centers)
length1 = abs(target[0] - r[0])
xbase = min(target[0], r[0])
for x in range(length1):
gpoint = self.grids[0].at(x, r[1])
if gpoint is None: continue
gpoint.walkable = True
gpoint.transparent = True
gpoint.color = (192, 192, 192)
length2 = abs(target[1] - r[1])
ybase = min(target[1], r[1])
for y in range(length2):
gpoint = self.grids[0].at(r[0], y)
if gpoint is None: continue
gpoint.walkable = True
gpoint.transparent = True
gpoint.color = (192, 192, 192)
for r in room_centers:
#print(r)
room_color = (randint(16, 24)*8, randint(16, 24)*8, randint(16, 24)*8)
#self.grids[0].at(r[0], r[1]).walkable = True
#self.grids[0].at(r[0], r[1]).color = room_color
halfx, halfy = randint(2, 11), randint(2,11)
for p_x in range(r[0] - halfx, r[0] + halfx):
for p_y in range(r[1] - halfy, r[1] + halfy):
gpoint = self.grids[0].at(p_x, p_y)
if gpoint is None: continue
gpoint.walkable = True
gpoint.transparent = True
gpoint.color = room_color
#print()
#print("[Python] Modifying:")
self.grids[0].at(10, 10).color = (255, 255, 255)
self.grids[0].at(10, 10).walkable = False
self.grids[0].visible = True
mcrfpy.modGrid(self.grids[0])
#self.grids = mcrfpy.listGrids()
#print(f"Sent grid: {repr(self.grids[0])}")
#print(f"Received grid: {repr(mcrfpy.listGrids()[0])}")
def animation_done(self, s):
print(f"The `{s}` animation completed.")
#self.menus = mcrfpy.listMenus()
# if (!PyArg_ParseTuple(args, "fsssiOOO", &duration, &parent, &target_type, &target_id, &field, &callback, &loop_obj, &values_obj)) return NULL;
def createAnimation(self):
print(self.menus)
self.menus = mcrfpy.listMenus()
self.menus[0].w = 500
self.menus[0].h = 200
print(self.menus)
mcrfpy.modMenu(self.menus[0])
print(self.menus)
mcrfpy.createAnimation(
3.0, # duration, seconds
"demobox1", # parent: a UIMenu or Grid key
"menu", # target type: 'menu', 'grid', 'caption', 'button', 'sprite', or 'entity'
0, # target id: integer index for menu or grid objs; None for grid/menu
"size", # field: 'position', 'size', 'bgcolor', 'textcolor', or 'sprite'
lambda: self.animation_done("demobox1"), #callback: callable once animation is complete
False, #loop: repeat indefinitely
[150, 100] # values: iterable of frames for 'sprite', lerp target for others
)
def createAnimation2(self):
mcrfpy.createAnimation(
5,
"demobox1",
"sprite",
0,
"sprite",
lambda: self.animation_done("sprite change"),
False,
[0, 1, 2, 1, 2, 0]
)
def keytest():
print("Key tested.")
def keyregistration():
print("Registering 'keytest'")
mcrfpy.registerPyAction("keytest", keytest)
print("Registering input")
print(mcrfpy.registerInputAction(15, "keytest")) # 15 = P
mcrfpy.registerPyAction("player_move_up", lambda: scene.player.move(0, -1))
mcrfpy.registerPyAction("player_move_left", lambda: scene.player.move(-1, 0))
mcrfpy.registerPyAction("player_move_down", lambda: scene.player.move(0, 1))
mcrfpy.registerPyAction("player_move_right", lambda: scene.player.move(1, 0))
mcrfpy.registerInputAction(ord('w') - ord('a'), "player_move_up")
mcrfpy.registerInputAction(ord('a') - ord('a'), "player_move_left")
mcrfpy.registerInputAction(ord('s') - ord('a'), "player_move_down")
mcrfpy.registerInputAction(ord('d') - ord('a'), "player_move_right")
def start():
global scene
#print("TestScene.start called")
scene = TestScene()
mcrfpy.refreshFov()
scene.updatehints()

48
src/scripts/UIMenu.py Normal file
View File

@ -0,0 +1,48 @@
class Caption:
def __init__(self, text, textsize, color):
self.text = text
self.textsize = textsize
self.color = color
def __repr__(self):
return f"<Caption text={self.text}, textsize={self.textsize}, color={self.color}>"
class Button:
def __init__(self, x, y, w, h, bgcolor, textcolor, text, actioncode):
self.x = x
self.y = y
self.w = w
self.h = h
self.bgcolor = bgcolor
self.textcolor = textcolor
self.text = text
self.actioncode = actioncode
def __repr__(self):
return f"<Button ({self.x}, {self.y}, {self.w}, {self.h}), bgcolor={self.bgcolor}, textcolor={self.textcolor}, actioncode={self.actioncode}>"
class Sprite:
def __init__(self, tex_index, sprite_index, x, y):
self.tex_index = tex_index
self.sprite_index = sprite_index
self.x = x
self.y = y
def __repr__(self):
return f"<Sprite tex_index={self.tex_index}, self.sprite_index={self.sprite_index}, x={self.x}, y={self.y}>"
class UIMenu:
def __init__(self, title, x, y, w, h, bgcolor, visible=False):
self.title = title
self.x = x
self.y = y
self.w = w
self.h = h
self.bgcolor = bgcolor
self.visible = visible
self.captions = []
self.buttons = []
self.sprites = []
def __repr__(self):
return f"<UIMenu title={repr(self.title)}, x={self.x}, y={self.y}, w={self.w}, h={self.h}, bgcolor={self.bgcolor}, visible={self.visible}, {len(self.captions)} captions, {len(self.buttons)} buttons, {len(self.sprites)} sprites>"

View File

@ -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

View File

@ -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),
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,10 @@
print("[Python] Attempting import")
import scriptable
print(f"[Python] calling fibonacci(8): {scriptable.fibonacci(8)}")
print(f"[Python] calling fibonacci(15): {scriptable.fibonacci(15)}")
import venv
print("[Python] Importing library installed with pip")
import numpy

View File

@ -1,677 +0,0 @@
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")
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()

View File

@ -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?

37
src/scripts/test_ui.py Normal file
View File

@ -0,0 +1,37 @@
import mcrfpy
mcrfpy.createTexture("./assets/test_portraits.png", 32, 8, 8)
from random import choice, randint
box_colors = [
(0, 0, 192),
(0, 192, 0),
(192, 0, 0),
(192, 192, 0),
(0, 192, 192),
(192, 0, 192)
]
text_colors = [
(0, 0, 255),
(0, 255, 0),
(255, 0, 0),
(255, 255, 0),
(0, 255, 255),
(255, 0, 255)
]
test_x = 500
test_y = 10
for i in range(40):
ui_name = f"test{i}"
mcrfpy.createMenu(ui_name, test_x, test_y, 400, 200)
mcrfpy.createCaption(ui_name, "Hello There", 18, choice(text_colors))
mcrfpy.createButton(ui_name, 250, 20, 100, 50, choice(box_colors), (0, 0, 0), "asdf", "testaction")
mcrfpy.createSprite(ui_name, 0, randint(0, 3), 650, 60, 5.0)
test_x -= 50
test_y += 50
if (test_x <= 50):
test_x = 500
#print(test_x)

View File

@ -1,168 +0,0 @@
Red #ED0A3F
Maroon #C32148
Scarlet #FD0E35
Brick Red #C62D42
English Vermilion #CC474B
Madder Lake #CC3336
Permanent Geranium Lake #E12C2C
Maximum Red #D92121
Chestnut #B94E48
Orange-Red #FF5349
Sunset Orange #FE4C40
Bittersweet #FE6F5E
Dark Venetian Red #B33B24
Venetian Red #CC553D
Light Venetian Red #E6735C
Vivid Tangerine #FF9980
Middle Red #E58E73
Burnt Orange #FF7034
Red-Orange #FF3F34
Orange #FF8833
Macaroni and Cheese #FFB97B
Middle Yellow Red #ECAC76
Mango Tango #E77200
Yellow-Orange #FFAE42
Maximum Yellow Red #F2BA49
Banana Mania #FBE7B2
Maize #F2C649
Orange-Yellow #F8D568
Goldenrod #FCD667
Dandelion #FED85D
Yellow #FBE870
Green-Yellow #F1E788
Middle Yellow #FFEB00
Olive Green #B5B35C
Spring Green #ECEBBD
Maximum Yellow #FAFA37
Canary #FFFF99
Lemon Yellow #FFFF9F
Maximum Green Yellow #D9E650
Middle Green Yellow #ACBF60
Inchworm #B0E313
Light Chrome Green #BEE64B
Yellow-Green #C5E17A
Maximum Green #5E8C31
Asparagus #7BA05B
Granny Smith Apple #9DE093
Fern #63B76C
Middle Green #4D8C57
Green #01A638
Medium Chrome Green #6CA67C
Forest Green #5FA777
Sea Green #93DFB8
Shamrock #33CC99
Mountain Meadow #1AB385
Jungle Green #29AB87
Caribbean Green #00CC99
Tropical Rain Forest #00755E
Middle Blue Green #8DD9CC
Pine Green #01796F
Maximum Blue Green #30BFBF
Robin's Egg Blue #00CCCC
Teal Blue #008080
Light Blue #8FD8D8
Aquamarine #458B74
Turquoise Blue #6CDAE7
Outer Space #2D383A
Sky Blue #76D7EA
Middle Blue #7ED4E6
Blue-Green #0095B7
Pacific Blue #009DC4
Cerulean #02A4D3
Maximum Blue #47ABCC
Blue (I) #2EB4E6
Cerulean Blue #339ACC
Cornflower #93CCEA
Green-Blue #2887C8
Midnight Blue #003366
Navy Blue #0066CC
Denim #1560BD
Blue (III) #0066FF
Cadet Blue #A9B2C3
Periwinkle #C3CDE6
Blue (II) #4570E6
Bluetiful #3C69E7
Wild Blue Yonder #7A89B8
Indigo #4F69C6
Manatee #8D90A1
Cobalt Blue #8C90C8
Celestial Blue #7070CC
Blue Bell #9999CC
Maximum Blue Purple #ACACE6
Violet-Blue #766EC8
Blue-Violet #6456B7
Ultramarine Blue #3F26BF
Middle Blue Purple #8B72BE
Purple Heart #652DC1
Royal Purple #6B3FA0
Violet (II) #8359A3
Medium Violet #8F47B3
Wisteria #C9A0DC
Lavender (I) #BF8FCC
Vivid Violet #803790
Maximum Purple #733380
Purple Mountains' Majesty #D6AEDD
Fuchsia #C154C1
Pink Flamingo #F2583E
Violet (I) #732E6C
Brilliant Rose #E667CE
Orchid #E29CD2
Plum #843179
Medium Rose #D96CBE
Thistle #D8BFD8
Mulberry #C8509B
Red-Violet #BB3385
Middle Purple #D982B5
Maximum Red Purple #A63A79
Jazzberry Jam #A50B5E
Eggplant #614051
Magenta #F653A6
Cerise #DA3287
Wild Strawberry #FF3399
Lavender (II) #FBAED2
Cotton Candy #FFB7D5
Carnation Pink #FFA6C9
Violet-Red #F7468A
Razzmatazz #E30B5C
Piggy Pink #FDD7E4
Carmine #E62E6B
Blush #DB5079
Tickle Me Pink #FC80A5
Mauvelous #F091A9
Salmon #FF91A4
Middle Red Purple #A55353
Mahogany #CA3435
Melon #FEBAAD
Pink Sherbert #F7A38E
Burnt Sienna #E97451
Brown #AF593E
Sepia #9E5B40
Fuzzy Wuzzy #87421F
Beaver #926F5B
Tumbleweed #DEA681
Raw Sienna #D27D46
Van Dyke Brown #664228
Tan #FA9D5A
Desert Sand #EDC9AF
Peach #FFCBA4
Burnt Umber #805533
Apricot #FDD5B1
Almond #EED9C4
Raw Umber #665233
Shadow #837050
Raw Sienna (I) #E6BC5C
Gold (I) #92926E
Gold (II) #E6BE8A
Silver #C9C0BB
Copper #DA8A67
Antique Brass #C88A65
Black #000000
Charcoal Gray #736A62
Gray #8B8680
Blue-Gray #C8C8CD
Timberwolf #D9D6CF
White #FFFFFF
Crayellow #F1D651[6]
Cool Mint #DDEBEC[6]
Oatmeal #D9DAD2[6]
Powder Blue #C0D5F0[6]

Some files were not shown because too many files have changed in this diff Show More