Skip to content

Commit 9561341

Browse files
authored
v1.1.0 (#2)
* Add SteamLobbyMetadataHandler to handle all synchronisation Now SynchronisedMetadata and PriceSynchroniser both use the same shared static class. Also fixes a remnant from when SynchronisedMetadata was not generic, so now new subtypes will not create their own SteamAPI subscriptions * Update Shop.cs * Update manifests and changelog * Add UpdateItemPrice * Add check for valid item category during registration * Fix SteamLobbyMetadataHandler not synchronising after leaving and joining a new lobby So this was a thing. Weirdly this passed a group beta test initially so I am really not sure what happened there, because after some digging the previous implementation should have never worked. * Update PriceSynchroniser to reload the store when prices are synced * Update docs and manifest
1 parent 36c0384 commit 9561341

File tree

7 files changed

+180
-106
lines changed

7 files changed

+180
-106
lines changed

PriceSynchroniser.cs

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,63 @@ namespace ContentWarningShop
55
{
66
internal class PriceSynchroniser
77
{
8-
private static Callback<LobbyCreated_t> cb_onLobbyCreated;
9-
private static Callback<LobbyEnter_t> cb_onLobbyEntered;
10-
private static bool IsHost => SteamMatchmaking.GetLobbyOwner(LobbyID) == SteamUser.GetSteamID();
11-
private static CSteamID LobbyID { get; set; }
12-
138
internal static void RegisterCallbacks()
149
{
15-
cb_onLobbyCreated = Callback<LobbyCreated_t>.Create(OnLobbyCreated);
16-
cb_onLobbyEntered = Callback<LobbyEnter_t>.Create(OnLobbyEntered);
10+
SteamLobbyMetadataHandler.OnLobbyJoined += SyncPrices;
11+
SteamLobbyMetadataHandler.OnLobbyDataUpdate += OnLobbyDataUpdate;
1712
}
1813

19-
private static void OnLobbyCreated(LobbyCreated_t e)
14+
private static void OnLobbyDataUpdate()
2015
{
21-
if (e.m_eResult != EResult.k_EResultOK)
16+
if (SteamLobbyMetadataHandler.IsHost)
2217
{
2318
return;
2419
}
25-
LobbyID = new(e.m_ulSteamIDLobby);
20+
SyncPrices();
21+
}
22+
23+
private static void SyncPrices()
24+
{
2625
foreach (var item in Shop._items)
2726
{
28-
var key = $"__{item.PersistentID}_price";
29-
SteamMatchmaking.SetLobbyData(LobbyID, key, $"{item.price}");
30-
Debug.Log($"Item price registered: {item.name} ({key}) = {item.price}");
27+
SyncPrice(item);
3128
}
3229
}
3330

34-
private static void OnLobbyEntered(LobbyEnter_t e)
31+
internal static void SyncPrice(Item item)
3532
{
36-
if (IsHost == true)
33+
if (SteamLobbyMetadataHandler.InLobby == false)
3734
{
3835
return;
3936
}
40-
LobbyID = new(e.m_ulSteamIDLobby);
41-
foreach (var item in Shop._items)
37+
var key = $"{ShopApiPlugin.MOD_GUID}_item_{item.PersistentID}_price";
38+
var changed = false;
39+
if (SteamLobbyMetadataHandler.IsHost)
40+
{
41+
SteamMatchmaking.SetLobbyData(SteamLobbyMetadataHandler.CurrentLobby, key, $"{item.price}");
42+
changed = true;
43+
}
44+
else
45+
{
46+
var strVal = SteamMatchmaking.GetLobbyData(SteamLobbyMetadataHandler.CurrentLobby, key);
47+
var val = int.Parse(strVal);
48+
changed = item.price != val;
49+
item.price = val;
50+
}
51+
if (changed && ShopHandler.Instance != null)
4252
{
43-
var key = $"__{item.PersistentID}_price";
44-
var strVal = SteamMatchmaking.GetLobbyData(LobbyID, key);
45-
item.price = int.Parse(strVal);
46-
Debug.Log($"Item price synchronised: {item.name} ({key}) = {item.price}");
53+
if (ShopHandler.Instance.m_CategoryItemDic.ContainsKey(item.Category) == false)
54+
{
55+
return;
56+
}
57+
var idx = ShopHandler.Instance.m_CategoryItemDic[item.Category].FindIndex(shopItem => shopItem.Item == item);
58+
var newItem = new ShopItem(item);
59+
ShopHandler.Instance.m_CategoryItemDic[item.Category][idx] = newItem;
60+
ShopHandler.Instance.m_ItemsForSaleDictionary[item.id] = newItem;
61+
// Does not actually call the RPC, this is the local effect
62+
ShopHandler.Instance.RPCA_ClearCart();
4763
}
64+
Debug.Log($"Item price synchronised: {item.name} ({key}) = {item.price}");
4865
}
4966
}
5067
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Use the `RegisterItem` method to register an `Item` to the shop. Ideally the ite
3434
Make sure to set the `persistentID`, `price`, `purchasable`, `Category`, and `icon` properties at the very least to ensure the item will show up correctly in the store.
3535

3636
> [!NOTE]
37-
> Item prices are automatically synchronised between players on lobby join. The price set by the lobby's host will be used for the entire lobby. Once a lobby has been created, the price can not be changed.
37+
> Item prices are automatically synchronised between players on lobby join. The price set by the lobby's host will be used for the entire lobby. Once a lobby has been created, the price of a registered item can be updated via `UpdateItemPrice`.
3838
3939
You can check if a custom item has been already registered via the `IsItemRegistered` method. The list of **all** registered custom items is also available via the `CustomItems` property.
4040

Shop.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,13 @@ public static void RegisterItem(Item item)
6363
Debug.LogWarning($"Item {item.displayName} ({item.persistentID}) already registered.");
6464
return;
6565
}
66+
if (item.Category == ShopItemCategory.Invalid)
67+
{
68+
throw new Exception($"Item {item.displayName} ({item.persistentID}) shop category is set to {nameof(ShopItemCategory.Invalid)}.");
69+
}
6670
_items.Add(item);
6771
SingletonAsset<ItemDatabase>.Instance.AddRuntimeEntry(item);
68-
Debug.LogWarning($"Registered custom item: {item.displayName} ({item.persistentID}) [{Assembly.GetCallingAssembly().GetSimpleName()}].");
72+
Debug.Log($"Registered custom item: {item.displayName} ({item.persistentID}) [{Assembly.GetCallingAssembly().GetSimpleName()}].");
6973
}
7074

7175
/// <summary>
@@ -98,6 +102,35 @@ public static void RegisterCustomDataEntries()
98102
}
99103
}
100104

105+
/// <summary>
106+
/// Updates the given item's price and synchronises it with other players.
107+
/// </summary>
108+
/// <remarks>
109+
/// Setting lobby metadata is only allowed if the current player is the lobby's owner. Use the return value to determine if the set was possible.
110+
/// </remarks>
111+
/// <param name="item"></param>
112+
/// <param name="price"></param>
113+
/// <returns>
114+
/// <see langword="true"/> if updating the price was successful (player is not in a lobby OR the owner of the current lobby).
115+
/// <see langword="false"/> if the player is in a lobby but is not the lobby's host.
116+
/// </returns>
117+
public static bool UpdateItemPrice(Item item, int price)
118+
{
119+
if (IsItemRegistered(item) == false)
120+
{
121+
Debug.LogWarning($"Item {item.name} ({item.persistentID}) is not registered with {ShopApiPlugin.MOD_NAME}");
122+
return false;
123+
}
124+
if (SteamLobbyMetadataHandler.IsHost == false && SteamLobbyMetadataHandler.InLobby)
125+
{
126+
Debug.LogError($"Tried updating item price when not the lobby host; this is not allowed.");
127+
return false;
128+
}
129+
item.price = price;
130+
PriceSynchroniser.SyncPrice(item);
131+
return true;
132+
}
133+
101134
/// <summary>
102135
/// Returns the list of item data data entries derived from <see cref="ItemDataEntry"/> in the given assembly.
103136
/// </summary>

SteamLobbyMetadataHandler.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using Steamworks;
2+
using UnityEngine;
3+
4+
namespace ContentWarningShop
5+
{
6+
internal static class SteamLobbyMetadataHandler
7+
{
8+
private static Callback<LobbyCreated_t> cb_onLobbyCreated;
9+
private static Callback<LobbyEnter_t> cb_onLobbyEntered;
10+
private static Callback<LobbyDataUpdate_t> cb_onLobbyDataUpdate;
11+
//
12+
internal static event Action? OnLobbyJoined;
13+
internal static event Action? OnLobbyDataUpdate;
14+
15+
internal static CSteamID CurrentLobby = CSteamID.Nil;
16+
// GetLobbyOwner returns nil if the current lobby is invalid / we are not in it
17+
internal static bool InLobby => SteamMatchmaking.GetLobbyOwner(CurrentLobby) != CSteamID.Nil;
18+
internal static bool IsHost => SteamMatchmaking.GetLobbyOwner(CurrentLobby) == SteamUser.GetSteamID();
19+
20+
static SteamLobbyMetadataHandler()
21+
{
22+
cb_onLobbyCreated = Callback<LobbyCreated_t>.Create(Steam_LobbyCreated);
23+
cb_onLobbyEntered = Callback<LobbyEnter_t>.Create(Steam_LobbyEntered);
24+
cb_onLobbyDataUpdate = Callback<LobbyDataUpdate_t>.Create(Steam_LobbyDataUpdated);
25+
}
26+
27+
private static void Steam_LobbyCreated(LobbyCreated_t e)
28+
{
29+
if (e.m_eResult == EResult.k_EResultOK)
30+
{
31+
CurrentLobby = new CSteamID(e.m_ulSteamIDLobby);
32+
OnLobbyJoined?.Invoke();
33+
}
34+
}
35+
36+
private static void Steam_LobbyEntered(LobbyEnter_t e)
37+
{
38+
// If we created the lobby, don't call the event again. Otherwise _currentLobby will be default by now
39+
if (InLobby == false)
40+
{
41+
CurrentLobby = new CSteamID(e.m_ulSteamIDLobby);
42+
OnLobbyJoined?.Invoke();
43+
}
44+
}
45+
46+
private static void Steam_LobbyDataUpdated(LobbyDataUpdate_t e)
47+
{
48+
if (e.m_ulSteamIDLobby != e.m_ulSteamIDMember)
49+
{
50+
return;
51+
}
52+
OnLobbyDataUpdate?.Invoke();
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)