feat(host): serve owned non-player content from Core's ownership truth
Follows Core's kit designations becoming generic active-item slots: the host reads `GET /club/active-items` (five always-present slots) instead of the removed `/club/kits`. Adds the consumables route and widens the club families to every content kind, all resolved from Core ownership + the FIFA catalog. An item the client sees is now an item Core actually owns.
This commit is contained in:
@@ -28,6 +28,7 @@ use serde_json::{json, Value};
|
||||
|
||||
use openfut_adapter_fifa17::fut::entities::ReverseEntityResolver;
|
||||
use openfut_adapter_fifa17::fut::item::{shape_item, ItemIdentityResolver};
|
||||
use openfut_adapter_fifa17::fut::item_state;
|
||||
use openfut_adapter_fifa17::fut::non_economy;
|
||||
use openfut_adapter_fifa17::fut::squad::SquadWireResolver;
|
||||
|
||||
@@ -81,7 +82,7 @@ fn trade_id_from_path(path: &str) -> Option<String> {
|
||||
/// snapshot persisted at listing time; a row written before snapshots existed
|
||||
/// degrades to the stub (honest, not fabricated).
|
||||
///
|
||||
/// `item_state` overrides the card's `itemState`. FIFA 17's vocabulary is the
|
||||
/// `state` overrides the card's `itemState`. FIFA 17's vocabulary is the
|
||||
/// 12-row `{const char*, int}` table at `0x180229cc0`, and `forSale` (5) is its
|
||||
/// value for an item offered for sale. The Python oracle stamps `listFS` on the
|
||||
/// seller's own pile instead — a token that does NOT EXIST in FIFA 17 (zero
|
||||
@@ -89,8 +90,8 @@ fn trade_id_from_path(path: &str) -> Option<String> {
|
||||
/// memory) and therefore decodes to `-1` through `FUN_180166660`, i.e. the client
|
||||
/// is handed an unrecognised `CARD_OFFERSTATE`. Where the binary contradicts the
|
||||
/// oracle, the binary wins.
|
||||
fn auction_record_as(l: &Listing, item_state: &str) -> Value {
|
||||
auction_record_tuned(l, item_state, None, 0)
|
||||
fn auction_record_as(l: &Listing, state: &str) -> Value {
|
||||
auction_record_tuned(l, state, None, 0)
|
||||
}
|
||||
|
||||
/// [`auction_record_as`] with the two fields the staging sold experiment varies.
|
||||
@@ -105,7 +106,7 @@ fn auction_record_as(l: &Listing, item_state: &str) -> Value {
|
||||
/// what makes the client's reaction attributable to the token.
|
||||
fn auction_record_tuned(
|
||||
l: &Listing,
|
||||
item_state: &str,
|
||||
state: &str,
|
||||
sold_bid_state: Option<&str>,
|
||||
coins_processed: i64,
|
||||
) -> Value {
|
||||
@@ -152,7 +153,7 @@ fn auction_record_tuned(
|
||||
.map(|mut card| {
|
||||
// Keep the wire identity and presentation state authoritative here.
|
||||
card["id"] = json!(item_id);
|
||||
card["itemState"] = json!(item_state);
|
||||
card["itemState"] = json!(state);
|
||||
card["untradeable"] = json!(false);
|
||||
card
|
||||
})
|
||||
@@ -160,7 +161,7 @@ fn auction_record_tuned(
|
||||
json!({
|
||||
"id": item_id,
|
||||
"resourceId": resource,
|
||||
"itemState": item_state,
|
||||
"itemState": state,
|
||||
"untradeable": false,
|
||||
})
|
||||
});
|
||||
@@ -204,9 +205,9 @@ fn auction_record_tuned(
|
||||
/// closed/sold echoes the buy path returns.
|
||||
fn auction_record(l: &Listing) -> Value {
|
||||
let state = if l.state == "active" {
|
||||
"forSale"
|
||||
item_state::FOR_SALE
|
||||
} else {
|
||||
"free"
|
||||
item_state::FREE
|
||||
};
|
||||
auction_record_as(l, state)
|
||||
}
|
||||
@@ -444,7 +445,7 @@ pub async fn handle_market_query(
|
||||
};
|
||||
let mut auctions: Vec<Value> = listings
|
||||
.iter()
|
||||
.map(|l| auction_record_as(l, "forSale"))
|
||||
.map(|l| auction_record_as(l, item_state::FOR_SALE))
|
||||
.collect();
|
||||
// STAGING ONLY. FIFA 17's bulk `DELETE …/trade/sold` verb only makes sense if
|
||||
// sold rows persist in the seller's pile until acknowledged, so the experiment
|
||||
@@ -456,7 +457,7 @@ pub async fn handle_market_query(
|
||||
for l in &sold {
|
||||
auctions.push(auction_record_tuned(
|
||||
l,
|
||||
"forSale",
|
||||
item_state::FOR_SALE,
|
||||
exp.bid_state,
|
||||
exp.coins_processed,
|
||||
));
|
||||
@@ -607,7 +608,7 @@ pub async fn handle_market_status(
|
||||
};
|
||||
let auctions: Vec<Value> = listings
|
||||
.iter()
|
||||
.map(|l| auction_record_tuned(l, "forSale", exp.bid_state, exp.coins_processed))
|
||||
.map(|l| auction_record_tuned(l, item_state::FOR_SALE, exp.bid_state, exp.coins_processed))
|
||||
.collect();
|
||||
eprintln!(
|
||||
"utas-host owner=RUST route=market-status requested={} returned={} query={}",
|
||||
@@ -723,7 +724,7 @@ pub async fn handle_market_buy(
|
||||
rec["tradeState"] = json!("closed");
|
||||
rec["bidState"] = json!("highest");
|
||||
rec["currentBid"] = json!(price);
|
||||
rec["itemData"]["itemState"] = json!("free");
|
||||
rec["itemData"]["itemState"] = json!(item_state::FREE);
|
||||
ok_json(&json!({ "auctionInfo": [rec], "credits": new_balance }))
|
||||
}
|
||||
// Insufficient funds surfaced by Core (concurrent debit) -> 461.
|
||||
@@ -1155,6 +1156,10 @@ mod tests {
|
||||
let pile = handle_market_query("active", &econ, &store, SoldExperiment::OFF).await;
|
||||
let rec = parse(&pile)["auctionInfo"][0].clone();
|
||||
assert_eq!(rec["itemData"]["itemState"], "forSale");
|
||||
assert!(
|
||||
item_state::is_recovered(rec["itemData"]["itemState"].as_str().unwrap()),
|
||||
"every emitted itemState must be in FIFA 17's own 12-row table"
|
||||
);
|
||||
assert_eq!(rec["itemData"]["rating"], 84);
|
||||
assert_eq!(rec["itemData"]["id"], 100004617i64);
|
||||
assert_eq!(rec["itemData"]["resourceId"], 169193);
|
||||
|
||||
Reference in New Issue
Block a user