feat: add timer-based tower spawning mechanic triggered by player desk interaction

This commit is contained in:
Niklas Haiden
2026-05-08 19:49:38 +02:00
parent 7266f7b569
commit bce77b816d
3 changed files with 43 additions and 12 deletions
+36 -8
View File
@@ -28,6 +28,7 @@ from src.settings import (
DOOR_HP, DOOR_HP,
FPS, FPS,
PLAYER_SPEED, PLAYER_SPEED,
TOWER_TABLE_ACTIVATION_TIME,
) )
from src.shop import Shop from src.shop import Shop
from src.startscreen import Startscreen from src.startscreen import Startscreen
@@ -65,6 +66,7 @@ class Game:
self.game_start_time = 0 self.game_start_time = 0
self.endscreen: Optional[Endscreen] = None self.endscreen: Optional[Endscreen] = None
self.current_room_key: Optional[tuple[int, int, int, int]] = None self.current_room_key: Optional[tuple[int, int, int, int]] = None
self.table_touch_start_time: Optional[int] = None
self.map = Map() self.map = Map()
self.active_door: Optional[Door] = None self.active_door: Optional[Door] = None
@@ -147,10 +149,11 @@ class Game:
self.player.hit_wall() self.player.hit_wall()
self.player.update_animation(dt) self.player.update_animation(dt)
self.ects.update(self.is_player_at_desk())
self.handle_room_table_collision()
player_rect = self.get_actor_rect(self.player) player_rect = self.get_actor_rect(self.player)
room_rect = self.map.get_current_room(player_rect) room_rect = self.map.get_current_room(player_rect)
is_at_desk = self.is_player_at_desk(player_rect, room_rect)
self.ects.update(is_at_desk)
self.handle_room_table_collision()
self.handle_room_change(room_rect) self.handle_room_change(room_rect)
self.update_items(player_rect, room_rect) self.update_items(player_rect, room_rect)
self.monsters.update( self.monsters.update(
@@ -159,7 +162,7 @@ class Game:
self.is_position_walkable, self.is_position_walkable,
self.get_monster_spawn_blockers(), self.get_monster_spawn_blockers(),
) )
self.update_tower() self.update_tower(room_rect, is_at_desk)
self.check_end_conditions() self.check_end_conditions()
def get_player_movement(self, dt: float) -> pygame.Vector2: def get_player_movement(self, dt: float) -> pygame.Vector2:
@@ -186,35 +189,58 @@ class Game:
row = int(position.y // tile_size) row = int(position.y // tile_size)
return self.map.is_accessible(column, row) return self.map.is_accessible(column, row)
def is_player_at_desk(self) -> bool: def is_player_at_desk(
self,
player_rect: Optional[pygame.Rect] = None,
room_rect: Optional[pygame.Rect] = None,
) -> bool:
"""Return True while the player's hitbox touches a desk in the current room.""" """Return True while the player's hitbox touches a desk in the current room."""
if player_rect is None:
player_rect = self.get_actor_rect(self.player) player_rect = self.get_actor_rect(self.player)
if room_rect is None:
room_rect = self.map.get_current_room(player_rect) room_rect = self.map.get_current_room(player_rect)
if room_rect is None: if room_rect is None:
return False return False
return any(desk.check_collision(player_rect) for desk in self.map.get_desks_for_room(room_rect)) return any(desk.check_collision(player_rect) for desk in self.map.get_desks_for_room(room_rect))
def update_tower(self) -> None: def update_tower(
"""Spawn a tower when enough ECTS are available and let it attack the zombie.""" self,
room_rect: Optional[pygame.Rect],
is_at_desk: bool,
) -> None:
"""Spawn a tower after the player studies at a table, then let it attack."""
player_rect = self.get_actor_rect(self.player)
room_rect = self.map.get_current_room(player_rect)
monster = self.monsters.monsters[0] if self.monsters.monsters else None monster = self.monsters.monsters[0] if self.monsters.monsters else None
if monster is None or not monster.is_alive: if monster is None or not monster.is_alive:
self.tower.expire_if_needed() self.tower.expire_if_needed()
self.update_table_touch_timer(is_at_desk)
return return
can_spawn_tower = self.update_table_touch_timer(is_at_desk)
damage = self.tower.update( damage = self.tower.update(
room_rect, room_rect,
self.get_tower_spawn_blockers(), self.get_tower_spawn_blockers(),
self.ects, self.ects,
monster.rect, monster.rect,
can_spawn=can_spawn_tower,
) )
if monster is not None and damage > 0: if monster is not None and damage > 0:
monster.take_damage(damage) monster.take_damage(damage)
def update_table_touch_timer(self, is_at_desk: bool) -> bool:
now = pygame.time.get_ticks()
if not is_at_desk:
self.table_touch_start_time = None
return False
if self.table_touch_start_time is None:
self.table_touch_start_time = now
return False
return now - self.table_touch_start_time >= TOWER_TABLE_ACTIVATION_TIME
def handle_room_change(self, room_rect: Optional[pygame.Rect]) -> None: def handle_room_change(self, room_rect: Optional[pygame.Rect]) -> None:
room_key = self.get_room_key(room_rect) if room_rect is not None else None room_key = self.get_room_key(room_rect) if room_rect is not None else None
if room_key == self.current_room_key: if room_key == self.current_room_key:
@@ -225,6 +251,7 @@ class Game:
self.item_manager.clear() self.item_manager.clear()
self.tower.clear(reset_spawned=True) self.tower.clear(reset_spawned=True)
self.table_touch_start_time = None
self.current_room_key = room_key self.current_room_key = room_key
def reset_active_encounter(self) -> None: def reset_active_encounter(self) -> None:
@@ -281,6 +308,7 @@ class Game:
self.shop = Shop(self.ects, self.item_manager.items, self.sound_manager) self.shop = Shop(self.ects, self.item_manager.items, self.sound_manager)
self.item_manager.clear() self.item_manager.clear()
self.current_room_key = None self.current_room_key = None
self.table_touch_start_time = None
self.tower.clear(reset_spawned=True) self.tower.clear(reset_spawned=True)
self.active_door = None self.active_door = None
self.doors_by_room = {} self.doors_by_room = {}
+2 -1
View File
@@ -67,6 +67,7 @@ TOWER_COST = 10
PROJECTILE_DMG = 2 PROJECTILE_DMG = 2
PROJECTILE_SPEED = 7 PROJECTILE_SPEED = 7
COLOR_PROJECTILE = (200, 200, 0) COLOR_PROJECTILE = (200, 200, 0)
TOWER_TABLE_ACTIVATION_TIME = 5000 # ms the player must stay at a table before tower spawn
TOWER_TIMER = 30000 # how long the tower stays and attack before it disappears TOWER_TIMER = 30000 # how long the tower stays and attack before it disappears
# --- ITEM SPAWN TIMER --- # --- ITEM SPAWN TIMER ---
@@ -149,7 +150,7 @@ SFX_BUY_FAIL = f"{SOUND_DIR}/buy_fail.wav"
SFX_MONSTER_HEALED = f"{SOUND_DIR}/monster_healed.wav" SFX_MONSTER_HEALED = f"{SOUND_DIR}/monster_healed.wav"
SFX_MONSTER_TRANSFORM = f"{SOUND_DIR}/monster_transform.wav" SFX_MONSTER_TRANSFORM = f"{SOUND_DIR}/monster_transform.wav"
BGM_NORMAL = f"{SOUND_DIR}/bgm_normal.wav" BGM_NORMAL = f"{SOUND_DIR}/bgm_creepy.wav"
BGM_CREEPY = f"{SOUND_DIR}/bgm_creepy.wav" BGM_CREEPY = f"{SOUND_DIR}/bgm_creepy.wav"
VOLUME_SFX = 0.6 VOLUME_SFX = 0.6
+2
View File
@@ -71,12 +71,14 @@ class Tower:
blocked_rects: list[pygame.Rect], blocked_rects: list[pygame.Rect],
ects, ects,
monster_rect: Optional[pygame.Rect], monster_rect: Optional[pygame.Rect],
can_spawn: bool = True,
) -> int: ) -> int:
"""Update tower spawning/shooting and return damage dealt to the monster.""" """Update tower spawning/shooting and return damage dealt to the monster."""
now = pygame.time.get_ticks() now = pygame.time.get_ticks()
total_damage = 0 total_damage = 0
if can_spawn:
self.try_spawn(room_rect, blocked_rects, ects) self.try_spawn(room_rect, blocked_rects, ects)
if self.tower_rect is not None and monster_rect is not None: if self.tower_rect is not None and monster_rect is not None: