From 3e8cd97fda2ec84733027ea23adb1522cb630162 Mon Sep 17 00:00:00 2001 From: Matteo Benedetto Date: Tue, 16 Jun 2026 19:15:35 +0200 Subject: [PATCH] Fix bomb explosions not killing rats and remove tracked pycache - Move Timer explosion from move() to collisions() so all units are registered in the collision system before the kill query runs. - Explosion units now set a bbox and kill rats that touch them. - Guard Rat.draw() so dead rats are not drawn. - Remove units/__pycache__ files from tracking. --- units/__pycache__/rat.cpython-313.pyc | Bin 12401 -> 0 bytes units/__pycache__/unit.cpython-313.pyc | Bin 3088 -> 0 bytes units/bomb.py | 63 +++++++++++++++---------- units/rat.py | 2 + 4 files changed, 41 insertions(+), 24 deletions(-) delete mode 100644 units/__pycache__/rat.cpython-313.pyc delete mode 100644 units/__pycache__/unit.cpython-313.pyc diff --git a/units/__pycache__/rat.cpython-313.pyc b/units/__pycache__/rat.cpython-313.pyc deleted file mode 100644 index 94bcdead184e79e577c9c5cb46fbb812327cfd11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12401 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 UA%hS5PDqk*^}t^QpJJ1L0hf{uPXGV_ diff --git a/units/bomb.py b/units/bomb.py index 66f8e15..3fae2d4 100644 --- a/units/bomb.py +++ b/units/bomb.py @@ -55,32 +55,31 @@ class Timer(Bomb): def move(self): self.age += self.speed - if self.age == AGE_THRESHOLD: - self.die() + if self.age >= AGE_THRESHOLD and not getattr(self, "exploded", False): + self.exploding = True - def die(self, unit=None, score=None): - """Handle bomb explosion and chain reactions using vectorized collision system.""" + def collisions(self): + """Explode in Pass 2 when every unit is registered in the collision system.""" + if getattr(self, "exploding", False) and not getattr(self, "exploded", False): + self.explode() + + def explode(self): + """Handle bomb explosion and chain reactions.""" score = 10 print("BOOM") - target_unit = unit if unit else self self.game.render_engine.play_sound("BOMB.WAV") + self.exploded = True - # Use base class cleanup with error handling - try: - if target_unit.id in self.game.units: - self.game.units.pop(target_unit.id) - except: - print(f"Unit {target_unit.id} already dead") + # Remove bomb + if self.id in self.game.units: + self.game.units.pop(self.id) - # Bomb-specific behavior: create explosion - self.game.unit_manager.spawn_unit(Explosion, target_unit.position) - - # Collect all explosion positions using vectorized approach + # Collect all explosion positions explosion_positions = [] # Check for chain reactions in all four directions for direction in ["N", "S", "E", "W"]: - x, y = target_unit.position + x, y = self.position while True: if not self.game.map.is_wall(x, y): explosion_positions.append((x, y)) @@ -95,28 +94,30 @@ class Timer(Bomb): elif direction == "W": x -= 1 - # Create all explosions at once + # Create visual explosions for pos in explosion_positions: self.game.unit_manager.spawn_unit(Explosion, pos) - # Use optimized collision system to get all rats in explosion area - # This replaces the nested loop with a single vectorized operation + # Kill all rats in explosion area (Pass 2: all units registered) victim_ids = self.game.collision_system.get_units_in_area( - explosion_positions, + explosion_positions, layer_filter=CollisionLayer.RAT ) - # Kill all victims with score multiplier for victim_id in victim_ids: victim = self.game.unit_manager.get_unit_by_id(victim_id) if victim and victim.id in self.game.units: - # Determine position based on partial_move - victim_pos = victim.position if victim.partial_move >= 0.5 else victim.position_before - if victim_pos in explosion_positions: + if victim.position in explosion_positions or victim.position_before in explosion_positions: victim.die(score=score) if score < 160: score *= 2 + def draw(self): + """Don't draw a bomb that has already exploded.""" + if getattr(self, "exploded", False): + return + super().draw() + class Explosion(Bomb): def __init__(self, game, position=(0,0), id=None): @@ -129,6 +130,20 @@ class Explosion(Bomb): self.age += self.speed if self.age >= AGE_THRESHOLD: self.die() + # Set bbox so lingering explosions can kill rats via collision system + x = self.position[0] * self.game.cell_size + y = self.position[1] * self.game.cell_size + self.bbox = (float(x), float(y), float(x + self.game.cell_size), float(y + self.game.cell_size)) + + def collisions(self): + """Lingering explosion kills any rat that touches it.""" + victim_ids = self.game.collision_system.get_collisions_for_unit( + self.id, CollisionLayer.EXPLOSION, tolerance=0 + ) + for _, victim_id in victim_ids: + victim = self.game.unit_manager.get_unit_by_id(victim_id) + if victim and victim.type in [UnitType.RAT_MALE, UnitType.RAT_FEMALE] and victim.id in self.game.units: + victim.die(score=10) def draw(self): image = self.game.graphics.assets["BMP_EXPLOSION"] diff --git a/units/rat.py b/units/rat.py index b47578b..1b7d947 100644 --- a/units/rat.py +++ b/units/rat.py @@ -171,6 +171,8 @@ class Rat(Unit): def draw(self): """Optimized draw using pre-calculated positions from move()""" + if self.id not in self.game.units: + return sex = self.sex if self.age > AGE_THRESHOLD else "BABY" image = self.game.graphics.rat_assets_textures[sex][self.direction] image_size = self.game.graphics.rat_image_sizes[sex][self.direction]