From bce77b816dc1816032b428f3489abc37ef22e1bd Mon Sep 17 00:00:00 2001 From: Niklas Haiden Date: Fri, 8 May 2026 19:49:38 +0200 Subject: [PATCH] feat: add timer-based tower spawning mechanic triggered by player desk interaction --- src/game.py | 48 ++++++++++++++++++++++++++++++++++++++---------- src/settings.py | 3 ++- src/tower.py | 4 +++- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/game.py b/src/game.py index bb04eb9..71054d4 100644 --- a/src/game.py +++ b/src/game.py @@ -28,6 +28,7 @@ from src.settings import ( DOOR_HP, FPS, PLAYER_SPEED, + TOWER_TABLE_ACTIVATION_TIME, ) from src.shop import Shop from src.startscreen import Startscreen @@ -65,6 +66,7 @@ class Game: self.game_start_time = 0 self.endscreen: Optional[Endscreen] = None self.current_room_key: Optional[tuple[int, int, int, int]] = None + self.table_touch_start_time: Optional[int] = None self.map = Map() self.active_door: Optional[Door] = None @@ -147,10 +149,11 @@ class Game: self.player.hit_wall() 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) 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.update_items(player_rect, room_rect) self.monsters.update( @@ -159,7 +162,7 @@ class Game: self.is_position_walkable, self.get_monster_spawn_blockers(), ) - self.update_tower() + self.update_tower(room_rect, is_at_desk) self.check_end_conditions() def get_player_movement(self, dt: float) -> pygame.Vector2: @@ -186,35 +189,58 @@ class Game: row = int(position.y // tile_size) 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.""" - player_rect = self.get_actor_rect(self.player) - room_rect = self.map.get_current_room(player_rect) + if player_rect is None: + player_rect = self.get_actor_rect(self.player) + if room_rect is None: + room_rect = self.map.get_current_room(player_rect) if room_rect is None: return False return any(desk.check_collision(player_rect) for desk in self.map.get_desks_for_room(room_rect)) - def update_tower(self) -> None: - """Spawn a tower when enough ECTS are available and let it attack the zombie.""" + def update_tower( + 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 if monster is None or not monster.is_alive: self.tower.expire_if_needed() + self.update_table_touch_timer(is_at_desk) return + can_spawn_tower = self.update_table_touch_timer(is_at_desk) damage = self.tower.update( room_rect, self.get_tower_spawn_blockers(), self.ects, monster.rect, + can_spawn=can_spawn_tower, ) if monster is not None and damage > 0: 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: room_key = self.get_room_key(room_rect) if room_rect is not None else None if room_key == self.current_room_key: @@ -225,6 +251,7 @@ class Game: self.item_manager.clear() self.tower.clear(reset_spawned=True) + self.table_touch_start_time = None self.current_room_key = room_key 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.item_manager.clear() self.current_room_key = None + self.table_touch_start_time = None self.tower.clear(reset_spawned=True) self.active_door = None self.doors_by_room = {} diff --git a/src/settings.py b/src/settings.py index 5a85e9e..a153594 100644 --- a/src/settings.py +++ b/src/settings.py @@ -67,6 +67,7 @@ TOWER_COST = 10 PROJECTILE_DMG = 2 PROJECTILE_SPEED = 7 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 # --- 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_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" VOLUME_SFX = 0.6 diff --git a/src/tower.py b/src/tower.py index 3a72d8e..e52a3ed 100644 --- a/src/tower.py +++ b/src/tower.py @@ -71,13 +71,15 @@ class Tower: blocked_rects: list[pygame.Rect], ects, monster_rect: Optional[pygame.Rect], + can_spawn: bool = True, ) -> int: """Update tower spawning/shooting and return damage dealt to the monster.""" now = pygame.time.get_ticks() total_damage = 0 - self.try_spawn(room_rect, blocked_rects, ects) + if can_spawn: + self.try_spawn(room_rect, blocked_rects, ects) if self.tower_rect is not None and monster_rect is not None: if now - self.last_shot >= fire_interval: