diff --git a/fifa17-recon/tools/utas_server.py b/fifa17-recon/tools/utas_server.py index f768ecf..bd88af2 100755 --- a/fifa17-recon/tools/utas_server.py +++ b/fifa17-recon/tools/utas_server.py @@ -1163,6 +1163,48 @@ def _club_stat_context(kind): +# FUT_CONSUM_STATS: answer the CONSUMABLES panel with CONSUMABLE counts. +# +# LIVE 2026-08-05, and this is the whole reason the tab was empty. The client asks +# GET club/stats/consumables 41 times a session and we answered it with the PLAYER +# stat set (players 205, playersGold 189 ...). The panel reads a different set of +# names entirely, so it was told about footballers when it asked about contracts. +# The player screenshot is unambiguous: seven categories -- Training, Contracts, +# Fitness, Healing, Chemistry Style, Manager League, Position Modifier -- every one +# reading 0, on a tab that is present and selectable. +# +# The vocabulary is not a string table: FUN_18012fd40 looks the `type` string up in +# the ATOM table and switches on 40 atom ids, default `return 0`. An unrecognised +# name is therefore INERT, not fatal, which is what makes appending these rows safe. +# +# COUNT THE SHELF, NOT THE STORE. The consumables we serve are a synthetic overlay +# and are never granted into the save, so STORE.items() holds none of them and +# counting it yields fourteen zeros -- which looks exactly like failure on screen and +# would make the experiment unreadable. +# +# Default ON: answering the consumables panel with player counts is wrong by +# inspection, not a judgement call. Set FUT_CONSUM_STATS=0 to go back. +CONSUM_STATS = os.environ.get("FUT_CONSUM_STATS", "1") == "1" + + +def _consumable_stat_rows(): + """[(stat name, count)] for the consumables panel, counted from the shelf.""" + if not (CONSUM_STATS and CONSUMABLES): + return [] + try: + import fut_consumables + import fut_club_stats + except Exception as e: # never break the panel over this + log(" CLUBSTATS: consumable rows unavailable (%s)" % e) + return [] + shelf = fut_consumables.starter_consumables(fut_consumables.CONSUMABLE_ID_BASE) + g = fut_club_stats.global_counts(shelf) + rows = [(fut_club_stats.VOCAB[sid], v) for sid, v in sorted(g.items()) + if sid >= 0x3C and sid in fut_club_stats.VOCAB] + log(" CLUBSTATS: %d consumable row(s) from a shelf of %d" % (len(rows), len(shelf))) + return rows + + def _club_stat_set(): """The complete global stat set, computed from what the club actually holds.""" items = STORE.items() @@ -1211,6 +1253,7 @@ def _club_stat_set(): ("trophiesFeaturedOnline", 0), # 0x36 ("trophiesSeasonOffline", 0), # 0x37 ] + counts += _consumable_stat_rows() # All four keys in every element -- see note 3 above. return [{"contextId": 1, "contextValue": 0, "type": t, "typeValue": int(v)} for t, v in counts]