62a65f599d
Remove the is_hidden_in_tunnel() checks from Explosion.draw() and Gas.draw() so explosions and gas clouds remain visible when their center falls inside a tunnel cell. Logic already spawned them in tunnel cells, but they were rendered invisible because the draw path returned early.
228 lines
8.2 KiB
Python
228 lines
8.2 KiB
Python
from .unit import Unit, UnitType
|
|
from . import rat
|
|
from .points import Point
|
|
from engine.collision_system import CollisionLayer, shrink_bbox
|
|
import uuid
|
|
import random
|
|
|
|
# Costanti
|
|
AGE_THRESHOLD = 200
|
|
NUCLEAR_TIMER = 50 # 1 second at ~50 FPS
|
|
EXPLOSION_BBOX_SHRINK = 0.25 # central 50% of the tile is lethal instantly
|
|
|
|
|
|
class Bomb(Unit):
|
|
draw_on_top = True
|
|
|
|
def __init__(self, game, position=(0,0), id=None, unit_type=UnitType.BOMB_TIMER):
|
|
super().__init__(game, position, id, collision_layer=CollisionLayer.BOMB, unit_type=unit_type)
|
|
# Specific attributes for bombs
|
|
self.speed = 4 # Bombs age faster
|
|
self.fight = False
|
|
|
|
def move(self):
|
|
pass
|
|
|
|
def collisions(self):
|
|
pass
|
|
|
|
def die(self, unit=None):
|
|
if not unit:
|
|
unit = self
|
|
self.game.units.pop(unit.id)
|
|
|
|
|
|
def draw(self):
|
|
n = self.age // 40
|
|
n= 3 -n +1
|
|
if n == 0:
|
|
n = 1
|
|
if n < 0:
|
|
n = 0
|
|
image = self.game.graphics.bomb_assets[n]
|
|
image_size = self.game.render_engine.get_image_size(image)
|
|
self.rat_image = image
|
|
partial_x, partial_y = 0, 0
|
|
|
|
x_pos = self.position_before[0] * self.game.cell_size + (self.game.cell_size - image_size[0]) // 2 + partial_x
|
|
y_pos = self.position_before[1] * self.game.cell_size + (self.game.cell_size - image_size[1]) // 2 + partial_y
|
|
|
|
if self.is_hidden_in_tunnel(image_size):
|
|
return
|
|
|
|
self.game.render_engine.draw_image(x_pos, y_pos, image, anchor="nw", tag="unit")
|
|
|
|
class Timer(Bomb):
|
|
def __init__(self, game, position=(0,0), id=None):
|
|
super().__init__(game, position, id, unit_type=UnitType.BOMB_TIMER)
|
|
|
|
def move(self):
|
|
self.age += self.speed
|
|
if self.age >= AGE_THRESHOLD and not getattr(self, "exploded", False):
|
|
self.exploding = True
|
|
|
|
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")
|
|
self.game.render_engine.play_sound("BOMB.WAV")
|
|
self.exploded = True
|
|
|
|
# Remove bomb
|
|
if self.id in self.game.units:
|
|
self.game.units.pop(self.id)
|
|
|
|
# Collect all explosion positions
|
|
explosion_positions = []
|
|
|
|
# Check for chain reactions in all four directions
|
|
for direction in ["N", "S", "E", "W"]:
|
|
x, y = self.position
|
|
while True:
|
|
if not self.game.map.is_wall(x, y):
|
|
explosion_positions.append((x, y))
|
|
else:
|
|
break
|
|
if direction == "N":
|
|
y -= 1
|
|
elif direction == "S":
|
|
y += 1
|
|
elif direction == "E":
|
|
x += 1
|
|
elif direction == "W":
|
|
x -= 1
|
|
|
|
# Create visual explosions
|
|
for pos in explosion_positions:
|
|
self.game.unit_manager.spawn_unit(Explosion, pos)
|
|
|
|
# Kill all rats in explosion area (Pass 2: all units registered)
|
|
victim_ids = self.game.collision_system.get_units_in_area(
|
|
explosion_positions,
|
|
layer_filter=CollisionLayer.RAT
|
|
)
|
|
|
|
cs = self.game.cell_size
|
|
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:
|
|
# Require the rat to be well inside an explosion tile
|
|
for pos in explosion_positions:
|
|
if victim.position == pos or victim.position_before == pos:
|
|
exp_bbox = shrink_bbox(
|
|
(pos[0] * cs, pos[1] * cs, (pos[0] + 1) * cs, (pos[1] + 1) * cs),
|
|
EXPLOSION_BBOX_SHRINK,
|
|
)
|
|
vb = victim.bbox
|
|
if (vb[0] < exp_bbox[2] and vb[2] > exp_bbox[0] and
|
|
vb[1] < exp_bbox[3] and vb[3] > exp_bbox[1]):
|
|
victim.die(score=score)
|
|
if score < 160:
|
|
score *= 2
|
|
break
|
|
|
|
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):
|
|
# Initialize with proper EXPLOSION layer and type
|
|
Unit.__init__(self, game, position, id, collision_layer=CollisionLayer.EXPLOSION, unit_type=UnitType.EXPLOSION)
|
|
self.speed = 20 # Bombs age faster * 5
|
|
self.fight = False
|
|
|
|
def move(self):
|
|
self.age += self.speed
|
|
if self.age >= AGE_THRESHOLD:
|
|
self.die()
|
|
# Set bbox so lingering explosions can kill rats via collision system.
|
|
# Only the central area is lethal.
|
|
cs = self.game.cell_size
|
|
cell_bbox = (self.position[0] * cs, self.position[1] * cs,
|
|
(self.position[0] + 1) * cs, (self.position[1] + 1) * cs)
|
|
self.bbox = shrink_bbox(cell_bbox, EXPLOSION_BBOX_SHRINK)
|
|
|
|
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"]
|
|
image_size = self.game.render_engine.get_image_size(image)
|
|
partial_x, partial_y = 0, 0
|
|
|
|
x_pos = self.position_before[0] * self.game.cell_size + (self.game.cell_size - image_size[0]) // 2 + partial_x
|
|
y_pos = self.position_before[1] * self.game.cell_size + (self.game.cell_size - image_size[1]) // 2 + partial_y
|
|
|
|
self.game.render_engine.draw_image(x_pos, y_pos, image, anchor="nw", tag="unit")
|
|
|
|
|
|
class NuclearBomb(Unit):
|
|
def __init__(self, game, position=(0,0), id=None):
|
|
super().__init__(game, position, id, collision_layer=CollisionLayer.BOMB, unit_type=UnitType.BOMB_NUCLEAR)
|
|
self.speed = 1 # Slow countdown
|
|
self.fight = False
|
|
self.timer = NUCLEAR_TIMER # 1 second timer
|
|
|
|
def move(self):
|
|
"""Count down the nuclear timer"""
|
|
self.timer -= 1
|
|
if self.timer <= 0:
|
|
self.explode()
|
|
|
|
def collisions(self):
|
|
pass
|
|
|
|
|
|
def explode(self):
|
|
"""Nuclear explosion that affects all rats on the map"""
|
|
print("NUCLEAR EXPLOSION!")
|
|
|
|
# Play nuclear explosion sound
|
|
self.game.render_engine.play_sound("nuke.wav")
|
|
|
|
# Trigger white screen effect
|
|
self.game.render_engine.trigger_white_flash()
|
|
|
|
# Remove the nuclear bomb from the game
|
|
if self.id in self.game.units:
|
|
self.game.units.pop(self.id)
|
|
|
|
# Kill 70% of all rats on the map
|
|
rats_to_kill = []
|
|
for unit in self.game.units.values():
|
|
if unit.type in [UnitType.RAT_MALE, UnitType.RAT_FEMALE]:
|
|
if random.random() < 0.7: # 70% chance to kill each rat
|
|
rats_to_kill.append(unit)
|
|
for unit in rats_to_kill:
|
|
unit.die(score=5)
|
|
|
|
print(f"Nuclear explosion killed {len(rats_to_kill)} rats!")
|
|
|
|
def draw(self):
|
|
"""Draw nuclear bomb on position"""
|
|
image = self.game.graphics.assets["BMP_NUCLEAR"]
|
|
image_size = self.game.render_engine.get_image_size(image)
|
|
|
|
x_pos = self.position[0] * self.game.cell_size + (self.game.cell_size - image_size[0]) // 2
|
|
y_pos = self.position[1] * self.game.cell_size + (self.game.cell_size - image_size[1]) // 2
|
|
|
|
if self.is_hidden_in_tunnel(image_size, position=self.position):
|
|
return
|
|
|
|
self.game.render_engine.draw_image(x_pos, y_pos, image, anchor="nw", tag="unit") |