Files
mice/units/unit.py
T
enne2 2f8e3e8b28 Fix gas poisoning, explosion hit detection and render order
- Move gas poisoning from Gas.move() to Gas.collisions() so all rats are
  registered in the collision system before the poison query runs.
- Shrink gas and explosion bboxes so rats must be well inside the tile to
  be poisoned/killed.
- Use AABB overlap instead of partial_move threshold for gas poisoning.
- Draw mobile units first, then top-layer effects (gas, mines, bombs,
  explosions) so rats appear under the gas.
- Add draw_on_top hint to Unit base class and top-layer units.
2026-06-16 19:32:50 +02:00

114 lines
3.8 KiB
Python

from abc import ABC, abstractmethod
import uuid
from enum import Enum, auto
class UnitType(Enum):
RAT_MALE = auto()
RAT_FEMALE = auto()
BOMB_TIMER = auto()
BOMB_NUCLEAR = auto()
GAS = auto()
MINE = auto()
POINT = auto()
EXPLOSION = auto()
class Unit(ABC):
"""
Abstract base class for all game units.
Attributes
----------
id : UUID
Unique identifier for the unit.
game : Game
Reference to the main game object.
position : tuple
The current position of the unit (x, y).
position_before : tuple
The position of the unit before the last update.
age : int
The age of the unit in game ticks.
speed : float
Movement speed of the unit.
partial_move : float
Partial movement progress for smooth animation.
bbox : tuple
Bounding box for collision detection (x1, y1, x2, y2).
stop : int
Number of ticks to remain stationary.
collision_layer : int
Collision layer for the optimized collision system.
type : UnitType
The specific type of the unit.
draw_last : bool
Render order hint: when True, the unit is drawn after all other
top-layer units so it always appears in the foreground (e.g. points).
Methods
-------
move()
Update unit position and state (must be implemented by subclasses).
draw()
Render the unit on screen (must be implemented by subclasses).
collisions()
Handle collisions with other units (optional override).
die()
Remove unit from game and handle cleanup.
"""
# Render order hint: units with draw_on_top=True are drawn after mobile
# units so they appear layered on top (e.g. gas, mines, explosions).
draw_on_top = False
# Render order hint: units with draw_last=True are drawn after all other
# top-layer units so they always appear in the foreground.
draw_last = False
def __init__(self, game, position=(0, 0), id=None, collision_layer=0, unit_type=None):
"""Initialize a unit with game reference and position."""
self.id = id if id else uuid.uuid4()
self.game = game
self.position = position
self.position_before = position
self.age = 0
self.speed = 1.0
self.partial_move = 0
self.bbox = (0.0, 0.0, 0.0, 0.0) # Ensure it's a tuple of floats
self.stop = 0
self.collision_layer = collision_layer
self.type = unit_type if unit_type else UnitType.POINT # Default type
@abstractmethod
def move(self):
"""Update unit position and state. Must be implemented by subclasses."""
pass
@abstractmethod
def draw(self):
"""Render the unit on screen. Must be implemented by subclasses."""
pass
def collisions(self):
"""Handle collisions with other units. Default implementation does nothing."""
pass
def is_hidden_in_tunnel(self, image_size, position=None):
"""Return True when the sprite center falls inside a tunnel cell."""
if position is None:
position = self.position_before
x_pos = position[0] * self.game.cell_size + (self.game.cell_size - image_size[0]) // 2
y_pos = position[1] * self.game.cell_size + (self.game.cell_size - image_size[1]) // 2
center_x = int(x_pos + image_size[0] / 2)
center_y = int(y_pos + image_size[1] / 2)
cell_x = center_x // self.game.cell_size
cell_y = center_y // self.game.cell_size
if not self.game.map.in_bounds(cell_x, cell_y):
return False
return self.game.map.is_tunnel(cell_x, cell_y)
def die(self, score=None):
"""Remove unit from game and handle basic cleanup."""
if self.id in self.game.units:
self.game.units.pop(self.id)