Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
106 changes: 106 additions & 0 deletions Nakama+Hiro/definitions/dev1/base-inventory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{
"items": {
"iron_sword": {
"name": "Iron Sword",
"description": "A sharp sword made of iron.",
"category": "weapons",
"item_sets": ["common_weapons"],
"max_count": 99,
"stackable": false,
"consumable": false,
"string_properties": {
"equipment_slot": "right_hand",
"rarity": "legendary"
},
"numeric_properties": {
"rank": 1
},
"disabled": false
},
"gold_stack": {
"name": "Gold Stack",
"description": "A stack of golden coins. So shiny!",
"category": "currency",
"max_count": 99,
"stackable": true,
"consumable": true,
"string_properties": {
"rarity": "uncommon"
},
"keep_zero": true,
"consume_reward": {
"guaranteed": {
"currencies": {
"coins": {
"min": 100
}
}
}
}
},
"bomb": {
"name": "Bomb",
"description": "Goes BOOM!",
"category": "weapons",
"max_count": 99,
"stackable": true,
"consumable": true,
"string_properties": {
"rarity": "epic"
},
"keep_zero": true,
"consume_reward": {
"guaranteed": {
"currencies": {
"coins": {
"min": 100
}
}
}
}
},
"small_crafting_bag": {
"name": "Small Crafting Bag",
"description": "A small bag of miscellaneous crafting materials.",
"category": "loot_bags",
"max_count": 99,
"stackable": false,
"consumable": true,
"string_properties": {
"rarity": "common"
},
"consume_reward": {
"max_rolls": 2,
"weighted": [
{
"items": {
"leather_scraps": {
"min": 1,
"max": 5
}
},
"weight": 50
},
{
"items": {
"bronze_nails": {
"min": 5,
"max": 100,
"multiple": 5
}
},
"weight": 50
}
]
}
}
},
"limits": {
"categories": {
"armor": 10
},
"item_sets": {
"shield": 5
}
}
}
3 changes: 2 additions & 1 deletion Nakama+Hiro/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runti
systems, err := hiro.Init(ctx, logger, nk, initializer, binPath, hiroLicense,
hiro.WithBaseSystem(fmt.Sprintf("definitions/%s/base-system.json", env), true),
hiro.WithChallengesSystem(fmt.Sprintf("definitions/%s/base-challenges.json", env), true),
hiro.WithEconomySystem(fmt.Sprintf("definitions/%s/base-economy.json", env), true))
hiro.WithEconomySystem(fmt.Sprintf("definitions/%s/base-economy.json", env), true),
hiro.WithInventorySystem(fmt.Sprintf("definitions/%s/base-inventory.json", env), true))
if err != nil {
return err
}
Expand Down
56 changes: 56 additions & 0 deletions UnityHiroInventory/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

# Created by https://www.gitignore.io/api/unity
# Edit at https://www.gitignore.io/?templates=unity

# Jetbrain Rider Cache
.idea/
Assets/Plugins/Editor/JetBrains*

# Visual Studio Code
.vscode/


### Unity ###
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Uu]ser[Ss]ettings/
Assets/AssetStoreTools*
# Unity local user project setting
UserSettings/

# Visual Studio cache directory
.vs/

# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.opendb
*.VC.db

# Unity3D generated meta files
*.pidb.meta
*.pdb.meta

# Unity3D Generated File On Crash Reports
sysinfo.txt

# Builds
*.apk
*.unitypackage

# End of https://www.gitignore.io/api/unity
8 changes: 8 additions & 0 deletions UnityHiroInventory/Assets/UnityHiroInventory.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions UnityHiroInventory/Assets/UnityHiroInventory/Editor.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

205 changes: 205 additions & 0 deletions UnityHiroInventory/Assets/UnityHiroInventory/Editor/AccountSwitcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Text;
using Nakama;
using Hiro;
using Hiro.Unity;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;

namespace HiroInventory.Editor
{
public class AccountSwitcherEditor : EditorWindow
{
[SerializeField] private VisualTreeAsset tree;

private DropdownField accountDropdown;
private Label usernamesLabel;

private readonly SortedDictionary<string, string> accountUsernames = new();

private const string AccountUsernamesKey = "AccountSwitcher_Usernames";

[MenuItem("Tools/Nakama/Account Switcher")]
public static void ShowWindow()
{
var inspectorType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.InspectorWindow");
var window = GetWindow<AccountSwitcherEditor>("Account Switcher", inspectorType);

window.Focus();
}

[MenuItem("Tools/Nakama/Clear Test Accounts")]
public static void ClearSavedAccounts()
{
EditorPrefs.DeleteKey(AccountUsernamesKey);
Debug.Log("Cleared all saved account usernames");

// Refresh any open Account Switcher windows
var windows = Resources.FindObjectsOfTypeAll<AccountSwitcherEditor>();
foreach (var window in windows)
{
window.accountUsernames.Clear();
window.UpdateUsernameLabels();
}
}

private void CreateGUI()
{
tree.CloneTree(rootVisualElement);

accountDropdown = rootVisualElement.Q<DropdownField>("account-dropdown");
accountDropdown.RegisterValueChangedCallback(SwitchAccount);

usernamesLabel = rootVisualElement.Q<Label>("usernames");

// Load saved usernames on startup
LoadAccountUsernames();
UpdateUsernameLabels();

if (!EditorApplication.isPlaying) return;

var rootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects();
foreach (var rootGameObject in rootGameObjects)
{
if (!rootGameObject.TryGetComponent<HiroInventoryController>(out var challengesController)) continue;

if (HiroCoordinator.Instance.GetSystem<NakamaSystem>().Session is Session session)
{
OnControllerInitialized(session);
}
else
{
challengesController.OnInitialized += OnControllerInitialized;
}
}
}

private void OnControllerInitialized(ISession session, HiroInventoryController challengesController = null)
{
accountUsernames[accountDropdown.choices[0]] = session.Username;
UpdateUsernameLabels();

if (challengesController != null)
{
challengesController.OnInitialized -= OnControllerInitialized;
}
}

private void LoadAccountUsernames()
{
var savedUsernames = EditorPrefs.GetString(AccountUsernamesKey, "");
if (string.IsNullOrEmpty(savedUsernames)) return;

try
{
var usernameData = JsonUtility.FromJson<SerializableStringDictionary>(savedUsernames);
accountUsernames.Clear();

foreach (var item in usernameData.items)
{
accountUsernames[item.key] = item.value;
}
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to load saved account usernames: {ex.Message}");
}
}

private void SaveAccountUsernames()
{
try
{
var usernameData = new SerializableStringDictionary();
foreach (var kvp in accountUsernames)
{
usernameData.items.Add(new SerializableKeyValuePair { key = kvp.Key, value = kvp.Value });
}

var json = JsonUtility.ToJson(usernameData);
EditorPrefs.SetString(AccountUsernamesKey, json);
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to save account usernames: {ex.Message}");
}
}

private async void SwitchAccount(ChangeEvent<string> changeEvt)
{
if (!EditorApplication.isPlaying) return;

var previousValue = changeEvt.previousValue;
var newValue = changeEvt.newValue;

var rootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects();
foreach (var rootGameObject in rootGameObjects)
{
if (!rootGameObject.TryGetComponent<HiroInventoryController>(out var challengesController)) continue;

var coordinator = HiroCoordinator.Instance as HiroInventoryCoordinator;
if (coordinator == null) return;
var nakamaSystem = coordinator.GetSystem<NakamaSystem>();

// Save username before switching
if (!string.IsNullOrEmpty(previousValue))
{
accountUsernames[previousValue] = nakamaSystem.Session.Username;
}

try
{
var newSession = await HiroInventoryCoordinator.NakamaAuthorizerFunc(accountDropdown.index)
.Invoke(nakamaSystem.Client);
(nakamaSystem.Session as Session)?.Update(newSession.AuthToken, newSession.RefreshToken);
await nakamaSystem.RefreshAsync();
accountUsernames[newValue] = newSession.Username;
challengesController.SwitchComplete();

SaveAccountUsernames();
break;
}
catch (ApiResponseException e)
{
Debug.LogWarning($"Error authenticating with Device ID: {e.Message}");
return;
}
}

UpdateUsernameLabels();
}

private void UpdateUsernameLabels()
{
var sb = new StringBuilder();
var index = 1;

foreach (var kvp in accountUsernames)
{
sb.Append(index);
sb.Append(": ");
sb.Append(kvp.Value);
sb.AppendLine();
index++;
}

usernamesLabel.text = sb.ToString();
}

[Serializable]
private class SerializableStringDictionary
{
public List<SerializableKeyValuePair> items = new();
}

[Serializable]
private class SerializableKeyValuePair
{
public string key;
public string value;
}
}
}
Loading