feat: add steal mechanic to Code Farm with 60s protection window and half-coin reward

This commit is contained in:
2026-06-22 17:33:03 +00:00
parent ebf6bf7f82
commit 412dbe3a3d
13 changed files with 302 additions and 14 deletions
+7
View File
@@ -15,6 +15,9 @@ MAX_WATERS_PER_PLOT = 3
WATER_REWARD_COINS = 6
WATER_REWARD_XP = 3
STEAL_GRACE_SECONDS = 60
STEAL_FRACTION = 0.5
@dataclass(frozen=True)
class Crop:
@@ -220,6 +223,10 @@ def effective_reward_xp(crop: Crop, xp_level: int = 0) -> int:
return round(crop.reward_xp * (1 + PERK_BY_KEY["xp"].step * xp_level))
def steal_reward_coins(crop: Crop, yield_level: int = 0, prestige: int = 0) -> int:
return max(1, round(effective_reward_coins(crop, yield_level, prestige) * STEAL_FRACTION))
def daily_reward(streak: int) -> int:
effective = min(max(streak, 1), DAILY_STREAK_CAP)
return DAILY_BASE + DAILY_STREAK_STEP * (effective - 1)
+77 -6
View File
@@ -133,7 +133,15 @@ def _plot_state(plot: dict, now: datetime) -> str:
return "growing"
def serialize_plot(plot: dict, *, viewer_uid: str, owner_uid: str, now: datetime) -> dict:
def serialize_plot(
plot: dict,
*,
viewer_uid: str,
owner_uid: str,
now: datetime,
yield_level: int = 0,
prestige: int = 0,
) -> dict:
state = _plot_state(plot, now)
crop = economy.crop_for(plot.get("crop_key", ""))
ready_at = _parse(plot.get("ready_at", ""))
@@ -149,6 +157,17 @@ def serialize_plot(plot: dict, *, viewer_uid: str, owner_uid: str, now: datetime
and viewer_uid not in watered
and len(watered) < economy.MAX_WATERS_PER_PLOT
)
can_steal = (
state == "ready"
and not is_owner
and bool(viewer_uid)
and bool(crop)
and ready_at is not None
and now >= ready_at + timedelta(seconds=economy.STEAL_GRACE_SECONDS)
)
steal_coins = (
economy.steal_reward_coins(crop, yield_level, prestige) if can_steal else 0
)
return {
"slot": plot.get("slot_index", 0),
"state": state,
@@ -162,6 +181,8 @@ def serialize_plot(plot: dict, *, viewer_uid: str, owner_uid: str, now: datetime
"watered_count": len(watered),
"max_waters": economy.MAX_WATERS_PER_PLOT,
"can_water": can_water,
"can_steal": can_steal,
"steal_coins": steal_coins,
"fertilize_cost": (
economy.fertilize_cost(remaining) if state == "growing" and is_owner else 0
),
@@ -174,9 +195,20 @@ def serialize_farm(
now = now or _now()
viewer_uid = viewer["uid"] if viewer else ""
owner_uid = owner["uid"]
prestige = _lvl(farm, "prestige")
growth_level = _lvl(farm, "perk_growth")
discount_level = _lvl(farm, "perk_discount")
yield_level = _lvl(farm, "perk_yield")
plots = get_plots(farm["uid"])
serialized_plots = [
serialize_plot(plot, viewer_uid=viewer_uid, owner_uid=owner_uid, now=now)
serialize_plot(
plot,
viewer_uid=viewer_uid,
owner_uid=owner_uid,
now=now,
yield_level=yield_level,
prestige=prestige,
)
for plot in plots
]
progress = economy.level_progress(int(farm.get("xp", 0)))
@@ -185,10 +217,6 @@ def serialize_farm(
next_tier = economy.next_ci_tier(ci_tier)
ci_entry = economy.CI_BY_TIER.get(ci_tier)
is_owner = viewer_uid == owner_uid
prestige = _lvl(farm, "prestige")
growth_level = _lvl(farm, "perk_growth")
discount_level = _lvl(farm, "perk_discount")
yield_level = _lvl(farm, "perk_yield")
streak = _lvl(farm, "streak")
daily_available = is_owner and _daily_available(farm, now)
return {
@@ -401,6 +429,49 @@ def water(visitor: dict, owner: dict, slot: int) -> dict:
}
def steal(thief: dict, owner: dict, slot: int) -> dict:
if thief["uid"] == owner["uid"]:
raise GameError("You cannot steal from your own farm.")
farm = ensure_farm(owner["uid"])
plot = _plot_at(farm["uid"], slot)
if not plot or not plot.get("crop_key"):
raise GameError("That plot is empty.")
now = _now()
if _plot_state(plot, now) != "ready":
raise GameError("That build is not ready.")
crop = economy.crop_for(plot.get("crop_key", ""))
if not crop:
raise GameError("Unknown crop type.")
ready_at = _parse(plot.get("ready_at", "")) or now
if now < ready_at + timedelta(seconds=economy.STEAL_GRACE_SECONDS):
raise GameError("That harvest is still protected.")
_plots().update(
{
"uid": plot["uid"],
"crop_key": "",
"planted_at": "",
"ready_at": "",
"watered_by": "[]",
"updated_at": _iso(now),
},
["uid"],
)
coins_gain = economy.steal_reward_coins(
crop, _lvl(farm, "perk_yield"), _lvl(farm, "prestige")
)
thief_farm = ensure_farm(thief["uid"])
_update_farm(
thief_farm["uid"],
{"coins": int(thief_farm.get("coins", 0)) + coins_gain},
)
return {
"slot": slot,
"crop": crop.key,
"coins": coins_gain,
"owner_uid": owner["uid"],
}
def leaderboard(limit: int = 25) -> list[dict]:
farms = sorted(
_farms().find(),