"""Income and social commands: daily, work, beg, crime, rob, give.""" from __future__ import annotations import random from datetime import date, timedelta import strings from ..pb_client import DatabaseError from . import house from .store import ( COOLDOWNS, JAIL_DURATION, PRESTIGE_SHOP, _commit, _cooldown_remaining, _is_jailed, _locked_by, _log, _now, _prestige_mult, _txn, get_user, ) from .house import _credit_house # --------------------------------------------------------------------------- # /daily # --------------------------------------------------------------------------- @_locked_by(0) async def do_daily(user_id: int) -> dict: 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"} daily_cd = timedelta(hours=18) if "korvaklapid" in user["items"] else COOLDOWNS["daily"] if cd := _cooldown_remaining(user, "daily", override_cd=daily_cd): return {"ok": False, "reason": "cooldown", "remaining": cd} today = _now().date() last_str = user.get("last_streak_date") last_date = date.fromisoformat(last_str) if last_str else None if last_date is None: streak = 1 elif (today - last_date).days == 1: streak = user["daily_streak"] + 1 elif "karikas" in user["items"]: streak = user["daily_streak"] # karikas: streak survives missed days else: streak = 1 # streak broken # Streak multiplier tiers if streak >= 14: streak_mult = 3.0 elif streak >= 7: streak_mult = 2.0 elif streak >= 3: streak_mult = 1.5 else: streak_mult = 1.0 vip = "lan_pass" in user["items"] vip_mult = 2.0 if vip else 1.0 daily_plus_level = (user.get("prestige_upgrades") or {}).get("daily_plus", 0) base = int(150 * (1.0 + daily_plus_level * PRESTIGE_SHOP["daily_plus"]["effect"])) earned = int(base * streak_mult * vip_mult) if "korvaklapid" in user["items"]: earned += 25 coin_mult, _ = _prestige_mult(user) earned = int(earned * coin_mult) # Investor interest (capped at 500/day to prevent runaway wealth) interest = 0 if "gaming_laptop" in user["items"] and user["balance"] > 0: interest = min(int(user["balance"] * 0.05), 500) earned += interest user["balance"] += earned user["last_daily"] = _now().isoformat() user["daily_streak"] = streak user["last_streak_date"] = today.isoformat() user["lifetime_earned"] = user.get("lifetime_earned", 0) + earned user["best_daily_streak"] = max(user.get("best_daily_streak", 0), streak) user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"]) await _commit(user_id, user) _txn("DAILY", user=user_id, earned=f"+{earned}", streak=streak, bal=user["balance"]) return { "ok": True, "earned": earned, "interest": interest, "streak": streak, "streak_mult": streak_mult, "vip": vip, "balance": user["balance"], } # --------------------------------------------------------------------------- # /work # --------------------------------------------------------------------------- _WORK_JOBS = strings.WORK_JOBS @_locked_by(0) async def do_work(user_id: int) -> dict: 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"} work_cd = timedelta(minutes=40) if "monitor" in user["items"] else COOLDOWNS["work"] if cd := _cooldown_remaining(user, "work", override_cd=work_cd): return {"ok": False, "reason": "cooldown", "remaining": cd} if jail := _is_jailed(user): return {"ok": False, "reason": "jailed", "remaining": jail} job, job_mult = random.choice(_WORK_JOBS) base = random.randint(15, 75) worker_mult = 1.5 if "gaming_hiir" in user["items"] else 1.0 desk_mult = 1.25 if "reguleeritav_laud" in user["items"] else 1.0 lucky = False if "energiajook" in user["items"] and random.random() < 0.30: lucky = True work_plus_level = (user.get("prestige_upgrades") or {}).get("work_plus", 0) work_plus_mult = 1.0 + work_plus_level * PRESTIGE_SHOP["work_plus"]["effect"] coin_mult, _ = _prestige_mult(user) earned = int(base * job_mult * worker_mult * desk_mult * (3.0 if lucky else 1.0) * work_plus_mult * coin_mult) user["balance"] += earned user["last_work"] = _now().isoformat() user["work_count"] = user.get("work_count", 0) + 1 user["lifetime_earned"] = user.get("lifetime_earned", 0) + earned user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"]) await _commit(user_id, user) _txn("WORK", user=user_id, earned=f"+{earned}", lucky=lucky, bal=user["balance"]) return { "ok": True, "earned": earned, "job": job, "lucky": lucky, "hiir": worker_mult > 1.0, "laud": desk_mult > 1.0, "balance": user["balance"], } # --------------------------------------------------------------------------- # /beg # --------------------------------------------------------------------------- _BEG_LINES = strings.BEG_LINES _BEG_JAIL_LINES = strings.BEG_JAIL_LINES @_locked_by(0) async def do_beg(user_id: int) -> dict: 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"} beg_cd = timedelta(minutes=3) if "hiirematt" in user["items"] else COOLDOWNS["beg"] if cd := _cooldown_remaining(user, "beg", override_cd=beg_cd): return {"ok": False, "reason": "cooldown", "remaining": cd} jailed = bool(_is_jailed(user)) beg_mult = 2 if "klaviatuur" in user["items"] else 1 coin_mult, _ = _prestige_mult(user) earned = int(random.randint(10, 40) * beg_mult * coin_mult) user["balance"] += earned user["last_beg"] = _now().isoformat() user["beg_count"] = user.get("beg_count", 0) + 1 user["lifetime_earned"] = user.get("lifetime_earned", 0) + earned user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"]) await _commit(user_id, user) _txn("BEG", user=user_id, earned=f"+{earned}", jailed=jailed, bal=user["balance"]) return { "ok": True, "earned": earned, "text": random.choice(_BEG_JAIL_LINES if jailed else _BEG_LINES), "klaviatuur": beg_mult > 1, "jailed": jailed, "balance": user["balance"], } # --------------------------------------------------------------------------- # /crime # --------------------------------------------------------------------------- _CRIME_WIN = strings.CRIME_WIN _CRIME_LOSE = strings.CRIME_LOSE @_locked_by(0) async def do_crime(user_id: int) -> dict: 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 cd := _cooldown_remaining(user, "crime"): return {"ok": False, "reason": "cooldown", "remaining": cd} if jail := _is_jailed(user): return {"ok": False, "reason": "jailed", "remaining": jail} user["last_crime"] = _now().isoformat() win_chance = 0.75 if "cat6" in user["items"] else 0.60 user["crimes_attempted"] = user.get("crimes_attempted", 0) + 1 if random.random() < win_chance: earned = random.randint(200, 500) if "mikrofon" in user["items"]: earned = int(earned * 1.3) user["balance"] += earned user["crimes_succeeded"] = user.get("crimes_succeeded", 0) + 1 user["lifetime_earned"] = user.get("lifetime_earned", 0) + earned user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"]) await _commit(user_id, user) _txn("CRIME_WIN", user=user_id, earned=f"+{earned}", bal=user["balance"]) return { "ok": True, "success": True, "earned": earned, "text": random.choice(_CRIME_WIN), "mikrofon": "mikrofon" in user["items"], "balance": user["balance"], } else: fine = random.randint(50, 150) user["balance"] = max(0, user["balance"] - fine) jailed = "gaming_tool" not in user["items"] if jailed: user["jailed_until"] = (_now() + JAIL_DURATION).isoformat() user["jailbreak_used"] = False user["times_jailed"] = user.get("times_jailed", 0) + 1 user["lifetime_lost"] = user.get("lifetime_lost", 0) + fine await _commit(user_id, user) await _credit_house(fine) _txn("CRIME_FAIL", user=user_id, fine=f"-{fine}", jailed=jailed, bal=user["balance"]) return { "ok": True, "success": False, "fine": fine, "text": random.choice(_CRIME_LOSE), "jailed": jailed, "balance": user["balance"], } # --------------------------------------------------------------------------- # /rob # --------------------------------------------------------------------------- @_locked_by(0, 1) async def do_rob(robber_id: int, target_id: int) -> dict: try: robber = await get_user(robber_id) except DatabaseError: return {"ok": False, "reason": "db_error"} if robber.get("eco_banned"): return {"ok": False, "reason": "banned"} try: target = await get_user(target_id) except DatabaseError: return {"ok": False, "reason": "db_error"} if cd := _cooldown_remaining(robber, "rob"): return {"ok": False, "reason": "cooldown", "remaining": cd} target_jailed = bool(_is_jailed(target)) if jail := _is_jailed(robber): if not target_jailed: return {"ok": False, "reason": "jailed", "remaining": jail} elif target_jailed: return {"ok": False, "reason": "target_jailed"} is_house = (house.HOUSE_ID is not None and target_id == house.HOUSE_ID) if is_house and target["balance"] < 50: return {"ok": False, "reason": "broke"} if not is_house and target["balance"] < 100: return {"ok": False, "reason": "broke"} robber["last_rob"] = _now().isoformat() if "anticheat" in target["items"] and not is_house: fine = random.randint(100, 200) robber["balance"] = max(0, robber["balance"] - fine) # Decrement anticheat uses uses = target.get("item_uses", {}).get("anticheat", 2) - 1 if "item_uses" not in target: target["item_uses"] = {} if uses <= 0: target["items"] = [i for i in target["items"] if i != "anticheat"] target["item_uses"].pop("anticheat", None) else: target["item_uses"]["anticheat"] = uses robber["lifetime_lost"] = robber.get("lifetime_lost", 0) + fine await _commit(robber_id, robber) await _commit(target_id, target) await _credit_house(fine) _txn("ROB_BLOCKED", robber=robber_id, victim=target_id, fine=f"-{fine}", robber_bal=robber["balance"], ac_uses_left=uses) return {"ok": True, "success": False, "reason": "valvur", "fine": fine} # Robbing the house has lower success (35%) but jackpot chance success_chance = 0.35 if is_house else (0.60 if "jellyfin" in robber["items"] else 0.45) if random.random() < success_chance: jackpot = is_house and random.random() < 0.10 if jackpot: pct = 0.40 elif is_house: pct = random.uniform(0.05, 0.15) else: pct = random.uniform(0.10, 0.25) stolen = max(10, min(int(target["balance"] * pct), target["balance"])) target["balance"] -= stolen prev_lifetime_earned = robber.get("lifetime_earned", 0) prev_biggest_win = robber.get("biggest_win", 0) prev_peak_balance = robber.get("peak_balance", 0) robber["balance"] += stolen robber["lifetime_earned"] = prev_lifetime_earned + stolen robber["biggest_win"] = max(prev_biggest_win, stolen) robber["peak_balance"] = max(prev_peak_balance, robber["balance"]) try: await _commit(robber_id, robber) except DatabaseError: return {"ok": False, "reason": "db_error"} try: await _commit(target_id, target) except DatabaseError: robber["balance"] -= stolen robber["lifetime_earned"] = prev_lifetime_earned robber["biggest_win"] = prev_biggest_win robber["peak_balance"] = prev_peak_balance try: await _commit(robber_id, robber) except DatabaseError as exc2: _log.critical( "do_rob rollback failed for robber %s after target commit failed: %s", robber_id, exc2, ) return {"ok": False, "reason": "db_error"} _txn("ROB_WIN", robber=robber_id, victim=target_id, stolen=f"+{stolen}", jackpot=jackpot, robber_bal=robber["balance"], victim_bal=target["balance"]) return {"ok": True, "success": True, "stolen": stolen, "balance": robber["balance"], "jackpot": jackpot} else: fine = random.randint(100, 250) robber["balance"] = max(0, robber["balance"] - fine) robber["lifetime_lost"] = robber.get("lifetime_lost", 0) + fine robber["biggest_loss"] = max(robber.get("biggest_loss", 0), fine) await _commit(robber_id, robber) await _credit_house(fine) _txn("ROB_FAIL", robber=robber_id, victim=target_id, fine=f"-{fine}", robber_bal=robber["balance"]) return {"ok": True, "success": False, "reason": "caught", "fine": fine, "balance": robber["balance"]} # --------------------------------------------------------------------------- # /give # --------------------------------------------------------------------------- @_locked_by(0, 1) async def do_give(giver_id: int, receiver_id: int, amount: int) -> dict: try: giver = await get_user(giver_id) except DatabaseError: return {"ok": False, "reason": "db_error"} if giver.get("eco_banned"): return {"ok": False, "reason": "banned"} if rem := _is_jailed(giver): return {"ok": False, "reason": "jailed", "remaining": rem} if giver["balance"] < amount: return {"ok": False, "reason": "insufficient"} try: receiver = await get_user(receiver_id) except DatabaseError: return {"ok": False, "reason": "db_error"} giver["balance"] -= amount receiver["balance"] += amount giver["total_given"] = giver.get("total_given", 0) + amount receiver["total_received"] = receiver.get("total_received", 0) + amount try: await _commit(giver_id, giver) except DatabaseError: return {"ok": False, "reason": "db_error"} try: await _commit(receiver_id, receiver) except DatabaseError: giver["balance"] += amount giver["total_given"] = max(0, giver.get("total_given", 0) - amount) try: await _commit(giver_id, giver) except DatabaseError as exc2: _log.critical( "do_give rollback failed for giver %s after receiver commit failed: %s", giver_id, exc2, ) return {"ok": False, "reason": "db_error"} _txn("GIVE", from_=giver_id, to=receiver_id, amount=amount, from_bal=giver["balance"], to_bal=receiver["balance"]) return { "ok": True, "giver_balance": giver["balance"], "receiver_balance": receiver["balance"], }