Fix verify-pass code bugs #317/#318/#319
Three small bugs surfaced by the #314 docstring-accuracy verify pass: #317 automation.scroll() dropped the x of its position argument: scroll() resolved (x, y) but called injectMouseEvent(MouseWheelScrolled, clicks, y), passing the scroll amount as x. injectMouseEvent now takes the scroll delta as its own parameter and scroll() forwards the real x/y. #318 GridView.texture always returned None (a TODO stub). It now returns a Texture wrapper sharing the underlying shared_ptr<PyTexture>, mirroring Grid.texture. (mcrfpy.Grid and mcrfpy.GridView are the same type post-#252, so this fixes both names.) #319 Entity.visible_entities(radius=None) raised TypeError: radius was parsed with the 'i' format code, which rejects None. It now parses radius as an object and treats None / omitted / -1 as "use the grid's default fov_radius"; a non-int, non-None radius raises a clear TypeError. - regression tests for each under tests/regression/ - api_surface snapshot re-baselined (visible_entities signature; texture property now Texture | None) and docs/stubs regenerated; frozen docstring gate still 100% closes #317 closes #318 closes #319 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KnywUddaFRhkxo5kijxJnv
This commit is contained in:
parent
265425321c
commit
98489a96fd
12 changed files with 303 additions and 55 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# McRogueFace API Reference
|
# McRogueFace API Reference
|
||||||
|
|
||||||
*Generated on 2026-06-21 09:39:04*
|
*Generated on 2026-06-21 10:11:50*
|
||||||
|
|
||||||
*This documentation was dynamically generated from the compiled module.*
|
*This documentation was dynamically generated from the compiled module.*
|
||||||
|
|
||||||
|
|
@ -1989,19 +1989,17 @@ Note:
|
||||||
|
|
||||||
**Returns:** None Called automatically when the entity moves if the grid has FOV configured.
|
**Returns:** None Called automatically when the entity moves if the grid has FOV configured.
|
||||||
|
|
||||||
#### `visible_entities(fov=None, radius: int = -1) -> list[Entity]`
|
#### `visible_entities(fov=None, radius: int | None = None) -> list[Entity]`
|
||||||
|
|
||||||
Get list of other entities visible from this entity's position.
|
Get list of other entities visible from this entity's position.
|
||||||
|
|
||||||
Note:
|
|
||||||
|
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
- `fov`: FOV algorithm to use (FOV enum or None to use grid.fov)
|
- `fov`: FOV algorithm to use (FOV enum or None to use grid.fov)
|
||||||
- `radius`: FOV radius as int; omit or pass -1 to use the grid's default fov_radius
|
- `radius`: FOV radius as int; pass None, omit, or pass -1 to use the grid's default fov_radius
|
||||||
|
|
||||||
**Returns:** List of Entity objects within field of view, excluding self
|
**Returns:** List of Entity objects within field of view, excluding self
|
||||||
|
|
||||||
**Raises:** ValueError: If entity is not associated with a grid radius does not accept None; omit the argument entirely to use the grid default.
|
**Raises:** ValueError: If entity is not associated with a grid TypeError: If radius is neither an int nor None
|
||||||
|
|
||||||
### Entity3D
|
### Entity3D
|
||||||
|
|
||||||
|
|
@ -2441,7 +2439,7 @@ Keyword Args:
|
||||||
- `rotate_with_camera`: Whether to rotate visually with parent Grid's camera_rotation (bool). False (default): stay screen-aligned. True: tilt with camera. Only affects children of UIGrid; ignored for other parents.
|
- `rotate_with_camera`: Whether to rotate visually with parent Grid's camera_rotation (bool). False (default): stay screen-aligned. True: tilt with camera. Only affects children of UIGrid; ignored for other parents.
|
||||||
- `rotation`: Rotation angle in degrees (clockwise around origin). Animatable property.
|
- `rotation`: Rotation angle in degrees (clockwise around origin). Animatable property.
|
||||||
- `shader`: Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.
|
- `shader`: Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.
|
||||||
- `texture` *(read-only)*: Texture used for tile rendering (None, read-only). Texture return is not yet implemented; always returns None.
|
- `texture` *(read-only)*: Texture used for tile rendering (Texture | None, read-only).
|
||||||
- `uniforms` *(read-only)*: Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/4 tuples, PropertyBinding, and CallableBinding.
|
- `uniforms` *(read-only)*: Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/4 tuples, PropertyBinding, and CallableBinding.
|
||||||
- `vert_margin`: Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).
|
- `vert_margin`: Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).
|
||||||
- `visible`: Whether the object is visible (bool). Invisible objects are not rendered or clickable.
|
- `visible`: Whether the object is visible (bool). Invisible objects are not rendered or clickable.
|
||||||
|
|
@ -2561,7 +2559,7 @@ Keyword Args:
|
||||||
- `rotate_with_camera`: Whether to rotate visually with parent Grid's camera_rotation (bool). False (default): stay screen-aligned. True: tilt with camera. Only affects children of UIGrid; ignored for other parents.
|
- `rotate_with_camera`: Whether to rotate visually with parent Grid's camera_rotation (bool). False (default): stay screen-aligned. True: tilt with camera. Only affects children of UIGrid; ignored for other parents.
|
||||||
- `rotation`: Rotation angle in degrees (clockwise around origin). Animatable property.
|
- `rotation`: Rotation angle in degrees (clockwise around origin). Animatable property.
|
||||||
- `shader`: Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.
|
- `shader`: Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.
|
||||||
- `texture` *(read-only)*: Texture used for tile rendering (None, read-only). Texture return is not yet implemented; always returns None.
|
- `texture` *(read-only)*: Texture used for tile rendering (Texture | None, read-only).
|
||||||
- `uniforms` *(read-only)*: Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/4 tuples, PropertyBinding, and CallableBinding.
|
- `uniforms` *(read-only)*: Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/4 tuples, PropertyBinding, and CallableBinding.
|
||||||
- `vert_margin`: Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).
|
- `vert_margin`: Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).
|
||||||
- `visible`: Whether the object is visible (bool). Invisible objects are not rendered or clickable.
|
- `visible`: Whether the object is visible (bool). Invisible objects are not rendered or clickable.
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>McRogueFace API Reference</h1>
|
<h1>McRogueFace API Reference</h1>
|
||||||
<p><em>Generated on 2026-06-21 09:39:04</em></p>
|
<p><em>Generated on 2026-06-21 10:11:50</em></p>
|
||||||
<p><em>This documentation was dynamically generated from the compiled module.</em></p>
|
<p><em>This documentation was dynamically generated from the compiled module.</em></p>
|
||||||
|
|
||||||
<div class="toc">
|
<div class="toc">
|
||||||
|
|
@ -2144,16 +2144,14 @@ Note:</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">visible_entities(fov=None, radius: int = -1) -> list[Entity]</code></h5>
|
<h5><code class="method-name">visible_entities(fov=None, radius: int | None = None) -> list[Entity]</code></h5>
|
||||||
<p>Get list of other entities visible from this entity's position.
|
<p>Get list of other entities visible from this entity's position.</p>
|
||||||
|
|
||||||
Note:</p>
|
|
||||||
<div style='margin-left: 20px;'>
|
<div style='margin-left: 20px;'>
|
||||||
<div><span class='arg-name'>fov</span>: FOV algorithm to use (FOV enum or None to use grid.fov)</div>
|
<div><span class='arg-name'>fov</span>: FOV algorithm to use (FOV enum or None to use grid.fov)</div>
|
||||||
<div><span class='arg-name'>radius</span>: FOV radius as int; omit or pass -1 to use the grid's default fov_radius</div>
|
<div><span class='arg-name'>radius</span>: FOV radius as int; pass None, omit, or pass -1 to use the grid's default fov_radius</div>
|
||||||
</div>
|
</div>
|
||||||
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> List of Entity objects within field of view, excluding self</p>
|
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> List of Entity objects within field of view, excluding self</p>
|
||||||
<p style='margin-left: 20px;'><span class='raises'>Raises:</span> ValueError: If entity is not associated with a grid radius does not accept None; omit the argument entirely to use the grid default.</p>
|
<p style='margin-left: 20px;'><span class='raises'>Raises:</span> ValueError: If entity is not associated with a grid TypeError: If radius is neither an int nor None</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2619,7 +2617,7 @@ Keyword Args:
|
||||||
<li><span class='property-name'>rotate_with_camera</span>: Whether to rotate visually with parent Grid's camera_rotation (bool). False (default): stay screen-aligned. True: tilt with camera. Only affects children of UIGrid; ignored for other parents.</li>
|
<li><span class='property-name'>rotate_with_camera</span>: Whether to rotate visually with parent Grid's camera_rotation (bool). False (default): stay screen-aligned. True: tilt with camera. Only affects children of UIGrid; ignored for other parents.</li>
|
||||||
<li><span class='property-name'>rotation</span>: Rotation angle in degrees (clockwise around origin). Animatable property.</li>
|
<li><span class='property-name'>rotation</span>: Rotation angle in degrees (clockwise around origin). Animatable property.</li>
|
||||||
<li><span class='property-name'>shader</span>: Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.</li>
|
<li><span class='property-name'>shader</span>: Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.</li>
|
||||||
<li><span class='property-name'>texture</span> (read-only): Texture used for tile rendering (None, read-only). Texture return is not yet implemented; always returns None.</li>
|
<li><span class='property-name'>texture</span> (read-only): Texture used for tile rendering (Texture | None, read-only).</li>
|
||||||
<li><span class='property-name'>uniforms</span> (read-only): Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/4 tuples, PropertyBinding, and CallableBinding.</li>
|
<li><span class='property-name'>uniforms</span> (read-only): Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/4 tuples, PropertyBinding, and CallableBinding.</li>
|
||||||
<li><span class='property-name'>vert_margin</span>: Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).</li>
|
<li><span class='property-name'>vert_margin</span>: Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).</li>
|
||||||
<li><span class='property-name'>visible</span>: Whether the object is visible (bool). Invisible objects are not rendered or clickable.</li>
|
<li><span class='property-name'>visible</span>: Whether the object is visible (bool). Invisible objects are not rendered or clickable.</li>
|
||||||
|
|
@ -2741,7 +2739,7 @@ Keyword Args:
|
||||||
<li><span class='property-name'>rotate_with_camera</span>: Whether to rotate visually with parent Grid's camera_rotation (bool). False (default): stay screen-aligned. True: tilt with camera. Only affects children of UIGrid; ignored for other parents.</li>
|
<li><span class='property-name'>rotate_with_camera</span>: Whether to rotate visually with parent Grid's camera_rotation (bool). False (default): stay screen-aligned. True: tilt with camera. Only affects children of UIGrid; ignored for other parents.</li>
|
||||||
<li><span class='property-name'>rotation</span>: Rotation angle in degrees (clockwise around origin). Animatable property.</li>
|
<li><span class='property-name'>rotation</span>: Rotation angle in degrees (clockwise around origin). Animatable property.</li>
|
||||||
<li><span class='property-name'>shader</span>: Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.</li>
|
<li><span class='property-name'>shader</span>: Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.</li>
|
||||||
<li><span class='property-name'>texture</span> (read-only): Texture used for tile rendering (None, read-only). Texture return is not yet implemented; always returns None.</li>
|
<li><span class='property-name'>texture</span> (read-only): Texture used for tile rendering (Texture | None, read-only).</li>
|
||||||
<li><span class='property-name'>uniforms</span> (read-only): Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/4 tuples, PropertyBinding, and CallableBinding.</li>
|
<li><span class='property-name'>uniforms</span> (read-only): Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/4 tuples, PropertyBinding, and CallableBinding.</li>
|
||||||
<li><span class='property-name'>vert_margin</span>: Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).</li>
|
<li><span class='property-name'>vert_margin</span>: Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).</li>
|
||||||
<li><span class='property-name'>visible</span>: Whether the object is visible (bool). Invisible objects are not rendered or clickable.</li>
|
<li><span class='property-name'>visible</span>: Whether the object is visible (bool). Invisible objects are not rendered or clickable.</li>
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@
|
||||||
. ftr VB CB
|
. ftr VB CB
|
||||||
. ftr VBI CBI
|
. ftr VBI CBI
|
||||||
.\}
|
.\}
|
||||||
.TH "MCRFPY" "3" "2026-06-21" "McRogueFace 0.2.7-prerelease-7drl2026-107-g3f39ee0" ""
|
.TH "MCRFPY" "3" "2026-06-21" "McRogueFace 0.2.7-prerelease-7drl2026-108-g2654253" ""
|
||||||
.hy
|
.hy
|
||||||
.SH McRogueFace API Reference
|
.SH McRogueFace API Reference
|
||||||
.PP
|
.PP
|
||||||
\f[I]Generated on 2026-06-21 09:39:04\f[R]
|
\f[I]Generated on 2026-06-21 10:11:50\f[R]
|
||||||
.PP
|
.PP
|
||||||
\f[I]This documentation was dynamically generated from the compiled
|
\f[I]This documentation was dynamically generated from the compiled
|
||||||
module.\f[R]
|
module.\f[R]
|
||||||
|
|
@ -2201,22 +2201,19 @@ Note:
|
||||||
.PP
|
.PP
|
||||||
\f[B]Returns:\f[R] None Called automatically when the entity moves if
|
\f[B]Returns:\f[R] None Called automatically when the entity moves if
|
||||||
the grid has FOV configured.
|
the grid has FOV configured.
|
||||||
.SS \f[V]visible_entities(fov=None, radius: int = -1) -> list[Entity]\f[R]
|
.SS \f[V]visible_entities(fov=None, radius: int | None = None) -> list[Entity]\f[R]
|
||||||
.PP
|
.PP
|
||||||
Get list of other entities visible from this entity\[cq]s position.
|
Get list of other entities visible from this entity\[cq]s position.
|
||||||
.PP
|
.PP
|
||||||
Note:
|
|
||||||
.PP
|
|
||||||
\f[B]Arguments:\f[R] - \f[V]fov\f[R]: FOV algorithm to use (FOV enum or
|
\f[B]Arguments:\f[R] - \f[V]fov\f[R]: FOV algorithm to use (FOV enum or
|
||||||
None to use grid.fov) - \f[V]radius\f[R]: FOV radius as int; omit or
|
None to use grid.fov) - \f[V]radius\f[R]: FOV radius as int; pass None,
|
||||||
pass -1 to use the grid\[cq]s default fov_radius
|
omit, or pass -1 to use the grid\[cq]s default fov_radius
|
||||||
.PP
|
.PP
|
||||||
\f[B]Returns:\f[R] List of Entity objects within field of view,
|
\f[B]Returns:\f[R] List of Entity objects within field of view,
|
||||||
excluding self
|
excluding self
|
||||||
.PP
|
.PP
|
||||||
\f[B]Raises:\f[R] ValueError: If entity is not associated with a grid
|
\f[B]Raises:\f[R] ValueError: If entity is not associated with a grid
|
||||||
radius does not accept None; omit the argument entirely to use the grid
|
TypeError: If radius is neither an int nor None
|
||||||
default.
|
|
||||||
.SS Entity3D
|
.SS Entity3D
|
||||||
.PP
|
.PP
|
||||||
Entity3D(pos=None, **kwargs)
|
Entity3D(pos=None, **kwargs)
|
||||||
|
|
@ -2724,8 +2721,7 @@ Animatable property.
|
||||||
When set, the drawable is rendered through the shader program.
|
When set, the drawable is rendered through the shader program.
|
||||||
Set to None to disable shader effects.
|
Set to None to disable shader effects.
|
||||||
- \f[V]texture\f[R] \f[I](read-only)\f[R]: Texture used for tile
|
- \f[V]texture\f[R] \f[I](read-only)\f[R]: Texture used for tile
|
||||||
rendering (None, read-only).
|
rendering (Texture | None, read-only).
|
||||||
Texture return is not yet implemented; always returns None.
|
|
||||||
- \f[V]uniforms\f[R] \f[I](read-only)\f[R]: Collection of shader
|
- \f[V]uniforms\f[R] \f[I](read-only)\f[R]: Collection of shader
|
||||||
uniforms (read-only access to collection).
|
uniforms (read-only access to collection).
|
||||||
Set uniforms via dict-like syntax: drawable.uniforms[`name'] = value.
|
Set uniforms via dict-like syntax: drawable.uniforms[`name'] = value.
|
||||||
|
|
@ -2889,8 +2885,7 @@ Animatable property.
|
||||||
When set, the drawable is rendered through the shader program.
|
When set, the drawable is rendered through the shader program.
|
||||||
Set to None to disable shader effects.
|
Set to None to disable shader effects.
|
||||||
- \f[V]texture\f[R] \f[I](read-only)\f[R]: Texture used for tile
|
- \f[V]texture\f[R] \f[I](read-only)\f[R]: Texture used for tile
|
||||||
rendering (None, read-only).
|
rendering (Texture | None, read-only).
|
||||||
Texture return is not yet implemented; always returns None.
|
|
||||||
- \f[V]uniforms\f[R] \f[I](read-only)\f[R]: Collection of shader
|
- \f[V]uniforms\f[R] \f[I](read-only)\f[R]: Collection of shader
|
||||||
uniforms (read-only access to collection).
|
uniforms (read-only access to collection).
|
||||||
Set uniforms via dict-like syntax: drawable.uniforms[`name'] = value.
|
Set uniforms via dict-like syntax: drawable.uniforms[`name'] = value.
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ sf::Keyboard::Key McRFPy_Automation::stringToKey(const std::string& keyName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject mouse event into the game engine
|
// Inject mouse event into the game engine
|
||||||
void McRFPy_Automation::injectMouseEvent(sf::Event::EventType type, int x, int y, sf::Mouse::Button button) {
|
void McRFPy_Automation::injectMouseEvent(sf::Event::EventType type, int x, int y, sf::Mouse::Button button, float scrollDelta) {
|
||||||
auto engine = getGameEngine();
|
auto engine = getGameEngine();
|
||||||
if (!engine) return;
|
if (!engine) return;
|
||||||
|
|
||||||
|
|
@ -141,8 +141,8 @@ void McRFPy_Automation::injectMouseEvent(sf::Event::EventType type, int x, int y
|
||||||
break;
|
break;
|
||||||
case sf::Event::MouseWheelScrolled:
|
case sf::Event::MouseWheelScrolled:
|
||||||
event.mouseWheelScroll.wheel = sf::Mouse::VerticalWheel;
|
event.mouseWheelScroll.wheel = sf::Mouse::VerticalWheel;
|
||||||
event.mouseWheelScroll.delta = static_cast<float>(x); // x is used for scroll amount
|
event.mouseWheelScroll.delta = scrollDelta; // #317: scroll amount is its own arg
|
||||||
event.mouseWheelScroll.x = x;
|
event.mouseWheelScroll.x = x; // position now honored on both axes
|
||||||
event.mouseWheelScroll.y = y;
|
event.mouseWheelScroll.y = y;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -600,8 +600,9 @@ PyObject* McRFPy_Automation::_scroll(PyObject* self, PyObject* args, PyObject* k
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject scroll event
|
// Inject scroll event (#317: forward the resolved x/y position; clicks is
|
||||||
injectMouseEvent(sf::Event::MouseWheelScrolled, clicks, y);
|
// the scroll delta, passed via its own argument).
|
||||||
|
injectMouseEvent(sf::Event::MouseWheelScrolled, x, y, sf::Mouse::Left, static_cast<float>(clicks));
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
@ -953,7 +954,7 @@ static PyMethodDef automationMethods[] = {
|
||||||
MCRF_ARGS_START
|
MCRF_ARGS_START
|
||||||
MCRF_ARG("clicks", "Number of scroll steps (positive = up, negative = down)")
|
MCRF_ARG("clicks", "Number of scroll steps (positive = up, negative = down)")
|
||||||
MCRF_ARG("pos", "Position as (x, y) tuple, [x, y] list, Vector, or None for current position")
|
MCRF_ARG("pos", "Position as (x, y) tuple, [x, y] list, Vector, or None for current position")
|
||||||
MCRF_NOTE("The x-coordinate of pos is currently unused; only the y-coordinate is applied to the scroll event position.")
|
MCRF_NOTE("Both the x and y of pos are applied to the scroll event position.")
|
||||||
)},
|
)},
|
||||||
{"mouseDown", (PyCFunction)McRFPy_Automation::_mouseDown, METH_VARARGS | METH_KEYWORDS,
|
{"mouseDown", (PyCFunction)McRFPy_Automation::_mouseDown, METH_VARARGS | METH_KEYWORDS,
|
||||||
MCRF_METHOD(automation, mouseDown,
|
MCRF_METHOD(automation, mouseDown,
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public:
|
||||||
static PyObject* _keyUp(PyObject* self, PyObject* args);
|
static PyObject* _keyUp(PyObject* self, PyObject* args);
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
static void injectMouseEvent(sf::Event::EventType type, int x, int y, sf::Mouse::Button button = sf::Mouse::Left);
|
static void injectMouseEvent(sf::Event::EventType type, int x, int y, sf::Mouse::Button button = sf::Mouse::Left, float scrollDelta = 0.0f);
|
||||||
static void injectKeyEvent(sf::Event::EventType type, sf::Keyboard::Key key);
|
static void injectKeyEvent(sf::Event::EventType type, sf::Keyboard::Key key);
|
||||||
static void injectTextEvent(sf::Uint32 unicode);
|
static void injectTextEvent(sf::Uint32 unicode);
|
||||||
static sf::Keyboard::Key stringToKey(const std::string& keyName);
|
static sf::Keyboard::Key stringToKey(const std::string& keyName);
|
||||||
|
|
|
||||||
|
|
@ -1315,13 +1315,26 @@ PyObject* UIEntity::visible_entities(PyUIEntityObject* self, PyObject* args, PyO
|
||||||
{
|
{
|
||||||
static const char* keywords[] = {"fov", "radius", nullptr};
|
static const char* keywords[] = {"fov", "radius", nullptr};
|
||||||
PyObject* fov_arg = nullptr;
|
PyObject* fov_arg = nullptr;
|
||||||
|
PyObject* radius_arg = nullptr;
|
||||||
int radius = -1; // -1 means use grid default
|
int radius = -1; // -1 means use grid default
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oi", const_cast<char**>(keywords),
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", const_cast<char**>(keywords),
|
||||||
&fov_arg, &radius)) {
|
&fov_arg, &radius_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #319: radius accepts an int, or None / omitted to use the grid default.
|
||||||
|
// The 'i' format code rejects None, so parse as an object and convert here.
|
||||||
|
if (radius_arg && radius_arg != Py_None) {
|
||||||
|
if (!PyLong_Check(radius_arg)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"visible_entities() radius must be an int or None");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
radius = static_cast<int>(PyLong_AsLong(radius_arg));
|
||||||
|
if (radius == -1 && PyErr_Occurred()) return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if entity has a grid
|
// Check if entity has a grid
|
||||||
if (!self->data || !self->data->grid) {
|
if (!self->data || !self->data->grid) {
|
||||||
PyErr_SetString(PyExc_ValueError, "Entity must be associated with a grid to find visible entities");
|
PyErr_SetString(PyExc_ValueError, "Entity must be associated with a grid to find visible entities");
|
||||||
|
|
@ -1449,14 +1462,14 @@ PyMethodDef UIEntity::methods[] = {
|
||||||
)},
|
)},
|
||||||
{"visible_entities", (PyCFunction)UIEntity::visible_entities, METH_VARARGS | METH_KEYWORDS,
|
{"visible_entities", (PyCFunction)UIEntity::visible_entities, METH_VARARGS | METH_KEYWORDS,
|
||||||
MCRF_METHOD(Entity, visible_entities,
|
MCRF_METHOD(Entity, visible_entities,
|
||||||
MCRF_SIG("(fov=None, radius: int = -1)", "list[Entity]"),
|
MCRF_SIG("(fov=None, radius: int | None = None)", "list[Entity]"),
|
||||||
MCRF_DESC("Get list of other entities visible from this entity's position."),
|
MCRF_DESC("Get list of other entities visible from this entity's position."),
|
||||||
MCRF_ARGS_START
|
MCRF_ARGS_START
|
||||||
MCRF_ARG("fov", "FOV algorithm to use (FOV enum or None to use grid.fov)")
|
MCRF_ARG("fov", "FOV algorithm to use (FOV enum or None to use grid.fov)")
|
||||||
MCRF_ARG("radius", "FOV radius as int; omit or pass -1 to use the grid's default fov_radius")
|
MCRF_ARG("radius", "FOV radius as int; pass None, omit, or pass -1 to use the grid's default fov_radius")
|
||||||
MCRF_RETURNS("List of Entity objects within field of view, excluding self")
|
MCRF_RETURNS("List of Entity objects within field of view, excluding self")
|
||||||
MCRF_RAISES("ValueError", "If entity is not associated with a grid")
|
MCRF_RAISES("ValueError", "If entity is not associated with a grid")
|
||||||
MCRF_NOTE("radius does not accept None; omit the argument entirely to use the grid default.")
|
MCRF_RAISES("TypeError", "If radius is neither an int nor None")
|
||||||
)},
|
)},
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
@ -1859,14 +1872,14 @@ PyMethodDef UIEntity_all_methods[] = {
|
||||||
)},
|
)},
|
||||||
{"visible_entities", (PyCFunction)UIEntity::visible_entities, METH_VARARGS | METH_KEYWORDS,
|
{"visible_entities", (PyCFunction)UIEntity::visible_entities, METH_VARARGS | METH_KEYWORDS,
|
||||||
MCRF_METHOD(Entity, visible_entities,
|
MCRF_METHOD(Entity, visible_entities,
|
||||||
MCRF_SIG("(fov=None, radius: int = -1)", "list[Entity]"),
|
MCRF_SIG("(fov=None, radius: int | None = None)", "list[Entity]"),
|
||||||
MCRF_DESC("Get list of other entities visible from this entity's position."),
|
MCRF_DESC("Get list of other entities visible from this entity's position."),
|
||||||
MCRF_ARGS_START
|
MCRF_ARGS_START
|
||||||
MCRF_ARG("fov", "FOV algorithm to use (FOV enum or None to use grid.fov)")
|
MCRF_ARG("fov", "FOV algorithm to use (FOV enum or None to use grid.fov)")
|
||||||
MCRF_ARG("radius", "FOV radius as int; omit or pass -1 to use the grid's default fov_radius")
|
MCRF_ARG("radius", "FOV radius as int; pass None, omit, or pass -1 to use the grid's default fov_radius")
|
||||||
MCRF_RETURNS("List of Entity objects within field of view, excluding self")
|
MCRF_RETURNS("List of Entity objects within field of view, excluding self")
|
||||||
MCRF_RAISES("ValueError", "If entity is not associated with a grid")
|
MCRF_RAISES("ValueError", "If entity is not associated with a grid")
|
||||||
MCRF_NOTE("radius does not accept None; omit the argument entirely to use the grid default.")
|
MCRF_RAISES("TypeError", "If radius is neither an int nor None")
|
||||||
)},
|
)},
|
||||||
// #296 - Label methods
|
// #296 - Label methods
|
||||||
{"add_label", (PyCFunction)UIEntity::py_add_label, METH_O,
|
{"add_label", (PyCFunction)UIEntity::py_add_label, METH_O,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "Profiler.h"
|
#include "Profiler.h"
|
||||||
#include "PyShader.h"
|
#include "PyShader.h"
|
||||||
|
#include "PyTexture.h"
|
||||||
#include "PyUniformCollection.h"
|
#include "PyUniformCollection.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
|
|
@ -771,9 +772,16 @@ int UIGridView::set_fill_color(PyUIGridViewObject* self, PyObject* value, void*
|
||||||
|
|
||||||
PyObject* UIGridView::get_texture(PyUIGridViewObject* self, void* closure)
|
PyObject* UIGridView::get_texture(PyUIGridViewObject* self, void* closure)
|
||||||
{
|
{
|
||||||
if (!self->data->ptex) Py_RETURN_NONE;
|
// #318: return a Texture wrapper sharing the underlying shared_ptr<PyTexture>,
|
||||||
// TODO: return texture wrapper
|
// mirroring UIGrid::get_texture. None only when the view has no texture.
|
||||||
Py_RETURN_NONE;
|
auto& texture = self->data->ptex;
|
||||||
|
if (!texture) Py_RETURN_NONE;
|
||||||
|
|
||||||
|
auto type = &mcrfpydef::PyTextureType;
|
||||||
|
auto obj = (PyTextureObject*)type->tp_alloc(type, 0);
|
||||||
|
if (!obj) return NULL;
|
||||||
|
obj->data = texture;
|
||||||
|
return (PyObject*)obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Float member getters/setters for GridView-specific float members (center_x, center_y, zoom, camera_rotation)
|
// Float member getters/setters for GridView-specific float members (center_x, center_y, zoom, camera_rotation)
|
||||||
|
|
@ -893,8 +901,10 @@ PyGetSetDef UIGridView::getsetters[] = {
|
||||||
MCRF_PROPERTY(zoom, "Zoom level for rendering (float). Values greater than 1.0 magnify; less than 1.0 shrink."), NULL},
|
MCRF_PROPERTY(zoom, "Zoom level for rendering (float). Values greater than 1.0 magnify; less than 1.0 shrink."), NULL},
|
||||||
{"fill_color", (getter)UIGridView::get_fill_color, (setter)UIGridView::set_fill_color,
|
{"fill_color", (getter)UIGridView::get_fill_color, (setter)UIGridView::set_fill_color,
|
||||||
MCRF_PROPERTY(fill_color, "Background fill color (Color). Drawn behind all tiles and entities."), NULL},
|
MCRF_PROPERTY(fill_color, "Background fill color (Color). Drawn behind all tiles and entities."), NULL},
|
||||||
|
// #318/#252: this type is exposed as BOTH mcrfpy.Grid and mcrfpy.GridView, so the
|
||||||
|
// docstring is kept type-neutral (accurate for either name).
|
||||||
{"texture", (getter)UIGridView::get_texture, NULL,
|
{"texture", (getter)UIGridView::get_texture, NULL,
|
||||||
MCRF_PROPERTY(texture, "Texture used for tile rendering (None, read-only). Texture return is not yet implemented; always returns None."), NULL},
|
MCRF_PROPERTY(texture, "Texture used for tile rendering (Texture | None, read-only)."), NULL},
|
||||||
// UIDrawable base properties - applied to GridView (the rendered object)
|
// UIDrawable base properties - applied to GridView (the rendered object)
|
||||||
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos,
|
{"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos,
|
||||||
MCRF_PROPERTY(pos, "Position of the grid as Vector (Vector)."), (void*)PyObjectsEnum::UIGRIDVIEW},
|
MCRF_PROPERTY(pos, "Position of the grid as Vector (Vector)."), (void*)PyObjectsEnum::UIGRIDVIEW},
|
||||||
|
|
|
||||||
|
|
@ -725,7 +725,7 @@ class Entity:
|
||||||
def update_visibility(self) -> None:
|
def update_visibility(self) -> None:
|
||||||
"""Recompute which cells are visible from this entity's position and update perspective_map."""
|
"""Recompute which cells are visible from this entity's position and update perspective_map."""
|
||||||
...
|
...
|
||||||
def visible_entities(self, fov=None, radius: int = -1) -> list[Entity]:
|
def visible_entities(self, fov=None, radius: int | None = None) -> list[Entity]:
|
||||||
"""Get list of other entities visible from this entity's position."""
|
"""Get list of other entities visible from this entity's position."""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
@ -903,7 +903,7 @@ class Grid:
|
||||||
rotation: Any # Rotation angle in degrees (clockwise around origin). Animatable property.
|
rotation: Any # Rotation angle in degrees (clockwise around origin). Animatable property.
|
||||||
shader: Any # Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.
|
shader: Any # Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.
|
||||||
size: Vector # Size of the grid widget as Vector (Vector, width x height in pixels).
|
size: Vector # Size of the grid widget as Vector (Vector, width x height in pixels).
|
||||||
texture: None # Texture used for tile rendering (None, read-only). Texture return is not yet implemented; always returns None.
|
texture: Texture | None # Texture used for tile rendering (Texture | None, read-only).
|
||||||
uniforms: Any # Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/...
|
uniforms: Any # Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/...
|
||||||
vert_margin: float # Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).
|
vert_margin: float # Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).
|
||||||
view: GridView | None # Auto-created GridView for rendering (GridView | None, read-only). When Grid is appended to a scene, this view is what actually renders.
|
view: GridView | None # Auto-created GridView for rendering (GridView | None, read-only). When Grid is appended to a scene, this view is what actually renders.
|
||||||
|
|
@ -1013,7 +1013,7 @@ class GridView:
|
||||||
rotation: Any # Rotation angle in degrees (clockwise around origin). Animatable property.
|
rotation: Any # Rotation angle in degrees (clockwise around origin). Animatable property.
|
||||||
shader: Any # Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.
|
shader: Any # Shader for GPU visual effects (Shader or None). When set, the drawable is rendered through the shader program. Set to None to disable shader effects.
|
||||||
size: Vector # Size of the grid widget as Vector (Vector, width x height in pixels).
|
size: Vector # Size of the grid widget as Vector (Vector, width x height in pixels).
|
||||||
texture: None # Texture used for tile rendering (None, read-only). Texture return is not yet implemented; always returns None.
|
texture: Texture | None # Texture used for tile rendering (Texture | None, read-only).
|
||||||
uniforms: Any # Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/...
|
uniforms: Any # Collection of shader uniforms (read-only access to collection). Set uniforms via dict-like syntax: drawable.uniforms['name'] = value. Supports float, vec2/3/...
|
||||||
vert_margin: float # Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).
|
vert_margin: float # Vertical margin override (float, 0 = use general margin). Invalid for horizontally-centered alignments (CENTER_LEFT, CENTER_RIGHT, CENTER).
|
||||||
view: GridView | None # Auto-created GridView for rendering (GridView | None, read-only). When Grid is appended to a scene, this view is what actually renders.
|
view: GridView | None # Auto-created GridView for rendering (GridView | None, read-only). When Grid is appended to a scene, this view is what actually renders.
|
||||||
|
|
|
||||||
68
tests/regression/issue_317_scroll_position_test.py
Normal file
68
tests/regression/issue_317_scroll_position_test.py
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
"""
|
||||||
|
Regression test for issue #317 -- automation.scroll() ignored the x-coordinate
|
||||||
|
of its position argument.
|
||||||
|
|
||||||
|
Root cause: scroll() resolved (x, y) from pos but called
|
||||||
|
injectMouseEvent(MouseWheelScrolled, clicks, y)
|
||||||
|
passing `clicks` as the x argument, so the resolved x was dropped AND the event's
|
||||||
|
mouseWheelScroll.x was set to the scroll amount. The fix gives the scroll delta
|
||||||
|
its own injectMouseEvent parameter and forwards the real x/y.
|
||||||
|
|
||||||
|
LIMITATION: no Python-observable handler currently consumes a scroll event's
|
||||||
|
position (GameEngine maps only the wheel *delta* to a wheel_up/wheel_down action),
|
||||||
|
so this test cannot assert the forwarded x end-to-end. It is therefore a smoke /
|
||||||
|
API-contract regression guard: scroll(clicks, pos=(x, y)) must accept any x, all
|
||||||
|
documented pos forms, and not raise after the injectMouseEvent refactor (which is
|
||||||
|
the part that changed how the scroll delta is passed). The x-forwarding itself is
|
||||||
|
verified by code inspection (McRFPy_Automation.cpp scroll/injectMouseEvent).
|
||||||
|
|
||||||
|
ASCII-only. Prints PASS/FAIL + exit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
from mcrfpy import automation
|
||||||
|
import sys
|
||||||
|
|
||||||
|
failures = []
|
||||||
|
|
||||||
|
|
||||||
|
def check(label, fn):
|
||||||
|
try:
|
||||||
|
fn()
|
||||||
|
ok = True
|
||||||
|
except Exception as e:
|
||||||
|
ok = False
|
||||||
|
label = "%s (raised %s: %s)" % (label, type(e).__name__, e)
|
||||||
|
if not ok:
|
||||||
|
failures.append(label)
|
||||||
|
print((" ok " if ok else " FAIL ") + label)
|
||||||
|
|
||||||
|
|
||||||
|
# A scene must be current for event injection to have a target.
|
||||||
|
scene = mcrfpy.Scene("issue317")
|
||||||
|
mcrfpy.current_scene = scene
|
||||||
|
|
||||||
|
# Vary x across the position argument; previously x was silently dropped.
|
||||||
|
check("scroll(3) no position", lambda: automation.scroll(3))
|
||||||
|
check("scroll(3, pos=None)", lambda: automation.scroll(3, pos=None))
|
||||||
|
check("scroll(3, (100, 50)) tuple x!=0", lambda: automation.scroll(3, (100, 50)))
|
||||||
|
check("scroll(-2, (0, 50)) x==0", lambda: automation.scroll(-2, (0, 50)))
|
||||||
|
check("scroll(1, [250, 75]) list", lambda: automation.scroll(1, [250, 75]))
|
||||||
|
check("scroll(5, pos=(640, 480)) keyword", lambda: automation.scroll(5, pos=(640, 480)))
|
||||||
|
check("scroll(-5, mcrfpy.Vector(12, 34)) Vector",
|
||||||
|
lambda: automation.scroll(-5, mcrfpy.Vector(12, 34)))
|
||||||
|
|
||||||
|
# Same y, different x must both be accepted (the regression dropped x entirely).
|
||||||
|
check("scroll(2, (10, 200))", lambda: automation.scroll(2, (10, 200)))
|
||||||
|
check("scroll(2, (900, 200))", lambda: automation.scroll(2, (900, 200)))
|
||||||
|
|
||||||
|
|
||||||
|
print("")
|
||||||
|
if failures:
|
||||||
|
print("FAIL -- %d check(s) failed:" % len(failures))
|
||||||
|
for f in failures:
|
||||||
|
print(" - " + f)
|
||||||
|
sys.exit(1)
|
||||||
|
print("PASS -- automation.scroll accepts a full (x, y) position across all forms "
|
||||||
|
"(x-forwarding verified by code inspection; no observable consumer yet).")
|
||||||
|
sys.exit(0)
|
||||||
70
tests/regression/issue_318_gridview_texture_test.py
Normal file
70
tests/regression/issue_318_gridview_texture_test.py
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
"""
|
||||||
|
Regression test for issue #318 -- GridView.texture always returned None.
|
||||||
|
|
||||||
|
Root cause: the getter had a `TODO: return texture wrapper` and fell through to
|
||||||
|
Py_RETURN_NONE even when self->data->ptex was non-null. The fix returns a
|
||||||
|
Texture wrapper sharing the underlying shared_ptr<PyTexture>, mirroring
|
||||||
|
UIGrid.texture.
|
||||||
|
|
||||||
|
Needs an asset; run via run_tests.py (cwd=build/) or from build/ so that
|
||||||
|
assets/kenney_tinydungeon.png resolves. ASCII-only. Prints PASS/FAIL + exit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
import sys
|
||||||
|
|
||||||
|
failures = []
|
||||||
|
|
||||||
|
|
||||||
|
def check(label, cond):
|
||||||
|
if not cond:
|
||||||
|
failures.append(label)
|
||||||
|
print((" ok " if cond else " FAIL ") + label)
|
||||||
|
|
||||||
|
|
||||||
|
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||||
|
|
||||||
|
# --- GridView over a textured grid: texture must now be returned -----------
|
||||||
|
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture)
|
||||||
|
view = mcrfpy.GridView(grid=grid, pos=(0, 0), size=(160, 160))
|
||||||
|
|
||||||
|
t = view.texture
|
||||||
|
check("GridView.texture is not None when the view has a texture", t is not None)
|
||||||
|
check("GridView.texture is a Texture", isinstance(t, mcrfpy.Texture))
|
||||||
|
if isinstance(t, mcrfpy.Texture):
|
||||||
|
# Wrapper should describe the same underlying sheet as the source.
|
||||||
|
check("sprite_width matches source (16)", t.sprite_width == 16)
|
||||||
|
check("sprite_height matches source (16)", t.sprite_height == 16)
|
||||||
|
check("matches grid.texture sprite dims",
|
||||||
|
grid.texture is not None
|
||||||
|
and t.sprite_width == grid.texture.sprite_width
|
||||||
|
and t.sprite_height == grid.texture.sprite_height)
|
||||||
|
|
||||||
|
# --- A grid created without an explicit texture still gets the engine default
|
||||||
|
# texture, so its view's texture is also non-None (the getter's None branch is
|
||||||
|
# defensive-only -- no public API produces a textureless grid). -------------
|
||||||
|
bare_grid = mcrfpy.Grid(grid_size=(8, 8))
|
||||||
|
bare_view = mcrfpy.GridView(grid=bare_grid, pos=(0, 0), size=(80, 80))
|
||||||
|
check("default-textured grid's view exposes a Texture (not None)",
|
||||||
|
isinstance(bare_view.texture, mcrfpy.Texture))
|
||||||
|
check("view texture matches its grid's texture source",
|
||||||
|
bare_view.texture.source == bare_grid.texture.source)
|
||||||
|
|
||||||
|
# --- property is read-only (NULL setter) -----------------------------------
|
||||||
|
try:
|
||||||
|
view.texture = texture
|
||||||
|
check("GridView.texture is read-only (assignment raises)", False)
|
||||||
|
except AttributeError:
|
||||||
|
check("GridView.texture is read-only (assignment raises)", True)
|
||||||
|
except Exception as e:
|
||||||
|
check("GridView.texture is read-only (raised %s)" % type(e).__name__, False)
|
||||||
|
|
||||||
|
|
||||||
|
print("")
|
||||||
|
if failures:
|
||||||
|
print("FAIL -- %d check(s) failed:" % len(failures))
|
||||||
|
for f in failures:
|
||||||
|
print(" - " + f)
|
||||||
|
sys.exit(1)
|
||||||
|
print("PASS -- GridView.texture returns the tile Texture (or None when absent).")
|
||||||
|
sys.exit(0)
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
"""
|
||||||
|
Regression test for issue #319 -- Entity.visible_entities(radius=None) raised
|
||||||
|
TypeError instead of applying the grid default.
|
||||||
|
|
||||||
|
Root cause: radius was parsed with the PyArg 'i' format code ("|Oi"), which
|
||||||
|
rejects Python None. The fix parses radius as an object ("|OO") and treats
|
||||||
|
None / omitted / -1 as "use the grid's default fov_radius".
|
||||||
|
|
||||||
|
ASCII-only source. Prints PASS/FAIL and sys.exit(0/1).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
import sys
|
||||||
|
|
||||||
|
failures = []
|
||||||
|
|
||||||
|
|
||||||
|
def check(label, cond):
|
||||||
|
if not cond:
|
||||||
|
failures.append(label)
|
||||||
|
print((" ok " if cond else " FAIL ") + label)
|
||||||
|
|
||||||
|
|
||||||
|
# --- grid + entities -------------------------------------------------------
|
||||||
|
grid = mcrfpy.Grid(grid_size=(20, 20))
|
||||||
|
grid.fov_radius = 7
|
||||||
|
for gx in range(20):
|
||||||
|
for gy in range(20):
|
||||||
|
gp = grid.at(gx, gy)
|
||||||
|
gp.walkable = True
|
||||||
|
gp.transparent = True
|
||||||
|
|
||||||
|
seeker = mcrfpy.Entity(grid_pos=(10, 10))
|
||||||
|
grid.entities.append(seeker)
|
||||||
|
neighbor = mcrfpy.Entity(grid_pos=(11, 10))
|
||||||
|
grid.entities.append(neighbor)
|
||||||
|
|
||||||
|
# --- the bug: radius=None must NOT raise (it used to TypeError) -------------
|
||||||
|
try:
|
||||||
|
res_none = seeker.visible_entities(radius=None)
|
||||||
|
check("visible_entities(radius=None) does not raise", True)
|
||||||
|
check("returns a list", isinstance(res_none, list))
|
||||||
|
except TypeError as e:
|
||||||
|
check("visible_entities(radius=None) does not raise (got TypeError: %s)" % e, False)
|
||||||
|
res_none = []
|
||||||
|
|
||||||
|
# Adjacent transparent neighbor should be visible.
|
||||||
|
def positions(lst):
|
||||||
|
return {(int(e.cell_pos.x), int(e.cell_pos.y)) for e in lst}
|
||||||
|
|
||||||
|
check("neighbor (11,10) visible with radius=None", (11, 10) in positions(res_none))
|
||||||
|
check("self excluded from results", (10, 10) not in positions(res_none))
|
||||||
|
|
||||||
|
# --- equivalence: None, omitted, and -1 all mean 'use grid default' --------
|
||||||
|
res_omitted = seeker.visible_entities()
|
||||||
|
res_default = seeker.visible_entities(radius=-1)
|
||||||
|
check("radius=None matches omitted", positions(res_none) == positions(res_omitted))
|
||||||
|
check("radius=None matches radius=-1", positions(res_none) == positions(res_default))
|
||||||
|
|
||||||
|
# --- explicit int radius still works ---------------------------------------
|
||||||
|
try:
|
||||||
|
res_int = seeker.visible_entities(radius=3)
|
||||||
|
check("radius=3 (int) works", isinstance(res_int, list) and (11, 10) in positions(res_int))
|
||||||
|
except Exception as e:
|
||||||
|
check("radius=3 (int) works (raised %s)" % e, False)
|
||||||
|
|
||||||
|
# A tiny radius can still see the immediately-adjacent neighbor.
|
||||||
|
res_one = seeker.visible_entities(radius=1)
|
||||||
|
check("radius=1 sees adjacent neighbor", (11, 10) in positions(res_one))
|
||||||
|
|
||||||
|
# --- fov + radius=None combined (None must be fine alongside fov) -----------
|
||||||
|
try:
|
||||||
|
res_combo = seeker.visible_entities(fov=mcrfpy.FOV.SHADOW, radius=None)
|
||||||
|
check("visible_entities(fov=SHADOW, radius=None) works", isinstance(res_combo, list))
|
||||||
|
except Exception as e:
|
||||||
|
check("visible_entities(fov=SHADOW, radius=None) works (raised %s)" % e, False)
|
||||||
|
|
||||||
|
# --- invalid radius type must raise a clear TypeError ----------------------
|
||||||
|
try:
|
||||||
|
seeker.visible_entities(radius="not an int")
|
||||||
|
check("radius='str' raises TypeError", False)
|
||||||
|
except TypeError:
|
||||||
|
check("radius='str' raises TypeError", True)
|
||||||
|
except Exception as e:
|
||||||
|
check("radius='str' raises TypeError (got %s)" % type(e).__name__, False)
|
||||||
|
|
||||||
|
|
||||||
|
print("")
|
||||||
|
if failures:
|
||||||
|
print("FAIL -- %d check(s) failed:" % len(failures))
|
||||||
|
for f in failures:
|
||||||
|
print(" - " + f)
|
||||||
|
sys.exit(1)
|
||||||
|
print("PASS -- visible_entities accepts radius=None (and -1/omitted) as the grid default.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
@ -487,7 +487,7 @@ submodule automation
|
||||||
meth resize :: resize(width, height) or (size) -> None
|
meth resize :: resize(width, height) or (size) -> None
|
||||||
meth set_behavior :: set_behavior(type, waypoints=None, turns: int = 0, path=None, pathfinder=None) -> None
|
meth set_behavior :: set_behavior(type, waypoints=None, turns: int = 0, path=None, pathfinder=None) -> None
|
||||||
meth update_visibility :: update_visibility() -> None
|
meth update_visibility :: update_visibility() -> None
|
||||||
meth visible_entities :: visible_entities(fov=None, radius: int = -1) -> list[Entity]
|
meth visible_entities :: visible_entities(fov=None, radius: int | None = None) -> list[Entity]
|
||||||
[Font]
|
[Font]
|
||||||
prop family: str (ro)
|
prop family: str (ro)
|
||||||
prop source: str (ro)
|
prop source: str (ro)
|
||||||
|
|
@ -558,7 +558,7 @@ submodule automation
|
||||||
prop rotate_with_camera: bool (rw)
|
prop rotate_with_camera: bool (rw)
|
||||||
prop rotation: Any (rw)
|
prop rotation: Any (rw)
|
||||||
prop shader: Any (rw)
|
prop shader: Any (rw)
|
||||||
prop texture: None (ro)
|
prop texture: Texture | None (ro)
|
||||||
prop uniforms: Any (ro)
|
prop uniforms: Any (ro)
|
||||||
prop vert_margin: float (rw)
|
prop vert_margin: float (rw)
|
||||||
prop visible: bool (rw)
|
prop visible: bool (rw)
|
||||||
|
|
@ -598,7 +598,7 @@ submodule automation
|
||||||
prop rotate_with_camera: bool (rw)
|
prop rotate_with_camera: bool (rw)
|
||||||
prop rotation: Any (rw)
|
prop rotation: Any (rw)
|
||||||
prop shader: Any (rw)
|
prop shader: Any (rw)
|
||||||
prop texture: None (ro)
|
prop texture: Texture | None (ro)
|
||||||
prop uniforms: Any (ro)
|
prop uniforms: Any (ro)
|
||||||
prop vert_margin: float (rw)
|
prop vert_margin: float (rw)
|
||||||
prop visible: bool (rw)
|
prop visible: bool (rw)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue