Skip to content
This repository was archived by the owner on May 17, 2023. It is now read-only.

Commit cd8cbf1

Browse files
authored
Make events actually work. Spawners remove dead things. (#177)
* Have spawners clean up when sprites are removed. * Make it actually work.
1 parent a66179f commit cd8cbf1

File tree

7 files changed

+84
-29
lines changed

7 files changed

+84
-29
lines changed

engine/builtin.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
List,
1111
)
1212

13-
from engine import scripts
14-
from engine.model import game_sprite
13+
from engine import (
14+
events,
15+
scripts,
16+
)
1517

1618
logger = logging.Logger("engine.builtin")
1719

@@ -54,7 +56,7 @@ class Spawner(scripts.SavesAPI, scripts.Script):
5456
spawn_script: Callable[[None], scripts.Script]
5557
spawn_script_kwargs: Dict[str, Any]
5658
num_spawns: int
57-
spawns: List[game_sprite.GameSprite]
59+
spawns: List[scripts.Entity]
5860
last_spawn: float
5961
spawn_rate_per_sec: float
6062
spawn_cooldown_secs: float
@@ -103,6 +105,11 @@ def on_start(self, owner: scripts.ScriptOwner):
103105
"""Triggered the first time this spawn is created."""
104106
self._state["location"] = owner.location
105107

108+
def set_api(self, api: scripts.GameAPI) -> None:
109+
"""Sets the API for this spawner."""
110+
scripts.SavesAPI.set_api(self, api)
111+
api.register_handler(events.SPRITE_REMOVED, self._cleanup_removed_sprite)
112+
106113
def _can_spawn(self, now: float) -> bool:
107114
return (
108115
len(self.spawns) < self.num_spawns
@@ -130,3 +137,10 @@ def _spawn(self):
130137
script=self.spawn_script(**self.spawn_script_kwargs),
131138
)
132139
self.spawns.append(sprite)
140+
141+
def _cleanup_removed_sprite(
142+
self,
143+
_event_name: str,
144+
event: events.SpriteRemoved,
145+
) -> None:
146+
self.spawns = [spawn for spawn in self.spawns if spawn.name != event.name]

engine/core.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,13 @@ def create_sprite(
139139
name: str,
140140
start_location: Tuple[int, int],
141141
script: Optional[scripts.Script],
142-
) -> None:
142+
) -> scripts.Entity:
143143
"""Creates a sprite."""
144144
if self.world is None:
145145
raise GameNotInitializedError()
146146

147147
_spec = self._spec.sprites[spec_name]
148-
self.world.create_sprite(_spec, name, start_location, script)
148+
return self.world.create_sprite(_spec, name, start_location, script)
149149

150150
def get_key_points(self, name: Optional[str] = None) -> Iterable[scripts.KeyPoint]:
151151
"""Queries for key points in the current region."""
@@ -169,6 +169,26 @@ def play_sound(self, name: str) -> None:
169169
"""Plays a sound."""
170170
self._sounds[name].play()
171171

172+
def register_handler(self, event_name: str, handler: scripts.EventHandler) -> None:
173+
"""Registers an event handler for a custom event."""
174+
self._events.register_handler(event_name, handler)
175+
176+
def unregister_handler(
177+
self,
178+
event_name: str,
179+
handler: scripts.EventHandler,
180+
) -> None:
181+
"""Unregisters an event handler."""
182+
self._events.unregister_handler(event_name, handler)
183+
184+
def fire_event(self, event_name: str, data: Any) -> None:
185+
"""Fires an event."""
186+
self._events.fire_event(event_name, data)
187+
188+
def clear_events(self) -> None:
189+
"""Clears all events."""
190+
self._events.clear_events()
191+
172192
@property
173193
def player_data(self) -> Dict[str, Any]:
174194
"""Gets the player's data."""

engine/event_manager.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ class EventManager:
1414
_handlers: Dict[str, List[scripts.EventHandler]]
1515

1616
def __init__(self):
17+
self.clear_events()
18+
19+
def clear_events(self) -> None:
20+
"""Unregisters all events in the manager."""
1721
self._handlers = collections.defaultdict(list)
1822

1923
def register_handler(self, event_name: str, handler: scripts.EventHandler) -> None:

engine/model/game_sprite.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class GameSprite(arcade.Sprite):
107107
This includes support for animations, facing directions, etc.
108108
"""
109109

110-
name: str
110+
_name: str
111111

112112
_spec: Optional[spec.GameSpriteSpec]
113113
animations: Optional[Animations] = None
@@ -137,7 +137,7 @@ def __init__(
137137
image_width=width,
138138
image_height=height,
139139
)
140-
self.name = name
140+
self._name = name
141141
self.set_facing(x=1.0, y=1.0)
142142
self._spec = sprite_spec
143143
self.script = script
@@ -190,6 +190,11 @@ def _get_direction(self) -> str:
190190

191191
return "right" if self.facing_x > 0 else "left"
192192

193+
@property
194+
def name(self) -> str:
195+
"""Gets the name of this sprite."""
196+
return self._name
197+
193198
@property
194199
def location(self) -> Tuple[float, float]:
195200
"""Gets the location of this sprite.

engine/model/world.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ class WorldState:
7272
region_states: Dict[str, RegionState]
7373

7474

75+
class _Core(scripts.GameAPI):
76+
"""Extended API for interacting with the core."""
77+
78+
def clear_events(self) -> None:
79+
"""Clears all events in the event handler."""
80+
81+
7582
class World:
7683
"""
7784
This class represents the world. It manages maintenance of the state of the world.
@@ -83,7 +90,7 @@ class World:
8390
# - Make as much as possible private; things outside this class are starting to poke
8491
# into it which adds extra coupling.
8592

86-
api: scripts.GameAPI
93+
_core: _Core
8794

8895
_player_sprite: player_sprite.PlayerSprite
8996

@@ -119,18 +126,16 @@ class World:
119126

120127
def __init__(
121128
self,
122-
api: scripts.GameAPI,
129+
core: _Core,
123130
game_spec: spec.GameSpec,
124131
initial_player_data: Dict[str, Any],
125132
):
126-
self.api = api
133+
self._core = core
127134
self._spec = game_spec
128135
self.sec_passed = 0.0
129136

130-
api.register_handler(events.SPRITE_REMOVED, self._queue_sprite_removal)
131-
132137
self._player_sprite = player_sprite.PlayerSprite(
133-
api=api,
138+
api=core,
134139
sprite_spec=game_spec.player_spec,
135140
initial_data=initial_player_data,
136141
)
@@ -177,6 +182,9 @@ def load_region(self, region_name: str, start_location: str) -> None:
177182
is_first_load = region_name not in self.regions_loaded
178183
self.regions_loaded.add(region_name)
179184

185+
self._core.clear_events()
186+
self._core.register_handler(events.SPRITE_REMOVED, self._queue_sprite_removal)
187+
180188
self._build_scene(tilemap)
181189
self._load_scripted_objects(tilemap, region_state, is_first_load)
182190

@@ -299,7 +307,7 @@ def create_sprite(
299307
name: str,
300308
start_location: Tuple[float, float],
301309
script: Optional[scripts.Script],
302-
) -> arcade.Sprite:
310+
) -> game_sprite.GameSprite:
303311
"""Adds a sprite to the model."""
304312
return self._create_sprite(
305313
sprite_spec,
@@ -344,7 +352,7 @@ def _create_sprite(
344352
if is_first_load:
345353
script.on_start(sprite)
346354

347-
script.set_api(self.api)
355+
script.set_api(self._core)
348356
script.set_owner(sprite)
349357

350358
return sprite
@@ -380,7 +388,7 @@ def _script_from_tiled_object(
380388
return self._load_script(properties)
381389
except NoScript:
382390
return scripts.ObjectScript(
383-
self.api,
391+
self._core,
384392
on_activate=properties.get("on_activate"),
385393
on_activate_args=scripts.extract_script_args(
386394
"on_activate_",
@@ -406,7 +414,7 @@ def _load_script(self, properties: Dict[str, Any]) -> scripts.Script:
406414
cls = scripts.load_script_class(properties["script"])
407415
args = scripts.extract_script_args("script_", properties)
408416
obj = cls(**args)
409-
obj.set_api(self.api)
417+
obj.set_api(self._core)
410418
return obj
411419

412420
def on_update(self, delta_time: float) -> None:
@@ -505,7 +513,7 @@ def _objs_in_front_of_player(self) -> Iterable[game_sprite.GameSprite]:
505513
def activate(self) -> None:
506514
"""Activates whatever is in front of the player."""
507515
if self._spec.world.activate_sound:
508-
self.api.play_sound(self._spec.world.activate_sound)
516+
self._core.play_sound(self._spec.world.activate_sound)
509517

510518
self._player_sprite.on_activate()
511519
for obj in self._objs_in_front_of_player():
@@ -517,7 +525,7 @@ def hit(self) -> None:
517525
"""Hit whatever is in front of the player."""
518526

519527
if self._spec.world.hit_sound:
520-
self.api.play_sound(self._spec.world.hit_sound)
528+
self._core.play_sound(self._spec.world.hit_sound)
521529

522530
for obj in self._objs_in_front_of_player():
523531
if obj.script is None:

engine/scripts.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ class KeyPoint:
3737
properties: Dict[str, Any]
3838

3939

40+
class Entity(Protocol):
41+
"""Defines something in the game: a player, a monster, etc."""
42+
43+
@property
44+
def name(self) -> str:
45+
"""Gets the name of this entity."""
46+
47+
48+
class Player(Protocol):
49+
"""Represents the player to scripts."""
50+
51+
4052
# A type that receives events when they are triggered.
4153
# Note that while this uses `Any`, all custom events defined by the engine use proper
4254
# types and any events defined by games are encouraged to do so as well. An event
@@ -62,7 +74,7 @@ def create_sprite(
6274
name: str,
6375
start_location: Tuple[float, float],
6476
script: "Optional[Script]",
65-
) -> None:
77+
) -> Entity:
6678
"""Creates a sprite."""
6779

6880
def get_key_points(self, name: Optional[str] = None) -> Iterable[KeyPoint]:
@@ -149,14 +161,6 @@ def custom_animation(self, value: Optional[str]) -> None:
149161
"""Sets the custom animation of the script owner."""
150162

151163

152-
class Entity(Protocol):
153-
"""Defines something in the game: a player, a monster, etc."""
154-
155-
156-
class Player(Protocol):
157-
"""Represents the player to scripts."""
158-
159-
160164
class Script:
161165
"""Base class for all scripts."""
162166

pylint.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ disable = [
372372
"suppressed-message", "useless-suppression", "deprecated-pragma",
373373
"use-symbolic-message-instead", "missing-module-docstring", "unspecified-encoding",
374374
"too-many-instance-attributes", "too-many-branches", "too-few-public-methods",
375-
"too-many-locals", "too-many-arguments"
375+
"too-many-locals", "too-many-arguments", "too-many-public-methods",
376376
]
377377

378378
# Enable the message, report, category or checker with the given id(s). You can

0 commit comments

Comments
 (0)