forked from retoor/devplacepy
Code Farm economy rebalance: market saturation, infrastructure/defense/cosmetics, mastery track, secondary leaderboards, admin eras, underdog bonus and weekly contracts
Every purchase/upgrade path (new and pre-existing) is now race-safe against concurrent requests via atomic conditional SQL updates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from .. import economy
|
||||
from .common import GameError, _iso, _lvl, _parse, conditional_update_farm
|
||||
from .farm import ensure_farm, get_farm
|
||||
|
||||
|
||||
def upgrade_defense(user: dict) -> dict:
|
||||
farm = ensure_farm(user["uid"])
|
||||
level = _lvl(farm, "defense_level")
|
||||
next_tier = economy.next_defense_tier(level)
|
||||
if not next_tier:
|
||||
raise GameError("Defense is already at the top tier.")
|
||||
rows = conditional_update_farm(
|
||||
farm["uid"],
|
||||
set_clause="coins = coins - :cost, defense_level = :new_level",
|
||||
where_clause="COALESCE(defense_level, 0) = :current_level AND coins >= :cost",
|
||||
params={"cost": next_tier.upgrade_cost, "new_level": next_tier.level, "current_level": level},
|
||||
)
|
||||
if rows == 0:
|
||||
farm = get_farm(user["uid"])
|
||||
current_level = _lvl(farm, "defense_level")
|
||||
if current_level != level:
|
||||
raise GameError("Defense already changed - refresh and try again.")
|
||||
raise GameError("Not enough coins to upgrade defense.")
|
||||
return {"defense_level": next_tier.level, "spent": next_tier.upgrade_cost}
|
||||
|
||||
|
||||
def charge_upkeep(farm: dict, now: datetime) -> dict:
|
||||
level = _lvl(farm, "defense_level")
|
||||
if level <= 0:
|
||||
return farm
|
||||
last_raw = farm.get("defense_last_upkeep_at") or ""
|
||||
last = _parse(last_raw)
|
||||
if not last:
|
||||
rows = conditional_update_farm(
|
||||
farm["uid"],
|
||||
set_clause="defense_last_upkeep_at = :ts",
|
||||
where_clause="(defense_last_upkeep_at IS NULL OR defense_last_upkeep_at = '') AND COALESCE(defense_level, 0) = :level",
|
||||
params={"ts": _iso(now), "level": level},
|
||||
)
|
||||
if rows:
|
||||
return {**farm, "defense_last_upkeep_at": _iso(now)}
|
||||
return get_farm(farm["user_uid"]) or farm
|
||||
elapsed_days = int((now - last).total_seconds() // 86400)
|
||||
if elapsed_days < 1:
|
||||
return farm
|
||||
days_due = min(elapsed_days, economy.UPKEEP_GRACE_DAYS)
|
||||
tier = economy.defense_tier(level)
|
||||
coins = int(farm.get("coins", 0))
|
||||
total_due = economy.daily_upkeep(tier, coins) * days_due
|
||||
new_timestamp = _iso(last + timedelta(days=elapsed_days))
|
||||
if coins >= total_due:
|
||||
rows = conditional_update_farm(
|
||||
farm["uid"],
|
||||
set_clause="coins = coins - :total_due, defense_last_upkeep_at = :new_ts",
|
||||
where_clause="defense_last_upkeep_at = :expected_last AND COALESCE(defense_level, 0) = :expected_level AND coins >= :total_due",
|
||||
params={
|
||||
"total_due": total_due,
|
||||
"new_ts": new_timestamp,
|
||||
"expected_last": last_raw,
|
||||
"expected_level": level,
|
||||
},
|
||||
)
|
||||
if rows == 0:
|
||||
return get_farm(farm["user_uid"]) or farm
|
||||
return {**farm, "coins": coins - total_due, "defense_last_upkeep_at": new_timestamp}
|
||||
new_level = max(0, level - 1)
|
||||
rows = conditional_update_farm(
|
||||
farm["uid"],
|
||||
set_clause="coins = 0, defense_level = :new_level, defense_last_upkeep_at = :new_ts",
|
||||
where_clause="defense_last_upkeep_at = :expected_last AND COALESCE(defense_level, 0) = :expected_level",
|
||||
params={"new_level": new_level, "new_ts": new_timestamp, "expected_last": last_raw, "expected_level": level},
|
||||
)
|
||||
if rows == 0:
|
||||
return get_farm(farm["user_uid"]) or farm
|
||||
return {
|
||||
**farm,
|
||||
"coins": 0,
|
||||
"defense_level": new_level,
|
||||
"defense_last_upkeep_at": new_timestamp,
|
||||
}
|
||||
Reference in New Issue
Block a user