sharedcontainers

Shared Containers (Group Kit)

Containers owned by a group rather than a character — a crew kit, a party stash, a team's shared crate — over the Container engine: the contents are pushed to every session the game's audience seam admits, and only those sessions may read or rearrange them. Items enter and leave through server-side game verbs (atomic exchanges with a member's bag); the wire carries request and move only, so a client is never trusted to say what it takes. Inert until an audience is composed.

Category Economy Seams 1 Services 1 Options 1 Wire ids 4 Unity types 1
Server
services.AddCrossplaySharedContainers();
Unity package
com.crossplay.sharedcontainers
Depends on
container

Seams you implement 1

Crossplay calls these; your game supplies them. Each ships an inert or permissive default, so register yours before services.AddCrossplaySharedContainers(); and it wins.

ISharedContainerAudience game SPI

The game's answer to "who sees this container?" — the one decision that makes a container SHARED. Consulted on every wire request and move (may this session act on it?) and on every change (who gets the fresh contents pushed?). The framework never knows what a scope is: a crew, a party, a team, a match, a household, a guild — the game names its containers and answers for them here.

Example
// A crew kit: "crew:{matchId}" is visible to everyone in that match.
public sealed class CrewKitAudience : ISharedContainerAudience
{
    public bool CanSee(ISession s, ContainerId c)
        => c.TryGetOwner("crew", out long matchId) && _matches.IsIn(matchId, s);
    public void CollectAudience(ContainerId c, List<ISession> into)
    {
        if (c.TryGetOwner("crew", out long matchId))
            _matches.CollectSessions(matchId, into);
    }
}
  • bool CanSee(ISession session, ContainerId container)

    May session read and rearrange container?

  • void CollectAudience(ContainerId container, List<ISession> into)

    Adds every session that should receive container's contents after a change to into (which the piece clears and reuses — never keep a reference).

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

ISharedContainerService

A container owned by a SCOPE rather than a character — a crew kit, a party stash, a team's shared ammo crate, a household chest — whose contents are pushed to every session the game says can see it. The item engine underneath is Crossplay.Container (atomic moves, stacking policy, persistence); this role adds the two things a shared container needs on top: WHICH containers are shared, and WHO sees them (ISharedContainerAudience).

  • event Action<ContainerId> Changed

    Raised after any change to a shared container — the handlers push its contents to the audience.

  • int CountOf(ContainerId id, byte[] item)

    Total count of an item across the container.

  • void Ensure(ContainerId id)

    Marks id as a shared container (creating it with the configured shape if it does not exist). Every other call does this implicitly.

  • IReadOnlyList<SharedContainerSlot> Get(ContainerId id)

    Occupied slots (a fresh list — a snapshot, not a view).

  • bool Give(ContainerId id, byte[] item, int count)

    Adds count of an opaque item, stacking then filling; all-or-nothing.

  • bool IsShared(ContainerId id)

    True once id has been shared through this service.

  • ushort Move(ContainerId id, int fromSlot, int toSlot)

    Moves / merges / swaps two slots. 0 on success, else a SharedContainerNotificationCodes value.

  • bool Take(ContainerId id, byte[] item, int count)

    Removes count of an item across stacks; all-or-nothing.

Configuration 1

Every tunable lives in an options object — there are no magic numbers to hunt for.

SharedContainerOptions

Tunables for shared containers (defaults here, never inline — rule 4). Bound from Crossplay:SharedContainers by the composition layer. One configuration applies to every shared container the piece creates; a game that needs a differently-shaped shared container ensures it on the engine itself with its own ContainerConfig before this piece first touches it.

  • bool GridFootprints { get; set; }

    When true, items occupy multi-cell footprints on a grid of GridWidth columns.

  • int GridWidth { get; set; }

    Grid width in cells when GridFootprints is on (0 = linear slots).

  • int MaxContainerIdLength { get; set; }

    Longest container id a client may name on the wire. An id is a small engine key (crew:42); anything longer is refused before it reaches the audience seam, so a hostile client cannot make the server hash, log or echo kilobytes of its choosing.

  • int MaxItemBytes { get; set; }

    Maximum opaque item blob size, in bytes.

  • int MaxStack { get; set; }

    Maximum stack count per slot (the engine's default policy).

  • int SlotCount { get; set; }

    Slots per shared container.

Wire messages 4

The protocol this piece speaks. Ids are allocated per piece so they can never collide.

7101 SharedContainerRequest

Client → server: send me the contents of the shared container ContainerId. The server answers only when this session is in the container's audience (the game's ISharedContainerAudience decides); otherwise the reply carries NotVisible and nothing else — a client cannot enumerate containers it was not given.

7102 SharedContainerContents

Server → client: a shared container's full contents. Sent in reply to a request, and PUSHED to every session in the container's audience after any change (a move, a game-side give/take), so a crew all see the same kit without polling.

7103 SharedContainerMoveRequest

Client → server: move / merge / swap two slots inside a shared container this session can see.

7104 SharedContainerMoveResponse

Server → client: the verdict on a SharedContainerMoveRequest. On success the fresh contents follow as a pushed SharedContainerContents, to every member of the audience.

Unity services you inject 1

Crossplay binds these in the client context. Inject and call them from your own MonoBehaviours and presenters.

ISharedContainersClient

Client SDK for shared containers: request the contents of a container this session may see, move slots inside it, and observe the snapshot the server pushes to every member after any change. The server decides who sees what; a refused request completes with a non-zero ReasonCode and no slots. What a container IS — a crew kit, a stash, a crate — and how it looks are the game's.

  • bool TryGetContents(string containerId, out SharedContainerContents contents)

    The last contents received for containerId, if any.

  • event Action<SharedContainerContents> ContentsChanged

    Raised whenever contents arrive — a reply, or a push after a change by anyone.

  • UniTask<SharedContainerContents> RequestAsync(string containerId, CancellationToken ct = default)

    Requests the contents. Completes with ReasonCode != 0 when the server refuses.

  • UniTask<SharedContainerMoveResponse> MoveAsync(string containerId, int fromSlot, int toSlot, CancellationToken ct = default)

    Moves / merges / swaps two slots. On success the fresh contents follow as a push.