2024-12-30 14:36:23 +08:00

285 lines
12 KiB
Python

import pygame
import math
from constants import *
from sprites import PLANT_DRAWINGS, ZOMBIE_DRAWINGS
class Plant:
def __init__(self, x, y, plant_type):
self.x = x
self.y = y
self.type = plant_type
stats = PLANT_STATS[plant_type]
self.health = stats["health"]
self.max_health = stats["health"]
self.cost = stats["cost"]
self.rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE + TOP_MARGIN, CELL_SIZE, CELL_SIZE)
self.shoot_timer = 0
self.shoot_cooldown = 150 # Frames between shots
self.eating_timer = 0
self.eating_cooldown = 300 # Frames between chomps
self.sun_timer = 0
self.animation_state = "idle"
self.damaged_state = 0
# Special abilities
self.is_freezing = plant_type == PlantType.SNOW_PEA
self.can_eat = plant_type == PlantType.CHOMPER
self.is_rose = plant_type == PlantType.ROSE_SHOOTER
def update(self):
if self.type in [PlantType.PEASHOOTER, PlantType.SNOW_PEA, PlantType.ROSE_SHOOTER]:
self.shoot_timer += 1
elif self.type == PlantType.SUNFLOWER:
self.sun_timer += 1
elif self.type == PlantType.CHOMPER and self.eating_timer > 0:
self.eating_timer -= 1
# Update damage state
health_percentage = self.health / self.max_health
if health_percentage <= 0.3:
self.damaged_state = 2
elif health_percentage <= 0.6:
self.damaged_state = 1
def can_shoot(self):
if self.type == PlantType.PEASHOOTER:
return self.shoot_timer >= 90
elif self.type == PlantType.SNOW_PEA:
return self.shoot_timer >= 90
elif self.type == PlantType.ROSE_SHOOTER:
return self.shoot_timer >= 100 # Slightly slower fire rate
return False
def can_produce_sun(self):
return self.type == PlantType.SUNFLOWER and self.sun_timer >= 360
def can_eat_zombie(self):
return self.type == PlantType.CHOMPER and self.eating_timer <= 0
def start_eating(self):
self.eating_timer = 300 # 5 seconds cooldown
def reset_timer(self):
if self.type in [PlantType.PEASHOOTER, PlantType.SNOW_PEA, PlantType.ROSE_SHOOTER]:
self.shoot_timer = 0
elif self.type == PlantType.SUNFLOWER:
self.sun_timer = 0
def draw(self, screen):
# Draw shadow
shadow_surface = pygame.Surface((CELL_SIZE, CELL_SIZE//4), pygame.SRCALPHA)
pygame.draw.ellipse(shadow_surface, (0, 0, 0, 64), (0, 0, CELL_SIZE, CELL_SIZE//4))
screen.blit(shadow_surface, (self.rect.x, self.rect.y + CELL_SIZE - CELL_SIZE//8))
# Draw plant using the corresponding drawing function
if self.type in PLANT_DRAWINGS:
PLANT_DRAWINGS[self.type](screen, self.rect.x, self.rect.y, CELL_SIZE)
# Draw health bar when damaged
if self.health < self.max_health:
health_width = max(0, (self.rect.width * self.health) // self.max_health)
health_rect = pygame.Rect(self.rect.x, self.rect.y - 5, health_width, 3)
pygame.draw.rect(screen, (255, 0, 0), health_rect)
class Zombie:
def __init__(self, row, zombie_type):
self.x = WINDOW_WIDTH / CELL_SIZE
self.y = row
self.type = zombie_type
stats = ZOMBIE_STATS[zombie_type]
self.health = stats["health"]
self.max_health = stats["health"]
self.speed = stats["speed"]
self.damage = stats["damage"]
self.rect = pygame.Rect(
self.x * CELL_SIZE,
self.y * CELL_SIZE + TOP_MARGIN,
CELL_SIZE,
CELL_SIZE
)
self.eating = False
self.stun_timer = 0
self.frozen_timer = 0
self.frozen = False
self.intoxicated_timer = 0
self.intoxicated = False
self.animation_state = "walking"
# Special abilities for newspaper zombie
if self.type == ZombieType.NEWSPAPER:
self.has_newspaper = True
self.enraged = False
else:
self.has_newspaper = False
self.enraged = False
# Special abilities for dancing zombie
if self.type == ZombieType.DANCING:
self.summon_timer = 300
else:
self.summon_timer = 0
def move(self):
if not self.eating and self.stun_timer <= 0:
actual_speed = self.speed
if self.frozen:
actual_speed *= 0.5
if self.intoxicated:
actual_speed *= 0.3
if self.type == ZombieType.NEWSPAPER and self.enraged:
actual_speed *= 1.5
self.x -= actual_speed / FPS
self.rect.x = self.x * CELL_SIZE
if self.stun_timer > 0:
self.stun_timer -= 1
if self.frozen:
self.frozen_timer -= 1
if self.frozen_timer <= 0:
self.frozen = False
if self.intoxicated:
self.intoxicated_timer -= 1
if self.intoxicated_timer <= 0:
self.intoxicated = False
def intoxicate(self):
self.intoxicated = True
self.intoxicated_timer = 300
def take_damage(self, damage):
self.health -= damage
if self.type == ZombieType.NEWSPAPER and self.has_newspaper and self.health <= self.max_health * 0.5:
self.has_newspaper = False
self.enraged = True
self.speed *= 1.5
def freeze(self):
self.frozen = True
self.frozen_timer = 300
def draw(self, screen):
# Draw shadow
shadow_surface = pygame.Surface((CELL_SIZE, CELL_SIZE//3), pygame.SRCALPHA)
pygame.draw.ellipse(shadow_surface, (0, 0, 0, 64), (0, 0, CELL_SIZE, CELL_SIZE//3))
screen.blit(shadow_surface, (self.rect.x, self.rect.y + CELL_SIZE - CELL_SIZE//6))
# Draw zombie using the corresponding drawing function
if self.type in ZOMBIE_DRAWINGS:
ZOMBIE_DRAWINGS[self.type](screen, self.rect.x, self.rect.y, CELL_SIZE)
# Draw frozen effect
if self.frozen:
ice_surface = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
ice_surface.fill((150, 217, 255, 128))
screen.blit(ice_surface, self.rect)
# Draw intoxicated effect
if self.intoxicated:
love_surface = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
time = pygame.time.get_ticks()
for i in range(3):
heart_x = self.rect.x + CELL_SIZE//2 + math.cos(time * 0.003 + i * 2) * 15
heart_y = self.rect.y + CELL_SIZE//3 + math.sin(time * 0.003 + i * 2) * 10
# Draw heart shape
pygame.draw.circle(screen, (255, 192, 203, 200), (int(heart_x - 5), int(heart_y)), 5)
pygame.draw.circle(screen, (255, 192, 203, 200), (int(heart_x + 5), int(heart_y)), 5)
pygame.draw.polygon(screen, (255, 192, 203, 200), [
(heart_x, heart_y + 8),
(heart_x - 10, heart_y),
(heart_x + 10, heart_y)
])
# Health bar
health_width = max(0, (self.rect.width * self.health) // self.max_health)
health_rect = pygame.Rect(self.rect.x, self.rect.y - 10, health_width, 5)
pygame.draw.rect(screen, (255, 0, 0), health_rect)
class Projectile:
def __init__(self, x, y, damage=20, speed=5, freezing=False, is_rose=False):
self.x = (x + 0.5) * CELL_SIZE
self.y = y * CELL_SIZE + TOP_MARGIN + CELL_SIZE // 2
self.damage = damage
self.speed = speed
self.freezing = freezing
self.is_rose = is_rose
self.active = True
self.size = 10 # Set all projectiles to the same size
self.rect = pygame.Rect(self.x - self.size//2, self.y - self.size//2, self.size, self.size)
self.trail_positions = []
self.trail_lifetime = 15 if is_rose else 10
self.glow_offset = 0
self.rotation = 0
self.color = (255, 192, 203) if is_rose else ((0, 191, 255) if freezing else (0, 255, 0))
self.alpha = 255
def move(self):
# Store current position for trail
self.trail_positions.append((self.rect.x, self.rect.y))
if len(self.trail_positions) > self.trail_lifetime:
self.trail_positions.pop(0)
self.rect.x += self.speed
self.rotation += 15 # Rotate 15 degrees per frame
# Update glow effect
self.glow_offset = abs(math.sin(pygame.time.get_ticks() * 0.01)) * 2
if self.rect.x > WINDOW_WIDTH:
self.active = False
def draw(self, screen):
# Draw trail with rose petals or regular trail
for i, (x, y) in enumerate(self.trail_positions):
alpha = int(255 * (i / len(self.trail_positions)) * 0.5)
size = int(4 * (i / len(self.trail_positions))) # Same size for all trails
trail_surface = pygame.Surface((12, 12), pygame.SRCALPHA) # Same size for all trails
if self.is_rose:
# Draw rose petal trail
petal_color = (255, 192, 203, alpha) # Pink with alpha
# Draw multiple petals for a more detailed trail
for angle in range(0, 360, 72):
rad = math.radians(angle + self.rotation)
petal_x = 6 + math.cos(rad) * size # Center at 6 (half of 12)
petal_y = 6 + math.sin(rad) * size
pygame.draw.circle(trail_surface, petal_color, (int(petal_x), int(petal_y)), size)
else:
color = (0, 191, 255, alpha) if self.freezing else (0, 255, 0, alpha)
pygame.draw.circle(trail_surface, color, (6, 6), size)
screen.blit(trail_surface, (x - 6, y - 6))
# Draw main projectile
if self.is_rose:
# Draw rose projectile at same size as others
glow_surface = pygame.Surface((20, 20), pygame.SRCALPHA)
glow_radius = 8 + self.glow_offset
glow_color = (255, 192, 203, 64) # Pink with transparency
pygame.draw.circle(glow_surface, glow_color, (10, 10), glow_radius)
screen.blit(glow_surface, (self.rect.x - 10, self.rect.y - 10))
# Main projectile
pygame.draw.circle(screen, (255, 192, 203), (self.rect.x, self.rect.y), 6) # Same size as others
pygame.draw.circle(screen, (255, 105, 180), (self.rect.x, self.rect.y), 4) # Inner color
# Add highlight
highlight_pos = (self.rect.x - 2, self.rect.y - 2)
pygame.draw.circle(screen, (255, 255, 255, 180), highlight_pos, 2)
else:
# Draw regular projectile
glow_color = (0, 191, 255, 64) if self.freezing else (0, 255, 0, 64)
main_color = (0, 191, 255) if self.freezing else (0, 200, 0)
inner_color = (173, 216, 230) if self.freezing else (150, 255, 150)
glow_surface = pygame.Surface((20, 20), pygame.SRCALPHA)
glow_radius = 8 + self.glow_offset
pygame.draw.circle(glow_surface, glow_color, (10, 10), glow_radius)
screen.blit(glow_surface, (self.rect.x - 10, self.rect.y - 10))
pygame.draw.circle(screen, main_color, (self.rect.x, self.rect.y), 6)
pygame.draw.circle(screen, inner_color, (self.rect.x, self.rect.y), 4)
highlight_pos = (self.rect.x - 2, self.rect.y - 2)
pygame.draw.circle(screen, (255, 255, 255, 180), highlight_pos, 2)