Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docfx/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"resource": [
{
"files": [
"images/**"
"img/**"
]
}
],
Expand All @@ -36,8 +36,9 @@
"modern"
],
"globalMetadata": {
"_appName": "multiclient",
"_appTitle": "multiclient",
"_appName": "Archipelago.MultiClient.Net",
"_appTitle": "Archipelago.MultiClient.Net",
"_appLogoPath": "img/logo/color-icon.svg",
"_enableSearch": true,
"pdf": false
}
Expand Down
109 changes: 109 additions & 0 deletions docfx/docs/helpers/datastore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# DataStorage

DataStorage support is included in the library. You may save values on the archipelago server in order to share them
across other player's sessions or to simply keep track of values outside your game's state.

The DataStorage provides an interface based on keys and their scope. By assigning a value to a key, that value is stored
on the server and by reading from a key a value is retrieved from the server.
Assigning and reading values from the store can be done using simple assignments `=`:

* `= session.DataStorage["Key"]`, read value from the data storage synchronously
* `session.DataStorage["Key"] =`, write value to the data storage asynchronously
* Complex objects need to be stored and retrieved in the form of a `JObject`, therefore you must wrap them into a
`JObject.FromObject()`

The DataStorage also provides methods to retrieve the value of a key asynchronously using `[key].GetAsync`.
To set the initial value of a key without overriding any existing value, the `[key].Initialize` method can be used.
If you're interested in keeping track of when a value of a certain key is changed by any client you can use the
`[key].OnValueChanged` handler to register a callback for when the value gets updated.

Mathematical operations on values stored on the server are supported using the following operators:

* `+`, Add right value to left value
* `-`, Subtract right value from left value
* `*`, Multiply left value by right value
* `/`, Divide left value by right value
* `%`, Gets remainder after dividing left value by right value
* `^`, Multiply left value by the power of the right value

Bitwise operations on values stored on the server are supported using the following operations:

* `+ Bitwise.Xor(x)`, apply logical exclusive OR to the left value using value x
* `+ Bitwise.Or(x)`, apply logical OR to the left value using value x
* `+ Bitwise.And(x)`, apply logical AND to the left value using value x
* `+ Bitwise.LeftShift(x)`, binary shift the left value to the left by x
* `+ Bitwise.RightShift(x)`, binary shift the left value to the right by x

Other operations on values stored on the server are supported using the following operations:

* `+ Operation.Min(x)`, get the lowest value of the left value and x
* `+ Operation.Max(x)`, get the highest value of the left value and x
* `+ Operation.Remove(x)`, when the left value is a list, removes the first element with value x
* `+ Operation.Pop(x)`, when the left value is a list or dictionary, removes the element at index x or key x
* `+ Operation.Update(x)`, when the left value is a list or dictionary, updates the dictionary with the keys/values in x

Operation specific callbacks are supported, these get called only once with the results of the current operation:

* `+ Callback.Add((oldValue, newValue) => {});`, calls this method after your operation or chain of operations are
processed by the server

Mathematical operations, bitwise operations and callbacks can be chained, given the extended syntax with `()` around
each operation.

Examples:

```csharp
var session = ArchipelagoSessionFactory.CreateSession("localhost", 38281);
session.TryConnectAndLogin("Timespinner", "Jarno", ItemsHandlingFlags.AllItems);

//Initializing
session.DataStorage["B"].Initialize(20); //Set initial value for B in global scope if it has no value assigned yet

//Storing/Updating
session.DataStorage[Scope.Slot, "SetPersonal"] = 20; //Set `SetPersonal` to 20, in scope of the current connected user\slot
session.DataStorage[Scope.Global, "SetGlobal"] = 30; //Set `SetGlobal` to 30, in global scope shared among all players (the default scope is global)
session.DataStorage["Add"] += 50; //Add 50 to the current value of `Add`
session.DataStorage["Divide"] /= 2; //Divide current value of `Divide` in half
session.DataStorage["Max"] += + Operation.Max(80); //Set `Max` to 80 if the stored value is lower than 80
session.DataStorage["Dictionary"] = JObject.FromObject(new Dictionary<string, int>()); //Set `Dictionary` to a Dictionary
session.DataStorage["SetObject"] = JObject.FromObject(new SomeClassOrStruct()); //Set `SetObject` to a custom object
session.DataStorage["BitShiftLeft"] += Bitwise.LeftShift(1); //Bitshift current value of `BitShiftLeft` to left by 1
session.DataStorage["Xor"] += Bitwise.Xor(0xFF); //Modify `Xor` using the Bitwise exclusive or operation
session.DataStorage["DifferentKey"] = session.DataStorage["A"] - 30; //Get value of `A`, Assign it to `DifferentKey` and then subtract 30
session.DataStorage["Array"] = new []{ "One", "Two" }; //Arrays can be stored directly, List's needs to be converted ToArray() first
session.DataStorage["Array"] += new []{ "Three" }; //Append array values to existing array on the server

//Chaining operations
session.DataStorage["Min"] = (session.DataStorage["Min"] + 40) + Operation.Min(100); //Add 40 to `Min`, then Set `Min` to 100 if `Min` is bigger than 100
session.DataStorage["C"] = ((session.DataStorage["C"] - 6) + Bitwise.RightShift(1)) ^ 3; //Subtract 6 from `C`, then multiply `C` by 2 using bitshifting, then take `C` to the power of 3

//Update callbacks
//EnergyLink deplete pattern, subtract 50, then set value to 0 if its lower than 0
session.DataStorage["EnergyLink"] = ((session.DataStorage["EnergyLink"] - 50) + Operation.Min(0)) + Callback.Add((oldData, newData) => {
var actualDepleted = (float)newData - (float)oldData; //calculate the actual change, might differ if there was less than 50 left on the server
});

//Keeping track of changes
session.DataStorage["OnChangeHandler"].OnValueChanged += (oldData, newData) => {
var changed = (int)newData - (int)oldData; //Keep track of changes made to `OnChangeHandler` by any client, and calculate the difference
};

//Keeping track of change (but for more complex data structures)
session.DataStorage["OnChangeHandler"].OnValueChanged += (oldData, newData) => {
var old_dict = oldData.ToObject<Dictionary<int, int>>();
var new_dict = newData.ToObject<Dictionary<int, int>>();
};

//Retrieving
session.DataStorage["Async"].GetAsync<string>(s => { string r = s }); //Retrieve value of `Async` asynchronously
float f = session.DataStorage["Float"]; //Retrieve value for `Float` synchronously and store it as a float
var d = session.DataStorage["DateTime"].To<DateTime>() //Retrieve value for `DateTime` as a DateTime struct
var array = session.DataStorage["Strings"].To<string[]>() //Retrieve value for `Strings` as string Array

//Handling anonymous object, if the target type is not known you can use `To<JObject>()` and use its interface to access the members
session.DataStorage["Anonymous"] = JObject.FromObject(new { Number = 10, Text = "Hello" }); //Set `Anonymous` to an anonymous object
var obj = session.DataStorage["Anonymous"].To<JObject>(); //Retrieve value for `Anonymous` where an anonymous object was stored
var number = (int)obj["Number"]; //Get value for anonymous object key `Number`
var text = (string)obj["Text"]; //Get value for anonymous object key `Text`

```
108 changes: 108 additions & 0 deletions docfx/docs/helpers/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Event Hooks

## ArchipelagoSocket

@"Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelper?text=ArchipelagoSocketHelper", accessible through
`Session.Socket`

| Event | Call Event |
|-------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
| @"Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelperDelagates.ErrorReceivedHandler?text=ErrorReceived" | Called when there is an error in the socket connection or while parsing a packet. |
| @"Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelperDelagates.PacketReceivedHandler?text=PacketReceived" | Called when a packet has been received from the server and identified. |
| @"Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelperDelagates.PacketsSentHandler?text=PacketsSent" | Called just before submitting a packet to the server. |
| @"Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelperDelagates.SocketClosedHandler?text=SocketClosed" | Called when the underlying socket connection has been closed. |
| @"Archipelago.MultiClient.Net.Helpers.ArchipelagoSocketHelperDelagates.SocketOpenedHandler?text=SocketOpened" | Called when the underlying socket connection is opened to the server. |

## ReceivedItemsHelper

@"Archipelago.MultiClient.Net.Helpers.IReceivedItemsHelper?text=ReceivedItemsHelper", accessible through `Session.Items`.

| Event | Call Event |
|--------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|
| @"Archipelago.MultiClient.Net.Helpers.ReceivedItemsHelper.ItemReceivedHandler?text=ItemReceived" | When an item is received. If multiple items are received in a single packet, the event is fired for each individual item. |

## LocationCheckHelper

@"Archipelago.MultiClient.Net.Helpers.LocationCheckHelper?text=LocationCheckHelper", accessible through
`Session.Locations`.

| Event | Call Event |
|------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| @"Archipelago.MultiClient.Net.Helpers.LocationCheckHelper.CheckedLocationsUpdatedHandler?text=CheckedLocationsUpdated" | Called when new locations are checked, such as another player using !collect |

## MessageLogHelper

@"Archipelago.MultiClient.Net.Helpers.MessageLogHelper?text=MessageLogHelper", accessible through `Session.MessageLog`

| Event | Call Event |
|-------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
| @"Archipelago.MultiClient.Net.Helpers.MessageLogHelper.MessageReceivedHandler?text=OnMessageReceived" | Called for each message that should be displayed for the player. |

### Message Logging

The Archipelago server can send messages to client to be displayed on screen as a sort of log, this is done by handling
the `PrintJsonPacket` packets. This library simplifies this process into a
@"Archipelago.MultiClient.Net.Helpers.IMessageLogHelper?text='single handler'".

```csharp
var session = ArchipelagoSessionFactory.CreateSession("localhost", 38281);
session.MessageLog.OnMessageReceived += OnMessageReceived;
session.TryConnectAndLogin("Timespinner", "Jarno", ItemsHandlingFlags.AllItems, new Version(0,3,5));

static void OnMessageReceived(LogMessage message)
{
DisplayOnScreen(message.ToString());
}
```

In some cased you might want extra information that is provided by the server in such cases you can use type checking

```csharp
static void OnMessageReceived(LogMessage message)
{
switch (message)
{
case ItemHintLogMessage hintLogMessage:
var receiver = itemSendLogMessage.Receiver;
var sender = itemSendLogMessage.Sender;
var networkItem = itemSendLogMessage.Item;
var found = hintLogMessage.IsFound;
break;
case ItemSendLogMessage itemSendLogMessage:
var receiver = itemSendLogMessage.Receiver;
var sender = itemSendLogMessage.Sender;
var networkItem = itemSendLogMessage.Item;
break;
}

DisplayOnScreen(message.ToString());
}
```

If you want more control over how the message is displayed, like for example you might want to color certain parts of
the message, then you can use the `Parts` property. This returns each part of the message in order of appearance with
the `Text` to be displayed and also the `Color` it would normally be displayed in. If `IsBackgroundColor` is true, then
the color should be applied to the message background instead. The MessagePart message can also contain additional
information that can be retrieved by type checking.

```csharp
foreach (part in message.Parts)
{
switch (part)
{
case ItemMessagePart itemMessagePart:
var itemId = itemMessagePart.ItemId;
var flags = itemMessagePart.Flags;
break;
case LocationMessagePart locationMessagePart:
var locationId = locationMessagePart.LocationId;
break;
case PlayerMessagePart playerMessagePart:
var slotId = playerMessagePart.SlotId;
var isCurrentPlayer = playerMessagePart.IsActivePlayer;
break;
}

DisplayOnScreen(part.Text, part.Color, part.IsBackgroundColor);
}
```
128 changes: 128 additions & 0 deletions docfx/docs/helpers/helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Helper Overview

```csharp
session.Socket // Payload-agnostic interface for sending/receving the most basic transmission units between client/server
session.Items // Helpers for handling receipt of items
session.Locations // Helpers for reporting visited locations to the server
session.Players // Helpers for translating player information such as number, alias, name etc.
session.DataStorage // Helpers for reading/writing data globally accessible to any client connected in the room
session.ConnectionInfo // Helpers for reading/handling useful information on the current connection
session.RoomState // Information on the state of the room
session.MessageLog // Interface for the server to push info messages to the user
```

## Players

The @"Archipelago.MultiClient.Net.Helpers.IPlayerHelper?text=Player Helper" provides methods for accessing details
about the other players currently connected to the Archipelago
session.

## Locations

The @"Archipelago.MultiClient.Net.Helpers.ILocationCheckHelper?text=Locations Helper" provides methods for accessing
information regarding the current player's locations, as well as updating the server on the status of their locations.

### Report Collected Location(s)

Call the following method to inform the server of locations whose items have been "found", and therefore should be
distributed if necessary:

```csharp
// Report multiple at once
session.Locations.CompleteLocationChecks(new []{1,3,8});

// Or report one at a time
session.Locations.CompleteLocationChecks(3);
```

The location ID used is of that defined in the AP world.

### Scout Location Checks

Scouting means asking the server what is stored in a specific location *without* collecting it. This can also be
utilized in order to create a hint, if - for instance - the current player knows what is on the location to inform other
players of this knowledge.

```csharp
session.Locations.ScoutLocationsAsync(locationInfoPacket => Console.WriteLine(locationInfoPacket.Locations.Count), HintCreationPolicy.CreateAndAnnounceOnce, new []{4, 5});
```

## Items

The @"Archipelago.MultiClient.Net.Helpers.IReceivedItemsHelper?text=Received Items Helper" provides methods for
checking the player's current inventory, and receiving items from the server.

### Received Item Callback Handler (Asynchronous)

```csharp
// Must go BEFORE a successful connection attempt
session.Items.ItemReceived += (receivedItemsHelper) => {
var itemReceivedName = receivedItemsHelper.PeekItemName() ?? $"Item: {itemId}";

// ... Handle item receipt here

receivedItemsHelper.DequeueItem();
};
```

*Note: This callback event will occur for every item on connection and reconnection.*

## RoomState

The @"Archipelago.MultiClient.Net.Helpers.IRoomStateHelper?text=RoomState Helper" provides access to values that
represent the current state of the multiworld room, with information such as the cost of a hint and or your current
accumulated amount of hint point or the permissions for things like forfeiting.

```csharp
Console.WriteLine($"You have {session.RoomState.HintPoints}, and need {session.RoomState.HintCost} for a hint");
```

## ConnectionInfo

The @"Archipelago.MultiClient.Net.Helpers.IConnectionInfoProvider?text=ConnectionInfo Helper" provides access to
values under which you are currently connected, such as your slot number or your currently used tags and item handling
flags.

```csharp
Console.WriteLine($"You are connected on slot {session.ConnectionInfo.Slot}, on team {session.ConnectionInfo.Team}");
```

## ArchipelagoSocket

The @"Archipelago.MultiClient.Net.Helpers.IConnectionInfoProvider?text=Socket Helper" is a lower level API allowing
for direct access to the socket which the session object uses to communicate with the Archipelago server. You may use
this object to hook onto when messages are received, or you may use it to send any packets defined in the library.
Various events are exposed to allow for receipt of errors or notifying of socket close.

```csharp
session.Socket.SendPacket(new SayPacket(){Text = "Woof woof!"});
```

## MessageLog

The Archipelago server can send messages to client to be displayed on screen as a sort of log, this is done by handling
the `PrintJsonPacket` packets. This library simplifies this process into a
@"Archipelago.MultiClient.Net.Helpers.IMessageLogHelper?text=single handler" that can be subscribed to with an
[event hook](events.md).

## DeathLink

DeathLink support is included in the library. You may enable it by creating a new
@"Archipelago.MultiClient.Net.BounceFeatures.DeathLink.DeathLinkService?text=DeathLinkService" from the
@"Archipelago.MultiClient.Net.BounceFeatures.DeathLink.DeathLinkProvider?text=DeathLinkProvider", and subscribing to the
`OnDeathLinkReceived` event. Deathlink can then be toggled on and off using the `EnableDeathlink` and `DisableDeathlink`
methods on the service.

```csharp
var session = ArchipelagoSessionFactory.CreateSession("localhost", 38281);

var deathLinkService = session.CreateDeathLinkService();

deathLinkService.OnDeathLinkReceived += (deathLinkObject) => {
// ... Kill your player(s).
};

deathLinkService.EnableDeathlink();
// ... On death:
deathLinkService.SendDeathLink(new DeathLink("Ijwu", "Died to exposure."));
```
Loading
Loading