Commit graph

208 commits

Author SHA1 Message Date
a4b1ab7d68 Grid layers: add HeightMap-based procedural generation methods
TileLayer (closes #200):
- apply_threshold(source, range, tile): Set tile index where heightmap value is in range
- apply_ranges(source, ranges): Apply multiple tile assignments in one pass

ColorLayer (closes #201):
- apply_threshold(source, range, color): Set fixed color where value is in range
- apply_gradient(source, range, color_low, color_high): Interpolate colors based on value
- apply_ranges(source, ranges): Apply multiple color assignments (fixed or gradient)

All methods return self for chaining. HeightMap size must match layer dimensions.
Later ranges override earlier ones if overlapping. Cells not matching any range are unchanged.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:35:44 -05:00
b7c5262abf HeightMap: improve dig_hill/dig_bezier API clarity
Rename parameters for clearer semantics:
- dig_hill: depth -> target_height
- dig_bezier: start_depth/end_depth -> start_height/end_height

The libtcod "dig" functions set terrain TO a target height, not
relative to current values. "target_height" makes this clearer.

Also add warnings for likely user errors:
- add_hill/dig_hill/dig_bezier with radius <= 0 (no-op)
- smooth with iterations <= 0 already raises ValueError

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:21:58 -05:00
f2711e553f HeightMap: add terrain generation methods (closes #195)
Add seven terrain generation methods wrapping libtcod heightmap functions:
- add_hill(center, radius, height): Add smooth hill
- dig_hill(center, radius, depth): Dig crater (use negative depth)
- add_voronoi(num_points, coefficients, seed): Voronoi-based features
- mid_point_displacement(roughness, seed): Diamond-square terrain
- rain_erosion(drops, erosion, sedimentation, seed): Erosion simulation
- dig_bezier(points, start_radius, end_radius, start_depth, end_depth): Carve paths
- smooth(iterations): Average neighboring cells

All methods return self for chaining. Includes 24 unit tests.

Note: dig_hill and dig_bezier use libtcod's "dig" semantics - use negative
depth values to actually dig below current terrain level.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:00:08 -05:00
d92d5f0274 HeightMap: add threshold operations that return new HeightMaps (closes #197)
Add three methods that create NEW HeightMap objects:
- threshold(range): preserve original values where in range, 0.0 elsewhere
- threshold_binary(range, value=1.0): set uniform value where in range
- inverse(): return (1.0 - value) for each cell

These operations are immutable - they preserve the original HeightMap.
Useful for masking operations with Grid.apply_threshold/apply_ranges.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:49:28 -05:00
b98b2be012 HeightMap: improve API consistency and add subscript support
Position argument flexibility:
- get(), get_interpolated(), get_slope(), get_normal() now accept:
  - Two separate args: hmap.get(5, 5)
  - Tuple: hmap.get((5, 5))
  - List: hmap.get([5, 5])
  - Vector: hmap.get(mcrfpy.Vector(5, 5))
- Uses PyPositionHelper for standardized parsing

Subscript support:
- Add __getitem__ as shorthand for get(): hmap[5, 5] or hmap[(5, 5)]

Range validation:
- count_in_range() now raises ValueError when min > max
- count_in_range() accepts both tuple and list

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:43:44 -05:00
c2877c8053 Replace deprecated PyWeakref_GetObject with PyWeakref_GetRef (closes #191)
PyWeakref_GetObject was deprecated in Python 3.13 and will be removed
in 3.15. The new PyWeakref_GetRef API returns a strong reference directly
and uses integer return codes for error handling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:48:06 -05:00
a81430991c libtcod as SYSTEM include, to ignore deprecations 2026-01-11 20:44:46 -05:00
bf8557798a Grid: add apply_threshold and apply_ranges for HeightMap (closes #199)
Add methods to apply HeightMap data to Grid walkable/transparent properties:
- apply_threshold(source, range, walkable, transparent): Apply properties
  to cells where HeightMap value is in the specified range
- apply_ranges(source, ranges): Apply multiple threshold rules in one pass

Features:
- Size mismatch between HeightMap and Grid raises ValueError
- Both methods return self for chaining
- Uses dynamic type lookup via module for HeightMap type checking
- First matching range wins in apply_ranges
- Cells not matching any range remain unchanged
- TCOD map is synced after changes for FOV/pathfinding

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:42:47 -05:00
8d6d564d6b HeightMap: add query methods (closes #196)
Add methods to query HeightMap values and statistics:
- get(pos): Get height value at integer coordinates
- get_interpolated(pos): Get bilinearly interpolated height at float coords
- get_slope(pos): Get slope angle (0 to pi/2) at position
- get_normal(pos, water_level): Get surface normal vector
- min_max(): Get (min, max) tuple of all values
- count_in_range(range): Count cells with values in range

All methods include proper bounds checking and error messages.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:42:33 -05:00
87444c2fd0 HeightMap: add GRID_MAX limit and input validation
Fixes potential integer overflow and invalid input issues:

- Add GRID_MAX constant (8192) to Common.h for global use
- Validate HeightMap dimensions against GRID_MAX to prevent
  integer overflow in w*h calculations (65536*65536 = 0)
- Add min > max validation for clamp() and normalize()
- Add unit tests for all new validation cases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:26:04 -05:00
c095be4b73 HeightMap: core class with scalar operations (closes #193)
Implement the foundational HeightMap class for procedural generation:

- HeightMap(size, fill=0.0) constructor with libtcod backend
- Immutable size property after construction
- Scalar operations returning self for method chaining:
  - fill(value), clear()
  - add_constant(value), scale(factor)
  - clamp(min=0.0, max=1.0), normalize(min=0.0, max=1.0)

Includes procedural generation spec document and unit tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:07:55 -05:00
b32f5af28c UIGridPathfinding: clear and separate A-star and Djikstra path systems 2026-01-10 22:09:45 -05:00
9eacedc624 Input Enums instead of strings. 2026-01-10 21:31:20 -05:00
d9411f94a4 Version bump: 0.2.0-prerelease-7drl2026 (d6ef29f) -> 0.2.1-prerelease-7drl2026 2026-01-10 08:55:50 -05:00
d6ef29f3cd Grid code quality improvements
* Grid [x, y] subscript - convenience for `.at()`
* Extract UIEntityCollection - cleanup of UIGrid.cpp
* Thread-safe type cache - PyTypeCache
* Exception-safe extend() - validate before modify
2026-01-10 08:37:31 -05:00
a77ac6c501 Monkey Patch support + Robust callback tracking
McRogueFace needs to accept callable objects (properties on C++ objects)
and also support subclassing (getattr on user objects). Only direct
properties were supported previously, now shadowing a callback by name
will allow custom objects to "just work".
- Added CallbackCache struct and is_python_subclass flag to UIDrawable.h
- Created metaclass for tracking class-level callback changes
- Updated all UI type init functions to detect subclasses
- Modified PyScene.cpp event dispatch to try subclass methods
2026-01-09 21:37:23 -05:00
1d11b020b0 Implement Scene subclass on_key callback support
Scene subclasses can now define on_key(self, key, state) methods that
receive keyboard events, matching the existing on_enter, on_exit, and
update lifecycle callbacks.

Changes:
- Rename call_on_keypress to call_on_key (consistent naming with property)
- Add triggerKeyEvent helper in McRFPy_API
- Call triggerKeyEvent from GameEngine when key_callable is not set
- Fix condition to check key_callable.isNone() (not just pointer existence)
- Handle both bound methods and instance-assigned callables

Usage:
    class GameScene(mcrfpy.Scene):
        def on_key(self, key, state):
            if key == "Escape" and state == "end":
                quit_game()

Property assignment (scene.on_key = callable) still works and takes
precedence when key_callable is set via the property setter.

Includes comprehensive test: tests/unit/scene_subclass_on_key_test.py

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:51:20 -05:00
b6eb70748a Remove YAGNI methods from performance systems
GridChunk: Remove getWorldBounds, markAllDirty, getVisibleChunks
- getWorldBounds: Chunk visibility handled by isVisible() instead
- markAllDirty: GridLayers uses per-cell markDirty() pattern
- getVisibleChunks: GridLayers computes visible range inline
- Keep dirtyChunks() for diagnostics

GridLayers: Remove getChunkCoords
- Trivial helper replaced by inline division throughout codebase

SpatialHash: Remove queryRect, totalEntities, cleanBucket
- queryRect: Exceeds #115 scope (only queryRadius required)
- totalEntities: Redundant with separate entity count tracking
- cleanBucket: Dead code - expired weak_ptrs cleaned during remove/update

All removals identified via cppcheck static analysis. Core functionality
of each system remains intact and actively used.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:40:13 -05:00
ae27e7deee delete unused file 2026-01-09 15:33:52 -05:00
2c320effc6 hashing bugfix: '<<' rather than '<<=' operator was used 2026-01-09 13:45:36 -05:00
a7ada7d65b distribution packaging 2026-01-09 12:00:59 -05:00
e6fa62f35d set version string for 7DRL2026 prerelease 2026-01-09 07:01:29 -05:00
ed85ccdf33 update to cpython 3.14.2 2026-01-09 07:00:15 -05:00
08c7c797a3 asset cleanup 2026-01-08 22:52:34 -05:00
1438044c6a mingw toolchain and final fixes for Windows. Closes #162 2026-01-08 21:16:27 -05:00
1f002e820c long -> intptr_t for casts. WIP: mingw cross-compilation for Windows (see #162) 2026-01-08 10:41:24 -05:00
2f4ebf3420 tests for the last few issues (these test scripts should work with recent APIs, while the rest of the test suite needs an overhaul) 2026-01-08 10:31:21 -05:00
a57f0875f8 Code editor window, lockable positions; send console output to the code editor to select and cut/copy output. Closes #170 2026-01-06 22:42:20 -05:00
75127ac9d1 mcrfpy.Mouse: a new class built for symmetry with mcrfpy.Keyboard. Closes #186 2026-01-06 21:39:01 -05:00
b0b17f4633 timer fixes: timers managed by engine can run in the background. Closes #180 2026-01-06 20:13:51 -05:00
2c20455003 support for Scene object as parent, from Python: closes #183 2026-01-06 14:04:53 -05:00
7e47050d6f bugfixes for .parent property - partial #183 solution 2026-01-06 10:21:50 -05:00
a4c2c04343 bugfix: segfault in Grid.at() due to internal types not exported to module
After #184/#189 made GridPoint and GridPointState internal-only types,
code using PyObject_GetAttrString(mcrf_module, "GridPoint") would get
NULL and crash when dereferencing.

Fixed by using the type directly via &mcrfpydef::PyUIGridPointType
instead of looking it up in the module.

Affected functions:
- UIGrid::py_at()
- UIGridPointState::get_point()
- UIEntity::at()
- UIGridPointState_to_PyObject()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 04:38:56 -05:00
f9b6cdef1c Python API improvements: Vectors, bounds, window singleton, hidden types
- #177: GridPoint.grid_pos property returns (x, y) tuple
- #179: Grid.grid_size returns Vector instead of tuple
- #181: Grid.center returns Vector instead of tuple
- #182: Caption.size/w/h read-only properties for text dimensions
- #184: mcrfpy.window singleton for window access
- #185: Removed get_bounds() method, use .bounds property instead
- #188: bounds/global_bounds return (pos, size) as pair of Vectors
- #189: Hide internal types from module namespace (iterators, collections)

Also fixed critical bug: Changed static PyTypeObject to inline in headers
to ensure single instance across translation units (was causing segfaults).

Closes #177, closes #179, closes #181, closes #182, closes #184, closes #185, closes #188, closes #189

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 23:00:48 -05:00
c6233fa47f Expand TileLayer and ColorLayer __init__ documentation; closes #190
Enhanced tp_doc strings for both layer types to include:
- What happens when grid_size=None (inherits from parent Grid)
- That layers are created via Grid.add_layer() rather than directly
- FOV-related methods for ColorLayer
- Tile index -1 meaning no tile/transparent for TileLayer
- fill_rect method documentation
- Comprehensive usage examples

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 22:24:36 -05:00
84d73e6aef Update old format scene/timer examples 2026-01-05 11:42:22 -05:00
02c512402e bugfix: segfault due to use of uninitialized Vector class reference 2026-01-05 10:31:41 -05:00
d2e4791f5a Positions are always mcrfpy.Vector, Vector/tuple/iterables expected as inputs, and for position-only inputs we permit x,y args to prevent requiring double-parens 2026-01-05 10:16:16 -05:00
016ca693b5 ImGui cleanup order: prevent error on exit by performing ImGui::SFML::Shutdown() before window close 2026-01-04 16:34:47 -05:00
9ab618079a .animate helper: create and start an animation directly on a target. Preferred use pattern; closes #175 2026-01-04 15:32:14 -05:00
d878c8684d Easing functions as enum 2026-01-04 12:59:28 -05:00
357c2ac7d7 Animation fixes: 0-duration edge case, integer value bug resolution 2026-01-04 00:45:16 -05:00
cec76b63dc Timer overhaul: update tests 2026-01-03 22:44:53 -05:00
5d41292bf6 Timer refactor: stopwatch-like semantics, mcrfpy.timers collection closes #173
Major Timer API improvements:
- Add `stopped` flag to Timer C++ class for proper state management
- Add `start()` method to restart stopped timers (preserves callback)
- Add `stop()` method that removes from engine but preserves callback
- Make `active` property read-write (True=start/resume, False=pause)
- Add `start=True` init parameter to create timers in stopped state
- Add `mcrfpy.timers` module-level collection (tuple of active timers)
- One-shot timers now set stopped=true instead of clearing callback
- Remove deprecated `setTimer()` and `delTimer()` module functions

Timer callbacks now receive (timer, runtime) instead of just (runtime).
Updated all tests to use new Timer API and callback signature.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 22:09:18 -05:00
fc95fc2844 scene transitions via Scene object 2026-01-03 13:53:18 -05:00
40c0eb2693 scripts - use scene object API 2026-01-03 11:02:40 -05:00
d7e34a3f72 Remove old scene management methods 2026-01-03 11:01:42 -05:00
48359b5a48 draft tutorial revisions 2026-01-03 11:01:10 -05:00
838da4571d update tests: new scene API 2026-01-03 10:59:52 -05:00
f62362032e feat: Grid camera defaults to tile (0,0) at top-left + center_camera() method (#169)
Changes:
- Default Grid center now positions tile (0,0) at widget's top-left corner
- Added center_camera() method to center grid's middle tile at view center
- Added center_camera((tile_x, tile_y)) to position tile at top-left of widget
- Uses NaN as sentinel to detect if user provided center values in kwargs
- Animation-compatible: center_camera() just sets center property, no special state

Behavior:
- center_camera() → grid's center tile at view center
- center_camera((0, 0)) → tile (0,0) at top-left corner
- center_camera((5, 10)) → tile (5,10) at top-left corner

Before: Grid(size=(320,240)) showed 3/4 of content off-screen (center=0,0)
After: Grid(size=(320,240)) shows tile (0,0) at top-left (center=160,120)

Closes #169

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 17:22:26 -05:00