implement missing stuff
This commit is contained in:
+91
-21
@@ -4,8 +4,6 @@ Controls:
|
||||
- WASD / arrow keys: move Niklas around the map
|
||||
- Space: toggle player state
|
||||
- Esc / Q: quit
|
||||
|
||||
The demo also places a few bad/angry actors as monsters in the rooms.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -20,20 +18,27 @@ from src.door import Door
|
||||
from src.ects import ECTS
|
||||
from src.endscreen import Endscreen
|
||||
from src.hud import HUD
|
||||
from src.item import Item, ItemManager
|
||||
from src.map import Map, color_void, screen_h, screen_w, tile_size
|
||||
from src.monster import MonsterManager
|
||||
from src.settings import DEMO_ACTOR_HEIGHT, DOOR_HEIGHT, DOOR_HP, DOOR_WIDTH, FPS, PLAYER_SPEED
|
||||
from src.settings import (
|
||||
BEER_BOTTLE_ECTS_VALUE,
|
||||
COFFEE_HEAL_VALUE,
|
||||
DEMO_ACTOR_HEIGHT,
|
||||
DOOR_HEIGHT,
|
||||
DOOR_HP,
|
||||
DOOR_WIDTH,
|
||||
FPS,
|
||||
PLAYER_SPEED,
|
||||
)
|
||||
from src.shop import Shop
|
||||
from src.startscreen import Startscreen
|
||||
from src.sounds import SoundManager
|
||||
from src.tower import Tower
|
||||
|
||||
|
||||
# Keep the actor close to the map scale. The map tiles are 30x30, so 2 tiles
|
||||
# high makes the character readable without covering whole rooms/walls.
|
||||
|
||||
|
||||
def tile_center(column: int, row: int) -> tuple[int, int]:
|
||||
"""Return the pixel center of a map tile."""
|
||||
|
||||
return column * tile_size + tile_size // 2, row * tile_size + tile_size // 2
|
||||
|
||||
|
||||
@@ -43,22 +48,30 @@ class Game:
|
||||
self.screen = pygame.display.set_mode((screen_w, screen_h))
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("monospace", 22, bold=True)
|
||||
self.item_font = pygame.font.SysFont("Arial", 16, bold=True)
|
||||
self.running = True
|
||||
|
||||
self.active_monster = None
|
||||
self.buy_requested = False
|
||||
|
||||
# --- SOUND & SYSTEMS ---
|
||||
self.sound_manager = SoundManager()
|
||||
self.startscreen = Startscreen(self.screen)
|
||||
self.hud = HUD()
|
||||
self.ects = ECTS()
|
||||
self.tower = Tower()
|
||||
self.item_manager = ItemManager()
|
||||
self.shop = Shop(self.ects, self.item_manager.items, self.sound_manager)
|
||||
self.tower = Tower(self.sound_manager)
|
||||
self.state = "startscreen"
|
||||
self.player_name: str = ""
|
||||
self.game_start_time = 0
|
||||
self.endscreen: Optional[Endscreen] = None
|
||||
self.current_item_room_key: Optional[tuple[int, int, int, int]] = None
|
||||
|
||||
self.map = Map()
|
||||
self.active_door: Optional[Door] = None
|
||||
self.doors_by_room: dict[tuple[int, int, int, int], Door] = {}
|
||||
|
||||
# Use Niklas as the player sprite.
|
||||
assets_dir = Path(__file__).resolve().parent.parent / "assets"
|
||||
player_asset = Actor.load_sprite_asset(
|
||||
assets_dir / "niklas_sprites",
|
||||
@@ -76,8 +89,6 @@ class Game:
|
||||
self.monsters.spawn_initial(self.get_monster_spawn_blockers())
|
||||
|
||||
def run(self, max_frames: Optional[int] = None) -> None:
|
||||
"""Run the demo. ``max_frames`` is only used for automated smoke tests."""
|
||||
|
||||
frame_count = 0
|
||||
while self.running and (max_frames is None or frame_count < max_frames):
|
||||
dt = min(self.clock.tick(FPS) / 1000.0, 0.05)
|
||||
@@ -90,6 +101,8 @@ class Game:
|
||||
self.player_name = self.startscreen.player_name
|
||||
self.game_start_time = pygame.time.get_ticks()
|
||||
self.state = "game"
|
||||
self.sound_manager.play_bgm("normal")
|
||||
|
||||
elif self.state == "game":
|
||||
self.handle_events()
|
||||
self.update(dt)
|
||||
@@ -117,8 +130,18 @@ class Game:
|
||||
self.running = False
|
||||
elif event.key == pygame.K_SPACE:
|
||||
self.player.toggle_side()
|
||||
self.sound_manager.play_sfx("monster_transform")
|
||||
elif event.key == pygame.K_e:
|
||||
self.buy_requested = True
|
||||
|
||||
def update(self, dt: float) -> None:
|
||||
if self.player.is_bad:
|
||||
self.active_monster = self.player
|
||||
self.sound_manager.play_bgm("creepy")
|
||||
else:
|
||||
self.active_monster = None
|
||||
self.sound_manager.play_bgm("normal")
|
||||
|
||||
movement = self.get_player_movement(dt)
|
||||
self.player.move_by(movement)
|
||||
|
||||
@@ -128,6 +151,7 @@ class Game:
|
||||
self.player.update_animation(dt)
|
||||
self.ects.update(self.is_player_at_desk())
|
||||
self.handle_room_table_collision()
|
||||
self.update_items()
|
||||
self.monsters.update(
|
||||
dt,
|
||||
self.player.position,
|
||||
@@ -144,7 +168,7 @@ class Game:
|
||||
- (keys[pygame.K_LEFT] or keys[pygame.K_a]),
|
||||
(keys[pygame.K_DOWN] or keys[pygame.K_s])
|
||||
- (keys[pygame.K_UP] or keys[pygame.K_w]),
|
||||
)
|
||||
)
|
||||
|
||||
if movement.length_squared() == 0:
|
||||
return movement
|
||||
@@ -157,8 +181,6 @@ class Game:
|
||||
return movement.normalize() * PLAYER_SPEED * dt
|
||||
|
||||
def is_position_walkable(self, position: pygame.Vector2) -> bool:
|
||||
"""Check if the actor's feet are standing on a walkable map tile."""
|
||||
|
||||
column = int(position.x // tile_size)
|
||||
row = int(position.y // tile_size)
|
||||
return self.map.is_accessible(column, row)
|
||||
@@ -194,6 +216,41 @@ class Game:
|
||||
if not monster.is_alive:
|
||||
self.tower.clear()
|
||||
|
||||
def update_items(self) -> None:
|
||||
"""Spawn room items and process one player purchase request."""
|
||||
|
||||
player_rect = self.get_actor_rect(self.player)
|
||||
room_rect = self.map.get_current_room(player_rect)
|
||||
room_key = self.get_room_key(room_rect) if room_rect is not None else None
|
||||
|
||||
if room_key != self.current_item_room_key:
|
||||
self.item_manager.clear()
|
||||
self.current_item_room_key = room_key
|
||||
|
||||
self.item_manager.update(room_rect, self.get_item_spawn_blockers())
|
||||
|
||||
if self.buy_requested:
|
||||
self.try_buy_touching_item(player_rect)
|
||||
self.buy_requested = False
|
||||
|
||||
def try_buy_touching_item(self, player_rect: pygame.Rect) -> None:
|
||||
item = self.item_manager.get_touching_item(player_rect)
|
||||
if item is None:
|
||||
return
|
||||
|
||||
if self.shop.buy_item(item):
|
||||
self.apply_item_effect(item)
|
||||
self.item_manager.remove_item(item)
|
||||
|
||||
def apply_item_effect(self, item: Item) -> None:
|
||||
if item.item_type == "beer":
|
||||
self.ects.upgrade_rate(BEER_BOTTLE_ECTS_VALUE)
|
||||
elif item.item_type == "coffee" and self.active_door is not None:
|
||||
self.active_door.current_hp = min(
|
||||
self.active_door.max_hp,
|
||||
self.active_door.current_hp + COFFEE_HEAL_VALUE,
|
||||
)
|
||||
|
||||
def check_end_conditions(self) -> None:
|
||||
if self.active_door is not None and self.active_door.current_hp <= 0:
|
||||
self.show_endscreen(won=False)
|
||||
@@ -210,6 +267,9 @@ class Game:
|
||||
|
||||
def restart_game(self) -> None:
|
||||
self.ects = ECTS()
|
||||
self.shop = Shop(self.ects, self.item_manager.items, self.sound_manager)
|
||||
self.item_manager.clear()
|
||||
self.current_item_room_key = None
|
||||
self.tower.clear()
|
||||
self.active_door = None
|
||||
self.doors_by_room = {}
|
||||
@@ -238,7 +298,7 @@ class Game:
|
||||
return
|
||||
|
||||
def get_or_create_door_for_room(self, room_rect: pygame.Rect) -> Door:
|
||||
key = (room_rect.x, room_rect.y, room_rect.width, room_rect.height)
|
||||
key = self.get_room_key(room_rect)
|
||||
if key not in self.doors_by_room:
|
||||
self.doors_by_room[key] = self.create_door_for_room(room_rect)
|
||||
return self.doors_by_room[key]
|
||||
@@ -293,26 +353,35 @@ class Game:
|
||||
blockers.append(pygame.Rect(self.active_door.x, self.active_door.y, DOOR_WIDTH, DOOR_HEIGHT))
|
||||
for monster in self.monsters.monsters:
|
||||
blockers.append(monster.rect)
|
||||
blockers.extend(item.rect for item in self.item_manager.items)
|
||||
return blockers
|
||||
|
||||
def get_item_spawn_blockers(self) -> list[pygame.Rect]:
|
||||
"""Rects that room items should not spawn on."""
|
||||
|
||||
blockers = self.get_tower_spawn_blockers()
|
||||
if self.tower.tower_rect is not None:
|
||||
blockers.append(self.tower.tower_rect)
|
||||
blockers.extend(item.rect for item in self.item_manager.items)
|
||||
return blockers
|
||||
|
||||
@staticmethod
|
||||
def get_room_key(room_rect: pygame.Rect) -> tuple[int, int, int, int]:
|
||||
return room_rect.x, room_rect.y, room_rect.width, room_rect.height
|
||||
|
||||
@staticmethod
|
||||
def get_actor_rect(actor: Actor) -> pygame.Rect:
|
||||
frame = actor.get_frame()
|
||||
return frame.get_rect(midbottom=(round(actor.position.x), round(actor.position.y)))
|
||||
|
||||
def find_start_position(self) -> tuple[int, int]:
|
||||
"""Find the first S tile and place the actor's feet near its center."""
|
||||
|
||||
for row_index, row in enumerate(self.map.map):
|
||||
column_index = row.find("S")
|
||||
if column_index != -1:
|
||||
# The S tile is at the very top of the current map, so place the
|
||||
# actor one tile lower to keep the sprite visible on screen.
|
||||
return (
|
||||
column_index * tile_size + tile_size // 2,
|
||||
(row_index + 1) * tile_size + tile_size // 2,
|
||||
)
|
||||
|
||||
return tile_size, tile_size
|
||||
|
||||
def get_elapsed_seconds(self) -> int:
|
||||
@@ -327,6 +396,7 @@ class Game:
|
||||
if self.active_door is not None and self.active_door.is_closed:
|
||||
self.active_door.draw(self.screen)
|
||||
|
||||
self.item_manager.draw(self.screen, 0, 0, self.item_font)
|
||||
self.tower.draw(self.screen, self.font)
|
||||
|
||||
# Draw by Y position so actors lower on the map appear in front.
|
||||
|
||||
Reference in New Issue
Block a user