"""Fishing minigame: catalogue, rolls, catch/sell flows.""" from __future__ import annotations import random from datetime import timedelta from ..pb_client import DatabaseError from .store import ( COOLDOWNS, _cooldown_remaining, _commit, _is_jailed, _locked_by, _now, _prestige_mult, _txn, get_user, ) # --------------------------------------------------------------------------- # Fish catalogue # --------------------------------------------------------------------------- FISH_CATALOGUE: dict[str, dict] = { # id: { rarity, weight=(min_g, max_g), coins=(min, max), exp } "sarj": {"rarity": "common", "weight": (50, 500), "coins": (3, 18), "exp": 3}, "ahven": {"rarity": "common", "weight": (80, 700), "coins": (5, 22), "exp": 3}, "koger": {"rarity": "common", "weight": (100, 800), "coins": (5, 20), "exp": 3}, "viidikas": {"rarity": "common", "weight": (10, 120), "coins": (2, 8), "exp": 2}, "latikas": {"rarity": "uncommon", "weight": (300, 2500), "coins": (20, 70), "exp": 6}, "karpkala": {"rarity": "uncommon", "weight": (500, 4000), "coins": (25, 80), "exp": 7}, "linask": {"rarity": "uncommon", "weight": (200, 2000), "coins": (18, 60), "exp": 6}, "haug": {"rarity": "rare", "weight": (500, 6000), "coins": (50, 180), "exp": 10}, "angerjas": {"rarity": "rare", "weight": (200, 1800), "coins": (40, 120), "exp": 10}, "siig": {"rarity": "rare", "weight": (200, 2000), "coins": (45, 130), "exp": 10}, "forell": {"rarity": "epic", "weight": (400, 4500), "coins": (100, 280), "exp": 15}, "koha": {"rarity": "epic", "weight": (600, 7000), "coins": (120, 300), "exp": 15}, "tougjas": {"rarity": "epic", "weight": (400, 4000), "coins": (90, 250), "exp": 14}, "lohe": {"rarity": "legendary","weight": (1500, 12000), "coins": (250, 700), "exp": 25}, "vimb": {"rarity": "legendary","weight": (200, 1200), "coins": (200, 600), "exp": 25}, } FISH_RARITY_WEIGHTS: dict[str, int] = { "junk": 15, "common": 45, "uncommon": 22, "rare": 12, "epic": 5, "legendary": 1, } def roll_fish(rarity_bump: bool = False) -> tuple[str, int]: """Roll a random fish. Returns (fish_id, weight_grams) or ('junk', 0). rarity_bump=True (kalavork item) shifts each catch one tier up. """ rarity_pool = list(FISH_RARITY_WEIGHTS.keys()) weights = list(FISH_RARITY_WEIGHTS.values()) chosen_rarity = random.choices(rarity_pool, weights=weights)[0] if chosen_rarity == "junk": return ("junk", 0) if rarity_bump: order = ["common", "uncommon", "rare", "epic", "legendary"] idx = order.index(chosen_rarity) if chosen_rarity in order else 0 chosen_rarity = order[min(idx + 1, len(order) - 1)] fish_of_rarity = [k for k, v in FISH_CATALOGUE.items() if v["rarity"] == chosen_rarity] if not fish_of_rarity: return ("junk", 0) fish_id = random.choice(fish_of_rarity) fish = FISH_CATALOGUE[fish_id] weight = random.randint(fish["weight"][0], fish["weight"][1]) return (fish_id, weight) # --------------------------------------------------------------------------- # /fish # --------------------------------------------------------------------------- @_locked_by(0) async def do_fish_start(user_id: int) -> dict: """Check cooldown + jail, set cooldown. Call before starting the fishing minigame.""" try: user = await get_user(user_id) except DatabaseError: return {"ok": False, "reason": "db_error"} if user.get("eco_banned"): return {"ok": False, "reason": "banned"} if jail := _is_jailed(user): return {"ok": False, "reason": "jailed", "remaining": jail} fish_cd = timedelta(seconds=90) if "ussipurk" in user["items"] else COOLDOWNS["fish"] if cd := _cooldown_remaining(user, "fish", override_cd=fish_cd): return {"ok": False, "reason": "cooldown", "remaining": cd} user["last_fish"] = _now().isoformat() await _commit(user_id, user) return {"ok": True} @_locked_by(0) async def do_fish_resolve(user_id: int, fish_id: str, weight: int) -> dict: """Add catch to inventory + update fish_book. Returns catch info incl. pre-calculated value.""" user = await get_user(user_id) if fish_id == "junk": _txn("FISH_JUNK", user=user_id) return {"ok": True, "type": "junk", "coins": 0, "exp": 0} if fish_id not in FISH_CATALOGUE: return {"ok": False, "reason": "invalid_fish"} fish = FISH_CATALOGUE[fish_id] min_c, max_c = fish["coins"] w_min, w_max = fish["weight"] weight_ratio = (weight - w_min) / max(1, w_max - w_min) base_coins = int(min_c + weight_ratio * (max_c - min_c)) coin_mult, _ = _prestige_mult(user) value = int(base_coins * coin_mult) exp = fish["exp"] book: dict = user.get("fish_book") or {} prev_count = book.get(fish_id, 0) book[fish_id] = prev_count + 1 user["fish_book"] = book user["total_fish_caught"] = user.get("total_fish_caught", 0) + 1 inv: list = list(user.get("fish_inventory") or []) inv.append({"fish_id": fish_id, "weight": weight, "value": value}) user["fish_inventory"] = inv await _commit(user_id, user) _txn("FISH", user=user_id, fish=fish_id, weight=weight, value=value) return { "ok": True, "type": "fish", "fish_id": fish_id, "weight": weight, "value": value, "exp": exp, "is_new": prev_count == 0, "total_caught": book[fish_id], } @_locked_by(0) async def do_fish_sell(user_id: int, indices: list[int] | None = None) -> dict: """Sell fish from inventory. indices=None sells all. Returns coins earned.""" user = await get_user(user_id) inv: list = list(user.get("fish_inventory") or []) if not inv: return {"ok": False, "reason": "empty"} if indices is None: to_sell = inv remaining = [] else: sell_idx = { (i if i >= 0 else len(inv) + i) for i in indices } sell_idx = {i for i in sell_idx if 0 <= i < len(inv)} to_sell = [inv[i] for i in sorted(sell_idx)] keep_idx = set(range(len(inv))) - sell_idx remaining = [inv[i] for i in sorted(keep_idx)] if not to_sell: return {"ok": False, "reason": "empty"} total_coins = sum(entry["value"] for entry in to_sell) user["fish_inventory"] = remaining user["balance"] = user.get("balance", 0) + total_coins user["lifetime_earned"] = user.get("lifetime_earned", 0) + total_coins user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"]) await _commit(user_id, user) _txn("FISH_SELL", user=user_id, count=len(to_sell), coins=f"+{total_coins}", bal=user["balance"]) return { "ok": True, "coins": total_coins, "count": len(to_sell), "balance": user["balance"], } async def do_fishbook(user_id: int) -> dict: """Return the user's fish book data including per-species inventory counts.""" user = await get_user(user_id) book: dict = user.get("fish_book") or {} inv: list = user.get("fish_inventory") or [] inv_counts: dict[str, int] = {} for entry in inv: fid = entry.get("fish_id", "") inv_counts[fid] = inv_counts.get(fid, 0) + 1 return { "ok": True, "book": book, "inv_counts": inv_counts, "total_fish_caught": user.get("total_fish_caught", 0), "unique_caught": len(book), "total_species": len(FISH_CATALOGUE), }