195 lines
7.5 KiB
Python
195 lines
7.5 KiB
Python
"""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),
|
|
}
|