import pygame from engine.plants import RARITY_COLORS from engine.relics import RELIC_POOL BG = (24, 30, 24) TEXT = (230, 235, 230) MUTED = (150, 155, 150) HINT = (255, 220, 120) PANEL = (30, 36, 30) PANEL_BORDER = (150, 190, 150) CARD = (36, 44, 36) CARD_OWNED = (46, 54, 42) GOLD = (245, 200, 90) OWNED = (110, 220, 255) # bright cyan: distinct from the legendary gold border TIER_ORDER = ("common", "rare", "epic", "legendary") TIER_LABELS = {"common": "Common", "rare": "Rare", "epic": "Epic", "legendary": "Legendary"} def _wrap(text, font, width): words = text.split() lines = [] cur = "" for w in words: if font.size(cur + " " + w)[0] > width and cur: lines.append(cur) cur = w else: cur = (cur + " " + w).strip() if cur: lines.append(cur) return lines class RelicsOverview: """Full-screen overlay listing every blessing, grouped by rarity, with the ones collected this run highlighted. Click a relic to inspect it up close. Host screens draw this onto their own surface (no pygame display ownership), so it can open mid-battle without tearing down the window.""" def __init__(self, screen, bold, small, tiny, pool=None, owned_keys=()): self.screen = screen self.bold = bold self.small = small self.tiny = tiny self.pool = list(pool if pool is not None else RELIC_POOL) self.owned = set(owned_keys) self.scroll = 0 self.detail = None w, h = screen.get_size() pw = min(760, w - 60) ph = min(500, h - 140) self.panel = pygame.Rect((w - pw) // 2, (h - ph) // 2, pw, ph) pad = 24 self.cards_top = self.panel.y + 78 self.inner = pygame.Rect(self.panel.x + pad, self.cards_top, self.panel.w - 2 * pad, self.panel.bottom - 20 - self.cards_top) self.close_rect = pygame.Rect(self.panel.right - 74, self.panel.y + 14, 58, 28) rank = {t: i for i, t in enumerate(TIER_ORDER)} self.pool.sort(key=lambda r: (rank.get(r.rarity, 9), r.name.lower())) cols = 3 gap = 14 cw = (self.inner.w - gap * (cols - 1)) // cols ch = 118 self.cards = [] x, y = self.inner.x, self.inner.y for i, r in enumerate(self.pool): self.cards.append((r, pygame.Rect(x, y, cw, ch))) if (i + 1) % cols == 0: x = self.inner.x y += ch + gap else: x += cw + gap self.content_h = y - self.inner.y if self.cards else 0 self.scroll_max = max(0, self.content_h - self.inner.h) def handle_wheel(self, y): if self.detail is not None: return self.scroll = max(0, min(self.scroll_max, self.scroll - y * 40)) def handle_click(self, pos): if self.detail is not None: self.detail = None return None if self.close_rect.collidepoint(pos): return "close" if not self.inner.collidepoint(pos): return "close" if not self.panel.collidepoint(pos) else None for r, rect in self.cards: draw_rect = rect.move(0, -self.scroll) if draw_rect.collidepoint(pos) and draw_rect.colliderect(self.inner): self.detail = r return None return None def _draw_card(self, r, rect): owned = r.key in self.owned bg = CARD_OWNED if owned else CARD border = OWNED if owned else RARITY_COLORS.get(r.rarity, (150, 150, 150)) pygame.draw.rect(self.screen, bg, rect, border_radius=8) pygame.draw.rect(self.screen, border, rect, 3 if owned else 1, border_radius=8) name = self.small.render(r.name, True, RARITY_COLORS.get(r.rarity, TEXT)) self.screen.blit(name, (rect.x + 10, rect.y + 8)) if owned: tag = self.tiny.render("OWNED", True, OWNED) self.screen.blit(tag, (rect.right - tag.get_width() - 8, rect.y + 10)) y = rect.y + 34 for ln in _wrap(r.desc, self.tiny, rect.w - 20)[:3]: self.screen.blit(self.tiny.render(ln, True, MUTED), (rect.x + 10, y)) y += 16 tip = self.tiny.render(f"{TIER_LABELS.get(r.rarity, r.rarity)}" + (" · click to inspect" if owned else " · not owned"), True, OWNED if owned else (110, 120, 110)) self.screen.blit(tip, (rect.x + 10, rect.bottom - 18)) def _draw_detail(self): r = self.detail if r is None: return shade = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA) shade.fill((0, 0, 0, 130)) self.screen.blit(shade, (0, 0)) w, h = 440, 230 panel = pygame.Rect((self.screen.get_width() - w) // 2, (self.screen.get_height() - h) // 2, w, h) pygame.draw.rect(self.screen, (36, 44, 36), panel, border_radius=10) pygame.draw.rect(self.screen, RARITY_COLORS.get(r.rarity, PANEL_BORDER), panel, 2, border_radius=10) name = self.bold.render(r.name, True, RARITY_COLORS.get(r.rarity, TEXT)) self.screen.blit(name, name.get_rect(midtop=(panel.centerx, panel.y + 22))) rar = self.small.render(TIER_LABELS.get(r.rarity, r.rarity).capitalize(), True, MUTED) self.screen.blit(rar, rar.get_rect(midtop=(panel.centerx, panel.y + 54))) y = panel.y + 92 for ln in _wrap(r.desc, self.small, panel.w - 48)[:5]: line = self.small.render(ln, True, TEXT) self.screen.blit(line, line.get_rect(midtop=(panel.centerx, y))) y += 26 owned = r.key in self.owned st = self.tiny.render("OWNED this run" if owned else "Not owned yet", True, OWNED if owned else MUTED) self.screen.blit(st, st.get_rect(midtop=(panel.centerx, y + 4))) close = self.tiny.render("Click anywhere to close", True, MUTED) self.screen.blit(close, close.get_rect(center=(panel.centerx, panel.bottom - 20))) def draw(self, mouse): overlay = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA) overlay.fill((0, 0, 0, 180)) self.screen.blit(overlay, (0, 0)) pygame.draw.rect(self.screen, PANEL, self.panel, border_radius=12) pygame.draw.rect(self.screen, PANEL_BORDER, self.panel, 2, border_radius=12) title = self.bold.render("All blessings", True, HINT) self.screen.blit(title, title.get_rect(midtop=(self.panel.centerx, self.panel.y + 14))) sub = self.tiny.render( f"{len(self.owned)} owned this run · cyan border = collected · click a card to inspect", True, MUTED) self.screen.blit(sub, sub.get_rect(midtop=(self.panel.centerx, self.panel.y + 44))) pygame.draw.rect(self.screen, (90, 110, 90), self.close_rect, border_radius=6) pygame.draw.rect(self.screen, PANEL_BORDER, self.close_rect, 1, border_radius=6) c = self.small.render("Close", True, TEXT) self.screen.blit(c, c.get_rect(center=self.close_rect.center)) old = self.screen.get_clip() self.screen.set_clip(self.inner) for r, rect in self.cards: draw_rect = rect.move(0, -self.scroll) if not draw_rect.colliderect(self.inner): continue self._draw_card(r, draw_rect) self.screen.set_clip(old) if self.scroll_max > 0: hint = ("▲" if self.scroll > 0 else "") + ("▼" if self.scroll < self.scroll_max else "") if hint: ht = self.tiny.render(hint, True, MUTED) self.screen.blit(ht, ht.get_rect(midbottom=(self.inner.centerx, self.inner.bottom))) if self.detail is not None: self._draw_detail()