404 lines
15 KiB
Python
404 lines
15 KiB
Python
"""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"],
|
|
}
|