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("DERRARIA", 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()