small update
boss prototype, auto planter, visual reworks, bug fixes
This commit is contained in:
+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)))
|
||||
|
||||
Reference in New Issue
Block a user