Skip to content

Conversation

@Roudenn
Copy link
Contributor

@Roudenn Roudenn commented Aug 12, 2025

About the PR

Stores are now predicted.

Requires #38712

Why / Balance

You can access store systems from Shared code, and also it works smoothly.

Technical details

Most of the StoreSystem server code was moved to the new SharedStoreSystem, the code was also refactored to support prediction.
Listings handling was reworked, now instead of storing all dynamically generated listings in the component and serializing all of it, now you can get a list of all listings with a call to StoreSystem directly. The only thing that is stored are changes to these listings in a dictionary.

Media

TODO

Requirements

Buying works moslty fine, but other interactions like currency withdrawing or conditional listings are mis-predicting and broken.

  • Fix store being empty when being predictevily opened for the first time
  • Fix currency insertion mispredict
  • Fix refunding mispredict
  • Test AccountOwner check and behavior with multiple clients interacting with the same shop
  • Remake all ListingConditions to prototypes for Networking optimization (?)
  • Move all open store actions to Shared (PAI, Revenant, PDA uplink) (requires all required PRs)

Breaking changes

Most of the old StoreSystem code was moved to the new SharedStoreSystem. It also includes partials, so StoreSystem partials on server-side were removed and also ported to Shared counterparts.
The API methods have been changed to use the Entity<T?> or Entity<T> pattern.
StoreSystem's UpdateUserInterface() public method was removed.
All ListingConditions moved to the Content.Shared.Store.Conditions namespace.
CurrencyComponent moved to the Content.Shared.Store.Components namespace and has NetworkedComponent, AutoGenerateComponentState attributes.
StoreComponent now has AutoGenerateComponentState attribute.
Removed StoreUpdateState BUI state, since StoreBoundUserInterface now relies on predicted/networked data from StoreComponent.
Removed StoreSystem.Command partial entirely, addcurrency command moved to the Content.Server.Store.Commands namespace at AddCurrency.cs class.
StoreSystem.CloseUi method was removed, if you still need it use SharedUserInterfaceSystem.CloseUi(uid, StoreUiKey.Key)

@PJBot PJBot added S: Untriaged Status: Indicates an item has not been triaged and doesn't have appropriate labels. S: Needs Review Status: Requires additional reviews before being fully accepted. Not to be replaced by S: Approved. size/L Denotes a PR that changes 1000-4999 lines. labels Aug 12, 2025
@slarticodefast
Copy link
Member

I always thought the main reason stores are not predicted is because networking them would be a very easy antag checker for cheat clients. Although in many other cases we prioritize smooth gameplay over potential for cheats.

Also this will heavily conflict with this PR #38712
Having the stores be remote entities means they are outside PVS range, meaning prediction would break unless you add a PVS override for players in interaction range to it.

@Roudenn
Copy link
Contributor Author

Roudenn commented Aug 12, 2025

I always thought the main reason stores are not predicted is because networking them would be a very easy antag checker for cheat clients. Although in many other cases we prioritize smooth gameplay over potential for cheats.

Antag detection cheats are already possible, and issue with cheats can be resolved only when components will support conditional networking, so only antags for example can receive component states related to this component. Probably should be an engine event.

@slarticodefast
Copy link
Member

when components will support conditional networking

They already do with SendOnlyToOwner and SessionSpecific, but those are rarely used because it needs some boilerplate if you want some kind of component whitelist, see SharedRevolutionarySystem. I have some ideas how the API could be improved for that.

But either way, that can't really be used for stores, because anyone has to be able to interact with them, not only antagonists.

@iaada iaada added P3: Standard Priority: Default priority for repository items. T: Refactor Type: Refactor of notable amount of codebase A: General Interactions Area: General in-game interactions that don't relate to another area. S: Draft Status: This is a draft and might need to be retriaged upon opening. and removed S: Untriaged Status: Indicates an item has not been triaged and doesn't have appropriate labels. labels Aug 12, 2025
@github-actions github-actions bot added the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Aug 20, 2025
@github-actions
Copy link
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Aug 20, 2025
@ElectroJr
Copy link
Member

FYI, FullListingsCatalog needs to be reworked (#40242) and is probably one of the reasons why your test file in space-wizards/RobustToolbox/pull/6166 was so large. So if this is going to be a decently sized refactor, it probably makes sense to also fix that in this PR.

And regarding conditional networking, I want to clarify that all comp states can have session-specific data. The ComponentGetState event handler can just check ComponentGetState.Player and customize the component state for the recipient (note that Player==null is a replay, and should contain all info), you don't necessarily need to use the stuff slarti mentioned.

SendOnlyToOwner, SessionSpecific, and ComponentGetStateAttemptEvent are all used to decide if a player is allowed to know about a component in the first place. E.g., for RevolutionaryComponent, you don't just want to modify the data in the comp state received by each player, you want to completely prevent them from even knowing that an entity even has the component.

IIRC currently PDAs just all have a store to prevent antag detection, so if that doesn't change, you don't need to use ComponentGetStateAttemptEvent. But handling per-session component states and dirtying the component is probably still not going to be all that simple.

@slarticodefast slarticodefast added the T: Prediction Type: Moving code to Content.Shared and making it predicted. label Sep 11, 2025
@github-actions github-actions bot added the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Oct 9, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 9, 2025

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Oct 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A: General Interactions Area: General in-game interactions that don't relate to another area. P3: Standard Priority: Default priority for repository items. S: Draft Status: This is a draft and might need to be retriaged upon opening. S: Needs Review Status: Requires additional reviews before being fully accepted. Not to be replaced by S: Approved. size/L Denotes a PR that changes 1000-4999 lines. T: Prediction Type: Moving code to Content.Shared and making it predicted. T: Refactor Type: Refactor of notable amount of codebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants