small update
boss prototype, auto planter, visual reworks, bug fixes
This commit is contained in:
+1
-1
@@ -27,7 +27,7 @@ class ExtractionScreen:
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption("Plants — Ability Extraction")
|
||||
pygame.display.set_caption("Flora — Ability Extraction")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 30)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
|
||||
+162
-27
@@ -4,10 +4,11 @@ from assets import icon, load_sprite
|
||||
from engine import collection, runes
|
||||
from engine.battle import Battle
|
||||
from engine.dominance import estimate_dominance, resolve_instantly
|
||||
from engine.plants import PASSIVES, RARITY_COLORS, RARITY_LABELS, plant_rarity
|
||||
from engine.growth import EVOLVE_COST, EVOLVE_STAGE_AT_LEVEL, can_evolve
|
||||
from engine.plants import PASSIVES, POOL, RARITY_COLORS, RARITY_LABELS, plant_rarity
|
||||
from engine.relics import RELIC_POOL
|
||||
from engine.skills import EFF_DOT, EFF_HOT, TARGET_ALLY, TARGET_ANY, TARGET_ENEMY
|
||||
from engine.types import type_multiplier
|
||||
from engine.types import TYPE_BEATS, type_multiplier
|
||||
from engine.unit import ROLE_NAMES, ROLE_TANK, TEAM_ENEMY, TEAM_PLAYER
|
||||
from ui.menu import _bracket_text, _draw_evo_badge
|
||||
from ui.relics_overview import RelicsOverview
|
||||
@@ -19,8 +20,8 @@ TYPE_COLORS = {
|
||||
"radiant": (245, 200, 90),
|
||||
"nocturnal": (150, 120, 220),
|
||||
"ice": (130, 200, 235),
|
||||
"garden": (120, 200, 120),
|
||||
"wildflower": (235, 130, 170),
|
||||
"garden": (235, 130, 170),
|
||||
"wildflower": (120, 200, 120),
|
||||
}
|
||||
WIN_W = 1040
|
||||
WIN_H = 720
|
||||
@@ -64,13 +65,13 @@ class Game:
|
||||
self.stage = stage
|
||||
self.relics = list(relics)
|
||||
if tower and stage:
|
||||
caption = f"Plants — Tower — Floor {stage}"
|
||||
caption = f"Flora — Tower — Floor {stage}"
|
||||
elif stage:
|
||||
caption = f"Plants — Floor {stage}"
|
||||
caption = f"Flora — Floor {stage}"
|
||||
elif tower:
|
||||
caption = "Plants — Tower Battle"
|
||||
caption = "Flora — Tower Battle"
|
||||
else:
|
||||
caption = "Plants — 4v4 Turn Battle (prototype)"
|
||||
caption = "Flora — 4v4 Turn Battle (prototype)"
|
||||
pygame.display.set_caption(caption)
|
||||
self.clock = pygame.time.Clock()
|
||||
self.battle = battle
|
||||
@@ -108,6 +109,12 @@ class Game:
|
||||
self._dt = 0.0
|
||||
self.back_rect = pygame.Rect(12, 12, 90, 32)
|
||||
self.auto_btn = pygame.Rect(112, 12, 90, 32)
|
||||
# "Back" confirmation modal + transient tank-redirect notice.
|
||||
self.confirm_back = False
|
||||
self.confirm_ok_rect = pygame.Rect(0, 0, 0, 0)
|
||||
self.confirm_no_rect = pygame.Rect(0, 0, 0, 0)
|
||||
self.tank_notice = ""
|
||||
self.tank_notice_until = 0
|
||||
# Auto-battle unlocks once the player has cleared floor 20 in the
|
||||
# normal stage battles (floor_progress counts the next unbeaten floor).
|
||||
self.auto_unlocked = collection.floor_progress() > 20
|
||||
@@ -206,6 +213,13 @@ class Game:
|
||||
running = False
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
||||
pos = to_logical(event.pos, self.scale)
|
||||
if self.confirm_back:
|
||||
if self.confirm_ok_rect.collidepoint(pos):
|
||||
self.confirm_back = False
|
||||
self.battle.winner = TEAM_ENEMY # insta-lose
|
||||
elif self.confirm_no_rect.collidepoint(pos):
|
||||
self.confirm_back = False
|
||||
continue
|
||||
if self.auto_unlocked and self.auto_btn.collidepoint(pos):
|
||||
self.auto_battle = not self.auto_battle
|
||||
continue
|
||||
@@ -219,8 +233,11 @@ class Game:
|
||||
self.reward_acknowledged = True
|
||||
continue
|
||||
if self.back_rect.collidepoint(pos):
|
||||
outcome = "back"
|
||||
running = False
|
||||
if self.battle.winner is None:
|
||||
self.confirm_back = True
|
||||
else:
|
||||
outcome = "back"
|
||||
running = False
|
||||
else:
|
||||
self._handle_click(pos)
|
||||
elif event.type == pygame.MOUSEMOTION:
|
||||
@@ -294,18 +311,27 @@ class Game:
|
||||
if rect.collidepoint(pos):
|
||||
if sk.cooldown_left > 0:
|
||||
return
|
||||
if cur.acted and not sk.extra_action:
|
||||
return # already acted this turn; only extra-action skills remain
|
||||
if oblivion and sk is not weakest:
|
||||
return
|
||||
if sk.target in (TARGET_ENEMY, TARGET_ALLY, TARGET_ANY):
|
||||
valid = b.valid_skill_targets(cur, sk)
|
||||
if len(valid) == 1:
|
||||
# Only one thing it can hit (e.g. last enemy alive, or a
|
||||
# tank blocking everything): use it straight away.
|
||||
self.pending_skill = None
|
||||
self._cast(sk, valid[0])
|
||||
if self.pending_skill is sk:
|
||||
# Clicking the selected skill AGAIN casts it (even if only
|
||||
# one target is available). With several valid targets the
|
||||
# player still needs to click one.
|
||||
valid = b.valid_skill_targets(cur, sk)
|
||||
if len(valid) == 1:
|
||||
self.pending_skill = None
|
||||
self._cast(sk, valid[0])
|
||||
else:
|
||||
self.pending_skill = sk
|
||||
else:
|
||||
self.pending_skill = sk if self.pending_skill is not sk else None
|
||||
# First click only SELECTS the skill (it must be clicked
|
||||
# again, or a target clicked, to cast).
|
||||
self.pending_skill = sk
|
||||
else:
|
||||
# No target needed (self / AoE): cast right away.
|
||||
self.pending_skill = None
|
||||
self._cast(sk, None)
|
||||
return
|
||||
@@ -319,8 +345,12 @@ class Game:
|
||||
if self.pending_skill:
|
||||
target = self._resolve_target(unit, self.pending_skill)
|
||||
if target:
|
||||
if self._was_tank_redirect(unit, self.pending_skill, target):
|
||||
self._set_tank_notice()
|
||||
self._cast(self.pending_skill, target)
|
||||
self.pending_skill = None
|
||||
self.pending_skill = None
|
||||
# Invalid target (e.g. clicking an enemy with an ally heal/shield
|
||||
# selected): do nothing and KEEP the skill selected.
|
||||
return
|
||||
if unit.team != TEAM_PLAYER:
|
||||
best = next((s for s in sorted(cur.skills, key=lambda s: s.priority)
|
||||
@@ -328,8 +358,19 @@ class Game:
|
||||
if best:
|
||||
target = self._resolve_target(unit, best)
|
||||
if target:
|
||||
if self._was_tank_redirect(unit, best, target):
|
||||
self._set_tank_notice()
|
||||
self._cast(best, target)
|
||||
|
||||
def _was_tank_redirect(self, unit, skill, target):
|
||||
"""True when `unit` (a backline enemy) was redirected to a tank."""
|
||||
return (skill.target == "enemy" and unit.team != TEAM_PLAYER
|
||||
and unit.alive and unit.role != "tank" and unit is not target)
|
||||
|
||||
def _set_tank_notice(self):
|
||||
self.tank_notice = "A tank is protecting that unit — attack the tank first!"
|
||||
self.tank_notice_until = pygame.time.get_ticks() + 3000
|
||||
|
||||
def _resolve_target(self, unit, skill):
|
||||
b = self.battle
|
||||
if unit in b.valid_skill_targets(b.current, skill):
|
||||
@@ -380,6 +421,8 @@ class Game:
|
||||
|
||||
def _update(self, dt):
|
||||
b = self.battle
|
||||
if self.tank_notice and pygame.time.get_ticks() >= self.tank_notice_until:
|
||||
self.tank_notice = ""
|
||||
self._consume_events()
|
||||
self._advance_hit_queue(dt)
|
||||
if b.winner is not None:
|
||||
@@ -603,9 +646,9 @@ class Game:
|
||||
elif self.stage:
|
||||
title_text = f"Floor {self.stage}"
|
||||
elif self.tower:
|
||||
title_text = "Plants — Tower Battle"
|
||||
title_text = "Flora — Tower Battle"
|
||||
else:
|
||||
title_text = "Plants — 4v4 Turn Battle"
|
||||
title_text = "Flora — 4v4 Turn Battle"
|
||||
title = self.font.render(title_text, True, TEXT)
|
||||
self.screen.blit(title, title.get_rect(center=(WIN_W // 2, 24)))
|
||||
pygame.draw.rect(self.screen, BTN, self.back_rect, border_radius=6)
|
||||
@@ -636,6 +679,75 @@ class Game:
|
||||
self._draw_reward_popup()
|
||||
if self.relic_overview is not None:
|
||||
self.relic_overview.draw(self.mouse_pos)
|
||||
if self.tank_notice and pygame.time.get_ticks() < self.tank_notice_until:
|
||||
self._draw_tank_notice()
|
||||
if self.confirm_back:
|
||||
self._draw_confirm_back()
|
||||
|
||||
def _draw_tank_notice(self):
|
||||
t = self.small.render(self.tank_notice, True, (255, 210, 90))
|
||||
bg = pygame.Surface((t.get_width() + 24, t.get_height() + 10), pygame.SRCALPHA)
|
||||
bg.fill((20, 24, 20, 210))
|
||||
rect = t.get_rect(center=(WIN_W // 2, 96))
|
||||
self.screen.blit(bg, (rect.x - 12, rect.y - 5))
|
||||
self.screen.blit(t, rect)
|
||||
|
||||
def _draw_confirm_back(self):
|
||||
overlay = pygame.Surface((WIN_W, WIN_H), pygame.SRCALPHA)
|
||||
overlay.fill((0, 0, 0, 140))
|
||||
self.screen.blit(overlay, (0, 0))
|
||||
w, h = 460, 170
|
||||
panel = pygame.Rect((WIN_W - w) // 2, (WIN_H - h) // 2, w, h)
|
||||
pygame.draw.rect(self.screen, (32, 38, 32), panel, border_radius=12)
|
||||
pygame.draw.rect(self.screen, (150, 70, 70), panel, 2, border_radius=12)
|
||||
title = self.bold.render("Leave the battle?", True, TEXT)
|
||||
self.screen.blit(title, title.get_rect(center=(panel.centerx, panel.y + 30)))
|
||||
msg = self.small.render("You will lose this battle.", True, MUTED)
|
||||
self.screen.blit(msg, msg.get_rect(center=(panel.centerx, panel.y + 58)))
|
||||
self.confirm_ok_rect = pygame.Rect(panel.centerx - 120, panel.bottom - 54, 100, 36)
|
||||
self.confirm_no_rect = pygame.Rect(panel.centerx + 20, panel.bottom - 54, 100, 36)
|
||||
pygame.draw.rect(self.screen, (150, 70, 70), self.confirm_ok_rect, border_radius=8)
|
||||
ok = self.bold.render("Leave", True, TEXT)
|
||||
self.screen.blit(ok, ok.get_rect(center=self.confirm_ok_rect.center))
|
||||
pygame.draw.rect(self.screen, BTN, self.confirm_no_rect, border_radius=8)
|
||||
no = self.bold.render("Cancel", True, TEXT)
|
||||
self.screen.blit(no, no.get_rect(center=self.confirm_no_rect.center))
|
||||
|
||||
def _defeat_tips(self):
|
||||
"""Contextual tips for the defeat screen, based on the player's state
|
||||
and the units they just brought into the lost battle."""
|
||||
tips = []
|
||||
if len(collection.owned()) < len(POOL) and sum(collection.pack_counts().values()) > 0:
|
||||
tips.append("Try to open packs to gain more allies!")
|
||||
if collection.available_water() >= 1:
|
||||
tips.append("You can use water to level up your plants and critters.")
|
||||
if collection.free_runes():
|
||||
tips.append("You can use runes to improve your fighting abilities.")
|
||||
used = list(self.battle.players)
|
||||
enemy_types = [e.plant_type for e in self.battle.enemies]
|
||||
for u in used:
|
||||
prog = collection.get_progress(u.name)
|
||||
if can_evolve(prog["level"], prog["stage"], prog.get("duplicates", 0)):
|
||||
tips.append("Try evolving your plants for better stats.")
|
||||
break
|
||||
for u in used:
|
||||
for etype in enemy_types:
|
||||
if TYPE_BEATS.get(etype) == u.plant_type:
|
||||
tips.append(f"Make sure to use the types to your advantage! "
|
||||
f"{u.plant_type} is weak against {etype}.")
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
for u in used:
|
||||
prog = collection.get_progress(u.name)
|
||||
target = prog["stage"] + 1
|
||||
if (target in EVOLVE_STAGE_AT_LEVEL
|
||||
and prog["level"] >= EVOLVE_STAGE_AT_LEVEL[target]
|
||||
and prog.get("duplicates", 0) < EVOLVE_COST[target]):
|
||||
tips.append("You can plant seeds from your plants to gain more of them.")
|
||||
break
|
||||
return tips
|
||||
|
||||
def _draw_reward_popup(self):
|
||||
b = self.battle
|
||||
@@ -656,8 +768,11 @@ class Game:
|
||||
kind = it.get("kind", "")
|
||||
# Display scaled to match the rest of the UI (aspect preserved).
|
||||
img = fit_surface(img, 60 if kind == "rune" else 80)
|
||||
tiles.append((img, label, img.get_width(), img.get_height() + 20,
|
||||
kind == "rune"))
|
||||
# Tile width must also fit the label, or a rune + pack in the
|
||||
# same row overlap (the rune's label spills into the pack tile).
|
||||
label_w = self.bold.size(label)[0]
|
||||
tiles.append((img, label, max(img.get_width(), label_w),
|
||||
img.get_height() + 20, kind == "rune"))
|
||||
else:
|
||||
tiles.append((None, label, max(90, self.bold.size(label)[0] + 20), 30, False))
|
||||
|
||||
@@ -698,7 +813,9 @@ class Game:
|
||||
if target >= max(img.get_width(), img.get_height()):
|
||||
continue
|
||||
img2 = fit_surface(img, target)
|
||||
tiles[i] = (img2, t[1], img2.get_width(), img2.get_height() + 20, t[4])
|
||||
label_w = self.bold.size(t[1])[0]
|
||||
tiles[i] = (img2, t[1], max(img2.get_width(), label_w),
|
||||
img2.get_height() + 20, t[4])
|
||||
changed = True
|
||||
if not changed:
|
||||
break
|
||||
@@ -706,6 +823,14 @@ class Game:
|
||||
panel_w = max(440, max_row_w + pad * 2)
|
||||
panel_h = head_h + tiles_h + 46
|
||||
|
||||
# Defeat tips: contextual advice shown in the popup's text box.
|
||||
tip_lines = []
|
||||
if b.winner != TEAM_PLAYER:
|
||||
for tip in self._defeat_tips():
|
||||
tip_lines.extend(wrap_text(tip, self.small, panel_w - pad * 2))
|
||||
tips_h = len(tip_lines) * 20 + (14 if tip_lines else 0)
|
||||
panel_h += tips_h
|
||||
|
||||
panel = pygame.Rect((WIN_W - panel_w) // 2, (WIN_H - panel_h) // 2, panel_w, panel_h)
|
||||
pygame.draw.rect(self.screen, (32, 38, 32), panel, border_radius=12)
|
||||
border = (150, 190, 150) if b.winner == TEAM_PLAYER else (200, 90, 90)
|
||||
@@ -722,7 +847,8 @@ class Game:
|
||||
x = panel.centerx - total // 2
|
||||
for img, label, tw, th, _ in r:
|
||||
if img is not None:
|
||||
self.screen.blit(img, (x, y))
|
||||
self.screen.blit(img, img.get_rect(center=(x + tw // 2,
|
||||
y + img.get_height() // 2)))
|
||||
lr = self.bold.render(label, True, HINT)
|
||||
self.screen.blit(lr, lr.get_rect(center=(x + tw // 2, y + img.get_height() + 10)))
|
||||
else:
|
||||
@@ -732,6 +858,12 @@ class Game:
|
||||
self.screen.blit(t, t.get_rect(center=box.center))
|
||||
x += tw + gap
|
||||
y += row_h[i] + 14
|
||||
if tip_lines:
|
||||
ty = panel.y + head_h + tiles_h + 10
|
||||
for line in tip_lines:
|
||||
t = self.small.render(line, True, (215, 220, 215))
|
||||
self.screen.blit(t, (panel.x + pad, ty))
|
||||
ty += 20
|
||||
hint = self.tiny.render("Click to continue", True, (150, 155, 150))
|
||||
self.screen.blit(hint, hint.get_rect(center=(panel.centerx, panel.bottom - 16)))
|
||||
|
||||
@@ -778,6 +910,8 @@ class Game:
|
||||
parts.append(f"Heal {sum(a for a, _ in stacks)}/t")
|
||||
if "form" in unit.status:
|
||||
parts.append(unit.status["form"].capitalize())
|
||||
if unit.status.get("boss_stacks", 0):
|
||||
parts.append(f"Power {unit.status['boss_stacks']}/3")
|
||||
return " | ".join(parts)
|
||||
|
||||
def _draw_cards(self):
|
||||
@@ -804,10 +938,11 @@ class Game:
|
||||
bar_color = (80, 220, 90) if pct > 0.5 else (220, 180, 40) if pct > 0.25 else (220, 60, 60)
|
||||
fill = int(bar_w * pct)
|
||||
pygame.draw.rect(self.screen, bar_color, (hp_x, rect.y + 7, fill, 10))
|
||||
shield = unit.status.get("shield", 0)
|
||||
shield = sum(amount for amount, _ in unit.status.get("shield", []))
|
||||
if shield > 0 and fill > 0:
|
||||
# The shield bar shows the FULL shield value, independent of the
|
||||
# unit's current HP (a low-HP unit still shows its whole shield).
|
||||
sh_w = int(bar_w * min(1.0, shield / unit.max_hp))
|
||||
sh_w = min(sh_w, fill)
|
||||
if sh_w > 0:
|
||||
tint = pygame.Surface((sh_w, 10), pygame.SRCALPHA)
|
||||
tint.fill((120, 185, 240, 140))
|
||||
@@ -1046,7 +1181,7 @@ class Game:
|
||||
if ready:
|
||||
oblivion_skill = min(ready, key=lambda s: s.power)
|
||||
for i, (sk, rect) in enumerate(self.skill_rects):
|
||||
ready = sk.cooldown_left <= 0
|
||||
ready = sk.cooldown_left <= 0 and (not unit.acted or sk.extra_action)
|
||||
blocked = oblivion and sk is not oblivion_skill
|
||||
if sk is self.pending_skill:
|
||||
bg = (120, 120, 40)
|
||||
|
||||
+200
-77
@@ -4,7 +4,7 @@ import pygame
|
||||
|
||||
from assets import icon as icon_path
|
||||
from assets import load_sprite
|
||||
from engine import collection
|
||||
from engine import bosses, collection
|
||||
from engine import runes
|
||||
from engine.growth import MAX_LEVEL, can_evolve, compute_stats, evolve_cost, xp_to_next
|
||||
from engine.plants import (HP_MULT, PASSIVES, POOL, RARITY_COLORS, RARITY_LABELS,
|
||||
@@ -34,8 +34,8 @@ TYPE_COLORS = {
|
||||
"radiant": (245, 200, 90),
|
||||
"nocturnal": (150, 120, 220),
|
||||
"ice": (130, 200, 235),
|
||||
"garden": (120, 200, 120),
|
||||
"wildflower": (235, 130, 170),
|
||||
"garden": (235, 130, 170),
|
||||
"wildflower": (120, 200, 120),
|
||||
}
|
||||
def _draw_star(screen, center, radius, color):
|
||||
import math
|
||||
@@ -84,7 +84,7 @@ class Menu:
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption("Plants")
|
||||
pygame.display.set_caption("Flora")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 36)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
@@ -118,6 +118,7 @@ class Menu:
|
||||
self.seed_rect = pygame.Rect(250, 352, 220, 30)
|
||||
self.runes_rect = pygame.Rect(480, 352, 170, 30)
|
||||
self.auto_farmer_rect = pygame.Rect(0, 0, 0, 0)
|
||||
self.auto_planter_rect = pygame.Rect(0, 0, 0, 0)
|
||||
self.runes_deconstruct_rect = pygame.Rect(WIN_W - 220, 16, 110, 34)
|
||||
self.runes_info_rect = pygame.Rect(WIN_W - 100, 16, 84, 34)
|
||||
self.runes_deconstruct = False
|
||||
@@ -153,6 +154,11 @@ class Menu:
|
||||
self.redeem_rect = pygame.Rect(WIN_W - 250, 50, 234, 30)
|
||||
self.history_rect = pygame.Rect(WIN_W - 250, 84, 234, 26)
|
||||
self.history_open = False
|
||||
self.shop_mode = False
|
||||
self.shop_rect = pygame.Rect(WIN_W - 250, 114, 234, 28)
|
||||
self.shop_btns = {}
|
||||
self.shop_msg = ""
|
||||
self.shop_msg_until = 0
|
||||
self._layout_packs()
|
||||
self.diff_rects = {i: pygame.Rect(220, 130 + (i - 1) * 74, 460, 58) for i in range(1, 6)}
|
||||
self.floor_rects = {i: pygame.Rect(220, 130 + (i - 1) * 74, 460, 58) for i in range(1, 6)}
|
||||
@@ -178,8 +184,9 @@ class Menu:
|
||||
self.coll_sort = None
|
||||
self.collection_tab = "plants"
|
||||
self.collection_tab_btns = {
|
||||
"plants": pygame.Rect(WIN_W // 2 - 170, 100, 150, 30),
|
||||
"runes": pygame.Rect(WIN_W // 2 + 20, 100, 150, 30),
|
||||
"plants": pygame.Rect(WIN_W // 2 - 250, 100, 150, 30),
|
||||
"bosses": pygame.Rect(WIN_W // 2 - 70, 100, 150, 30),
|
||||
"runes": pygame.Rect(WIN_W // 2 + 110, 100, 150, 30),
|
||||
}
|
||||
self.coll_rune_tier = 0
|
||||
self.coll_rune_color = None
|
||||
@@ -206,27 +213,18 @@ class Menu:
|
||||
"level": pygame.Rect(767, 50, 56, 28),
|
||||
"stage": pygame.Rect(827, 50, 58, 28),
|
||||
}
|
||||
self.convert_mode = False
|
||||
self.convert_rect = pygame.Rect(WIN_W // 2 - 330, WIN_H - 56, 210, 36)
|
||||
self.soil_msg = None # dict describing a soil popup (harvest result)
|
||||
self.soil_fert_info = False
|
||||
self.water_info = False
|
||||
self.soil_notice = ""
|
||||
self.soil_notice_until = 0
|
||||
self.convert_pack_btns = {}
|
||||
for i, pt in enumerate(REWARD_PACK_TYPES):
|
||||
col = i % 3
|
||||
row = i // 3
|
||||
x = WIN_W // 2 - 165 + col * 115
|
||||
y = WIN_H // 2 - 25 + row * 52
|
||||
self.convert_pack_btns[pt] = pygame.Rect(x, y, 105, 42)
|
||||
self._layout_buttons()
|
||||
|
||||
def _reinit(self):
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption("Plants")
|
||||
pygame.display.set_caption("Flora")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 36)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
@@ -293,16 +291,16 @@ class Menu:
|
||||
"Plants": pygame.Rect(x0, y, bw, bh),
|
||||
"Battle": pygame.Rect(x0 + bw + gap, y, bw, bh),
|
||||
}
|
||||
# Side buttons (square-ish): Seeds & Runes left, Soil & Collection right.
|
||||
# Side buttons (square-ish): Soil & Seeds left, Runes & Collection right.
|
||||
# Fitted into the lower half, keeping clear of the bottom row.
|
||||
sq, sq_gap = 104, 16
|
||||
ry = WIN_H - 72 - 2 * sq - sq_gap - 10
|
||||
self.side_buttons = {
|
||||
"Runes": pygame.Rect(34, ry, sq, sq),
|
||||
"Soil": pygame.Rect(34, ry, sq, sq),
|
||||
"Seeds": pygame.Rect(34, ry + sq + sq_gap, sq, sq),
|
||||
}
|
||||
rx = WIN_W - 34 - sq
|
||||
self.side_buttons["Soil"] = pygame.Rect(rx, ry, sq, sq)
|
||||
self.side_buttons["Runes"] = pygame.Rect(rx, ry, sq, sq)
|
||||
self.side_buttons["Collection"] = pygame.Rect(rx, ry + sq + sq_gap, sq, sq)
|
||||
|
||||
def _seed_pack_sprite(self, pt, size=110):
|
||||
@@ -356,6 +354,7 @@ class Menu:
|
||||
collection.accrue_coins()
|
||||
collection.accrue_fertilizer()
|
||||
collection.auto_farm()
|
||||
collection.auto_plant()
|
||||
self._passive_check_at = pygame.time.get_ticks() + 1000
|
||||
if self.page != self._prev_page:
|
||||
self.grid_scroll = 0
|
||||
@@ -399,14 +398,16 @@ class Menu:
|
||||
if self.type_info is not None:
|
||||
self.type_info = None
|
||||
return None
|
||||
if self.convert_mode:
|
||||
for pt, rect in self.convert_pack_btns.items():
|
||||
if self.shop_mode:
|
||||
if self.shop_msg and pygame.time.get_ticks() < self.shop_msg_until:
|
||||
return None # a "bought" notice is showing; ignore clicks briefly
|
||||
for pt, rect in self.shop_btns.items():
|
||||
if rect.collidepoint(pos):
|
||||
if collection.convert_water_to_pack(pt):
|
||||
self.owned = collection.owned()
|
||||
self.convert_mode = False
|
||||
err = collection.buy_pack(pt)
|
||||
self.shop_msg = err or f"Bought a {pt.capitalize()} pack!"
|
||||
self.shop_msg_until = pygame.time.get_ticks() + 1800
|
||||
return None
|
||||
self.convert_mode = False
|
||||
self.shop_mode = False
|
||||
return None
|
||||
if self.page == "seeds":
|
||||
if self.history_open:
|
||||
@@ -421,6 +422,9 @@ class Menu:
|
||||
if self.redeem_rect.collidepoint(pos):
|
||||
collection.redeem_tower_pack()
|
||||
return None
|
||||
if self.shop_rect.collidepoint(pos):
|
||||
self.shop_mode = True
|
||||
return None
|
||||
for pt, (card, btn) in self.pack_rects.items():
|
||||
if btn.move(0, -self.seeds_scroll).collidepoint(pos):
|
||||
result = collection.open_pack(pt)
|
||||
@@ -453,6 +457,8 @@ class Menu:
|
||||
if rect.collidepoint(pos):
|
||||
self.collection_tab = key
|
||||
return None
|
||||
if self.collection_tab == "bosses":
|
||||
return None # boss cards are informational only
|
||||
if self.collection_tab == "runes":
|
||||
if self._click_coll_rune_filter(pos):
|
||||
return None
|
||||
@@ -488,9 +494,6 @@ class Menu:
|
||||
self.water_mode = not self.water_mode
|
||||
return None
|
||||
if self.water_mode:
|
||||
if self.convert_rect.collidepoint(pos) and collection.available_water() >= 5:
|
||||
self.convert_mode = True
|
||||
return None
|
||||
if self.water_exit_btn.collidepoint(pos):
|
||||
self.water_mode = False
|
||||
return None
|
||||
@@ -592,6 +595,16 @@ class Menu:
|
||||
"activated" if active else "deactivated")
|
||||
self.soil_notice_until = pygame.time.get_ticks() + 1800
|
||||
return None
|
||||
if self.auto_planter_rect.collidepoint(pos):
|
||||
if not collection.auto_planter_owned():
|
||||
reason = collection.buy_auto_planter()
|
||||
self.soil_notice = reason or "Auto planter bought!"
|
||||
else:
|
||||
active = collection.toggle_auto_planter()
|
||||
self.soil_notice = "Auto planter " + (
|
||||
"activated" if active else "deactivated")
|
||||
self.soil_notice_until = pygame.time.get_ticks() + 1800
|
||||
return None
|
||||
for idx, rect in self.soil_seed_rects:
|
||||
if rect.collidepoint(pos):
|
||||
collection.plant_seed(idx)
|
||||
@@ -727,8 +740,8 @@ class Menu:
|
||||
if self.type_info is not None:
|
||||
draw_type_popup(self.screen, WIN_W, WIN_H, self.type_info,
|
||||
self.bold, self.small, self.tiny)
|
||||
if self.convert_mode:
|
||||
self._draw_convert_pack()
|
||||
if self.shop_mode:
|
||||
self._draw_shop()
|
||||
if self.tower_info:
|
||||
self._draw_tower_info()
|
||||
if self.soil_msg:
|
||||
@@ -796,7 +809,7 @@ class Menu:
|
||||
|
||||
def _draw_menu(self):
|
||||
self._draw_resources()
|
||||
title = self.font.render("Plants", True, TEXT)
|
||||
title = self.font.render("Flora", True, TEXT)
|
||||
self.screen.blit(title, title.get_rect(center=(WIN_W // 2, 86)))
|
||||
y = 130
|
||||
for line in OVERVIEW:
|
||||
@@ -819,7 +832,7 @@ class Menu:
|
||||
else:
|
||||
t = self.bold.render(label, True, TEXT)
|
||||
self.screen.blit(t, t.get_rect(center=rect.center))
|
||||
# side buttons: Seeds (left), Soil & Collection (right)
|
||||
# side buttons: Soil & Seeds (left), Runes & Collection (right)
|
||||
soil_locked = not collection.soil_unlocked()
|
||||
for label, rect in self.side_buttons.items():
|
||||
hover = rect.collidepoint(mouse)
|
||||
@@ -857,8 +870,8 @@ class Menu:
|
||||
n = collection.SOIL_GATE_PLANTS - len(collection.owned())
|
||||
hint = self.tiny.render(f"Own {collection.SOIL_GATE_PLANTS} plants to unlock Soil"
|
||||
f" ({n} to go)", True, (110, 115, 110))
|
||||
self.screen.blit(hint, hint.get_rect(midright=(self.side_buttons["Soil"].right,
|
||||
self.side_buttons["Soil"].top - 6)))
|
||||
self.screen.blit(hint, hint.get_rect(midleft=(self.side_buttons["Soil"].left,
|
||||
self.side_buttons["Soil"].top - 6)))
|
||||
|
||||
def _draw_battle_setup(self):
|
||||
self._draw_page("Select Floors",
|
||||
@@ -972,6 +985,12 @@ class Menu:
|
||||
pygame.draw.rect(self.screen, (150, 190, 150), self.history_rect, 1, border_radius=6)
|
||||
ht = self.tiny.render("History (last 10)", True, TEXT)
|
||||
self.screen.blit(ht, ht.get_rect(center=self.history_rect.center))
|
||||
shov = self.shop_rect.collidepoint(mouse)
|
||||
pygame.draw.rect(self.screen, (90, 120, 90) if shov else BTN,
|
||||
self.shop_rect, border_radius=6)
|
||||
pygame.draw.rect(self.screen, (150, 190, 150), self.shop_rect, 1, border_radius=6)
|
||||
sh = self.tiny.render("Pack Shop", True, TEXT)
|
||||
self.screen.blit(sh, sh.get_rect(center=self.shop_rect.center))
|
||||
# scrollable grid of packs, each showing its sprite at native size
|
||||
old_clip = self.screen.get_clip()
|
||||
self.screen.set_clip(self.seeds_scroll_area)
|
||||
@@ -1143,7 +1162,7 @@ class Menu:
|
||||
self.screen.blit(rp, (panel.right - 50, y))
|
||||
low, high = runes.per_rune_chance(runes.PACK_DROP_CHANCE)
|
||||
sub = self.tiny.render(
|
||||
f"{rpct:.0f}% for any rune — brown is rarer than the others",
|
||||
f"{rpct:.0f}% for any rune — brown & pearl are rarer than the others",
|
||||
True, MUTED)
|
||||
self.screen.blit(sub, (panel.x + 150, y + 22))
|
||||
if pt in ALL_TYPES:
|
||||
@@ -1152,26 +1171,54 @@ class Menu:
|
||||
close = self.tiny.render("Click anywhere to close", True, MUTED)
|
||||
self.screen.blit(close, close.get_rect(center=(panel.centerx, panel.bottom - 24)))
|
||||
|
||||
def _draw_convert_pack(self):
|
||||
def _draw_shop(self):
|
||||
overlay = pygame.Surface((WIN_W, WIN_H), pygame.SRCALPHA)
|
||||
overlay.fill((0, 0, 0, 160))
|
||||
self.screen.blit(overlay, (0, 0))
|
||||
w, h = 380, 210
|
||||
w, h = 640, 400
|
||||
panel = pygame.Rect((WIN_W - w) // 2, (WIN_H - h) // 2, w, h)
|
||||
pygame.draw.rect(self.screen, PANEL, panel, border_radius=12)
|
||||
pygame.draw.rect(self.screen, (60, 70, 60), panel, 2, border_radius=12)
|
||||
title = self.bold.render("Convert 5 water → a pack of your choice", True, TEXT)
|
||||
self.screen.blit(title, title.get_rect(center=(panel.centerx, panel.y + 28)))
|
||||
pygame.draw.rect(self.screen, ACCENT, panel, 2, border_radius=12)
|
||||
title = self.bold.render("Pack Shop", True, ACCENT)
|
||||
self.screen.blit(title, title.get_rect(center=(panel.centerx, panel.y + 26)))
|
||||
coins = collection.available_coins()
|
||||
bal = self.small.render(f"Coins: {coins}", True, TEXT)
|
||||
total_w = (16 + 4) + bal.get_width()
|
||||
bx = panel.centerx - total_w // 2
|
||||
if self.coin_icon:
|
||||
self.screen.blit(self.coin_icon, (bx, panel.y + 52))
|
||||
self.screen.blit(bal, (bx + 20, panel.y + 52))
|
||||
buyable = ("lesser", "general", "radiant", "nocturnal", "ice", "garden", "wildflower")
|
||||
cols = 4
|
||||
cw, chh, gap = 132, 118, 12
|
||||
x0 = panel.x + 24
|
||||
y0 = panel.y + 96
|
||||
self.shop_btns = {}
|
||||
mouse = self.mouse_pos
|
||||
for pt, rect in self.convert_pack_btns.items():
|
||||
for i, pt in enumerate(buyable):
|
||||
x = x0 + (i % cols) * (cw + gap)
|
||||
y = y0 + (i // cols) * (chh + gap)
|
||||
rect = pygame.Rect(x, y, cw, chh)
|
||||
cost = collection.PACK_COSTS[pt]
|
||||
hover = rect.collidepoint(mouse)
|
||||
bg = (90, 120, 90) if hover else BTN
|
||||
pygame.draw.rect(self.screen, bg, rect, border_radius=6)
|
||||
pygame.draw.rect(self.screen, (150, 190, 150), rect, 1, border_radius=6)
|
||||
tcol = TYPE_COLORS.get(pt, TEXT)
|
||||
draw_type_icon(self.screen, pt, rect.center, self.tiny, tcol, 26, center=True)
|
||||
hint = self.tiny.render("Click elsewhere to cancel", True, MUTED)
|
||||
self.screen.blit(hint, hint.get_rect(center=(panel.centerx, panel.bottom - 18)))
|
||||
afford = coins >= cost
|
||||
pygame.draw.rect(self.screen, (90, 120, 90) if (hover and afford) else (BTN if afford else (45, 50, 45)),
|
||||
rect, border_radius=8)
|
||||
pygame.draw.rect(self.screen, (150, 190, 150) if afford else (70, 80, 70),
|
||||
rect, 1, border_radius=8)
|
||||
spr = self._seed_pack_sprite(pt, 48)
|
||||
if spr:
|
||||
self.screen.blit(spr, spr.get_rect(center=(rect.centerx, rect.y + 30)))
|
||||
nm = self.tiny.render(pt.capitalize(), True, TEXT if afford else MUTED)
|
||||
self.screen.blit(nm, nm.get_rect(center=(rect.centerx, rect.y + 60)))
|
||||
pr = self.tiny.render(f"{cost} coins", True, HINT if afford else MUTED)
|
||||
self.screen.blit(pr, pr.get_rect(center=(rect.centerx, rect.y + 86)))
|
||||
self.shop_btns[pt] = rect
|
||||
note = self.tiny.render("Tower packs can't be bought.", True, MUTED)
|
||||
self.screen.blit(note, note.get_rect(center=(panel.centerx, panel.bottom - 18)))
|
||||
if self.shop_msg and pygame.time.get_ticks() < self.shop_msg_until:
|
||||
mt = self.small.render(self.shop_msg, True, DANGER if "Not enough" in self.shop_msg else (120, 220, 120))
|
||||
self.screen.blit(mt, mt.get_rect(center=(panel.centerx, panel.y + 356)))
|
||||
|
||||
def _draw_soil(self):
|
||||
self._draw_page("Soil", "Retrieve seeds from plants, plant them, and grow copies.")
|
||||
@@ -1217,6 +1264,26 @@ class Menu:
|
||||
f"Buy Auto Farmer — {collection.AUTO_FARMER_COST} coins",
|
||||
True, TEXT if can_afford else MUTED)
|
||||
self.screen.blit(af_txt, af_txt.get_rect(center=self.auto_farmer_rect.center))
|
||||
ap_owned = collection.auto_planter_owned()
|
||||
ap_active = collection.auto_planter_active()
|
||||
self.auto_planter_rect = pygame.Rect(WIN_W - 270, 180, 250, 28)
|
||||
ap_hover = self.auto_planter_rect.collidepoint(self.mouse_pos)
|
||||
pygame.draw.rect(self.screen,
|
||||
(90, 120, 90) if (ap_active or ap_hover) else BTN,
|
||||
self.auto_planter_rect, border_radius=6)
|
||||
pygame.draw.rect(self.screen,
|
||||
(150, 190, 150) if ap_active else (70, 80, 70),
|
||||
self.auto_planter_rect, 1, border_radius=6)
|
||||
if ap_owned:
|
||||
ap_txt = self.tiny.render(
|
||||
"Auto Planter: active" if ap_active else "Auto Planter: off",
|
||||
True, ACCENT if ap_active else MUTED)
|
||||
else:
|
||||
can_afford = collection.available_coins() >= collection.AUTO_PLANTER_COST
|
||||
ap_txt = self.tiny.render(
|
||||
f"Buy Auto Planter — {collection.AUTO_PLANTER_COST} coins",
|
||||
True, TEXT if can_afford else MUTED)
|
||||
self.screen.blit(ap_txt, ap_txt.get_rect(center=self.auto_planter_rect.center))
|
||||
if self.soil_notice and pygame.time.get_ticks() < self.soil_notice_until:
|
||||
nt = self.bold.render(self.soil_notice, True, (235, 130, 130))
|
||||
self.screen.blit(nt, nt.get_rect(center=(WIN_W // 2, 148)))
|
||||
@@ -1492,14 +1559,6 @@ class Menu:
|
||||
self._draw_grid(owned, reveal_all=True, level_mode=self.water_mode, data=data)
|
||||
if self.water_mode:
|
||||
water = collection.available_water()
|
||||
can_convert = water >= 5
|
||||
pygame.draw.rect(self.screen, BTN_HOVER if can_convert else (45, 50, 45),
|
||||
self.convert_rect, border_radius=6)
|
||||
pygame.draw.rect(self.screen, (150, 190, 150) if can_convert else (70, 80, 70),
|
||||
self.convert_rect, 1, border_radius=6)
|
||||
ct = self.tiny.render(f"Convert 5 water → pack" + ("" if can_convert else " (need 5)"),
|
||||
True, TEXT if can_convert else MUTED)
|
||||
self.screen.blit(ct, ct.get_rect(center=self.convert_rect.center))
|
||||
pygame.draw.rect(self.screen, BTN_HOVER, self.water_exit_btn, border_radius=6)
|
||||
pygame.draw.rect(self.screen, (150, 190, 150), self.water_exit_btn, 2, border_radius=6)
|
||||
et = self.tiny.render("Exit water mode", True, TEXT)
|
||||
@@ -1574,10 +1633,12 @@ class Menu:
|
||||
bcol = DANGER if self.runes_deconstruct_msg else ACCENT
|
||||
if self.runes_target is None:
|
||||
btxt = self.runes_deconstruct_msg or \
|
||||
"Deconstruct — pick a rune to improve, then sacrifice 10 runes"
|
||||
f"Deconstruct — pick a rune to improve, then sacrifice " \
|
||||
f"{runes.RUNE_SACRIFICE_COST} runes"
|
||||
else:
|
||||
btxt = self.runes_deconstruct_msg or \
|
||||
f"Pick 10 runes to sacrifice ({len(self.runes_sacrifice)}/10) — " \
|
||||
f"Pick {runes.RUNE_SACRIFICE_COST} runes to sacrifice " \
|
||||
f"({len(self.runes_sacrifice)}/{runes.RUNE_SACRIFICE_COST}) — " \
|
||||
"click the target again to change it"
|
||||
b = self.tiny.render(btxt, True, bcol)
|
||||
self.screen.blit(b, b.get_rect(center=(WIN_W // 2, 124)))
|
||||
@@ -1609,7 +1670,7 @@ class Menu:
|
||||
if ic:
|
||||
self.screen.blit(ic, ic.get_rect(center=(rect.centerx, rect.y + 30)))
|
||||
owner = collection.rune_location(rune["id"])
|
||||
max_pct = runes.BROWN_PCT_MAX if rune["color"] == "brown" else runes.PCT_MAX
|
||||
max_pct = runes.max_pct(rune["color"])
|
||||
is_max = rune.get("pct", 0) >= max_pct
|
||||
is_target = self.runes_deconstruct and self.runes_target \
|
||||
and rune["id"] == self.runes_target["id"]
|
||||
@@ -1841,7 +1902,7 @@ class Menu:
|
||||
if not rune:
|
||||
return
|
||||
if self.runes_target is None:
|
||||
max_pct = runes.BROWN_PCT_MAX if rune["color"] == "brown" else runes.PCT_MAX
|
||||
max_pct = runes.max_pct(rune["color"])
|
||||
if rune.get("pct", 0) >= max_pct:
|
||||
self.runes_deconstruct_msg = f"{rune['name']} is already at max roll"
|
||||
return
|
||||
@@ -1857,13 +1918,14 @@ class Menu:
|
||||
return
|
||||
if rune_id in self.runes_sacrifice:
|
||||
self.runes_sacrifice.remove(rune_id)
|
||||
elif len(self.runes_sacrifice) >= 10:
|
||||
self.runes_deconstruct_msg = "Already picked 10 runes"
|
||||
elif len(self.runes_sacrifice) >= runes.RUNE_SACRIFICE_COST:
|
||||
self.runes_deconstruct_msg = \
|
||||
f"Already picked {runes.RUNE_SACRIFICE_COST} runes"
|
||||
return
|
||||
else:
|
||||
self.runes_sacrifice.append(rune_id)
|
||||
self.runes_deconstruct_msg = ""
|
||||
if len(self.runes_sacrifice) == 10:
|
||||
if len(self.runes_sacrifice) == runes.RUNE_SACRIFICE_COST:
|
||||
self._open_runes_confirm()
|
||||
return
|
||||
|
||||
@@ -1884,7 +1946,7 @@ class Menu:
|
||||
overlay = pygame.Surface((WIN_W, WIN_H), pygame.SRCALPHA)
|
||||
overlay.fill((0, 0, 0, 160))
|
||||
self.screen.blit(overlay, (0, 0))
|
||||
w, h = 560, 400
|
||||
w, h = 560, 450
|
||||
panel = pygame.Rect((WIN_W - w) // 2, (WIN_H - h) // 2, w, h)
|
||||
pygame.draw.rect(self.screen, PANEL, panel, border_radius=12)
|
||||
pygame.draw.rect(self.screen, (150, 190, 150), panel, 2, border_radius=12)
|
||||
@@ -1943,11 +2005,12 @@ class Menu:
|
||||
pygame.draw.rect(self.screen, DANGER, panel, 2, border_radius=12)
|
||||
rune = self.runes_confirm["rune"]
|
||||
name = rune["name"]
|
||||
max_pct = runes.BROWN_PCT_MAX if rune["color"] == "brown" else runes.PCT_MAX
|
||||
max_pct = runes.max_pct(rune["color"])
|
||||
current = rune.get("pct", 0)
|
||||
title = self.bold.render("Reroll rune?", True, TEXT)
|
||||
msg = self.small.render(
|
||||
f"Sacrifice 10 runes to improve {name} to {current + 1}–{max_pct}%?",
|
||||
f"Sacrifice {runes.RUNE_SACRIFICE_COST} runes to improve {name} to "
|
||||
f"{current + 1}–{max_pct}%?",
|
||||
True, TEXT)
|
||||
self.screen.blit(title, title.get_rect(center=(panel.centerx, panel.y + 34)))
|
||||
self.screen.blit(msg, msg.get_rect(center=(panel.centerx, panel.y + 60)))
|
||||
@@ -1979,14 +2042,73 @@ class Menu:
|
||||
rect, border_radius=6)
|
||||
pygame.draw.rect(self.screen, (150, 190, 150) if active else (70, 80, 70),
|
||||
rect, 1, border_radius=6)
|
||||
t = self.tiny.render("Plants" if key == "plants" else "Runes", True, TEXT)
|
||||
t = self.tiny.render(
|
||||
"Plants" if key == "plants" else "Bosses" if key == "bosses" else "Runes",
|
||||
True, TEXT)
|
||||
self.screen.blit(t, t.get_rect(center=rect.center))
|
||||
if self.collection_tab == "runes":
|
||||
self._draw_collection_runes()
|
||||
elif self.collection_tab == "bosses":
|
||||
self._draw_collection_bosses()
|
||||
else:
|
||||
self._draw_grid(self.names, reveal_all=False, show_evo=False,
|
||||
reveal_owned=True, y0=144)
|
||||
|
||||
def _draw_collection_bosses(self):
|
||||
mouse = self.mouse_pos
|
||||
fought = collection.bosses_fought()
|
||||
cw, ch, gap = 230, 260, 20
|
||||
cols = 3
|
||||
total = cols * cw + (cols - 1) * gap
|
||||
x0 = (WIN_W - total) // 2
|
||||
y0 = 150
|
||||
self.boss_cards = []
|
||||
for i, (name, spec) in enumerate(bosses.BOSS_SPECS.items()):
|
||||
x = x0 + (i % cols) * (cw + gap)
|
||||
y = y0 + (i // cols) * (ch + gap)
|
||||
rect = pygame.Rect(x, y, cw, ch)
|
||||
self.boss_cards.append((name, rect))
|
||||
known = name in fought
|
||||
pygame.draw.rect(self.screen, PANEL, rect, border_radius=8)
|
||||
pygame.draw.rect(self.screen, (150, 120, 80) if known else (60, 70, 60),
|
||||
rect, 1, border_radius=8)
|
||||
spr = self._boss_sprite(name)
|
||||
sprite_rect = pygame.Rect(rect.centerx - 60, rect.y + 12, 120, 120)
|
||||
if spr:
|
||||
ic = fit_surface(spr, 120)
|
||||
if known:
|
||||
self.screen.blit(ic, ic.get_rect(center=sprite_rect.center))
|
||||
else:
|
||||
dim = ic.copy()
|
||||
dim.set_alpha(70)
|
||||
self.screen.blit(dim, dim.get_rect(center=sprite_rect.center))
|
||||
pygame.draw.rect(self.screen, (70, 78, 70), sprite_rect, 1)
|
||||
nm = self.small.render(name if known else "???", True, TEXT if known else MUTED)
|
||||
self.screen.blit(nm, nm.get_rect(center=(rect.centerx, rect.y + 150)))
|
||||
if known:
|
||||
atk, spd, dfn, hp = spec["stats"]
|
||||
st = self.tiny.render(f"Atk {atk} Spd {spd} Def {dfn} HP {int(hp * HP_MULT)}",
|
||||
True, MUTED)
|
||||
self.screen.blit(st, st.get_rect(center=(rect.centerx, rect.y + 176)))
|
||||
lines = wrap_text(spec["desc"], self.tiny, cw - 24)
|
||||
ty = rect.y + 198
|
||||
for ln in lines[:3]:
|
||||
self.screen.blit(self.tiny.render(ln, True, (170, 175, 170)),
|
||||
(rect.x + 12, ty))
|
||||
ty += 16
|
||||
drops = "Drops: " + ", ".join(spec["drop_runes"])
|
||||
dr = self.tiny.render(drops, True, (235, 200, 120))
|
||||
self.screen.blit(dr, dr.get_rect(center=(rect.centerx, rect.bottom - 20)))
|
||||
else:
|
||||
t = self.tiny.render("Not yet fought", True, (110, 115, 110))
|
||||
self.screen.blit(t, t.get_rect(center=(rect.centerx, rect.y + 180)))
|
||||
|
||||
def _boss_sprite(self, name):
|
||||
key = f"boss_{name}"
|
||||
if key not in self.seed_pack_sprites:
|
||||
self.seed_pack_sprites[key] = load_sprite(bosses.boss_sprite(name))
|
||||
return self.seed_pack_sprites[key]
|
||||
|
||||
def _draw_collection_runes(self):
|
||||
mouse = self.mouse_pos
|
||||
self._draw_coll_rune_filters()
|
||||
@@ -2108,8 +2230,7 @@ class Menu:
|
||||
self.screen.blit(ic, ic.get_rect(center=(panel.centerx, panel.y + 78)))
|
||||
eff = self.small.render(runes.summary(color), True, TEXT)
|
||||
self.screen.blit(eff, eff.get_rect(center=(panel.centerx, panel.y + 122)))
|
||||
dmg = self.tiny.render(runes.effect_text(color, runes.PCT_MAX if color != "brown" else runes.BROWN_PCT_MAX),
|
||||
True, MUTED)
|
||||
dmg = self.tiny.render(runes.effect_text(color, runes.max_pct(color)), True, MUTED)
|
||||
self.screen.blit(dmg, dmg.get_rect(center=(panel.centerx, panel.y + 146)))
|
||||
t1 = self.small.render(f"Tier {runes.tier(color)} · Max roll {runes.roll_range(color)}",
|
||||
True, HINT)
|
||||
@@ -2124,8 +2245,7 @@ class Menu:
|
||||
if self.coll_rune_sort_best:
|
||||
inv = collection.rune_inventory()
|
||||
best = {c: max((r.get("pct", 0) for r in inv if r["color"] == c), default=0) for c in colors}
|
||||
colors.sort(key=lambda c: best[c] / (runes.BROWN_PCT_MAX if c == "brown" else runes.PCT_MAX),
|
||||
reverse=True)
|
||||
colors.sort(key=lambda c: best[c] / runes.max_pct(c), reverse=True)
|
||||
return colors
|
||||
|
||||
def _draw_detail(self):
|
||||
@@ -2390,12 +2510,15 @@ class Menu:
|
||||
sprite_rect = pygame.Rect(rect.centerx - 44, rect.y + 12, 88, 88)
|
||||
if spr:
|
||||
ic = fit_surface(spr, 88)
|
||||
self.screen.blit(ic, ic.get_rect(center=sprite_rect.center))
|
||||
if spr and not found:
|
||||
dim = ic.copy()
|
||||
dim.set_alpha(70)
|
||||
self.screen.blit(dim, dim.get_rect(center=sprite_rect.center))
|
||||
pygame.draw.rect(self.screen, (70, 78, 70), sprite_rect, 1)
|
||||
if found:
|
||||
self.screen.blit(ic, ic.get_rect(center=sprite_rect.center))
|
||||
else:
|
||||
# Dim only the sprite (matches the runes tab): blitting the
|
||||
# full icon first then the dim copy would cancel out.
|
||||
dim = ic.copy()
|
||||
dim.set_alpha(70)
|
||||
self.screen.blit(dim, dim.get_rect(center=sprite_rect.center))
|
||||
pygame.draw.rect(self.screen, (70, 78, 70), sprite_rect, 1)
|
||||
name_txt = "???" if not found else name
|
||||
t = self.tiny.render(name_txt, True, TEXT if found else MUTED)
|
||||
self.screen.blit(t, t.get_rect(center=(rect.centerx, rect.y + 110)))
|
||||
|
||||
+177
-16
@@ -2,10 +2,11 @@ import pygame
|
||||
|
||||
from assets import icon as icon_path
|
||||
from assets import load_sprite
|
||||
from engine import collection
|
||||
from engine import bosses, collection
|
||||
from engine.growth import compute_stats
|
||||
from engine.plants import HP_MULT, PASSIVES, POOL, RARITY_COLORS, RARITY_LABELS, plant_rarity
|
||||
from engine.roster import floor_enemy_level, roll_enemies
|
||||
from engine.types import ALL_TYPES
|
||||
from engine.unit import ROLE_ATTACKER, ROLE_ICONS, ROLE_NAMES, ROLE_SUPPORT, ROLE_TANK
|
||||
from ui.menu import _bracket_text, _draw_evo_badge
|
||||
from ui.runes import draw_rune_chips
|
||||
@@ -17,8 +18,8 @@ TYPE_COLORS = {
|
||||
"radiant": (245, 200, 90),
|
||||
"nocturnal": (150, 120, 220),
|
||||
"ice": (130, 200, 235),
|
||||
"garden": (120, 200, 120),
|
||||
"wildflower": (235, 130, 170),
|
||||
"garden": (235, 130, 170),
|
||||
"wildflower": (120, 200, 120),
|
||||
}
|
||||
|
||||
BG = (24, 30, 24)
|
||||
@@ -44,7 +45,7 @@ class TeamSelect:
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption(header or "Plants — Tower")
|
||||
pygame.display.set_caption(header or "Flora — Tower")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 30)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
@@ -68,6 +69,8 @@ class TeamSelect:
|
||||
self.pool_names = sorted((n for n in self.names if n in collection.owned()),
|
||||
key=lambda n: (n not in self.favs, n))
|
||||
self.sprites = {n: self._load(n) for n in self.names}
|
||||
for bname in bosses.BOSS_SPECS:
|
||||
self.sprites[bname] = self._load(bosses.boss_sprite(bname))
|
||||
self.rune_icons = {}
|
||||
self.role_icons = {}
|
||||
self.allies = []
|
||||
@@ -82,6 +85,12 @@ class TeamSelect:
|
||||
self._layout_enemy_cards()
|
||||
self._layout_enemy_strip()
|
||||
self._layout_slots()
|
||||
self.pool_filter = None # optional elemental type filter for the ally pool
|
||||
self.pool_exclude_type = None # right-click: show everything BUT this type
|
||||
self.pool_type_rects = []
|
||||
self.pool_role = None # optional role filter for the ally pool
|
||||
self.pool_exclude_role = None # right-click: show everything BUT this role
|
||||
self.pool_role_rects = []
|
||||
self._layout_pool()
|
||||
self.roll_rect = pygame.Rect(WIN_W // 2 - 260, WIN_H - 60, 170, 40)
|
||||
self.next_rect = pygame.Rect(WIN_W // 2 + 90, WIN_H - 60, 180, 40)
|
||||
@@ -157,13 +166,28 @@ class TeamSelect:
|
||||
sl_h, gap = SLOT_H, SLOT_GAP
|
||||
self.ally_rects = [pygame.Rect(24, 170 + i * (sl_h + gap), SLOT_W, sl_h) for i in range(n)]
|
||||
|
||||
def _display_pool(self):
|
||||
"""The ally pool, filtered by element type and/or role when active.
|
||||
Left-click filters include a type/role; right-click excludes it."""
|
||||
result = self.pool_names
|
||||
if self.pool_filter is not None:
|
||||
result = [n for n in result if POOL[n]["type"] == self.pool_filter]
|
||||
elif self.pool_exclude_type is not None:
|
||||
result = [n for n in result if POOL[n]["type"] != self.pool_exclude_type]
|
||||
if self.pool_role is not None:
|
||||
result = [n for n in result if POOL[n]["role"] == self.pool_role]
|
||||
elif self.pool_exclude_role is not None:
|
||||
result = [n for n in result if POOL[n]["role"] != self.pool_exclude_role]
|
||||
return result
|
||||
|
||||
def _layout_pool(self):
|
||||
self.pool_rects = {}
|
||||
cols = min(4, max(1, len(self.pool_names)))
|
||||
shown = self._display_pool()
|
||||
cols = min(4, max(1, len(shown)))
|
||||
cw, ch, gap = 170, 64, 10
|
||||
x0 = 290
|
||||
y0 = 170
|
||||
for i, name in enumerate(self.pool_names):
|
||||
for i, name in enumerate(shown):
|
||||
x = x0 + (i % cols) * (cw + gap)
|
||||
y = y0 + (i // cols) * (ch + gap)
|
||||
self.pool_rects[name] = pygame.Rect(x, y, cw, ch)
|
||||
@@ -171,7 +195,7 @@ class TeamSelect:
|
||||
self.pool_area = pygame.Rect(x0 - 4, y0 - 4,
|
||||
cols * cw + (cols - 1) * gap + 8,
|
||||
WIN_H - y0 - 100)
|
||||
rows = (len(self.pool_names) + cols - 1) // cols
|
||||
rows = (len(shown) + cols - 1) // cols
|
||||
self.pool_scroll_max = max(0, rows * (ch + gap) - gap - self.pool_area.h)
|
||||
self.pool_scroll = getattr(self, "pool_scroll", 0)
|
||||
self.pool_scroll = max(0, min(self.pool_scroll, self.pool_scroll_max))
|
||||
@@ -190,6 +214,10 @@ class TeamSelect:
|
||||
if not self.detail_open:
|
||||
self.pool_scroll = max(0, min(self.pool_scroll_max,
|
||||
self.pool_scroll - event.y * 40))
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
|
||||
pos = to_logical(event.pos, self.scale)
|
||||
if not self.detail_open:
|
||||
self._click_right(pos)
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
||||
pos = to_logical(event.pos, self.scale)
|
||||
if self.detail_open:
|
||||
@@ -269,12 +297,55 @@ class TeamSelect:
|
||||
if rect.collidepoint(pos) and i < len(self.enemies):
|
||||
self._open_detail(self.enemies[i], self.enemies)
|
||||
return None
|
||||
for ptype, rect in self.pool_type_rects:
|
||||
if rect.collidepoint(pos):
|
||||
self.pool_filter = ptype # None = "All", else the chosen type
|
||||
self.pool_exclude_type = None
|
||||
self.pool_scroll = 0
|
||||
self._layout_pool()
|
||||
return None
|
||||
for role, rect in self.pool_role_rects:
|
||||
if rect.collidepoint(pos):
|
||||
self.pool_role = role # None = "All", else the chosen role
|
||||
self.pool_exclude_role = None
|
||||
self.pool_scroll = 0
|
||||
self._layout_pool()
|
||||
return None
|
||||
for name, rect in self.pool_rects.items():
|
||||
if rect.move(0, -self.pool_scroll).collidepoint(pos):
|
||||
if name in self.allies:
|
||||
self.allies.remove(name) # click a selected plant to unselect it
|
||||
else:
|
||||
self._open_detail(name, self.pool_names)
|
||||
self._open_detail(name, self._display_pool())
|
||||
return None
|
||||
return None
|
||||
|
||||
def _click_right(self, pos):
|
||||
"""Right-click on a filter icon EXCLUDES that type/role (everything but it)."""
|
||||
if self.type_info is not None:
|
||||
self.type_info = None
|
||||
return None
|
||||
for ptype, rect in self.pool_type_rects:
|
||||
if rect.collidepoint(pos):
|
||||
if ptype is None:
|
||||
self.pool_filter = None
|
||||
self.pool_exclude_type = None
|
||||
else:
|
||||
self.pool_exclude_type = ptype
|
||||
self.pool_filter = None
|
||||
self.pool_scroll = 0
|
||||
self._layout_pool()
|
||||
return None
|
||||
for role, rect in self.pool_role_rects:
|
||||
if rect.collidepoint(pos):
|
||||
if role is None:
|
||||
self.pool_role = None
|
||||
self.pool_exclude_role = None
|
||||
else:
|
||||
self.pool_exclude_role = role
|
||||
self.pool_role = None
|
||||
self.pool_scroll = 0
|
||||
self._layout_pool()
|
||||
return None
|
||||
return None
|
||||
|
||||
@@ -334,8 +405,12 @@ class TeamSelect:
|
||||
return tag
|
||||
|
||||
def _draw_role_type_tag(self, name, pos, font, center=False, ptype=None):
|
||||
role_name = ROLE_NAMES.get(POOL[name]["role"], POOL[name]["role"])
|
||||
rs = font.render(role_name, True, RARITY_COLORS.get(plant_rarity(name), TEXT))
|
||||
if bosses.is_boss(name):
|
||||
role_name = "Boss"
|
||||
rs = font.render(role_name, True, (235, 200, 120))
|
||||
else:
|
||||
role_name = ROLE_NAMES.get(POOL[name]["role"], POOL[name]["role"])
|
||||
rs = font.render(role_name, True, RARITY_COLORS.get(plant_rarity(name), TEXT))
|
||||
sep = font.render(" · ", True, MUTED)
|
||||
ptype = ptype or collection.unit_type(name)
|
||||
tic = load_type_icon(ptype, 16) if ptype else None
|
||||
@@ -381,7 +456,7 @@ class TeamSelect:
|
||||
tag = self._draw_role_type_tag(name, (rect.centerx, rect.y + 178), self.small, center=True,
|
||||
ptype=self.enemy_types[i] if i < len(self.enemy_types) else None)
|
||||
self._draw_enemy_level(tag, i)
|
||||
stats = POOL[name]["stats"]
|
||||
stats = bosses.boss_stats(name) if bosses.is_boss(name) else POOL[name]["stats"]
|
||||
st = self.tiny.render(f"Atk {stats[0]} Spd {stats[1]} Def {stats[2]} HP {int(stats[3] * HP_MULT)}",
|
||||
True, MUTED)
|
||||
self.screen.blit(st, st.get_rect(center=(rect.centerx, rect.y + 216)))
|
||||
@@ -414,6 +489,7 @@ class TeamSelect:
|
||||
self._draw_enemy_strip()
|
||||
if self.show_slots:
|
||||
self._draw_slots(self.ally_rects, self.allies, "Your Allies")
|
||||
self._draw_pool_filters()
|
||||
self._draw_pool()
|
||||
self._draw_tower_enemies()
|
||||
for i, rect in enumerate(self.preset_rects):
|
||||
@@ -495,6 +571,69 @@ class TeamSelect:
|
||||
draw_rune_chips(self.screen, self._rune_icon, rune_colors,
|
||||
rect.right - 40 - chip_w, rect.y + 32)
|
||||
|
||||
def _draw_pool_filters(self):
|
||||
"""Type + role filter row above the ally pool: 'All' plus one icon per
|
||||
element and per role. Left-click highlights green (include only), and
|
||||
right-click highlights red (exclude that type/role)."""
|
||||
self.pool_type_rects = []
|
||||
self.pool_role_rects = []
|
||||
y, h = 142, 24
|
||||
INC = (120, 220, 120)
|
||||
EXC = (235, 110, 110)
|
||||
tl = self.tiny.render("Type:", True, MUTED)
|
||||
self.screen.blit(tl, (290, y + 5))
|
||||
x = 330
|
||||
all_rect = pygame.Rect(x, y, 36, h)
|
||||
active = self.pool_filter is None and self.pool_exclude_type is None
|
||||
pygame.draw.rect(self.screen, (90, 120, 90) if active else (45, 50, 45),
|
||||
all_rect, border_radius=4)
|
||||
pygame.draw.rect(self.screen, HINT if active else (70, 80, 70),
|
||||
all_rect, 1, border_radius=4)
|
||||
t = self.tiny.render("All", True, TEXT if active else MUTED)
|
||||
self.screen.blit(t, t.get_rect(center=all_rect.center))
|
||||
self.pool_type_rects.append((None, all_rect))
|
||||
x += 42
|
||||
for ptype in ALL_TYPES:
|
||||
rect = pygame.Rect(x, y, 24, h)
|
||||
inc = self.pool_filter == ptype
|
||||
exc = self.pool_exclude_type == ptype
|
||||
pygame.draw.rect(self.screen, (45, 50, 45), rect, border_radius=4)
|
||||
border = INC if inc else EXC if exc else (70, 80, 70)
|
||||
pygame.draw.rect(self.screen, border, rect, 2 if (inc or exc) else 1,
|
||||
border_radius=4)
|
||||
tic = load_type_icon(ptype, 22)
|
||||
if tic:
|
||||
self.screen.blit(tic, tic.get_rect(center=rect.center))
|
||||
self.pool_type_rects.append((ptype, rect))
|
||||
x += 28
|
||||
x += 36 # a little space between the type and role filters
|
||||
rl = self.tiny.render("Role:", True, MUTED)
|
||||
self.screen.blit(rl, (x, y + 5))
|
||||
x += 42
|
||||
all_role = pygame.Rect(x, y, 36, h)
|
||||
active = self.pool_role is None and self.pool_exclude_role is None
|
||||
pygame.draw.rect(self.screen, (90, 120, 90) if active else (45, 50, 45),
|
||||
all_role, border_radius=4)
|
||||
pygame.draw.rect(self.screen, HINT if active else (70, 80, 70),
|
||||
all_role, 1, border_radius=4)
|
||||
t = self.tiny.render("All", True, TEXT if active else MUTED)
|
||||
self.screen.blit(t, t.get_rect(center=all_role.center))
|
||||
self.pool_role_rects.append((None, all_role))
|
||||
x += 42
|
||||
for role in (ROLE_ATTACKER, ROLE_SUPPORT, ROLE_TANK):
|
||||
rect = pygame.Rect(x, y, 24, h)
|
||||
inc = self.pool_role == role
|
||||
exc = self.pool_exclude_role == role
|
||||
pygame.draw.rect(self.screen, (45, 50, 45), rect, border_radius=4)
|
||||
border = INC if inc else EXC if exc else (70, 80, 70)
|
||||
pygame.draw.rect(self.screen, border, rect, 2 if (inc or exc) else 1,
|
||||
border_radius=4)
|
||||
ric = self._role_icon(role, 22)
|
||||
if ric:
|
||||
self.screen.blit(ric, ric.get_rect(center=rect.center))
|
||||
self.pool_role_rects.append((role, rect))
|
||||
x += 28
|
||||
|
||||
def _draw_pool(self):
|
||||
old_clip = self.screen.get_clip()
|
||||
self.screen.set_clip(self.pool_area)
|
||||
@@ -574,7 +713,11 @@ class TeamSelect:
|
||||
name = self.detail_name
|
||||
if name is None:
|
||||
return
|
||||
spec = POOL[name]
|
||||
is_boss_unit = bosses.is_boss(name)
|
||||
spec = None if is_boss_unit else POOL[name]
|
||||
boss_stats_t = bosses.boss_stats(name) if is_boss_unit else None
|
||||
boss_skills = bosses.make_boss(name, 20).skills if is_boss_unit else None
|
||||
boss_desc = bosses.BOSS_SPECS[name]["desc"] if is_boss_unit else None
|
||||
shade = pygame.Surface((WIN_W, WIN_H))
|
||||
shade.fill((0, 0, 0))
|
||||
shade.set_alpha(120)
|
||||
@@ -592,8 +735,11 @@ class TeamSelect:
|
||||
self.screen.blit(ic, ic.get_rect(center=(self.detail_rect.x + 84, self.detail_rect.y + 100)))
|
||||
t = self.bold.render(name, True, TEXT)
|
||||
self.screen.blit(t, (self.detail_rect.x + 160, self.detail_rect.y + 34))
|
||||
rar = plant_rarity(name)
|
||||
rt = self.tiny.render(RARITY_LABELS.get(rar, rar), True, RARITY_COLORS.get(rar, TEXT))
|
||||
if is_boss_unit:
|
||||
rt = self.tiny.render("Boss", True, (235, 200, 120))
|
||||
else:
|
||||
rar = plant_rarity(name)
|
||||
rt = self.tiny.render(RARITY_LABELS.get(rar, rar), True, RARITY_COLORS.get(rar, TEXT))
|
||||
self.screen.blit(rt, (self.detail_rect.x + 160 + t.get_width() + 8, self.detail_rect.y + 38))
|
||||
enemy_type = None
|
||||
if self.detail_is_enemy:
|
||||
@@ -604,19 +750,34 @@ class TeamSelect:
|
||||
name, (self.detail_rect.x + 160, self.detail_rect.y + 62), self.small,
|
||||
ptype=enemy_type)
|
||||
|
||||
if not self.detail_is_enemy and name in collection.owned():
|
||||
if not self.detail_is_enemy and not is_boss_unit and name in collection.owned():
|
||||
prog = collection.get_progress(name)
|
||||
lv = self.tiny.render(f"Level {prog['level']} · Stage {prog['stage']}", True, HINT)
|
||||
self.screen.blit(lv, (self.detail_rect.x + 160, self.detail_rect.y + 120))
|
||||
atk, spd, dfn, hp = compute_stats(spec["stats"], prog["level"], prog["stage"])
|
||||
else:
|
||||
atk, spd, dfn, hp = spec["stats"]
|
||||
atk, spd, dfn, hp = boss_stats_t if is_boss_unit else spec["stats"]
|
||||
st = self.small.render(f"Atk {atk} Spd {spd}", True, TEXT)
|
||||
self.screen.blit(st, (self.detail_rect.x + 160, self.detail_rect.y + 148))
|
||||
st = self.small.render(f"Def {dfn} HP {int(hp * HP_MULT)}", True, TEXT)
|
||||
self.screen.blit(st, (self.detail_rect.x + 160, self.detail_rect.y + 174))
|
||||
|
||||
y = self.detail_rect.y + 210
|
||||
if is_boss_unit:
|
||||
self.screen.blit(self.bold.render("Boss", True, HINT), (self.detail_rect.x + 24, y))
|
||||
lines = wrap_text(" " + boss_desc, self.small, self.detail_rect.w - 48)
|
||||
for i, ln in enumerate(lines):
|
||||
self.screen.blit(self.small.render(ln, True, TEXT),
|
||||
(self.detail_rect.x + 24, y + 22 + i * 20))
|
||||
y += 26 + len(lines) * 20 + 2
|
||||
for sk in boss_skills:
|
||||
bracket = _bracket_text(sk)
|
||||
label = f"{sk.name} [{bracket}]" if bracket else sk.name
|
||||
self.screen.blit(self.bold.render(label, True, HINT), (self.detail_rect.x + 24, y))
|
||||
d = self.small.render(" " + sk.description, True, TEXT)
|
||||
self.screen.blit(d, (self.detail_rect.x + 24, y + 20))
|
||||
y += 42
|
||||
return
|
||||
p = spec.get("passive", "")
|
||||
if p:
|
||||
pname, pdesc = PASSIVES.get(p, (p, ""))
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ class PresetScreen:
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption("Plants — Team Presets")
|
||||
pygame.display.set_caption("Flora — Team Presets")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 30)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
@@ -67,7 +67,7 @@ class PresetScreen:
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption("Plants — Team Presets")
|
||||
pygame.display.set_caption("Flora — Team Presets")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 30)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ class RelicChoiceScreen:
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption("Plants — Blessing")
|
||||
pygame.display.set_caption("Flora — Blessing")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 36)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ class RuneScreen:
|
||||
self.plant = plant
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption(f"Plants — Runes · {plant}")
|
||||
pygame.display.set_caption(f"Flora — Runes · {plant}")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 30)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
@@ -377,7 +377,7 @@ class RuneScreen:
|
||||
overlay = pygame.Surface((WIN_W, WIN_H), pygame.SRCALPHA)
|
||||
overlay.fill((0, 0, 0, 160))
|
||||
self.screen.blit(overlay, (0, 0))
|
||||
w, h = 560, 400
|
||||
w, h = 560, 450
|
||||
panel = pygame.Rect((WIN_W - w) // 2, (WIN_H - h) // 2, w, h)
|
||||
pygame.draw.rect(self.screen, PANEL, panel, border_radius=12)
|
||||
pygame.draw.rect(self.screen, (150, 190, 150), panel, 2, border_radius=12)
|
||||
|
||||
+3
-3
@@ -25,8 +25,8 @@ TYPE_COLORS = {
|
||||
"radiant": (245, 200, 90),
|
||||
"nocturnal": (150, 120, 220),
|
||||
"ice": (130, 200, 235),
|
||||
"garden": (120, 200, 120),
|
||||
"wildflower": (235, 130, 170),
|
||||
"garden": (235, 130, 170),
|
||||
"wildflower": (120, 200, 120),
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class StarterScreen:
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption("Plants — Choose your starting pinwheel")
|
||||
pygame.display.set_caption("Flora — Choose your starting pinwheel")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 30)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ class ConfirmScreen:
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption("Plants — Tower")
|
||||
pygame.display.set_caption("Flora — Tower")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 30)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
@@ -81,7 +81,7 @@ class TowerScreen:
|
||||
pygame.init()
|
||||
self.window, self.scale = make_window(WIN_W, WIN_H)
|
||||
self.screen = pygame.Surface((WIN_W, WIN_H))
|
||||
pygame.display.set_caption("Plants — Tower")
|
||||
pygame.display.set_caption("Flora — Tower")
|
||||
self.clock = pygame.time.Clock()
|
||||
self.font = pygame.font.SysFont("segoeui", 36)
|
||||
self.bold = pygame.font.SysFont("segoeui", 20, bold=True)
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ TYPE_COLORS = {
|
||||
"radiant": (245, 200, 90),
|
||||
"nocturnal": (150, 120, 220),
|
||||
"ice": (130, 200, 235),
|
||||
"garden": (120, 200, 120),
|
||||
"wildflower": (235, 130, 170),
|
||||
"garden": (235, 130, 170),
|
||||
"wildflower": (120, 200, 120),
|
||||
}
|
||||
STRONG = (120, 210, 120)
|
||||
WEAK = (225, 130, 130)
|
||||
|
||||
Reference in New Issue
Block a user