Projectdd/menu_system.py

675 lines
22 KiB
Plaintext

import pygame
from settings import *
class Button:
"""Reusable button class for UI"""
def __init__(self, x, y, width, height, text, color, text_color, hover_color):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.color = color
self.hover_color = hover_color
self.text_color = text_color
self.is_hovered = False
def draw(self, screen, font):
current_color = self.hover_color if self.is_hovered else self.color
pygame.draw.rect(screen, current_color, self.rect)
pygame.draw.rect(screen, (255, 255, 255), self.rect, 2)
text_surface = font.render(self.text, True, self.text_color)
text_rect = text_surface.get_rect(center=self.rect.center)
screen.blit(text_surface, text_rect)
def check_hover(self, mouse_pos):
self.is_hovered = self.rect.collidepoint(mouse_pos)
def is_clicked(self, mouse_pos):
return self.rect.collidepoint(mouse_pos)
class InputBox:
"""Text input field for naming saves"""
def __init__(self, x, y, width, height, text=''):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.active = False
self.cursor_visible = True
self.cursor_timer = 0
def handle_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
self.active = True
else:
self.active = False
if event.type == pygame.KEYDOWN:
if self.active:
if event.key == pygame.K_BACKSPACE:
self.text = self.text[:-1]
elif event.key == pygame.K_RETURN:
return "submit"
elif len(self.text) < 30:
if event.unicode.isprintable():
self.text += event.unicode
return None
def update(self, dt):
self.cursor_timer += dt
if self.cursor_timer > 0.5:
self.cursor_visible = not self.cursor_visible
self.cursor_timer = 0
def draw(self, screen, font):
pygame.draw.rect(screen, (50, 50, 50), self.rect)
color = (100, 200, 100) if self.active else (100, 100, 100)
pygame.draw.rect(screen, color, self.rect, 2)
text_surface = font.render(self.text, True, (255, 255, 255))
screen.blit(text_surface, (self.rect.x + 10, self.rect.y + 8))
if self.active and self.cursor_visible:
cursor_x = self.rect.x + 10 + text_surface.get_width()
pygame.draw.line(screen, (255, 255, 255),
(cursor_x, self.rect.y + 5),
(cursor_x, self.rect.y + self.rect.height - 5))
class MainMenu:
"""Main menu screen"""
def __init__(self, game):
self.game = game
self.button_width = 300
self.button_height = 60
self.button_x = (SCREEN_WIDTH - self.button_width) // 2
self.new_game_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2 - 100,
self.button_width,
self.button_height,
"New Game",
(50, 100, 150),
(255, 255, 255),
(100, 150, 200)
)
self.load_game_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2,
self.button_width,
self.button_height,
"Load Game",
(50, 100, 150),
(255, 255, 255),
(100, 150, 200)
)
self.settings_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2 + 70,
self.button_width,
self.button_height,
"Settings",
(100, 100, 100),
(255, 255, 255),
(150, 150, 150)
)
self.quit_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2 + 140,
self.button_width,
self.button_height,
"Quit",
(150, 50, 50),
(255, 255, 255),
(200, 100, 100)
)
self.font_title = pygame.font.SysFont("consolas", 72, bold=True)
self.font_button = pygame.font.SysFont("consolas", 24)
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return "quit"
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if self.new_game_btn.is_clicked(event.pos):
return "new_game"
elif self.load_game_btn.is_clicked(event.pos):
return "load_game"
elif self.settings_btn.is_clicked(event.pos):
return "settings"
elif self.quit_btn.is_clicked(event.pos):
return "quit"
return None
def update(self):
mouse_pos = pygame.mouse.get_pos()
self.new_game_btn.check_hover(mouse_pos)
self.load_game_btn.check_hover(mouse_pos)
self.settings_btn.check_hover(mouse_pos)
self.quit_btn.check_hover(mouse_pos)
def draw(self, screen):
screen.fill(BACKGROUND_COLOR)
title = self.font_title.render("PROJECTDD", True, (255, 255, 255))
title_rect = title.get_rect(center=(SCREEN_WIDTH // 2, 100))
screen.blit(title, title_rect)
self.new_game_btn.draw(screen, self.font_button)
self.load_game_btn.draw(screen, self.font_button)
self.settings_btn.draw(screen, self.font_button)
self.quit_btn.draw(screen, self.font_button)
pygame.display.flip()
class SettingsMenu:
"""Settings menu for fullscreen toggle"""
def __init__(self, game):
self.game = game
self.font_title = pygame.font.SysFont("consolas", 36, bold=True)
self.font_label = pygame.font.SysFont("consolas", 20)
self.font_button = pygame.font.SysFont("consolas", 18)
self.button_width = 200
self.button_height = 50
# Fullscreen toggle
self.fullscreen_btn = Button(
SCREEN_WIDTH // 2 - 100,
SCREEN_HEIGHT // 2 - 50,
200,
50,
f"Fullscreen: {'ON' if FULLSCREEN else 'OFF'}",
(100, 100, 100),
(255, 255, 255),
(150, 150, 150)
)
# Back button
self.back_btn = Button(
SCREEN_WIDTH // 2 - 100,
SCREEN_HEIGHT // 2 + 50,
200,
50,
"Back",
(100, 100, 150),
(255, 255, 255),
(150, 150, 200)
)
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return "back"
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
return "back"
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
# Check fullscreen button
if self.fullscreen_btn.is_clicked(event.pos):
self.game.toggle_fullscreen()
self.fullscreen_btn.text = f"Fullscreen: {'ON' if FULLSCREEN else 'OFF'}"
# Check back button
if self.back_btn.is_clicked(event.pos):
return "back"
return None
def update(self, dt):
mouse_pos = pygame.mouse.get_pos()
self.back_btn.check_hover(mouse_pos)
self.fullscreen_btn.check_hover(mouse_pos)
def draw(self, screen):
screen.fill(BACKGROUND_COLOR)
title = self.font_title.render("Settings", True, (255, 255, 255))
title_rect = title.get_rect(center=(SCREEN_WIDTH // 2, 80))
screen.blit(title, title_rect)
# Draw fullscreen button
self.fullscreen_btn.draw(screen, self.font_button)
# Draw back button
self.back_btn.draw(screen, self.font_button)
pygame.display.flip()
class NewGameDialog:
"""Dialog for naming a new game"""
def __init__(self, game):
self.game = game
self.font_title = pygame.font.SysFont("consolas", 36, bold=True)
self.font_label = pygame.font.SysFont("consolas", 20)
self.name_input = InputBox(
SCREEN_WIDTH // 2 - 150,
SCREEN_HEIGHT // 2 - 20,
300,
50,
"My World"
)
self.button_width = 120
self.button_height = 50
self.create_btn = Button(
SCREEN_WIDTH // 2 - 260,
SCREEN_HEIGHT // 2 + 80,
self.button_width,
self.button_height,
"Create",
(50, 150, 50),
(255, 255, 255),
(100, 200, 100)
)
self.cancel_btn = Button(
SCREEN_WIDTH // 2 + 140,
SCREEN_HEIGHT // 2 + 80,
self.button_width,
self.button_height,
"Cancel",
(150, 50, 50),
(255, 255, 255),
(200, 100, 100)
)
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return "cancel"
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
return "cancel"
self.name_input.handle_event(event)
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if self.create_btn.is_clicked(event.pos):
if self.name_input.text.strip():
return f"create:{self.name_input.text}"
elif self.cancel_btn.is_clicked(event.pos):
return "cancel"
return None
def update(self, dt):
self.name_input.update(dt)
mouse_pos = pygame.mouse.get_pos()
self.create_btn.check_hover(mouse_pos)
self.cancel_btn.check_hover(mouse_pos)
def draw(self, screen):
screen.fill(BACKGROUND_COLOR)
title = self.font_title.render("Create New World", True, (255, 255, 255))
title_rect = title.get_rect(center=(SCREEN_WIDTH // 2, 80))
screen.blit(title, title_rect)
label = self.font_label.render("World Name:", True, (255, 255, 255))
screen.blit(label, (SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2 - 60))
self.name_input.draw(screen, self.font_label)
self.create_btn.draw(screen, self.font_label)
self.cancel_btn.draw(screen, self.font_label)
pygame.display.flip()
class LoadGameDialog:
"""Dialog for selecting a save to load"""
def __init__(self, game, save_system):
self.game = game
self.save_system = save_system
self.font_title = pygame.font.SysFont("consolas", 36, bold=True)
self.font_label = pygame.font.SysFont("consolas", 18)
self.font_small = pygame.font.SysFont("consolas", 14)
self.saves = self.save_system.get_save_files()
self.selected_index = 0
self.scroll_offset = 0
self.visible_saves = 5
self.button_width = 120
self.button_height = 50
self.load_btn = Button(
SCREEN_WIDTH // 2 - 260,
SCREEN_HEIGHT - 100,
self.button_width,
self.button_height,
"Load",
(50, 150, 50),
(255, 255, 255),
(100, 200, 100)
)
self.delete_btn = Button(
SCREEN_WIDTH // 2 - 65,
SCREEN_HEIGHT - 100,
self.button_width,
self.button_height,
"Delete",
(150, 50, 50),
(255, 255, 255),
(200, 100, 100)
)
self.cancel_btn = Button(
SCREEN_WIDTH // 2 + 140,
SCREEN_HEIGHT - 100,
self.button_width,
self.button_height,
"Cancel",
(100, 100, 150),
(255, 255, 255),
(150, 150, 200)
)
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return "cancel"
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
return "cancel"
elif event.key == pygame.K_UP:
self.selected_index = max(0, self.selected_index - 1)
elif event.key == pygame.K_DOWN:
self.selected_index = min(len(self.saves) - 1, self.selected_index + 1)
elif event.key == pygame.K_RETURN:
if self.saves:
return f"load:{self.selected_index}"
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if self.load_btn.is_clicked(event.pos):
if self.saves:
return f"load:{self.selected_index}"
elif self.delete_btn.is_clicked(event.pos):
if self.saves:
return f"delete:{self.selected_index}"
elif self.cancel_btn.is_clicked(event.pos):
return "cancel"
for i in range(self.visible_saves):
save_idx = self.scroll_offset + i
if save_idx < len(self.saves):
y = 150 + i * 80
if pygame.Rect(50, y, SCREEN_WIDTH - 100, 70).collidepoint(event.pos):
self.selected_index = save_idx
elif event.button == 4:
self.scroll_offset = max(0, self.scroll_offset - 1)
elif event.button == 5:
self.scroll_offset = min(max(0, len(self.saves) - self.visible_saves),
self.scroll_offset + 1)
return None
def update(self):
mouse_pos = pygame.mouse.get_pos()
self.load_btn.check_hover(mouse_pos)
self.delete_btn.check_hover(mouse_pos)
self.cancel_btn.check_hover(mouse_pos)
if self.selected_index < self.scroll_offset:
self.scroll_offset = self.selected_index
elif self.selected_index >= self.scroll_offset + self.visible_saves:
self.scroll_offset = self.selected_index - self.visible_saves + 1
def draw(self, screen):
screen.fill(BACKGROUND_COLOR)
title = self.font_title.render("Load Game", True, (255, 255, 255))
title_rect = title.get_rect(center=(SCREEN_WIDTH // 2, 50))
screen.blit(title, title_rect)
if not self.saves:
no_saves = self.font_label.render("No saves found!", True, (200, 100, 100))
screen.blit(no_saves, (SCREEN_WIDTH // 2 - no_saves.get_width() // 2, SCREEN_HEIGHT // 2))
else:
for i in range(self.visible_saves):
save_idx = self.scroll_offset + i
if save_idx < len(self.saves):
save = self.saves[save_idx]
y = 150 + i * 80
if save_idx == self.selected_index:
pygame.draw.rect(screen, (60, 100, 140), pygame.Rect(50, y, SCREEN_WIDTH - 100, 70))
else:
pygame.draw.rect(screen, (40, 40, 40), pygame.Rect(50, y, SCREEN_WIDTH - 100, 70))
pygame.draw.rect(screen, (150, 150, 150), pygame.Rect(50, y, SCREEN_WIDTH - 100, 70), 2)
filename = save["name"]
parts = filename.replace("save_", "").replace(".json", "").split("_")
world_name = parts[0] if parts else "Unknown"
name_text = self.font_label.render(f"World: {world_name}", True, (255, 255, 255))
screen.blit(name_text, (70, y + 10))
from datetime import datetime
mod_time = datetime.fromtimestamp(save["modified"]).strftime("%Y-%m-%d %H:%M:%S")
time_text = self.font_small.render(f"Saved: {mod_time}", True, (200, 200, 200))
screen.blit(time_text, (70, y + 40))
self.load_btn.draw(screen, self.font_label)
self.delete_btn.draw(screen, self.font_label)
self.cancel_btn.draw(screen, self.font_label)
pygame.display.flip()
class PauseMenu:
"""Pause menu overlay"""
def __init__(self, game):
self.game = game
self.button_width = 250
self.button_height = 50
self.button_x = (SCREEN_WIDTH - self.button_width) // 2
self.resume_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2 - 100,
self.button_width,
self.button_height,
"Resume",
(50, 150, 50),
(255, 255, 255),
(100, 200, 100)
)
self.save_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2,
self.button_width,
self.button_height,
"Save Game",
(50, 100, 150),
(255, 255, 255),
(100, 150, 200)
)
self.backup_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2 + 60,
self.button_width,
self.button_height,
"Backup",
(150, 150, 50),
(255, 255, 255),
(200, 200, 100)
)
self.quit_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2 + 120,
self.button_width,
self.button_height,
"Quit to Menu",
(150, 50, 50),
(255, 255, 255),
(200, 100, 100)
)
self.font_title = pygame.font.SysFont("consolas", 48, bold=True)
self.font_button = pygame.font.SysFont("consolas", 20)
self.message = ""
self.message_timer = 0
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return "quit"
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
return "resume"
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if self.resume_btn.is_clicked(event.pos):
return "resume"
elif self.save_btn.is_clicked(event.pos):
self.show_message("Game saved!")
return "save"
elif self.backup_btn.is_clicked(event.pos):
self.show_message("Backup created!")
return "backup"
elif self.quit_btn.is_clicked(event.pos):
return "quit_to_menu"
return None
def show_message(self, text):
self.message = text
self.message_timer = 2.0
def update(self, dt):
mouse_pos = pygame.mouse.get_pos()
self.resume_btn.check_hover(mouse_pos)
self.save_btn.check_hover(mouse_pos)
self.backup_btn.check_hover(mouse_pos)
self.quit_btn.check_hover(mouse_pos)
if self.message_timer > 0:
self.message_timer -= dt
def draw(self, screen):
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
overlay.set_alpha(128)
overlay.fill((0, 0, 0))
screen.blit(overlay, (0, 0))
title = self.font_title.render("PAUSED", True, (255, 255, 255))
title_rect = title.get_rect(center=(SCREEN_WIDTH // 2, 80))
screen.blit(title, title_rect)
self.resume_btn.draw(screen, self.font_button)
self.save_btn.draw(screen, self.font_button)
self.backup_btn.draw(screen, self.font_button)
self.quit_btn.draw(screen, self.font_button)
if self.message_timer > 0:
message = self.font_button.render(self.message, True, (100, 255, 100))
message_rect = message.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT - 50))
screen.blit(message, message_rect)
pygame.display.flip()
class DeathScreen:
"""Death screen with respawn and quit options"""
def __init__(self, game):
self.game = game
self.button_width = 250
self.button_height = 60
self.button_x = (SCREEN_WIDTH - self.button_width) // 2
self.respawn_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2 - 50,
self.button_width,
self.button_height,
"Respawn",
(50, 150, 50),
(255, 255, 255),
(100, 200, 100)
)
self.quit_btn = Button(
self.button_x,
SCREEN_HEIGHT // 2 + 50,
self.button_width,
self.button_height,
"Quit to Menu",
(150, 50, 50),
(255, 255, 255),
(200, 100, 100)
)
self.font_title = pygame.font.SysFont("consolas", 72, bold=True)
self.font_button = pygame.font.SysFont("consolas", 24)
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return "quit"
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if self.respawn_btn.is_clicked(event.pos):
return "respawn"
elif self.quit_btn.is_clicked(event.pos):
return "quit_to_menu"
return None
def update(self, dt):
mouse_pos = pygame.mouse.get_pos()
self.respawn_btn.check_hover(mouse_pos)
self.quit_btn.check_hover(mouse_pos)
def draw(self, screen):
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
overlay.set_alpha(200)
overlay.fill((0, 0, 0))
screen.blit(overlay, (0, 0))
title = self.font_title.render("YOU DIED", True, (255, 0, 0))
title_rect = title.get_rect(center=(SCREEN_WIDTH // 2, 100))
screen.blit(title, title_rect)
self.respawn_btn.draw(screen, self.font_button)
self.quit_btn.draw(screen, self.font_button)
pygame.display.flip()