Work orders: anyone can pool reagents, paid by value
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f7daa4e44d
commit
9ff7a22a5d
3 changed files with 161 additions and 44 deletions
116
game.py
116
game.py
|
|
@ -2189,10 +2189,8 @@ def can_start(world: dict, player: dict, action: dict) -> str | None:
|
||||||
if not order or order["status"] != "open" or order["claimed"] != player["id"]:
|
if not order or order["status"] != "open" or order["claimed"] != player["id"]:
|
||||||
return "Work order done" if order and order["status"] == "done" else "Work order closed"
|
return "Work order done" if order and order["status"] == "done" else "Work order closed"
|
||||||
for item, qty in fire_inputs(world, action).items():
|
for item, qty in fire_inputs(world, action).items():
|
||||||
if order and item in order["provided"]:
|
pooled = order["provided"].get(item, 0) if order else 0
|
||||||
if order["provided"][item] < qty:
|
if pooled + player["bank"].get(item, 0) < qty:
|
||||||
return f"Out of {ITEMS[item]['name'].lower()}"
|
|
||||||
elif player["bank"].get(item, 0) < qty:
|
|
||||||
return f"Out of {ITEMS[item]['name'].lower()}"
|
return f"Out of {ITEMS[item]['name'].lower()}"
|
||||||
if action.get("special") == "tend" and _room(world["city"]["fires"][action["tend"]], player, action["tier"]) < 0:
|
if action.get("special") == "tend" and _room(world["city"]["fires"][action["tend"]], player, action["tier"]) < 0:
|
||||||
return f"Your pile in the {FIRES[action['tend']]['name'].lower()} is full"
|
return f"Your pile in the {FIRES[action['tend']]['name'].lower()} is full"
|
||||||
|
|
@ -2227,11 +2225,13 @@ def _complete(world: dict, player: dict, action: dict, now: float, rng: random.R
|
||||||
order = working_order(world, player, action)
|
order = working_order(world, player, action)
|
||||||
inputs = fire_inputs(world, action)
|
inputs = fire_inputs(world, action)
|
||||||
warmed = inputs is not action["inputs"]
|
warmed = inputs is not action["inputs"]
|
||||||
own = {item: qty for item, qty in inputs.items() if not order or item not in order["provided"]}
|
own, used = dict(inputs), {}
|
||||||
if order:
|
if order:
|
||||||
for item, qty in inputs.items():
|
used = _draw(order, inputs)
|
||||||
if item in order["provided"]:
|
for share in used.values():
|
||||||
order["provided"][item] -= qty
|
for item, qty in share.items():
|
||||||
|
own[item] -= qty
|
||||||
|
own = {item: qty for item, qty in own.items() if qty}
|
||||||
if own and rng.random() < brew.get("keep", 0):
|
if own and rng.random() < brew.get("keep", 0):
|
||||||
summary["kept"] += 1
|
summary["kept"] += 1
|
||||||
else:
|
else:
|
||||||
|
|
@ -2362,7 +2362,7 @@ def _complete(world: dict, player: dict, action: dict, now: float, rng: random.R
|
||||||
sent = min(outputs.get(order["item"], 0), order["qty"] - order["delivered"])
|
sent = min(outputs.get(order["item"], 0), order["qty"] - order["delivered"])
|
||||||
if sent:
|
if sent:
|
||||||
outputs[order["item"]] -= sent
|
outputs[order["item"]] -= sent
|
||||||
_deliver(world, order, player, sent, now)
|
_deliver(world, order, player, sent, now, used, inputs)
|
||||||
summary["delivered"] = summary.get("delivered", 0) + sent
|
summary["delivered"] = summary.get("delivered", 0) + sent
|
||||||
for item, qty in outputs.items():
|
for item, qty in outputs.items():
|
||||||
if not qty:
|
if not qty:
|
||||||
|
|
@ -2513,16 +2513,18 @@ def take_summary(player: dict, summary: dict) -> dict:
|
||||||
|
|
||||||
|
|
||||||
# --- work orders -----------------------------------------------------------
|
# --- work orders -----------------------------------------------------------
|
||||||
# A player asks the guild for something. A crafting order names a recipe: the
|
# A player asks the guild for something. A crafting order names a recipe: anyone
|
||||||
# requester can put in some of its reagents, one crafter claims it and makes it
|
# can put its reagents into the order's pool, one crafter claims it and makes it
|
||||||
# with the normal action (their own materials for the rest), and each piece goes
|
# with the normal action (pool first, their own materials for the rest), and each
|
||||||
# straight to the requester. A materials request is open to anyone who has the
|
# piece goes straight to the requester. A materials request is open to anyone who
|
||||||
# items. Reagents and the reward are held by the order, paid out as it's filled,
|
# has the items. Reagents and the reward are held by the order, paid out as it's
|
||||||
# and handed back if it's cancelled or runs out of time.
|
# filled, and handed back to whoever put them in if it's cancelled, runs out of
|
||||||
|
# time, or finishes with some left over.
|
||||||
|
|
||||||
ORDER_LIMIT = 5 # open orders per player
|
ORDER_LIMIT = 5 # open orders per player
|
||||||
ORDER_DAYS = 3 # before an order expires and is refunded
|
ORDER_DAYS = 3 # before an order expires and is refunded
|
||||||
ORDER_MAX = {"craft": 500, "gather": 2000}
|
ORDER_MAX = {"craft": 500, "gather": 2000}
|
||||||
|
INPUT_SHARE = 0.5 # of each piece's reward, split by value among whoever's reagents went in
|
||||||
|
|
||||||
|
|
||||||
def _order_sources() -> tuple[dict, dict]:
|
def _order_sources() -> tuple[dict, dict]:
|
||||||
|
|
@ -2581,37 +2583,70 @@ def post_order(world: dict, player: dict, kind: str, item: str, qty, provide, re
|
||||||
order = {"id": world["next_order_id"], "kind": kind, "by": player["id"], "at": now,
|
order = {"id": world["next_order_id"], "kind": kind, "by": player["id"], "at": now,
|
||||||
"expires": now + ORDER_DAYS * DAY / speed(world), "item": item, "qty": qty, "action": source,
|
"expires": now + ORDER_DAYS * DAY / speed(world), "item": item, "qty": qty, "action": source,
|
||||||
"skill": action["skill"], "level": action["level"], "provided": provided, "reward": total,
|
"skill": action["skill"], "level": action["level"], "provided": provided, "reward": total,
|
||||||
"paid": 0, "delivered": 0, "held": 0, "claimed": None, "from": {}, "status": "open"}
|
"paid": 0, "delivered": 0, "held": 0, "claimed": None, "from": {}, "status": "open",
|
||||||
|
"gave": {player["id"]: dict(provided)} if provided else {}, "earned": {}}
|
||||||
world["next_order_id"] += 1
|
world["next_order_id"] += 1
|
||||||
world["orders"].append(order)
|
world["orders"].append(order)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _gave(order: dict) -> dict:
|
||||||
|
"""Who has reagents in the pool: {player: {item: qty}}, oldest first. Older orders only had the requester's."""
|
||||||
|
return order.setdefault("gave", {order["by"]: dict(order["provided"])} if order["provided"] else {})
|
||||||
|
|
||||||
|
|
||||||
def provide_reagent(world: dict, player: dict, order_id, item: str) -> str | None:
|
def provide_reagent(world: dict, player: dict, order_id, item: str) -> str | None:
|
||||||
"""Put a reagent into your own order after posting it, enough for what's still to be made."""
|
"""Put a reagent into a crafting order's pool, up to what's still to be made."""
|
||||||
order = order_by_id(world, order_id)
|
order = order_by_id(world, order_id)
|
||||||
if not order or order["by"] != player["id"] or order["status"] != "open" or order["kind"] != "craft":
|
if not order or order["status"] != "open" or order["kind"] != "craft":
|
||||||
return "No such order"
|
return "No such order"
|
||||||
action = ACTION_BY_ID[order["action"]]
|
action = ACTION_BY_ID[order["action"]]
|
||||||
if item not in action["inputs"] or item in order["provided"]:
|
if item not in action["inputs"]:
|
||||||
return "Already provided" if item in order["provided"] else "That isn't in the recipe"
|
return "That isn't in the recipe"
|
||||||
need = action["inputs"][item] * _batches(action, order["item"], order["qty"] - order["delivered"])
|
need = action["inputs"][item] * _batches(action, order["item"], order["qty"] - order["delivered"])
|
||||||
if player["bank"].get(item, 0) < need:
|
short = need - order["provided"].get(item, 0)
|
||||||
return f"{ITEMS[item]['name']}: {player['bank'].get(item, 0)} / {need}"
|
if short <= 0:
|
||||||
bank_take(player, item, need)
|
return "Already provided"
|
||||||
order["provided"][item] = need
|
qty = min(player["bank"].get(item, 0), short)
|
||||||
|
if not qty:
|
||||||
|
return f"No {ITEMS[item]['name'].lower()} in your bank"
|
||||||
|
bank_take(player, item, qty)
|
||||||
|
order["provided"][item] = order["provided"].get(item, 0) + qty
|
||||||
|
mine = _gave(order).setdefault(player["id"], {})
|
||||||
|
mine[item] = mine.get(item, 0) + qty
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _draw(order: dict, inputs: dict) -> dict:
|
||||||
|
"""Take one run's reagents from the pool, oldest first: {player: {item: qty}} of whose went in."""
|
||||||
|
used = {}
|
||||||
|
for item, qty in inputs.items():
|
||||||
|
take = min(order["provided"].get(item, 0), qty)
|
||||||
|
if not take:
|
||||||
|
continue
|
||||||
|
order["provided"][item] -= take
|
||||||
|
for uid, items in _gave(order).items():
|
||||||
|
part = min(items.get(item, 0), take)
|
||||||
|
if part:
|
||||||
|
items[item] -= part
|
||||||
|
used.setdefault(uid, {})[item] = part
|
||||||
|
take -= part
|
||||||
|
if not take:
|
||||||
|
break
|
||||||
|
return used
|
||||||
|
|
||||||
|
|
||||||
def _refund(world: dict, order: dict, now: float) -> None:
|
def _refund(world: dict, order: dict, now: float) -> None:
|
||||||
|
"""Hand back what's left in the pool to whoever put it in, and the unpaid reward to the requester."""
|
||||||
|
for uid, items in _gave(order).items():
|
||||||
|
player = world["players"].get(uid)
|
||||||
|
for item, qty in items.items():
|
||||||
|
if qty and player:
|
||||||
|
bank_add(world, player, item, qty, now, empty_summary())
|
||||||
|
order["provided"], order["gave"] = {}, {}
|
||||||
owner = world["players"].get(order["by"])
|
owner = world["players"].get(order["by"])
|
||||||
if not owner:
|
if owner:
|
||||||
return
|
owner["gold"] += order["reward"] - order["paid"]
|
||||||
for item, qty in order["provided"].items():
|
|
||||||
if qty:
|
|
||||||
bank_add(world, owner, item, qty, now, empty_summary())
|
|
||||||
order["provided"] = {}
|
|
||||||
owner["gold"] += order["reward"] - order["paid"]
|
|
||||||
order["paid"] = order["reward"]
|
order["paid"] = order["reward"]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2692,12 +2727,26 @@ def deliver_order(world: dict, player: dict, order_id, now: float) -> str | None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _deliver(world: dict, order: dict, giver: dict, qty: int, now: float) -> None:
|
def _deliver(world: dict, order: dict, giver: dict, qty: int, now: float,
|
||||||
|
used: dict | None = None, inputs: dict | None = None) -> None:
|
||||||
|
"""Pay for `qty` pieces. Whoever's reagents went into them (`used` of the run's `inputs`) shares
|
||||||
|
INPUT_SHARE of the pay by value; the requester's own reagents leave theirs with the crafter."""
|
||||||
order["delivered"] += qty
|
order["delivered"] += qty
|
||||||
order["from"][giver["id"]] = order["from"].get(giver["id"], 0) + qty
|
order["from"][giver["id"]] = order["from"].get(giver["id"], 0) + qty
|
||||||
order["held"] += qty
|
order["held"] += qty
|
||||||
pay = round(order["reward"] * order["delivered"] / order["qty"]) - order["paid"]
|
pay = round(order["reward"] * order["delivered"] / order["qty"]) - order["paid"]
|
||||||
order["paid"] += pay
|
order["paid"] += pay
|
||||||
|
worth, base = sum(ITEMS[item]["value"] * n for item, n in (inputs or {}).items()), pay
|
||||||
|
for uid, items in (used or {}).items():
|
||||||
|
helper = world["players"].get(uid)
|
||||||
|
if uid == order["by"] or not helper or not worth:
|
||||||
|
continue
|
||||||
|
share = min(pay, round(base * INPUT_SHARE * sum(ITEMS[i]["value"] * n for i, n in items.items()) / worth))
|
||||||
|
if share:
|
||||||
|
helper["gold"] += share
|
||||||
|
earned = order.setdefault("earned", {})
|
||||||
|
earned[uid] = earned.get(uid, 0) + share
|
||||||
|
pay -= share
|
||||||
giver["gold"] += pay
|
giver["gold"] += pay
|
||||||
owner = world["players"].get(order["by"])
|
owner = world["players"].get(order["by"])
|
||||||
if owner:
|
if owner:
|
||||||
|
|
@ -2705,6 +2754,7 @@ def _deliver(world: dict, order: dict, giver: dict, qty: int, now: float) -> Non
|
||||||
if order["delivered"] >= order["qty"]:
|
if order["delivered"] >= order["qty"]:
|
||||||
order["status"] = "done"
|
order["status"] = "done"
|
||||||
order["done"] = now
|
order["done"] = now
|
||||||
|
_refund(world, order, now) # leftover reagents
|
||||||
log(world, now, "order", f"{giver['name']} filled {owner['name'] if owner else 'someone'}'s order: "
|
log(world, now, "order", f"{giver['name']} filled {owner['name'] if owner else 'someone'}'s order: "
|
||||||
f"{amount(order['item'], order['qty'])}")
|
f"{amount(order['item'], order['qty'])}")
|
||||||
|
|
||||||
|
|
@ -4419,7 +4469,7 @@ def content() -> dict:
|
||||||
"city_uses": {item: city_uses(item) for item in ITEMS},
|
"city_uses": {item: city_uses(item) for item in ITEMS},
|
||||||
"prayers": [{**prayer, "text": describe_prayer(prayer)} for prayer in PRAYERS], "max_prayers": MAX_PRAYERS,
|
"prayers": [{**prayer, "text": describe_prayer(prayer)} for prayer in PRAYERS], "max_prayers": MAX_PRAYERS,
|
||||||
"potion_charges": POTION_CHARGES, "order_recipes": ORDER_RECIPES, "order_raw": ORDER_RAW,
|
"potion_charges": POTION_CHARGES, "order_recipes": ORDER_RECIPES, "order_raw": ORDER_RAW,
|
||||||
"order_limit": ORDER_LIMIT, "order_max": ORDER_MAX, "problems": PROBLEMS, "fittings": list(FITTING_BY_ID.values()),
|
"order_limit": ORDER_LIMIT, "order_max": ORDER_MAX, "input_share": INPUT_SHARE, "problems": PROBLEMS, "fittings": list(FITTING_BY_ID.values()),
|
||||||
"equip_slots": EQUIP_SLOTS, "eat_below": EAT_BELOW, "class_speed": CLASS_SPEED,
|
"equip_slots": EQUIP_SLOTS, "eat_below": EAT_BELOW, "class_speed": CLASS_SPEED,
|
||||||
"styles": STYLES, "damage_types": DAMAGE_TYPES, "weights": WEIGHTS, "vulnerable": VULNERABLE,
|
"styles": STYLES, "damage_types": DAMAGE_TYPES, "weights": WEIGHTS, "vulnerable": VULNERABLE,
|
||||||
"resistant": RESISTANT, "jewel_stats": JEWEL_STATS, "stations": list(STATIONS), "class_style": CLASS_STYLE, "block_chance": BLOCK_CHANCE,
|
"resistant": RESISTANT, "jewel_stats": JEWEL_STATS, "stations": list(STATIONS), "class_style": CLASS_STYLE, "block_chance": BLOCK_CHANCE,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ let orderTab = "take"; // work orders: for you, all open, yours
|
||||||
let draft = null; // a work order being written
|
let draft = null; // a work order being written
|
||||||
let seenOrders = null; // order ids already known, so only new ones toast
|
let seenOrders = null; // order ids already known, so only new ones toast
|
||||||
let deliveredSeen = {}; // how much of each order had arrived
|
let deliveredSeen = {}; // how much of each order had arrived
|
||||||
|
let earnedSeen = {}; // gold each order had paid you for reagents
|
||||||
let pickedClass = null; // registration
|
let pickedClass = null; // registration
|
||||||
const tabs = {}; // skill -> selected recipe tab
|
const tabs = {}; // skill -> selected recipe tab
|
||||||
let refreshing = false;
|
let refreshing = false;
|
||||||
|
|
@ -1478,28 +1479,39 @@ function orderNews(state) {
|
||||||
}
|
}
|
||||||
const was = deliveredSeen[o.id];
|
const was = deliveredSeen[o.id];
|
||||||
if (o.by === uid && was !== undefined && o.delivered > was) toast(`📦 ${item(o.item).name} ×${fmt(o.delivered - was)}`, "good");
|
if (o.by === uid && was !== undefined && o.delivered > was) toast(`📦 ${item(o.item).name} ×${fmt(o.delivered - was)}`, "good");
|
||||||
|
const got = (o.earned || {})[uid] || 0, had = earnedSeen[o.id];
|
||||||
|
if (had !== undefined && got > had) toast(`🤝 ${item(o.item).name} · +${fmt(got - had)} gold`, "good");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
seenOrders = new Set(orders.map((o) => o.id));
|
seenOrders = new Set(orders.map((o) => o.id));
|
||||||
deliveredSeen = Object.fromEntries(orders.map((o) => [o.id, o.delivered]));
|
deliveredSeen = Object.fromEntries(orders.map((o) => [o.id, o.delivered]));
|
||||||
|
earnedSeen = Object.fromEntries(orders.map((o) => [o.id, (o.earned || {})[uid] || 0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recipe slots on a crafting order. Anyone can put reagents in the pool; the crafter brings the rest.
|
||||||
function orderSlots(o) {
|
function orderSlots(o) {
|
||||||
const a = C.actions.find((x) => x.id === o.action), out = a.outputs[o.item];
|
const a = C.actions.find((x) => x.id === o.action), out = a.outputs[o.item];
|
||||||
const left = Math.ceil((o.qty - o.delivered) / out), all = Math.ceil(o.qty / out);
|
const left = Math.ceil((o.qty - o.delivered) / out), all = Math.ceil(o.qty / out);
|
||||||
|
const worth = Object.entries(a.inputs).reduce((sum, [id, per]) => sum + item(id).value * per, 0);
|
||||||
return Object.entries(a.inputs).map(([id, per]) => {
|
return Object.entries(a.inputs).map(([id, per]) => {
|
||||||
const name = item(id).name;
|
const name = item(id).name, pool = o.provided[id] || 0, have = S.me.bank[id] || 0;
|
||||||
if (id in o.provided) {
|
if (o.status !== "open") {
|
||||||
return `<div class="ro-slot filled" title="${esc(`${name}: provided`)}"><span class="doll-slot">${item(id).icon}
|
return `<div class="ro-slot open"><span class="doll-slot empty-req" title="${esc(name)}">${item(id).icon}</span>
|
||||||
<span class="swap-qty">${fmt(per * all)}</span><span class="tick">✓</span></span></div>`;
|
<span class="ro-have muted">×${fmt(per * all)}</span></div>`;
|
||||||
}
|
}
|
||||||
const need = per * (o.status === "open" ? left : all), have = S.me.bank[id] || 0;
|
const need = per * left;
|
||||||
// On your own open order, an empty slot can still be filled from your bank.
|
const who = Object.entries(o.gave || {}).filter(([, items]) => items[id]).map(([uid, items]) => `${playerName(uid)} ${fmt(items[id])}`);
|
||||||
const fill = o.by === S.uid && o.status === "open";
|
const tip = [`${name}: ${fmt(pool)} / ${fmt(need)}`, ...who].join(" · ");
|
||||||
return `<div class="ro-slot open">${fill
|
if (pool >= need) {
|
||||||
? `<button class="doll-slot empty-req" data-order-fill="${o.id}" data-item="${id}" ${have < need ? "disabled" : ""} title="${esc(`${name}: ${fmt(have)} / ${fmt(need)}`)}">${item(id).icon}</button>`
|
return `<div class="ro-slot filled" title="${esc(tip)}"><span class="doll-slot">${item(id).icon}
|
||||||
: `<span class="doll-slot empty-req" title="${esc(`${name}: crafter brings`)}">${item(id).icon}</span>`}
|
<span class="swap-qty">${fmt(need)}</span><span class="tick">✓</span></span></div>`;
|
||||||
<span class="ro-have ${o.status !== "open" ? "muted" : have < need ? "bad" : "good"}">${o.status !== "open" ? `×${fmt(need)}` : `${fmt(have)} / ${fmt(need)}`}</span></div>`;
|
}
|
||||||
|
const give = Math.min(have, need - pool);
|
||||||
|
// What putting them in pays you once they're crafted: your value's slice of the reagents' share.
|
||||||
|
const share = o.by === S.uid ? 0 : Math.round(o.reward / o.qty * out * C.input_share * item(id).value / worth * give);
|
||||||
|
const fill = give ? `${tip} · +${fmt(give)}${share ? ` → ${fmt(share)} gold` : ""}` : `${tip} · 🎒 0`;
|
||||||
|
return `<div class="ro-slot open"><button class="doll-slot empty-req" data-order-fill="${o.id}" data-item="${id}" ${give ? "" : "disabled"} title="${esc(fill)}">${item(id).icon}</button>
|
||||||
|
<span class="ro-have ${pool + have < need ? "bad" : "good"}">${fmt(pool)} / ${fmt(need)}</span></div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1536,6 +1548,7 @@ function orderCard(o) {
|
||||||
<div class="sub">${mine ? "Yours" : esc(playerName(o.by))} · ${ago(o.at)}${o.held ? ` · ${fmt(o.held)} waiting for bank space` : ""}</div></div>
|
<div class="sub">${mine ? "Yours" : esc(playerName(o.by))} · ${ago(o.at)}${o.held ? ` · ${fmt(o.held)} waiting for bank space` : ""}</div></div>
|
||||||
<span class="ro-req ${short && !mine ? "bad" : ""}" title="${esc(`${skill.name} ${o.level}`)}">${skill.icon} ${o.level}</span></div>
|
<span class="ro-req ${short && !mine ? "bad" : ""}" title="${esc(`${skill.name} ${o.level}`)}">${skill.icon} ${o.level}</span></div>
|
||||||
${o.kind === "craft" ? `<div class="ro-slots">${orderSlots(o)}</div>` : ""}
|
${o.kind === "craft" ? `<div class="ro-slots">${orderSlots(o)}</div>` : ""}
|
||||||
|
${Object.keys(o.earned || {}).length ? `<div class="small muted">🤝 ${Object.entries(o.earned).map(([uid, n]) => `${esc(playerName(uid))} ${fmt(n)}`).join(", ")}</div>` : ""}
|
||||||
${progress}
|
${progress}
|
||||||
<div class="foot"><span class="item-chip ro-reward">${COIN} ${reward}</span>${status}<span class="btns">${buttons}</span></div>
|
<div class="foot"><span class="item-chip ro-reward">${COIN} ${reward}</span>${status}<span class="btns">${buttons}</span></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
|
||||||
|
|
@ -1679,6 +1679,60 @@ class WorkOrderTest(unittest.TestCase):
|
||||||
self.assertEqual(self.mike["bank"].get("iron_warhammer"), 1)
|
self.assertEqual(self.mike["bank"].get("iron_warhammer"), 1)
|
||||||
self.assertEqual(self.fred["bank"], {}) # used his own plank and cores, never needed bars
|
self.assertEqual(self.fred["bank"], {}) # used his own plank and cores, never needed bars
|
||||||
|
|
||||||
|
def test_anyone_can_put_reagents_in_and_gets_a_share(self):
|
||||||
|
self.mike["gold"] = 200
|
||||||
|
game.post_order(self.world, self.mike, "craft", "iron_warhammer", 1, [], 140, T0)
|
||||||
|
order = self.world["orders"][0]
|
||||||
|
self.dave["bank"] = {"iron_bar": 2}
|
||||||
|
self.mike["bank"] = {"iron_bar": 5}
|
||||||
|
self.assertIsNone(game.provide_reagent(self.world, self.dave, order["id"], "iron_bar"))
|
||||||
|
self.assertIsNone(game.provide_reagent(self.world, self.mike, order["id"], "iron_bar"))
|
||||||
|
self.assertEqual(order["provided"], {"iron_bar": 3}) # only what's needed
|
||||||
|
self.assertEqual(self.mike["bank"], {"iron_bar": 4})
|
||||||
|
self.assertEqual(game.provide_reagent(self.world, self.dave, order["id"], "iron_bar"), "Already provided")
|
||||||
|
self.fred["bank"] = {"oak_plank": 1, "clay_core": 3}
|
||||||
|
self.assertIsNone(game.work_order(self.world, self.fred, order["id"], T0))
|
||||||
|
game.process_player(self.world, self.fred, T0 + 600, random.Random(1))
|
||||||
|
# Reagents are worth 84 a run; Dave's 2 bars (20) earn 20/84 of half the reward.
|
||||||
|
self.assertEqual(self.dave["gold"], 17)
|
||||||
|
self.assertEqual(self.fred["gold"], 123) # the requester's own bar leaves its share with the crafter
|
||||||
|
self.assertEqual(order["earned"], {"dave": 17})
|
||||||
|
|
||||||
|
def test_crafter_tops_up_a_short_pool(self):
|
||||||
|
self.mike["gold"] = 200
|
||||||
|
game.post_order(self.world, self.mike, "craft", "iron_warhammer", 1, [], 140, T0)
|
||||||
|
order = self.world["orders"][0]
|
||||||
|
self.dave["bank"] = {"iron_bar": 1}
|
||||||
|
game.provide_reagent(self.world, self.dave, order["id"], "iron_bar")
|
||||||
|
self.fred["bank"] = {"iron_bar": 1, "oak_plank": 1, "clay_core": 3}
|
||||||
|
self.assertEqual(game.work_order(self.world, self.fred, order["id"], T0), "Out of iron bar")
|
||||||
|
self.fred["bank"]["iron_bar"] = 2
|
||||||
|
self.assertIsNone(game.work_order(self.world, self.fred, order["id"], T0))
|
||||||
|
game.process_player(self.world, self.fred, T0 + 600, random.Random(1))
|
||||||
|
self.assertEqual(self.mike["bank"].get("iron_warhammer"), 1)
|
||||||
|
self.assertNotIn("iron_bar", self.fred["bank"])
|
||||||
|
|
||||||
|
def test_pooled_reagents_go_back_to_whoever_gave_them(self):
|
||||||
|
self.mike["gold"] = 200
|
||||||
|
game.post_order(self.world, self.mike, "craft", "iron_warhammer", 1, [], 140, T0)
|
||||||
|
order = self.world["orders"][0]
|
||||||
|
self.dave["bank"] = {"clay_core": 3}
|
||||||
|
game.provide_reagent(self.world, self.dave, order["id"], "clay_core")
|
||||||
|
game.cancel_order(self.world, self.mike, order["id"], T0)
|
||||||
|
self.assertEqual(self.dave["bank"], {"clay_core": 3})
|
||||||
|
self.assertEqual(self.mike["gold"], 200)
|
||||||
|
|
||||||
|
def test_leftover_reagents_return_when_filled_from_a_bank(self):
|
||||||
|
self.mike["gold"] = 200
|
||||||
|
game.post_order(self.world, self.mike, "craft", "iron_warhammer", 1, [], 140, T0)
|
||||||
|
order = self.world["orders"][0]
|
||||||
|
self.dave["bank"] = {"clay_core": 3}
|
||||||
|
game.provide_reagent(self.world, self.dave, order["id"], "clay_core")
|
||||||
|
self.fred["bank"] = {"iron_warhammer": 1}
|
||||||
|
self.assertIsNone(game.deliver_order(self.world, self.fred, order["id"], T0))
|
||||||
|
self.assertEqual(order["status"], "done")
|
||||||
|
self.assertEqual(self.dave["bank"], {"clay_core": 3})
|
||||||
|
|
||||||
|
|
||||||
class Always:
|
class Always:
|
||||||
"""An rng where every roll comes up: a gem, a drop, whatever's on offer."""
|
"""An rng where every roll comes up: a gem, a drop, whatever's on offer."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue