GS2-Formation

Party and equipment formation feature

It is a common specification to combine the resources you own into a single composition. This is the case when organizing multiple characters into a party, or organizing items such as weapons and armor as an equipment set.

GS2-Formation is a microservice that defines “what slots exist”, “what can be attached to each slot”, and “how many of the same type of formation can be held” as master data, and manages each player’s formation contents.

Terminology

graph LR
  MoldModel --> FormModel
  FormModel --> SlotModel
  PropertyFormModel --> SlotModel2[SlotModel]

  Mold --> Form
  Form --> Slot
  PropertyForm --> Slot2[Slot]
Term Description
MoldModel A type for holding multiple instances of the same kind of formation (parties, equipment sets, etc.). It has the concept of capacity (storage slots)
FormModel The configuration definition of a Form associated with a Mold. Defines what slots it has
PropertyFormModel A standalone formation definition referenced by property ID. Used in a form where one character has one formation, without going through Mold
SlotModel Slot definition. The properties that can be attached to a slot can be restricted with a regular expression
Mold The Mold instance per player. Has a capacity and can create Forms up to the maximum capacity
Form A formation instance within a Mold. Identified by index
PropertyForm A formation instance per property ID
Slot Holds the resource actually attached by the player

Forms

To implement character equipment functionality, you can prepare multiple slots such as weapons, helmets, gauntlets, torsos, legs, and feet, and configure settings so that only items compatible with each slot can be equipped.

In Form Model, the types of slots that exist and the types of items that can be organized into each slot are defined as master data.

Choosing between Mold and PropertyForm

flowchart LR
  subgraph Mold
    M1["Form #0"]
    M2["Form #1"]
    M3["Form #2 (empty)"]
  end
  subgraph PropertyForm
    P1["Formation of character-001"]
    P2["Formation of character-002"]
  end
  • Use Mold when: you want to manage a set of formations (such as Party 1 to N) by number (index) and expand the maximum number held (capacity) as a growth element
  • Use PropertyForm when: the formation is tied 1:1 to an external resource ID (propertyId), such as per-character or per-equipment-set

The MoldModel of Mold has initialMaxCapacity (initial capacity) and maxCapacity (expansion upper limit), allowing capacity to be increased or decreased according to player growth or monetization strategies.

Targets that can be attached to slots

The following resources can be attached to a Slot of a Form / PropertyForm. When attaching, the “proof-of-ownership signature” issued by GS2 is passed as SlotWithSignature.

propertyType Attachment Target
gs2_inventory ItemSet managed by GS2-Inventory
gs2_simple_inventory SimpleItem managed by GS2-Inventory (Simple)
gs2_dictionary Entry managed by GS2-Dictionary

By setting a regular expression in the slot model’s (SlotModel) propertyRegex, you can restrict which property IDs can be attached to a specific slot. For example, you can implement a restriction such as “only Items matching weapon-* can be attached to the weapon slot”.

Managing master data

By registering master data, you can configure the data and behavior available to the microservice.

The types of master data are as follows:

  • MoldModel: Mold storage slot configuration
  • FormModel: Slot configuration of Forms under a Mold
  • PropertyFormModel: PropertyForm slot configuration

Example JSON of master data

{
  "version": "2019-09-09",
  "moldModels": [
    {
      "name": "party",
      "metadata": "Party formation",
      "initialMaxCapacity": 3,
      "maxCapacity": 10,
      "formModel": {
        "name": "party",
        "slots": [
          { "name": "leader", "propertyRegex": "character-.*" },
          { "name": "member-1", "propertyRegex": "character-.*" },
          { "name": "member-2", "propertyRegex": "character-.*" }
        ]
      }
    }
  ],
  "propertyFormModels": [
    {
      "name": "equipment",
      "metadata": "Character equipment",
      "slots": [
        { "name": "weapon", "propertyRegex": "weapon-.*" },
        { "name": "armor",  "propertyRegex": "armor-.*" }
      ]
    }
  ]
}

Master data can be registered from the Management Console, or you can set up a workflow that reflects data from GitHub or registers via CI using GS2-Deploy.

Script Triggers

Setting updateMoldScript updateFormScript updatePropertyFormScript in the namespace allows custom scripts to be executed before and after formation data updates. Scripts support both synchronous and asynchronous execution, with asynchronous execution supporting external integration via GS2-Script or Amazon EventBridge.

The main event triggers and script setting names that can be configured are as follows:

  • updateMoldScript (completion notification: updateMoldDone): before and after Mold updates
  • updateFormScript (completion notification: updateFormDone): before and after Form updates
  • updatePropertyFormScript (completion notification: updatePropertyFormDone): before and after PropertyForm updates

Transaction Actions

GS2-Formation provides the following transaction actions:

  • Verify Action: Verify capacity (maximum storage quantity)
  • Consume Action: Subtract capacity
  • Acquire Action: Add capacity, set capacity, set formation contents (Form / PropertyForm), apply acquire actions to organized resources

By using “Apply acquire actions to organized resources” as an acquire action, it is possible to perform processes such as directly adding experience points to a character organized in a specific party slot. Additionally, by setting “Add capacity” as a reward, it is possible to automatically expand formation slots upon achieving a specific mission.

Implementation example

Persisting formation in Mold

Form Slots can register ItemSets/SimpleItems managed by GS2-Inventory or Entries managed by GS2-Dictionary. To set either of these, a proof-of-ownership signature must be added.

See the description of each service for information on how to obtain a proof-of-ownership signature.

propertyType Types

  • gs2_inventory – Attach an ItemSet managed by GS2-Inventory
  • gs2_simple_inventory – Attach a SimpleItem managed by GS2-Inventory
  • gs2_dictionary – Attach an Entry managed by GS2-Dictionary
    var result = await gs2.Formation.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Mold(
        moldName: "mold-0001"
    ).Form(
        index: 0
    ).SetFormAsync(
        slots: new [] {
            new Gs2.Unity.Gs2Formation.Model.EzSlotWithSignature
            {
                Name = "slot-0001",
                PropertyType = "gs2_dictionary",
                Body = "body",
                Signature = "signature",
            },
        },
        keyId: "key-0001"
    );
    const auto Domain = Gs2->Formation->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Mold(
        "mold-0001" // moldName
    )->Form(
        0 // index
    );
    const auto Future = Domain->SetForm(
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Formation::Model::FSlotWithSignature>>>();
            v->Add({'name': 'slot-0001', 'propertyType': 'gs2_dictionary', 'body': 'body', 'signature': 'signature'});
            return v;
        }(),
        "key-0001"
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();

Increasing Mold Capacity

Capacity (maximum storage quantity) cannot be increased directly via the SDK for game engines. Allowing client-driven capacity manipulation would expose the value to tampering, so the design intentionally requires capacity changes to be performed through transaction actions.

Use GS2-Exchange to increase capacity as a reward, such as through exchange with premium currency. By configuring the Formation capacity-add action in the acquireActions of GS2-Exchange, capacity expansion can be realized as an extension of granting paid items or mission rewards.

Get Mold information

Get information about a single Mold (current capacity).

    var item = await gs2.Formation.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Mold(
        moldName: "mold-0001"
    ).ModelAsync();
    const auto Domain = Gs2->Formation->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Mold(
        "mold-0001" // moldName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Item = Future->GetTask().Result();

Get a list of formation contents in Mold

    var items = await gs2.Formation.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Mold(
        moldName: "mold-0001"
    ).FormsAsync(
    ).ToListAsync();
    const auto Domain = Gs2->Formation->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Mold(
        "mold-0001" // moldName
    );
    const auto It = Domain->Forms(
    );
    TArray<Gs2::UE5::Formation::Model::FEzMoldPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

Get formation content in Mold

    var item = await gs2.Formation.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Mold(
        moldName: "mold-0001"
    ).Form(
        index: 0
    ).ModelAsync();
    const auto Domain = Gs2->Formation->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Mold(
        "mold-0001" // moldName
    )->Form(
        0 // index
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Item = Future->GetTask().Result();

Deleting a Form

Delete formation contents that are no longer needed. The Mold capacity itself is preserved, and the slot at index becomes reusable as an empty slot.

    var result = await gs2.Formation.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Mold(
        moldName: "mold-0001"
    ).Form(
        index: 0
    ).DeleteFormAsync(
    );
    const auto Future = Gs2->Formation->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Mold(
        "mold-0001" // moldName
    )->Form(
        0 // index
    )->DeleteForm(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

Persisting formation in Property Form

Form Slots can register ItemSets managed by GS2-Inventory or Entries managed by GS2-Dictionary. To set either of these, a proof-of-ownership signature must be added.

See the description of each service for information on how to obtain a proof-of-ownership signature.

    var result = await gs2.Formation.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).PropertyForm(
        formModelName: "form-0001",
        propertyId: "property-0001"
    ).SetPropertyFormAsync(
        slots: new [] {
            new Gs2.Unity.Gs2Formation.Model.EzSlotWithSignature
            {
                Name = "slot-0001",
                PropertyType = "gs2_dictionary",
                Body = "body",
                Signature = "signature",
            },
        },
        keyId: "key-0001"
    );
    const auto Domain = Gs2->Formation->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->PropertyForm(
        "form-0001", // formModelName
        "property-0001" // propertyId
    );
    const auto Future = Domain->SetPropertyForm(
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Formation::Model::FSlotWithSignature>>>();
            v->Add({'name': 'slot-0001', 'propertyType': 'gs2_dictionary', 'body': 'body', 'signature': 'signature'});
            return v;
        }(),
        "key-0001"
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();

Get formation content in Property Form

    var item = await gs2.Formation.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).PropertyForm(
        formModelName: "form-0001",
        propertyId: "property-0001"
    ).ModelAsync();
    const auto Domain = Gs2->Formation->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->PropertyForm(
        "form-0001", // formModelName
        "property-0001" // propertyId
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Item = Future->GetTask().Result();

Deleting a Property Form

    var result = await gs2.Formation.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).PropertyForm(
        formModelName: "form-0001",
        propertyId: "property-0001"
    ).DeletePropertyFormAsync(
    );
    const auto Future = Gs2->Formation->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->PropertyForm(
        "form-0001", // formModelName
        "property-0001" // propertyId
    )->DeletePropertyForm(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

Obtain a signature for formation information

When you want to pass the contents of a Form / PropertyForm to another system in a form that guarantees it has not been tampered with, you can obtain signed formation information. This can be used, for example, in a use case that sends the formation contents to the server at the start of a battle.

    var domain = await gs2.Formation.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Mold(
        moldName: "mold-0001"
    ).Form(
        index: 0
    ).GetFormWithSignatureAsync(
        keyId: "key-0001"
    );
    const auto Future = Gs2->Formation->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Mold(
        "mold-0001" // moldName
    )->Form(
        0 // index
    )->GetFormWithSignature(
        "key-0001" // keyId
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

Resource Management and Notes

Specifying Properties

Properties to be placed in formation slots are specified by property ID. By setting a regular expression for property IDs in the slot model’s propertyRegex, you can restrict which values can be assigned to that slot.

Proof-of-Ownership Signatures and Post-Formation Caveats

When setting items managed by GS2-Inventory to a slot, you can specify the formation target using a signed item set (proof-of-ownership signature) issued by GS2-Inventory. The ownership guarantee provided by the signed item set is valid only at the time of composition. If the item is subsequently sold or consumed, the formation data remains unchanged. Therefore, when consuming or selling an item in GS2-Inventory, you must first verify — in the client or a script — that the item is not currently in use in a GS2-Formation slot.

Preventing Deletion of Items in Formation via ItemSet.referenceOf

When using Standard Inventory (GS2-Inventory), you can prevent accidental consumption or deletion of items that are currently in formation by using the referenceOf field on ItemSet.

When placing an item into a formation slot, set a string that identifies the slot (e.g., a GRN) in ItemSet.referenceOf. The Inventory Model settings in Standard Inventory include an option to “prohibit consumption when referenceOf is set”. When this option is enabled, any item whose ItemSet.referenceOf is non-empty cannot be consumed or sold, effectively locking the item at the system level while it is in use in a formation.

Detailed Reference