From 261e0bc2238adc0d46d1c964225562e07680b387 Mon Sep 17 00:00:00 2001 From: Niklas Haiden Date: Fri, 8 May 2026 19:02:28 +0200 Subject: [PATCH] tower changes --- src/tower.py | 180 ++++++++++++++++++++++++--------------------------- 1 file changed, 85 insertions(+), 95 deletions(-) diff --git a/src/tower.py b/src/tower.py index 8f80a64..d057819 100644 --- a/src/tower.py +++ b/src/tower.py @@ -1,23 +1,22 @@ -import pygame -import random import math +import random +from typing import Optional + +import pygame + from src.settings import TOWER_COST, PROJECTILE_DMG, PROJECTILE_SPEED, COLOR_PROJECTILE, TOWER_TIMER -tower_size = 30 -projectile_size = 8 -fire_interval = 1000 # ms zwischen Schüssen -respawn_delay = 10000 # ms bis neues Feld spawnt +tower_size = 30 +projectile_size = 20 +fire_interval = 1000 # ms zwischen Schüssen class Tower: def __init__(self) -> None: - # Feld und Tower -> None wenn nicht aktiv - self.field_rect: pygame.Rect = None - self.tower_rect: pygame.Rect = None + self.tower_rect: Optional[pygame.Rect] = None self.projectiles: list[dict] = [] - self.tower_start: int = None - self.despawn_timer: int = None + self.tower_start: Optional[int] = None self.last_shot: int = 0 # Sprite laden für den Tower @@ -28,41 +27,45 @@ class Tower: print(f"Error loading tower sprite: {e}") self.tower_image = None - def field_spawn(self, room_rect: pygame.Rect, blocked_rects: list[pygame.Rect]): - # soll Tower Feld an zufälliger freier Position im Raum spawnen - for i in range(50): - x = random.randint(room_rect.left + tower_size, room_rect.right - tower_size * 2) - y = random.randint(room_rect.top + tower_size, room_rect.bottom - tower_size * 2) - rect = pygame.Rect(x, y, tower_size, tower_size) + # Projectile sprite: the tower throws books. + try: + self.projectile_image = pygame.image.load("assets/items/book.png").convert_alpha() + self.projectile_image = pygame.transform.scale(self.projectile_image, (projectile_size, projectile_size)) + except Exception: + self.projectile_image = None - # freie Position finden - nicht auf dem Tisch oder blockierten Rects - if not any(rect.colliderect(b) for b in blocked_rects): - self.field_rect = rect - return + def try_spawn(self, room_rect: Optional[pygame.Rect], blocked_rects: list[pygame.Rect], ects) -> bool: + """Spawn and pay for one tower as soon as the player has enough ECTS.""" - def try_build(self, player_rect: pygame.Rect, keys: pygame.key.ScancodeWrapper, ects): + if room_rect is None or self.tower_rect is not None: + return False + if ects.ects_balance < TOWER_COST: + return False - # Baut Tower wenn Spieler E drückt, auf Feld steht und genug ects hat - if not self.field.rect or self.tower.rect: - return False - if not (self.field_rect.colliderect(player_rect) and keys[pygame.K_e] and ects.balance >= TOWER_COST): - return False + rect = self._random_free_rect(room_rect, blocked_rects) + if rect is None: + return False - ects.balance -= TOWER_COST - self.tower_rect = self.field_rect - self.field_rect = None - self.tower_start = pygame.time.get_ticks() - return True + ects.decrease_balance(TOWER_COST) + self.tower_rect = rect + self.tower_start = pygame.time.get_ticks() + self.projectiles = [] + return True - def update(self, player_rect: pygame.Rect, keys: pygame.key.ScancodeWrapper, ects, monster_rect: pygame.Rect, room_rect: pygame.Rect, blocked_rects: list[pygame.Rect]): + def update( + self, + room_rect: Optional[pygame.Rect], + blocked_rects: list[pygame.Rect], + ects, + monster_rect: Optional[pygame.Rect], + ) -> int: + """Update tower spawning/shooting and return damage dealt to the monster.""" - # ruft jeden Frame auf und gibt den Schaden auf Monster zurück now = pygame.time.get_ticks() total_damage = 0 - self.try_build(player_rect, keys, ects) + self.try_spawn(room_rect, blocked_rects, ects) - # schießen dann, wenn Tower und Monster vorhanden sind if self.tower_rect and monster_rect: if now - self.last_shot >= fire_interval: dx = monster_rect.centerx - self.tower_rect.centerx @@ -74,84 +77,71 @@ class Tower: "vel_x": (dx / distance) * PROJECTILE_SPEED, "vel_y": (dy / distance) * PROJECTILE_SPEED, }) - self.last_shot = now - # Bewegung der Projektile und Kollision mit Monster prüfen - for p in self.projectiles[:]: - p["x"] += p["vel_x"] - p["y"] += p["vel_y"] + for projectile in self.projectiles[:]: + projectile["x"] += projectile["vel_x"] + projectile["y"] += projectile["vel_y"] - if monster_rect and pygame.Rect(int(p["x"]), int(p["y"]), projectile_size, projectile_size).colliderect(monster_rect): + projectile_rect = pygame.Rect( + int(projectile["x"]), + int(projectile["y"]), + projectile_size, + projectile_size, + ) + if monster_rect and projectile_rect.colliderect(monster_rect): total_damage += PROJECTILE_DMG - self.projectiles.remove(p) + self.projectiles.remove(projectile) - # wenn der Tower abgelaufen ist, soll er despawnen und ein Timer starten if self.tower_rect and self.tower_start and now - self.tower_start >= TOWER_TIMER: - self.tower_rect = None - self.despawn_time = now - self.projectiles = [] - - # ein neues Feld soll spawnen nach dem Respawn Delay - if not self.tower_rect and not self.field_rect and self.despawn_time: - if now - self.despawn_time >= respawn_delay: - self.spawn_field(room_rect, blocked_rects) - self.despawn_time = None + self.clear() return total_damage - def draw(self, surface: pygame.Surface, font: pygame.font.Font, cam_x: int = 0, cam_y: int = 0): - # Feld zeichnen - goldenes Rechteck mit "T" als Hinweis - if self.field_rect: - r = pygame.Rect(self.field_rect.x - cam_x, self.field_rect.y - cam_y, tower_size, tower_size) - pygame.draw.rect(surface, (160,120,30), r) - pygame.draw.rect(surface, (220,180,60), r, 2) - surface.blit(font.render("T", True, (255, 255, 255)), (r.x + 12, r.y + 10)) - - # Tower Zeichnen mit Sprite - als Fallback grünes Rechteck if self.tower_rect and self.tower_start: - r = pygame.Rect(self.tower_rect.x - cam_x, self.tower_rect.y - cam_y, tower_size, tower_size) + draw_rect = pygame.Rect( + self.tower_rect.x - cam_x, + self.tower_rect.y - cam_y, + tower_size, + tower_size, + ) - # berechnet die verbleibende Zeit in Sekunden sec = max(0, TOWER_TIMER - (pygame.time.get_ticks() - self.tower_start)) // 1000 if self.tower_image: - surface.blit(self.tower_image, r) + surface.blit(self.tower_image, draw_rect) else: - pygame.draw.rect(surface, (40, 160, 80), r) - pygame.draw.rect(surface, (80, 220, 120), r, 2) - - # Restzeit der Tower über dem Sprite anzeigen - surface.blit(font.render(str(sec), True, (255, 255, 255)), (r.x + 8, r.y - 18)) - - # alle aktiven Projektile zeichnen - for p in self.projectiles: - pygame.draw.rect(surface, COLOR_PROJECTILE, - pygame.Rect(int(p["x"]) - cam_x, int(p["y"]) - cam_y, projectile_size, projectile_size)) - - - # alles zurücksetzen, wenn der Raum verlassen wird - def clear(self) -> None: - self.field_rect = None - self.tower_rect = None - self.projectiles = [] - self.despawn_timer = None - - - - - - - - - - - - + pygame.draw.rect(surface, (40, 160, 80), draw_rect) + pygame.draw.rect(surface, (80, 220, 120), draw_rect, 2) + surface.blit(font.render(str(sec), True, (255, 255, 255)), (draw_rect.x + 8, draw_rect.y - 18)) + for projectile in self.projectiles: + projectile_rect = pygame.Rect( + int(projectile["x"]) - cam_x, + int(projectile["y"]) - cam_y, + projectile_size, + projectile_size, + ) + if self.projectile_image: + surface.blit(self.projectile_image, projectile_rect) + else: + pygame.draw.rect(surface, COLOR_PROJECTILE, projectile_rect) + def clear(self) -> None: + self.tower_rect = None + self.projectiles = [] + self.tower_start = None + @staticmethod + def _random_free_rect(room_rect: pygame.Rect, blocked_rects: list[pygame.Rect]) -> Optional[pygame.Rect]: + for _ in range(50): + x = random.randint(room_rect.left + tower_size, room_rect.right - tower_size * 2) + y = random.randint(room_rect.top + tower_size, room_rect.bottom - tower_size * 2) + rect = pygame.Rect(x, y, tower_size, tower_size) + if not any(rect.colliderect(blocked) for blocked in blocked_rects): + return rect + return None