From 9323f6ad52593ee5b0797e3d01efc5d39b6b2ba4 Mon Sep 17 00:00:00 2001 From: Chris Molozian Date: Mon, 3 Nov 2025 11:13:20 +0000 Subject: [PATCH 1/8] Various small improvements to Hiro Challenges. --- Nakama+Hiro/.cookie | Bin 16 -> 0 bytes Nakama+Hiro/.editorconfig | 12 + Nakama+Hiro/.gitignore | 247 +++++++- .../definitions/dev1/base-challenges.json | 372 ++++++------ .../definitions/dev1/base-economy.json | 12 +- .../definitions/dev1/base-leaderboards.json | 20 + Nakama+Hiro/lib/.gitignore | 2 - Nakama+Hiro/lib/.gitkeep | 0 Nakama+Hiro/local.yml | 17 + Nakama+Hiro/main.go | 11 +- .../Scripts/ChallengeParticipantView.cs | 42 +- .../Scripts/ChallengeView.cs | 54 +- .../Scripts/HiroChallengesController.cs | 574 +++++++++--------- .../Scripts/HiroChallengesCoordinator.cs | 21 +- 14 files changed, 862 insertions(+), 522 deletions(-) delete mode 100644 Nakama+Hiro/.cookie create mode 100644 Nakama+Hiro/.editorconfig create mode 100644 Nakama+Hiro/definitions/dev1/base-leaderboards.json delete mode 100644 Nakama+Hiro/lib/.gitignore create mode 100644 Nakama+Hiro/lib/.gitkeep create mode 100644 Nakama+Hiro/local.yml diff --git a/Nakama+Hiro/.cookie b/Nakama+Hiro/.cookie deleted file mode 100644 index 16d48c75c771e73202efc3b6052f9041d2c25f14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 YcmeB;7b|L9==o?pL&stDY2WSx06x+PP5=M^ diff --git a/Nakama+Hiro/.editorconfig b/Nakama+Hiro/.editorconfig new file mode 100644 index 0000000..99d8e0a --- /dev/null +++ b/Nakama+Hiro/.editorconfig @@ -0,0 +1,12 @@ +## editorconfig.org + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.go] +indent_style = tab diff --git a/Nakama+Hiro/.gitignore b/Nakama+Hiro/.gitignore index 5ed4b8a..51c700c 100644 --- a/Nakama+Hiro/.gitignore +++ b/Nakama+Hiro/.gitignore @@ -1 +1,246 @@ -local.yml \ No newline at end of file +.cookie +local.yml +lib/*.bin + +# Created by https://gitignore.io/api/linux,macos,windows,go,intellij+all,sublimetext,visualstudiocode +# Edit at https://gitignore.io?templates=linux,macos,windows,go,intellij+all,sublimetext,visualstudiocode + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### SublimeText ### +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json +sftp-config-alt*.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://gitignore.io/api/linux,macos,windows,go,intellij+all,sublimetext,visualstudiocode + diff --git a/Nakama+Hiro/definitions/dev1/base-challenges.json b/Nakama+Hiro/definitions/dev1/base-challenges.json index 353c51b..c1eb298 100644 --- a/Nakama+Hiro/definitions/dev1/base-challenges.json +++ b/Nakama+Hiro/definitions/dev1/base-challenges.json @@ -1,203 +1,203 @@ { - "challenges": { - "speed_runner": { - "reward_tiers": [ - { - "rank_min": 1, - "rank_max": 3, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 300 + "challenges": { + "speed_runner": { + "reward_tiers": [ + { + "rank_min": 1, + "rank_max": 3, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 300 + }, + "gems": { + "min": 15 + } + } + } + } }, - "gems": { - "min": 15 + { + "rank_min": 4, + "rank_max": 10, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 150 + }, + "gems": { + "min": 8 + } + } + } + } } - } + ], + "additional_properties": { + "display_name": "Speed Runner", + "description": "Score the most points by collecting crystals!", + "category": "arcade", + "difficulty": "expert", + "dungeon_id": "crystal_caverns" + }, + "max_num_score": 3, + "start_delay_max_sec": 60, + "ascending": false, + "operator": "best", + "duration": { + "min_sec": 1800, + "max_sec": 3600 + }, + "players": { + "min": 2, + "max": 100 } - } }, - { - "rank_min": 4, - "rank_max": 10, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 150 + "team_conquest": { + "reward_tiers": [ + { + "rank_min": 1, + "rank_max": 1, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 500 + }, + "gems": { + "min": 25 + } + } + } + } }, - "gems": { - "min": 8 - } - } - } - } - } - ], - "additional_properties": { - "display_name": "Speed Runner", - "description": "Score the most points by collecting crystals!", - "category": "arcade", - "difficulty": "expert", - "dungeon_id": "crystal_caverns" - }, - "max_num_score": 3, - "start_delay_max_sec": 60, - "ascending": false, - "operator": "best", - "duration": { - "min_sec": 1800, - "max_sec": 3600 - }, - "players": { - "min": 2, - "max": 100 - } - }, - "team_conquest": { - "reward_tiers": [ - { - "rank_min": 1, - "rank_max": 1, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 500 + { + "rank_min": 2, + "rank_max": 5, + "reward": { + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 250 + }, + "gems": { + "min": 12 + } + } + } + } + } }, - "gems": { - "min": 25 + { + "rank_min": 6, + "rank_max": 20, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 100 + }, + "gems": { + "min": 5 + } + } + } + } } - } + ], + "additional_properties": { + "display_name": "Team Conquest", + "description": "Capture and hold the most strategic points with your team!", + "category": "team_pvp", + "difficulty": "hard", + "map_id": "fortress_battleground" + }, + "max_num_score": 10, + "start_delay_max_sec": 120, + "ascending": false, + "operator": "best", + "duration": { + "min_sec": 3600, + "max_sec": 7200 + }, + "players": { + "min": 4, + "max": 50 } - } }, - { - "rank_min": 2, - "rank_max": 5, - "reward": { - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 250 - }, - "gems": { - "min": 12 - } - } - } - } - } - }, - { - "rank_min": 6, - "rank_max": 20, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 100 + "survival_gauntlet": { + "reward_tiers": [ + { + "rank_min": 1, + "rank_max": 5, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 400 + }, + "gems": { + "min": 20 + } + } + } + } }, - "gems": { - "min": 5 - } - } - } - } - } - ], - "additional_properties": { - "display_name": "Team Conquest", - "description": "Capture and hold the most strategic points with your team!", - "category": "team_pvp", - "difficulty": "hard", - "map_id": "fortress_battleground" - }, - "max_num_score": 10, - "start_delay_max_sec": 120, - "ascending": false, - "operator": "best", - "duration": { - "min_sec": 3600, - "max_sec": 7200 - }, - "players": { - "min": 4, - "max": 50 - } - }, - "survival_gauntlet": { - "reward_tiers": [ - { - "rank_min": 1, - "rank_max": 5, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 400 - }, - "gems": { - "min": 20 - } - } - } - } - }, - { - "rank_min": 6, - "rank_max": 15, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 200 - }, - "gems": { - "min": 10 - } - } - } - } - }, - { - "rank_min": 16, - "rank_max": 50, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 75 + { + "rank_min": 6, + "rank_max": 15, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 200 + }, + "gems": { + "min": 10 + } + } + } + } }, - "gems": { - "min": 3 + { + "rank_min": 16, + "rank_max": 50, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 75 + }, + "gems": { + "min": 3 + } + } + } + } } - } + ], + "additional_properties": { + "display_name": "Survival Gauntlet", + "description": "Survive endless waves of enemies for as long as possible!", + "category": "survival", + "difficulty": "medium", + "arena_id": "shadow_arena" + }, + "max_num_score": 3, + "start_delay_max_sec": 30, + "ascending": false, + "operator": "best", + "duration": { + "min_sec": 900, + "max_sec": 1800 + }, + "players": { + "min": 2, + "max": 200 } - } } - ], - "additional_properties": { - "display_name": "Survival Gauntlet", - "description": "Survive endless waves of enemies for as long as possible!", - "category": "survival", - "difficulty": "medium", - "arena_id": "shadow_arena" - }, - "max_num_score": 3, - "start_delay_max_sec": 30, - "ascending": false, - "operator": "best", - "duration": { - "min_sec": 900, - "max_sec": 1800 - }, - "players": { - "min": 2, - "max": 200 - } } - } } diff --git a/Nakama+Hiro/definitions/dev1/base-economy.json b/Nakama+Hiro/definitions/dev1/base-economy.json index 41a8172..f5e5702 100644 --- a/Nakama+Hiro/definitions/dev1/base-economy.json +++ b/Nakama+Hiro/definitions/dev1/base-economy.json @@ -1,8 +1,8 @@ { - "initialize_user": { - "currencies": { - "coins": 999, - "gems": 50 + "initialize_user": { + "currencies": { + "coins": 999, + "gems": 50 + } } - } -} \ No newline at end of file +} diff --git a/Nakama+Hiro/definitions/dev1/base-leaderboards.json b/Nakama+Hiro/definitions/dev1/base-leaderboards.json new file mode 100644 index 0000000..091176b --- /dev/null +++ b/Nakama+Hiro/definitions/dev1/base-leaderboards.json @@ -0,0 +1,20 @@ +{ + "leaderboards": [ + { + "authoritative": false, + "id": "global_leaderboard", + "operator": "best", + "//reset_schedule": "No reset schedule.", + "reset_schedule": "", + "sort_order": "desc" + }, + { + "authoritative": false, + "id": "weekly_leaderboard", + "operator": "best", + "//reset_schedule": "At 00:00 UTC+0 on Monday.", + "reset_schedule": "0 0 * * 1", + "sort_order": "desc" + } + ] +} diff --git a/Nakama+Hiro/lib/.gitignore b/Nakama+Hiro/lib/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/Nakama+Hiro/lib/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/Nakama+Hiro/lib/.gitkeep b/Nakama+Hiro/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Nakama+Hiro/local.yml b/Nakama+Hiro/local.yml new file mode 100644 index 0000000..f231217 --- /dev/null +++ b/Nakama+Hiro/local.yml @@ -0,0 +1,17 @@ +console: + max_message_size_bytes: 409600 +leaderboard: + blacklist_rank_cache: + - "*" +logger: + level: "DEBUG" +runtime: + env: + - "ENV=dev1" + - "HIRO_LICENSE=" +session: + token_expiry_sec: 86400 # 24 hours + refresh_token_expiry_sec: 604800 # 7 days +socket: + max_message_size_bytes: 4096 # reserved buffer + max_request_size_bytes: 131072 diff --git a/Nakama+Hiro/main.go b/Nakama+Hiro/main.go index 73e29e5..d0842cb 100644 --- a/Nakama+Hiro/main.go +++ b/Nakama+Hiro/main.go @@ -15,16 +15,6 @@ import ( ) func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error { - if err := nk.LeaderboardCreate(ctx, "weekly_leaderboard", false, "desc", "best", - "0 0 * * 1", map[string]interface{}{}, true); err != nil { - // Handle error. - } - - if err := nk.LeaderboardCreate(ctx, "global_leaderboard", false, "desc", "best", - "", map[string]interface{}{}, true); err != nil { - // Handle error. - } - err := createTournament(ctx, logger, nk, "daily-dash", "0 12 * * *", "Daily Dash", "Dash past your opponents for high scores and big rewards!", 86400, 0, 1, false) if err != nil { // Handle error. @@ -71,6 +61,7 @@ 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.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)) if err != nil { diff --git a/UnityHiroChallenges/Assets/UnityHiroChallenges/Scripts/ChallengeParticipantView.cs b/UnityHiroChallenges/Assets/UnityHiroChallenges/Scripts/ChallengeParticipantView.cs index c7f8393..3c734a0 100644 --- a/UnityHiroChallenges/Assets/UnityHiroChallenges/Scripts/ChallengeParticipantView.cs +++ b/UnityHiroChallenges/Assets/UnityHiroChallenges/Scripts/ChallengeParticipantView.cs @@ -1,33 +1,47 @@ +// Copyright 2025 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using Hiro; using UnityEngine.UIElements; namespace HiroChallenges { - public class ChallengeParticipantView + public sealed class ChallengeParticipantView { - private Label usernameLabel; - private Label scoreLabel; - private Label subScoreLabel; - private Label rankLabel; + private Label _usernameLabel; + private Label _scoreLabel; + private Label _subScoreLabel; + private Label _rankLabel; public void SetVisualElement(VisualElement visualElement) { - usernameLabel = visualElement.Q