Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
50edabe
initialize project
trandromeda Dec 4, 2025
16d8feb
add teams config
trandromeda Dec 4, 2025
4b49ce8
Delete UnityHiroTeams/Assets/UnityHiroEventLeaderboards.meta
trandromeda Dec 4, 2025
818d224
update client DLLs
trandromeda Dec 10, 2025
a85a42c
add team tabs and update UI
trandromeda Dec 11, 2025
9bc73e3
update inputHandler
trandromeda Dec 11, 2025
6b22b8b
update README asset
trandromeda Dec 11, 2025
a85a09d
rename detail tab to team tab
trandromeda Dec 11, 2025
90a0d91
separate view logic into TeamsView
trandromeda Dec 11, 2025
4f16dbf
Team user actions now working
DapperDino Dec 11, 2025
2935478
Started work on team chat
DapperDino Dec 12, 2025
2277000
rename TeamUser -> TeamMember
trandromeda Dec 13, 2025
31a8601
rename to playerMemberState and TeamMemberSTate
trandromeda Dec 14, 2025
ab6729d
update team header with public info
trandromeda Dec 14, 2025
164adf5
add new styles for icon buttons, modal headers, and default unity pop…
trandromeda Dec 14, 2025
4208b47
create and register teams ActivityCalculator
trandromeda Dec 14, 2025
58047ab
update reference to global USS styles
trandromeda Dec 14, 2025
1b3b143
add search UI and logic, clean up classes
trandromeda Dec 14, 2025
b3c27d0
modal UI clean up
trandromeda Dec 14, 2025
26da6e1
update team stats and team wallet views
trandromeda Dec 14, 2025
c408a3d
udpate team wallet currencies
trandromeda Dec 14, 2025
7e58e53
update team preview and stats fetching
trandromeda Dec 14, 2025
d7cb9f4
get stats from metadata, rename score to points
trandromeda Dec 14, 2025
dac61d4
rename stats
trandromeda Dec 14, 2025
d8ae943
refactor styles
trandromeda Dec 14, 2025
e571ab5
don't call refreshteams when selectin chat tab
trandromeda Dec 14, 2025
9622188
add mailbox UI
trandromeda Dec 15, 2025
6b06667
add mailbox logic and update debug modal to support more stat updates
trandromeda Dec 15, 2025
8f1b941
add team achievements for testing reward mailbox
trandromeda Dec 15, 2025
1f9d228
simplify reward mailbox sample code
trandromeda Dec 15, 2025
001be2c
fix and simplify reward mailbox behaviour
trandromeda Dec 15, 2025
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
  •  
  •  
  •  
103 changes: 103 additions & 0 deletions Nakama+Hiro/definitions/dev1/base-teams.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"initial_max_team_size": 30,
"max_team_size": 50,
"allow_multiple_teams": false,
"wallet": {
"currencies": {
"team_coins": 1000,
"team_gems": 100,
"raid_tokens": 5
}
},
"stats": {
"stats_public": {
"wins": { "value": 0 },
"level": { "value": 1 },
"points": { "value": 0 }
},
"stats_private": {
"private_rating": { "value": 1000 }
}
},
"gifts": {
"gifts": {
"daily_gift": {
"name": "Daily Team Gift",
"description": "Contribute together for rewards!",
"category": "daily",
"max_count": 100,
"max_contributor_count": 5,
"reset_schedule": "0 0 * * *",
"duration_sec": 86400,
"contribution_cost": {
"currencies": {
"team_coins": { "min": 10 }
}
},
"contribution_reward": {
"guaranteed": {
"currencies": {
"team_coins": { "min": 5 }
}
}
},
"rewards": [
{
"min_count": 25,
"contributor_reward": {
"guaranteed": {
"currencies": {
"team_coins": { "min": 50 }
}
}
},
"noncontributor_reward": {
"guaranteed": {
"currencies": {
"team_coins": { "min": 25 }
}
}
}
},
{
"min_count": 50,
"contributor_reward": {
"guaranteed": {
"currencies": {
"team_coins": { "min": 100 }
}
}
},
"noncontributor_reward": {
"guaranteed": {
"currencies": {
"team_coins": { "min": 50 }
}
}
}
},
{
"min_count": 100,
"contributor_reward": {
"guaranteed": {
"currencies": {
"team_coins": { "min": 200 }
}
}
},
"noncontributor_reward": {
"guaranteed": {
"currencies": {
"team_coins": { "min": 100 }
}
}
}
}
]
}
}
},
"reward_mailbox": {
"max_size": 100
}
}
6 changes: 3 additions & 3 deletions Nakama+Hiro/local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ console:
max_message_size_bytes: 409600
leaderboard:
blacklist_rank_cache:
- "*"
- "*"
logger:
level: "DEBUG"
runtime:
env:
- "ENV=dev1"
- "HIRO_LICENSE="
- "ENV=dev1"
- "HIRO_LICENSE="
session:
token_expiry_sec: 86400 # 24 hours
refresh_token_expiry_sec: 604800 # 7 days
Expand Down
82 changes: 80 additions & 2 deletions Nakama+Hiro/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/heroiclabs/hiro"
"github.com/heroiclabs/nakama-common/runtime"
"google.golang.org/protobuf/encoding/protojson"
)

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
Expand Down Expand Up @@ -64,17 +65,44 @@ func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runti
hiro.WithLeaderboardsSystem(fmt.Sprintf("definitions/%s/base-leaderboards.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.WithEventLeaderboardsSystem(fmt.Sprintf("definitions/%s/base-event-leaderboards.json", env), true))
hiro.WithEventLeaderboardsSystem(fmt.Sprintf("definitions/%s/base-event-leaderboards.json", env), true),
hiro.WithTeamsSystem(fmt.Sprintf("definitions/%s/base-teams.json", env), true))
if err != nil {
return err
}
_ = systems

// Activity calculator for Teams
// For simplicity, member count is a proxy for team activity - more members = more active team
if teamsSystem := systems.GetTeamsSystem(); teamsSystem != nil {
teamsSystem.SetActivityCalculator(calculateTeamActivity)
logger.Info("Teams activity calculator registered")
}

// Unregister the default team stats update RPC and replace with custom version that triggers achievements
if err := hiro.UnregisterRpc(initializer, hiro.RpcId_RPC_ID_TEAMS_STATS_UPDATE); err != nil {
return err
}
if err := initializer.RegisterRpc(
hiro.RpcId_RPC_ID_TEAMS_STATS_UPDATE.String(),
rpcTeamStatsUpdateWithMailboxRewardGrant(systems),
); err != nil {
return err
}
logger.Info("Custom team stats update RPC registered with achievement triggers")

logger.Info("Module loaded in %dms", time.Since(initStart).Milliseconds())

return nil
}

func calculateTeamActivity(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, team *hiro.Team) int64 {
if team == nil {
return 0
}

return int64(len(team.Members))
}

func createTournament(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, id, resetSchedule, title, description string, duration, maxSize, maxNumScore int, joinRequired bool) error {
authoritative := false // true by default
sortOrder := "desc" // one of: "desc", "asc"
Expand All @@ -91,3 +119,53 @@ func createTournament(ctx context.Context, logger runtime.Logger, nk runtime.Nak
}
return nil
}

func rpcTeamStatsUpdateWithMailboxRewardGrant(systems hiro.Hiro) func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
return func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
if !ok {
return "", errors.New("no user ID in context")
}

request := &hiro.TeamStatUpdateRequest{}
if err := protojson.Unmarshal([]byte(payload), request); err != nil {
return "", err
}

teamsSystem := systems.GetTeamsSystem()

// Call the actual stats update
statList, err := teamsSystem.StatsUpdate(ctx, logger, nk, userID, request.Id, request.Public, request.Private)
if err != nil {
return "", err
}

// Grant rewards to mailbox when level milestones are hit
if levelStat, ok := statList.Public["level"]; ok {
var reward *hiro.Reward

switch levelStat.Value {
case 2:
reward = &hiro.Reward{Currencies: map[string]int64{"team_coins": 50}}
case 5:
reward = &hiro.Reward{Currencies: map[string]int64{"team_coins": 100}}
case 10:
reward = &hiro.Reward{Currencies: map[string]int64{"team_coins": 200}}
}

if reward != nil {
_, err := teamsSystem.RewardMailboxGrant(ctx, logger, nk, userID, request.Id, reward)
if err != nil {
logger.Warn("Failed to grant level milestone reward: %v", err)
}
}
}

response, err := protojson.Marshal(statList)
if err != nil {
return "", err
}

return string(response), nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,6 @@ public static NakamaSystem.AuthorizerFunc NakamaAuthorizerFunc(int index = 0)

return async client =>
{
// Due to the Account Switcher tool, we might need to log out before re-authenticating.
var nakamaSystem = Instance.GetSystem<NakamaSystem>();
if (nakamaSystem.Session != null)
{
await client.SessionLogoutAsync(nakamaSystem.Session);
}

// Attempt to load a previous session if it is still valid.
var authToken = PlayerPrefs.GetString($"{playerPrefsAuthToken}_{index}");
var refreshToken = PlayerPrefs.GetString($"{playerPrefsRefreshToken}_{index}");
Expand All @@ -86,17 +79,11 @@ public static NakamaSystem.AuthorizerFunc NakamaAuthorizerFunc(int index = 0)

// Add an hour, so we check whether the token is within an hour of expiration to refresh it.
var expiredDate = DateTime.UtcNow.AddHours(1);
if (session != null && !session.HasRefreshExpired(expiredDate))
{
return session;
}
if (session != null && !session.HasRefreshExpired(expiredDate)) return session;

// Attempt to read the device ID to use for Authentication.
var deviceId = PlayerPrefs.GetString(playerPrefsDeviceId, SystemInfo.deviceUniqueIdentifier);
if (deviceId == SystemInfo.unsupportedIdentifier)
{
deviceId = Guid.NewGuid().ToString();
}
if (deviceId == SystemInfo.unsupportedIdentifier) deviceId = Guid.NewGuid().ToString();

session = await client.AuthenticateDeviceAsync($"{deviceId}_{index}");

Expand All @@ -105,10 +92,7 @@ public static NakamaSystem.AuthorizerFunc NakamaAuthorizerFunc(int index = 0)
PlayerPrefs.SetString($"{playerPrefsAuthToken}_{index}", session.AuthToken);
PlayerPrefs.SetString($"{playerPrefsRefreshToken}_{index}", session.RefreshToken);

if (session.Created)
{
Debug.LogFormat("New user account '{0}' created.", session.UserId);
}
if (session.Created) Debug.LogFormat("New user account '{0}' created.", session.UserId);

return session;
};
Expand All @@ -125,4 +109,4 @@ protected override void SystemsInitializeFailed(Exception e)
ReceivedStartError?.Invoke(e);
}
}
}
}
56 changes: 56 additions & 0 deletions UnityHiroTeams/.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 UnityHiroTeams/Assets/UnityHiroTeams.meta

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

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

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

Loading