Conversation
9b27e77 to
12be0ad
Compare
6eca7f7 to
a99ad8b
Compare
- Refactor unit sharing system into three modes: enabled, t2cons, disabled - Fix /take command blockage when sharing is restricted - Decouple Unit Market transfers from sharing restrictions - Remove redundant unit transfer check from tax resource sharing - Messaging when transfers are incomplete - common unit sharing logic goes in common, with some limited intellisense support - Add isEconomicUnitDef function to support "combat" and identify only economic units (factories, assist units, energy/metal buildings) - ui changes to support this, including greying the transfer button (with a tooltip) in the advanced player list. Addresses Issue beyond-all-reason#4416 and incorporates PR feedback.
- Add modoption `player_metal_send_threshold` to defer metal tax until a sender’s cumulative sent metal exceeds the threshold; energy unchanged (taxed only by `tax_resource_sharing_amount`). - Track cumulative sent metal per sender and expose via team rules params: `metal_share_cumulative_sent`, `metal_share_threshold`, `resource_share_tax_rate`. - UI (AdvPlayersList): while dragging the share slider - Energy shows received/sent preview. - Metal shows amount_to_send/max_allowance and caps the slider by min(my metal, receiver free, remaining allowance). - Right-justified label with auto-sized grey background to fit two-number display. - Echo on send uses i18n: “Sent Xm[/Ym allowed by the player send threshold]” (optional bracket when threshold > 0). - Refactor: add `common/luaUtilities/resource_share_tax.lua` and use it from both gadget and UI to keep the math in one place. - Safety: clamps against receiver max share, handles 100% tax case, guards against negatives. Default threshold is 0 (immediate tax if base rate > 0). No changes to unit-sharing logic. - i18n: add `ui.playersList.chat.sentMetalSimple` and `ui.playersList.chat.sentMetalThreshold` (en).
a99ad8b to
6c721e6
Compare
Introduces mode-first sharing configuration where UI modes set/lock/hide individual modoptions. Enables composable sharing policies with clear separation between UI orchestration and gadget enforcement. - Tag sharing modoptions with sharing_category for UI grouping - Add sharingoptions.json with 4 modes: no_sharing, limited_sharing, enabled, customize - Include JSON schema + CI validation for mode configuration - Support allowRanked flag to disable ranked queue for experimental modes - ui and sorting issues handled with the `depends_on=parent` flag on modoptions.lua always putting that option after its parent - passes the mode onto the game through a system mod option `_sharing_mode_selected`. This required giving the game a little knowledge of modes, but it owns them anyway and it is limited to a single helper file * ui and sorting issues * disable mod options entirely based on mode whitelist. This required giving the game a little knowledge of modes, but it owns them anyway and it is limited to a single helper file
6c721e6 to
1069815
Compare
The share slider was rendering behind other UI elements because commit 064e1ee changed the widget layer from -4 to 1. Lower layer numbers render first (behind) and higher layer numbers render later (on top). Reverting to layer -4 fixes the z-index issue while preserving the team transfer API improvements. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Keith Harvey <keithdanielharvey@gmail.com>
The problemUser StoryAs a lobby hostI want to be able to configure a fun group of mod options to enable the game to be played with sharing options restricted. As a devD1) I want to be able to online new rules quickly Discussion of Technical Limitations of Status QuoStateful GadgetsGadgets in BAR are little emperors, so if you add a gadget that effects unit transfers, you need to know about every other gadget that effects unit transfers. This leads to a proliferation of sync calls between gadgets as system complexity grows Runtime Game RulesIf you accept (D6) as a requirement, then think about the following example: gui_advplayerslist needs to draw the
Then they click into that and it now needs to know
Helpers get you around this to some extent by just having the UI layer call helpers after parsing state consistently, but whenever you want to express realtime game rules, this logic, and the spider web of state, becomes onerous to track down for every caller into a functional helper. Complexity LimitsThis is somewhat secondary, but the current system really CANNOT support internal complexity outside the engine, because it is interwoven with the engine inherently with the gadget system. The old tax_resource_sharing gadget, for example, relied on AllowResourceTransfer from the engine directly. So our game code calls out to the engine to Initiate a transfer, we intercept the engine validation hook, evaluate it against our gadget logic, stop the transfer, then reinitialize a transfer through the engine. The psychological load of this pattern on developers is enormous. You not only have to know intimately every other gadget that is listening to AllowResourceTransfer, but you have to know they're going to respect your state flags when your re-transfer comes back around to AllowX from the engine the second time. We control the game code. There is no reason to talk to the engine about transferring units between teams when that logic lives with the game. This same pattern repeats verbatim with AllowUnitTransfer. Unit testing this in isolation of the engine becomes problematic. There is no way to isolate state internal to a game module in useful ways if you have to go through these black boxed spring hooks you fired off yourself. The Engine Wants Out AnywayThe engine team has already signalled they want to get out of dealing with the "transfer reason" passthrough for AllowUnitTransfer. It is currently a "capture" boolean on AllowUnitTransfer, which is WOEFULLY inadequate for trying to figure out who called TransferUnit from data during the round-trip (so again, you're back to stateful flags between gadgets). Better would just be to call our game module and have callers conform to our internal API. Ideal Replacement System Design Goals
Technical SolutionIntroduce a new module to own team transfers. Sharing OptionsSharing options are a single dropdown under the Sharing tab that allows developers to lock/hide/show specific mod options for a given 'sharing mode'. [Screenshot of Sharing Tab] Team Transfer Gadget APIThe outside API exposes things that mirror the current Spring API, but internally we build a list of policies that we create a cache derived current game state for every combination of my_team -> ally_team. This gives a number of advantages:
PoliciesPolicies are a way to define dynamic, cacheable game rules in bar. This same pattern could be similarly replicated for every game rule we have but I think team transfer is a good domain to start, because it is in need of the capabilities this sort of factoring offers. [team_transfer/api_gadgets] [initialize] > evaluate many [policies] present in current sharing option [Team Transfer Architecture Diagram] Case Study demonstrating how to build building_unlocks_sharing.lua |
8400bc2 to
f3fbabc
Compare
6e22288 to
2639ae9
Compare
9c3b18a to
17e9892
Compare
a058b07 to
b93a8e4
Compare
2fa8883 to
ff0fa4f
Compare
4affe98 to
aa3e22d
Compare
244e0ec to
f4d256a
Compare
acacce8 to
c299092
Compare
0a3d006 to
ce774d4
Compare
5b33353 to
8cb6740
Compare
Technical Goals
The purpose is a self-contained
team_transfer/module that cuts off its own API from the engine and state. That last bit is important because it allows the API to talk explicitly about the plan and not the execution.This moves execution to one place, which is convenient because that serves as our one bit of code that bridges back over to the engine. So as we online new engine capabilities like
/take, we have a place to put it.Future Goals
The goal here would be to gradually take over our own behavior entirely, centralize the SyncedActionFallback directly related to sharing within this module, and have all other game modules talk to us for anything sharing related. This greatly simplifies our ability to reason about a comprehensive solution to sharing in the long run. And it would provide an example LUA implementation that other games could use if the engine ever wanted to get out of the deciding who goes where when. That would be extremely clean. Engine is just raw hooks to explicitly do something now. Games listen, for example, to player abandoned, then tell the engine what to do. The game does its thing, but the rules around that behavior live entirely in team transfer.
Implementation Overview
policy.ForAlliedCommands.WhenGuard.Allow()API Examples
The fluent API enables readable policy definitions:
This was a personal stylistic choice we could definitely convert to something purely functional or whatever pattern we want to expose our API in.
Architecture Benefits
/takeLegacy System Integration
AllowResourceTransfer/AllowUnitTransferhooks still supportedgame_team_transfer.luateam_transfer/policies/directoryWhy So Big?
Unfortunately this PR needs to do a couple of novel things at once. It is unit testing, that's a significant part of that LOC, and it's pulling in engine hooks and adding a pipeline system to abstract them for the rest of the module. That code, to me, is mostly boilerplate to accomplish the goals. In most languages it would be generics and library code, and maybe we could do that in LUA - but it seemed ambitious to just move all of this directly into common and try to genericize it before we even had implementation number 1. That would not make this PR less complicated, because we're still pulling in the entire team transfer system and engine calls, so we need some boilerplate for that.
Testing Status
README_test_runner.mdwith comprehensive testing frameworkThese are the least finished part of this. Working on that now.
Game Testing
Works but needs massive amounts more testing.
Remaining Work