small update
boss prototype, auto planter, visual reworks, bug fixes
This commit is contained in:
+118
-22
@@ -49,6 +49,8 @@ def init_starter_pinwheel(ptype: str) -> bool:
|
||||
"fertilizer_accrued_at": 0.0,
|
||||
"auto_farmer": False,
|
||||
"auto_farmer_active": False,
|
||||
"auto_planter": False,
|
||||
"auto_planter_active": False,
|
||||
"coins": 0,
|
||||
"coins_accrued_at": 0.0,
|
||||
"water_used": 0,
|
||||
@@ -59,6 +61,7 @@ def init_starter_pinwheel(ptype: str) -> bool:
|
||||
"open_history": [],
|
||||
"lesser_opened": 0,
|
||||
"floor": 1,
|
||||
"bosses_fought": [],
|
||||
"soil": {"seed_cd_until": 0.0, "seeds": [], "plots": []},
|
||||
}
|
||||
save(data)
|
||||
@@ -80,6 +83,8 @@ def _default() -> dict:
|
||||
"fertilizer_accrued_at": 0.0,
|
||||
"auto_farmer": False,
|
||||
"auto_farmer_active": False,
|
||||
"auto_planter": False,
|
||||
"auto_planter_active": False,
|
||||
"coins": 0,
|
||||
"coins_accrued_at": 0.0,
|
||||
"water_used": 0,
|
||||
@@ -91,6 +96,7 @@ def _default() -> dict:
|
||||
"lesser_opened": 0,
|
||||
"soil": {"seed_cd_until": 0.0, "seeds": [], "plots": []},
|
||||
"floor": 1,
|
||||
"bosses_fought": [],
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +134,9 @@ def load() -> dict:
|
||||
data.setdefault("soil", {"seed_cd_until": 0.0, "seeds": [], "plots": []})
|
||||
data.setdefault("floor", 1)
|
||||
data.setdefault("wins", 0)
|
||||
data.setdefault("auto_planter", False)
|
||||
data.setdefault("auto_planter_active", False)
|
||||
data.setdefault("bosses_fought", [])
|
||||
# normalize runes: older saves lack the per-rune stat roll, and use the old
|
||||
# color names (blue/red/white/yellow) instead of the current gems.
|
||||
for r in data.setdefault("runes", []):
|
||||
@@ -252,8 +261,9 @@ def grant_xp(names, xp: int, count_battle: bool = True, fertilizer: int = None)
|
||||
for n in names:
|
||||
prog = data["progress"][n]
|
||||
bonus = gap * CATCH_UP_PER_GAP if prog["level"] == low else 0
|
||||
# all XP gains are cut by 2/3 (only a third is awarded)
|
||||
gain = (xp + bonus) // 3 if (xp + bonus) > 0 else 0
|
||||
# all XP gains are cut to a sixth (half of the old third) so leveling
|
||||
# takes twice as long; water (a direct +1 level) is unaffected
|
||||
gain = (xp + bonus) // 6 if (xp + bonus) > 0 else 0
|
||||
prog["xp"] += gain
|
||||
while prog["level"] < MAX_LEVEL and prog["xp"] >= xp_to_next(prog["level"]):
|
||||
prog["xp"] -= xp_to_next(prog["level"])
|
||||
@@ -338,6 +348,7 @@ def available_fertilizer() -> int:
|
||||
FERTILIZER_CAP = 100 # fertilizer stops accruing once this cap is reached
|
||||
FERTILIZER_PER_HOUR = 1 # test build: earn 1 fertilizer every real hour
|
||||
AUTO_FARMER_COST = 100 # coins (WIP value)
|
||||
AUTO_PLANTER_COST = 200 # coins: plants retrieved seeds automatically
|
||||
|
||||
|
||||
def add_fertilizer(amount: int) -> None:
|
||||
@@ -404,6 +415,37 @@ def buy_auto_farmer() -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def auto_planter_owned() -> bool:
|
||||
return bool(load().get("auto_planter", False))
|
||||
|
||||
|
||||
def auto_planter_active() -> bool:
|
||||
"""Whether the owned auto planter is currently toggled on."""
|
||||
return bool(load().get("auto_planter_active", False))
|
||||
|
||||
|
||||
def toggle_auto_planter() -> bool:
|
||||
"""Turn the owned auto planter on/off. Returns the new active state."""
|
||||
data = load()
|
||||
data["auto_planter_active"] = not data.get("auto_planter_active", False)
|
||||
save(data)
|
||||
return data["auto_planter_active"]
|
||||
|
||||
|
||||
def buy_auto_planter() -> str:
|
||||
"""Buy the auto planter for AUTO_PLANTER_COST coins. Returns '' or a reason."""
|
||||
data = load()
|
||||
if data.get("auto_planter", False):
|
||||
return "Already owned"
|
||||
if data.get("coins", 0) < AUTO_PLANTER_COST:
|
||||
return "Not enough coins"
|
||||
data["coins"] -= AUTO_PLANTER_COST
|
||||
data["auto_planter"] = True
|
||||
data["auto_planter_active"] = True
|
||||
save(data)
|
||||
return ""
|
||||
|
||||
|
||||
def buy_rune(tier: int):
|
||||
"""Buy a random rune of `tier` (1 = common types, 2 = brown) for coins.
|
||||
Returns the rune dict on success or a reason string."""
|
||||
@@ -502,6 +544,30 @@ def roll_pack_rune(data=None):
|
||||
return add_rune(data=data, save_now=False)
|
||||
|
||||
|
||||
# --- bosses ---------------------------------------------------------
|
||||
def bosses_fought() -> set:
|
||||
"""Boss names the player has defeated at least once."""
|
||||
return set(load().get("bosses_fought", []))
|
||||
|
||||
|
||||
def mark_boss_fought(name: str) -> None:
|
||||
data = load()
|
||||
fought = data.setdefault("bosses_fought", [])
|
||||
if name not in fought:
|
||||
fought.append(name)
|
||||
save(data)
|
||||
|
||||
|
||||
def boss_rune_drop(name: str):
|
||||
"""Roll a boss's signature rune drop. Much higher chance than normal rune
|
||||
drops. Returns a rune dict or None."""
|
||||
from . import bosses
|
||||
if random.random() >= bosses.BOSS_RUNE_DROP_CHANCE:
|
||||
return None
|
||||
color = random.choice(bosses.boss_drop_runes(name))
|
||||
return add_rune(color)
|
||||
|
||||
|
||||
def rune_slots(name, data=None) -> int:
|
||||
"""Permanent rune slots for `name` (1 + evolutions ever achieved)."""
|
||||
data = data or load()
|
||||
@@ -544,20 +610,20 @@ def free_runes(data=None) -> list:
|
||||
|
||||
def reroll_rune(rune_id: int, sacrificed_ids) -> str:
|
||||
"""Reroll `rune_id`'s stat to a higher value (at least 1% better, never
|
||||
above its max roll), sacrificing the given 10 runes. Equipped sacrifices are
|
||||
removed from their plants. Returns '' on success or a reason string."""
|
||||
above its max roll), sacrificing runes.RUNE_SACRIFICE_COST runes. Equipped
|
||||
sacrifices are removed from their plants. Returns '' on success or a reason."""
|
||||
data = load()
|
||||
rune_list = data.get("runes", [])
|
||||
target = next((r for r in rune_list if r["id"] == rune_id), None)
|
||||
if not target:
|
||||
return "No such rune"
|
||||
max_pct = runes.BROWN_PCT_MAX if target["color"] == "brown" else runes.PCT_MAX
|
||||
max_pct = runes.max_pct(target["color"])
|
||||
current = target.get("pct", 0)
|
||||
if current >= max_pct:
|
||||
return "Already max roll"
|
||||
sac_set = set(sacrificed_ids)
|
||||
if len(sac_set) != 10:
|
||||
return "Pick exactly 10 runes to sacrifice"
|
||||
if len(sac_set) != runes.RUNE_SACRIFICE_COST:
|
||||
return f"Pick exactly {runes.RUNE_SACRIFICE_COST} runes to sacrifice"
|
||||
if rune_id in sac_set:
|
||||
return "Cannot sacrifice the rune being upgraded"
|
||||
by_id = {r["id"]: r for r in rune_list}
|
||||
@@ -629,20 +695,6 @@ def add_tower_packs(count: int) -> None:
|
||||
save(data)
|
||||
|
||||
|
||||
def convert_water_to_pack(pack_type: str) -> bool:
|
||||
"""Spend 5 water from the pool to gain one pack of the chosen type."""
|
||||
data = load()
|
||||
if pack_type not in REWARD_PACK_TYPES:
|
||||
return False
|
||||
if data.get("water", 0) < 5:
|
||||
return False
|
||||
data["water"] -= 5
|
||||
packs = data.setdefault("packs", {t: 0 for t in PACK_TYPES})
|
||||
packs[pack_type] = packs.get(pack_type, 0) + 1
|
||||
save(data)
|
||||
return True
|
||||
|
||||
|
||||
def evolve(name) -> bool:
|
||||
"""Evolve at the milestone level by consuming duplicates."""
|
||||
data = load()
|
||||
@@ -750,7 +802,7 @@ def add_lesser_pack() -> list:
|
||||
|
||||
|
||||
def add_pack(pack_type: str, count: int = 1) -> list:
|
||||
"""Grant `count` packs of a specific type. Returns the granted type list."""
|
||||
"""Add `count` seed packs of `pack_type`."""
|
||||
data = load()
|
||||
packs = data.setdefault("packs", {t: 0 for t in PACK_TYPES})
|
||||
packs[pack_type] = packs.get(pack_type, 0) + count
|
||||
@@ -758,6 +810,33 @@ def add_pack(pack_type: str, count: int = 1) -> list:
|
||||
return [pack_type] * count
|
||||
|
||||
|
||||
# Pack shop (seed packs page): coins buy packs. Tower packs can't be bought.
|
||||
PACK_COSTS = {
|
||||
"lesser": 50,
|
||||
"general": 100,
|
||||
"radiant": 100,
|
||||
"nocturnal": 100,
|
||||
"ice": 100,
|
||||
"garden": 100,
|
||||
"wildflower": 100,
|
||||
}
|
||||
|
||||
|
||||
def buy_pack(pack_type: str) -> str:
|
||||
"""Buy a pack of `pack_type` with coins. Returns '' on success or a reason."""
|
||||
cost = PACK_COSTS.get(pack_type)
|
||||
if cost is None:
|
||||
return "That pack can't be bought"
|
||||
data = load()
|
||||
if data.get("coins", 0) < cost:
|
||||
return "Not enough coins"
|
||||
data["coins"] -= cost
|
||||
packs = data.setdefault("packs", {t: 0 for t in PACK_TYPES})
|
||||
packs[pack_type] = packs.get(pack_type, 0) + 1
|
||||
save(data)
|
||||
return ""
|
||||
|
||||
|
||||
def battle_wins() -> int:
|
||||
"""Total number of battle wins the player has recorded."""
|
||||
return load().get("wins", 0)
|
||||
@@ -1189,6 +1268,23 @@ def auto_farm() -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def auto_plant() -> str:
|
||||
"""If the player owns an auto planter that's toggled on, automatically plant
|
||||
a retrieved seed into the first empty plot (if any seeds are available).
|
||||
Returns '' on success or when idle, else a reason."""
|
||||
data = load()
|
||||
if not data.get("auto_planter", False):
|
||||
return "No auto planter"
|
||||
if not data.get("auto_planter_active", False):
|
||||
return "Auto planter off"
|
||||
soil = data.setdefault("soil", {"seed_cd_until": 0.0, "seeds": [], "plots": []})
|
||||
if not soil.get("seeds", []):
|
||||
return "" # nothing to plant
|
||||
if len(soil.get("plots", [])) >= unlocked_plots(data, soil):
|
||||
return "" # no empty plot
|
||||
return plant_seed(0)
|
||||
|
||||
|
||||
def harvest_plot(index: int) -> str:
|
||||
"""Harvest a grown plot, gaining a copy (duplicate) of the plant."""
|
||||
data = load()
|
||||
|
||||
Reference in New Issue
Block a user