Compare commits

...
Author SHA1 Message Date
adminandClaude Opus 4.5 580188de98 Fix iOS archive script: use Unity-iPhone scheme, remove xcpretty
- Always use "Unity-iPhone" scheme (Unity's default app scheme)
- Remove xcpretty dependency (not installed on build machine)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 22:08:16 -08:00
adminandClaude Opus 4.5 63f1e5eca4 Use existing APPLE_ID credentials for TestFlight upload
Reuse the same credentials already used for Mac notarization instead
of requiring a separate App Store Connect API key.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 21:55:21 -08:00
e24dace474 Add iOS TestFlight workflow for manual builds (#5655)
- New workflow_dispatch-only workflow for iOS TestFlight builds
- build_unity_ios.sh: Builds Unity iOS player (generates Xcode project)
- archive_ios.sh: Archives and exports IPA using xcodebuild
- upload_testflight.sh: Uploads IPA to TestFlight via App Store Connect API

Required secrets:
- IOS_CERTIFICATE: Apple Distribution certificate (.p12, base64)
- IOS_CERTIFICATE_PWD: Certificate password
- IOS_PROVISIONING_PROFILE: App Store provisioning profile (base64)
- APP_STORE_CONNECT_API_KEY: JSON with key_id, issuer_id, and key

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 21:34:39 -08:00
eb558ee8b8 Improve rain particle randomization and wind scaling (#5654)
* Additional layout improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Wire up ambient volume slider

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Improve rain particle randomization and wind scaling

- Add per-particle speed variation (0.7x to 1.3x) to break up "sheet" effect
- Scale wind offset proportionally to rain speed so angle stays consistent
- Add rainWindMultiplier setting to Inspector for easy tuning

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 21:32:49 -08:00
9616f31cfc Show health status inline in admin console nav bar (#5653)
Replace the Health link that navigated to a separate page with an
inline status indicator that auto-refreshes every 10 seconds using
htmx. This provides at-a-glance health visibility without requiring
navigation.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 20:52:24 -08:00
cf18884cc7 Additional layout improvements (#5652)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 20:51:23 -08:00
ce3ed26cc2 Consider alliances when vassals decide to mobilize (#5650)
* Consider alliances when vassals decide to mobilize

Change chosenMobilizeIfAdjacentEnemyCommand to use FactionUtils.hostileNeighbors
instead of ProvinceUtils.adjacentHostiles. The latter treats any province owned
by a different faction as hostile, causing vassals to mobilize (and withhold
supplies) even when bordered only by allied provinces.

Now vassals will correctly send supplies to their faction head when their only
neighbors are allies or have truces, rather than unnecessarily mobilizing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Delete misleading ProvinceUtils.adjacentHostiles method

This method was named "adjacentHostiles" but actually returned provinces
owned by any different faction, ignoring alliance/truce relationships.
It had only one production usage which was just fixed to use the correct
FactionUtils.hostileNeighbors method instead.

Deleting to avoid future confusion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add tests for FactionUtils.hostileNeighbors

Test coverage for:
- Returns hostile (no relationship) neighbors
- Excludes allied provinces
- Excludes truce provinces
- Excludes unruled provinces
- Excludes own faction's provinces
- Returns empty for province with no neighbors
- Correctly filters mixed neighbor types

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 20:47:25 -08:00
b928c3779e Improve HandleCapturedHero layout (#5651)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 20:35:48 -08:00
6b90112568 Replace failure_horn.mp3 with licensed Negative Effect 04.wav (#5649)
Replace the last unlicensed sound effect with Negative Effect 04.wav
from the purchased Magic Spells Sound Effects LITE pack.

This completes the asset audit - all required items are now resolved.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 20:09:11 -08:00
d2cb5328cc Handle game loading failures gracefully (#5648)
Wrap ensureGameLoaded in try-catch to prevent one corrupted/broken game
from blocking all game loading for a user. On failure:
- Log loudly to stderr with CRITICAL prefix and stack trace
- Send exception to Sentry for monitoring
- Return false so other games can continue loading

This ensures players can still access their other games even if one
game's save data is corrupted or fails to deserialize.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:52:56 -08:00
f090efe04a Add fullscreen settings and F11 shortcut (#5647)
* Add fullscreen toggle, resolution dropdown, and F11 shortcut

- Add fullscreen toggle to settings panel
- Add resolution dropdown (same as connection canvas)
- F11 key toggles fullscreen from anywhere
- Resolution change preserves current fullscreen/windowed mode

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Wire up fullscreen toggle and resolution dropdown in Unity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:52:24 -08:00
291026e044 Add ambient volume setting and improved thunder sounds (#5646)
* Add ambient volume setting for weather sounds

- Add ambientSlider to SettingsPanelController with static AmbientVolume property
- Weather effects (rain, thunderstorm, blizzard) respect ambient volume
- Volume updates in real-time when slider is adjusted during playback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add distant thunder sound effect

Source: Freesound #351526 by LittleRainySeasons (CC BY 4.0)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add more thunder sound effects

- thunder_loud.mp3 - mokasza (CC BY 4.0, Freesound #810746)
- thunder_crack.wav - OneSoundToRuleThemAll (CC BY 4.0, Freesound #238796)
- thunder_clap.wav - FreqMan (CC BY 4.0, Freesound #32544)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Wire up thunder sounds and ambient volume slider in Unity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:34:45 -08:00
173569f82a End game when all human players are defeated (#5645)
When all human factions are eliminated or have no provinces ruled by
faction leaders, the game now ends automatically instead of continuing
to run AI commands indefinitely.

Changes:
- Add endGame() method to Engine trait and EngineImpl to create a
  GameEnded action result
- Add allHumansDefeated() check in GameController.performAiCommands
- Check both before the AI loop (handles game load case) and after
  each batch of AI commands (handles AI destroying human factions)
- Handle edge case where game state has no factions yet (initialization)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:32:08 -08:00
a37d540411 Add resolution change detection to hex grid (#5644)
* Add resolution change detection to hex grid

Detects when the mapArea size changes (e.g., resolution change) and
rebuilds the grid with new metrics. Preserves terrain textures while
recreating all UI elements with correct sizes for the new resolution.

- Track mapArea size and detect changes in Update()
- RebuildGrid() destroys and recreates cells with new HexMetrics
- OnGridRebuilt event notifies ShardokGameController to refresh labels

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update Unity scene and remove unused turnHistoryButtonText

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:16:42 -08:00
14a2eab47d Add Unity client support for VassalRises notification (#5643)
Adds notification generator and dispatcher registration for the VassalRises
notification added in #5642. Displays appropriate messages when a vassal
rises to lead a faction after all leaders are killed or imprisoned.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 18:19:00 -08:00
481f42d01c Add sound effect hooks to weather effects (#5641)
* Add sound effect hooks to WeatherEffectAnimator

Audio clips for weather sounds (assign in Inspector):
- rainLoopSound: gentle pitter-patter for normal rain
- thunderstormLoopSound: more intense rain for thunderstorm
- thunderSound: one-shot thunder clap (plays on lightning flash)
- blizzardLoopSound: intense wind sound

Volume controls:
- loopVolume: volume for looping sounds (default 0.5)
- thunderVolume: volume for thunder (default 0.8)

Sound behavior:
- Rain: plays rainLoopSound continuously
- Thunderstorm: plays thunderstormLoopSound + thunderSound on each flash
- Snow: no sound
- Blizzard: plays blizzardLoopSound continuously
- Sun/Clouds: no sound

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Support multiple thunder sounds with random selection

- Change thunderSound field to thunderSounds array
- Each lightning flash picks a random sound from the array
- Allows assigning Lightning Spelll 01-08 for variety

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add rain loop sound effect from Freesound

- rain_loop.ogg by aesqe (CC BY 4.0)
- Source: https://freesound.org/people/aesqe/sounds/37618/
- Rain falling on clay roof tiles, loopable
- Added attribution to docs/ASSET_AUDIT.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Document CC BY 4.0 sound effects for weather and runaway

Added to ASSET_AUDIT.md:
- rain_loop.ogg by aesqe (Freesound #37618) - for rain effect
- blizzard_wind_loop.wav by nsstudios (Freesound #651540) - for blizzard
- runaway.mp3 by Yap_Audio_Production (Freesound #218997) - replaces unlicensed file

Note: Files must be downloaded manually from Freesound (requires login).
Only 1 unlicensed sound effect remaining: failure_horn.mp3

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Remove stray download file

* Add rain and blizzard wind sound effects

- rain_loop.ogg by aesqe (CC BY 4.0, Freesound #37618)
- blizzard_wind_loop.wav by nsstudios (CC BY 4.0, Freesound #651540)

Location: Assets/Shardok/Sounds/

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Replace runaway.mp3 with licensed version

MedievalArmyRunningLoop by Yap_Audio_Production (CC BY 4.0)
Source: https://freesound.org/people/Yap_Audio_Production/sounds/218997/

This resolves the licensing issue for runaway.mp3.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Wire up weather sound effects in scene

- Assign rain_loop.ogg to rainLoopSound
- Assign blizzard_wind_loop.wav to blizzardLoopSound
- Configure thunder sounds array

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 17:31:22 -08:00
49af7c7524 Add vassal rises to leader when faction has no available leaders (#5642)
* Add vassal rises to leader when faction has no available leaders

When all faction leaders are killed or imprisoned, the strongest vassal
(by HeroUtils.power) is now promoted to faction leader instead of
destroying the faction. This prevents "zombie factions" that have
provinces and armies but can't act due to imprisoned leaders.

Changes:
- Add VassalRises notification and VassalRisesMessage LLM request types
- Add proto definitions for new types in notification and LLM protos
- Add proto converters for VassalRises types
- Create VassalRisesPromptGenerator for LLM narrative generation
- Modify CheckForFactionChangesAction to detect imprisoned leaders and
  promote vassals when no available leaders remain
- Generate appropriate notifications and LLM messages for vassal rises

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Enhance VassalRises prompt with previous leader details

Add previousLeaderIds to VassalRisesMessage to track fallen/imprisoned
leaders. The prompt generator now includes detailed descriptions for
each previous leader including:
- Their name and full backstory via HeroDescriptionGenerator
- Status: KILLED IN ACTION or IMPRISONED in [province] by [faction]

This gives the LLM more context to generate a message that properly
acknowledges and references the fallen/captured leaders.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 17:21:16 -08:00
b6dcc48bdd Add weather visual effects to Shardok battles (#5640)
* Add weather visual effects to Shardok battles

Adds full-screen weather effects based on the current weather conditions:
- SUN: Subtle brightness overlay
- CLOUDS: Slight dimming overlay
- RAIN: Falling rain particles with wind influence
- THUNDERSTORM: Heavy rain + periodic lightning flashes
- SNOW: Falling snowflakes with drift animation
- BLIZZARD: Heavy snow with strong horizontal wind

The WeatherEffectAnimator creates particles on a dedicated canvas
overlay and updates them continuously. Wind speed from the weather
data influences particle movement.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add procedural sprite generation and pre-made sprite assets

- Add PNG sprites for rain drops and snowflakes in Assets/Shardok/Sprites/
- Add procedural sprite generation as fallback if sprites not assigned
- Update tooltip to indicate sprites are optional
- Clean up generated textures in OnDestroy to prevent memory leaks

The component now works out of the box without requiring any assets
to be manually assigned. Sprites will be generated procedurally if
the inspector fields are left empty.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Remove procedural sprite generation, fix canvas render mode

- Remove procedural sprite generation code (use pre-made PNGs instead)
- Force canvas to Screen Space - Overlay mode so particles fall
  correctly relative to the screen instead of the 3D world

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add Weather Canvas and wire up WeatherEffectAnimator in scene

- Add Weather Canvas with Screen Overlay for weather effects
- Wire up rainDropSprite and snowFlakeSprite references
- Connect weatherEffectAnimator to ShardokGameController

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix particle positioning to use screen coordinates

- Anchor particles to bottom-left (0,0) so anchoredPosition is in
  screen pixel coordinates
- Use Screen.width/height directly instead of canvas rect size
- Remove unused _canvasRect field

This ensures particles appear correctly on screen regardless of
canvas RectTransform configuration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix particle positioning using canvas rect size

- Use canvas RectTransform rect.size instead of Screen dimensions
- This properly accounts for CanvasScaler settings
- Anchor particles to center (0.5, 0.5) and position relative to center
- Wrap-around logic uses half-width/height from center

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Double particle count and size for all weather effects

- Rain: 100→200 particles, 2x12→4x24 size
- Thunderstorm: 200→400 particles
- Snow: 80→160 particles, 3-8→6-16 size
- Blizzard: 250→500 particles

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 14:49:35 -08:00
7d5320220c Remove PregeneratedText proto and unused service handling (#5637)
* Remove PregeneratedText proto and unused service handling

Now that clients no longer request pregenerated texts (they're sent via
normal ClientTextStore streaming), remove the unused proto messages:
- PregeneratedTextRequest/Response from eagle.proto
- pregenerated_text.proto file entirely
- Empty case handler in EagleServiceImpl.scala

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Remove pregenerated_text.proto reference from protos.csproj

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 12:52:12 -08:00
3eda670759 Replace free_icons with licensed Asset Store equivalents (#5639)
Replace all icons in Assets/free_icons/ (unknown license) with
equivalents from purchased Asset Store packs and delete the folder:

- blizzard.png → 16_blizzard_nobg.png (4000_Fantasy_Icons)
- rain.png → 12_Magic_rain_nobg.png (4000_Fantasy_Icons)
- thunderstorm.png → 27_Storm_nobg.png (4000_Fantasy_Icons)
- wind.png → 23_Light_blow_nobg.png (4000_Fantasy_Icons)
- thermometer.png → startFire.png (existing licensed asset)

These icons were only used in Map Editor (dev tool).

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 12:50:02 -08:00
ea79b80d7b Replace snow.png with 16_blizzard_nobg.png from Asset Store (#5636)
Replace the free_icons/snow.png (unknown license) with
16_blizzard_nobg.png from 4000_Fantasy_Icons (purchased Asset Store
pack) in all locations:
- Gameplay.unity (Province Info blizzard indicator)
- Dominion Wide.prefab
- Dominion Two Rows.prefab
- Map Editor.unity

Delete the now-unused snow.png file.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 12:22:31 -08:00
2da74769b9 Remove PregeneratedTextRequest handling (#5633)
Pregenerated texts are now populated into each game's ClientTextStore
and sent via normal text streaming (see #5630). This removes the
separate PregeneratedTextRequest/Response mechanism.

- Remove PregeneratedText import from EagleServiceImpl
- Replace PregeneratedTextRequest handler with no-op for backward compatibility
- Remove getPregeneratedClientText method from GamesManager
- Remove unused pregenerated_text_scala_proto dependency from BUILD

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 12:21:16 -08:00
bb52212f9f Update asset audit to reflect current state (#5634)
* Update asset audit to reflect current state

- 2 sound effects remaining to replace (failure_horn.mp3, runaway.mp3)
- List the 8 weather icons in free_icons folder
- Reorganize action items into Must Replace / Low Priority / Resolved
- Update recommendation section

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Delete unused weather icons (cloud.png, sun.png)

These icons were not referenced anywhere in the codebase.
Update asset audit to reflect 6 remaining weather icons.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 11:34:04 -08:00
81e8aad93b Remove client-side pregenerated text mechanism (#5635)
* Remove client-side pregenerated text mechanism

Pregenerated texts (hero names, backstories) are now sent to clients
via normal ClientTextStore streaming (thanks to #5630), so the separate
pregenerated text request/response mechanism is no longer needed.

- Remove ClientPregeneratedText.cs class and its Unity component
- Remove PregeneratedTextRequest sending from ConnectionHandler
- Remove PregeneratedTextResponse handling from PersistentClientConnection
- Remove ILobbySubscriber.ReceivePregeneratedTextUpdate method
- Update ClientTextProvider.GetTextEntry to not check pregenerated cache

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add resolved hero names to AvailableLeader for lobby display

The previous commit removed client-side pregenerated text storage, but
lobby UI (hero dropdowns for Create/Join game) still needed hero names.
This fix has the server resolve names and send them in AvailableLeader.name,
so the client can display them immediately without text lookups.

- Add name field to AvailableLeader proto message
- Server resolves names from ClientTextStore (running games) or
  PregeneratedClientTextStore (new game options)
- HeroDropdownController uses PreResolvedNames when available
- RunningGameItem/WaitingGameItem use leader.Name with fallback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 11:02:49 -08:00
f0bb59b5e5 Don't warn about river crossing when marching to own province (#5632)
The river crossing warning is only relevant when assaulting enemy
territory. When marching to a province owned by the same faction,
there's no assault so the river crossing isn't a concern.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 09:01:40 -08:00
ff09bda820 Populate pregenerated texts into game-specific ClientTextStore (#5630)
When creating or loading a game, copy the relevant pregenerated texts
(hero names, backstories, initial chronicle) from the global store into
the game's ClientTextStore as complete texts.

This ensures:
- Games are self-contained and immune to TSV file changes
- Texts are saved with the game and sent to clients normally
- Old games get the pregenerated texts they need on load

This is step 1 of removing the separate pregenerated text mechanism.
The global store is still used as a lookup source, but texts are now
stored in the regular completeTexts map for each game.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 08:20:59 -08:00
09919e63eb Layout tweaks and fix provider icon/invalid account bugs (#5631)
* Layout tweaks to game list prefabs and Gameplay scene

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix provider icon bug and handle invalid stored accounts

Bug fixes:
1. Provider icon bug: Session transfer was storing "session_transfer" as
   the provider instead of using the actual provider (discord, twitch,
   etc.) from the response. Now uses response.User.Provider.

2. User not found: When stored account validation fails with "user not
   found" (NotFound gRPC status), the invalid account is now removed from
   storage instead of just clearing the current selection.

3. NullReferenceException: When _createConnection() fails (no valid token),
   _internalConnectEagle() was continuing to use _persistentClientConnection
   which was null. Now returns early if connection creation failed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 08:19:40 -08:00
65d5e6867e Sanitize OAuth username for display name suggestion (#5629)
When suggesting a display name from the OAuth provider's username,
sanitize it to meet our validation requirements:
- Replace spaces with underscores
- Remove invalid characters (only allow alphanumeric + underscore)
- Collapse multiple underscores into one
- Trim leading/trailing underscores
- Truncate to 20 characters max
- Return empty if result is less than 3 characters

This prevents users from seeing a pre-filled invalid name that they
would need to manually fix.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 07:39:03 -08:00
f6c52ad5ab Use download buttons for both Windows and Mac in welcome email (#5628)
Change the Mac download from a text link to a button to match
the Windows download button.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 07:34:55 -08:00
3f1f9ebf07 Send welcome email after invitation flow completion (#5627)
After a user completes the invitation flow (OAuth sign-in, display name
selection), send them a welcome email with:
- Download instructions for Windows and Mac
- Download links for both platforms
- Deep link button to complete sign-in automatically
- Info about which provider/account they used

Also extends session transfer code expiration from 5 minutes to 1 hour
to give users time to install the game before the deep link expires.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 07:11:46 -08:00
9c279d85bd Fix resolution not being restored after fullscreen exit (#5626)
When minimizing the game for OAuth browser flow, we were storing
fullscreen mode but not resolution. When restoring, we only set
Screen.fullScreen=true, which uses whatever windowed resolution
Unity had stored (often 1280x720).

Now we:
- Store both fullscreen mode AND resolution (width/height) before exiting
- Use Screen.SetResolution() to restore both together

This affects both WindowFocusManager (OAuth flow) and ConnectionHandler
(new user sign-in flow).

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 07:00:31 -08:00
e91e349d76 Fix accounts page delete behavior (#5625)
- Remove confirmation dialog for deleting non-pending invitations (already
  redeemed/expired/revoked, no need to confirm)
- Reload page after delete actions instead of htmx partial updates (the
  search endpoint returns rows with different columns than the accounts page)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 06:55:19 -08:00
358086154d Improve login tutorial for new users from invitation flow (#5624)
Update the sign-in tutorial text to:
- Mention the "Complete Sign-In with Eagle0" button on the download page
- Clarify they should use the same account they created

Also exit fullscreen when showing the auth panel to new users (no stored
accounts) so they can see the browser with the sign-in link, then restore
fullscreen after successful login.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 06:51:54 -08:00
7a6e871914 Fix chronicle dismiss crash from stale StartingState (#5623)
Root cause: HandleStartingState unconditionally overwrites ChronicleEntries.
During startup, multiple StartingState messages can arrive - a stale one
(e.g., from stateAfter(1) early in the game before chronicles existed)
arriving after a current state would wipe out existing entries.

Fix:
1. Don't overwrite existing chronicle entries with an empty list, since
   chronicles are historical records that accumulate (never disappear)
2. Close the chronicle canvas if entries are cleared while visible
   (defense-in-depth + correct UX behavior)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 06:43:57 -08:00
287b31f4c2 Use named pipes for Windows deep link IPC (#5622)
On Windows, when a second instance is launched via deep link, the URL
needs to be passed to the existing instance. Previously we just
activated the window and exited, losing the URL.

Now uses named pipes (the standard .NET IPC mechanism):
- First instance creates a NamedPipeServerStream and listens
- Second instance connects, sends the URL, then exits
- First instance receives the URL and processes it via OnDeepLinkReceived

This is the same pattern used by Electron's requestSingleInstanceLock().

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 21:38:47 -08:00
18c64713bc Fix Accounts page filter conflicts and event handling (#5621)
- Add unique IDs to filter inputs to prevent hx-include conflicts
- Fix invitation actions to only refresh invitations table, not users
- Use ID-based selectors instead of name-based for HTMX includes

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 21:29:21 -08:00
cb19483f34 Fix session transfer deep link arriving before auth client ready (#5620)
When the app is launched via a deep link (e.g., from the invitation
download page), the deep link can arrive in Awake() before
ConnectionHandler calls SetServerUrls(). This caused the session
transfer to silently fail because _authClient was null.

Fix by queuing the session transfer code and processing it once
SetServerUrls() is called and the auth client is available.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 21:21:14 -08:00
19e2f22b57 Combine Users and Invitations into single Accounts tab (#5619)
Merges the separate Users and Invitations pages into a unified
two-column layout under a single "Accounts" navigation tab.

- New /accounts route with side-by-side Users and Invitations panels
- Each panel has independent search/filter functionality
- Responsive layout stacks columns on smaller screens
- Keeps existing /users and /invitations routes for HTMX partial updates

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 21:17:59 -08:00
d8d3cb1317 Use gRPC for session transfer instead of HTTP (#5618)
* Use gRPC for session transfer instead of HTTP

The session transfer was using an HTTP endpoint on port 8080 which isn't
exposed to clients. Convert to use gRPC on port 40033 like all other auth
methods.

- Add ExchangeSessionTransferCode RPC to auth.proto
- Update AuthClient.cs to call gRPC instead of HTTP
- Add gRPC handler in Go auth service
- Remove HttpClient and SessionTransferResponse class from C#

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add exchangeSessionTransferCode to Scala auth service

The Scala Eagle server also implements the Auth gRPC service and needs
the new method. Add a stub that proxies to the external Go auth service
when available, or returns UNIMPLEMENTED when not configured.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 19:51:17 -08:00
a999f8b936 Add single-instance enforcement for Windows deep links (#5617)
* Add single-instance enforcement for Windows deep links

On Windows, URL schemes just launch the executable - they don't redirect
to an existing instance like macOS does. This causes deep links to open
a new game window instead of activating the existing one.

Added SingleInstanceEnforcer which:
- Uses a named mutex to detect if another instance is running
- If launched via deep link (eagle0://) and another instance exists:
  - Finds the existing window via FindWindow
  - Brings it to foreground via SetForegroundWindow
  - Exits the duplicate instance
- Normal launches (without deep link) still allow multiple instances

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix single-instance detection: use process name instead of window title

- Use Process.GetProcessesByName instead of FindWindow - more reliable
- Use Environment.Exit(0) instead of Application.Quit() - works early in lifecycle
- Increase sleep to 500ms to ensure window activation completes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 19:40:24 -08:00
5035fe1a70 Add session transfer for seamless sign-in after invitation (#5615)
After completing OAuth on the web invitation flow, users can now click a
"Complete Sign-In in Eagle0" button to automatically sign in to the game
without going through OAuth again.

Implementation:
- New SessionTransferService generates one-time codes after OAuth
- Codes are 256-bit random, expire in 5 minutes, single-use
- Download page shows deep link button: eagle0://auth/session/{code}
- Unity client handles deep link, exchanges code for tokens via HTTP
- Login screen shows hint for users coming from invitations

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 19:20:12 -08:00
1c1066c1d8 Fix installer self-update not completing (#5616)
Two issues fixed:
1. WebView Close() was calling Destroy() directly from a goroutine,
   but this didn't properly terminate the event loop on the main thread.
   Now uses Dispatch(Terminate()) to properly exit the event loop.

2. handleInstallerReplacement was using fmt.Println instead of the UI,
   so the WebView showed "Initializing..." while the actual logic was
   invisible. Now uses ui.UpdateStatus/ShowError/etc for visibility.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 19:13:04 -08:00
d3d72ff8dc Add LoadIntoImage method to ResourceFetcher (#5613)
Adds LoadIntoImage alongside LoadIntoRawImage for loading runtime
textures into Unity Image components (which support Preserve Aspect
in layout groups). Factored out common functionality into helpers:
- CreateTexture: creates Texture2D from bytes or placeholder
- CreateSprite: creates Sprite from Texture2D
- CleanupRawImageTexture/CleanupImageSprite: cleanup helpers

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 19:00:53 -08:00
149872bea7 Fix OAuth timeout showing exception popup (#5612)
Changed Debug.LogError to Debug.LogWarning for handled OAuth errors.
The SimpleErrorHandler subscribes to Unity log messages and shows
popups for errors, but OAuth timeouts are expected conditions that
are already displayed in the status text.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:56:22 -08:00
0216ceb26b Fix OAuth deep link redirect closing before permission dialog (#5611)
The auto-redirect with window.close() was closing the page before
users could accept the browser's permission dialog to open the app.

Changed to a button-based approach where the user clicks to return
to the app, giving them control over when the deep link triggers.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:41:30 -08:00
5b840a4523 Fix Overlay Mesh transform for Shardok map (#5607)
* Fix Overlay Mesh transform for Shardok map

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix Province Info Panel being disabled on dominion toggle

Changed DominionViewToggleClicked to hide only the panel content
instead of disabling the entire controller GameObject.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add iOS Addressables content state and image meta file

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update Unity-generated files (csproj, fonts, addressables settings)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:33:38 -08:00
a51cad0ca0 Raise timing log thresholds from 10ms/50ms to 500ms (#5610)
Only log timing warnings when operations take more than 500ms,
reducing noise from normal operation while still catching slow
operations worth investigating.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:32:58 -08:00
04928f0e2c Register eagle0:// URL scheme on Windows for OAuth deep links (#5609)
* Register eagle0:// URL scheme on Windows for OAuth deep links

The Windows installer now registers the eagle0:// URL scheme in the
registry (HKEY_CURRENT_USER\Software\Classes\eagle0) pointing to the
game executable. This allows OAuth callbacks to redirect back to the
app automatically.

Also updated OAuthManager to check command line arguments for deep
links since Windows passes URL scheme launches as command line args.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add golang.org/x/sys dependency for Windows registry access

The Windows installer uses golang.org/x/sys/windows/registry to register
the eagle0:// URL scheme. This adds the required dependency to go.mod,
go.sum, and MODULE.bazel.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:32:39 -08:00
132abfa7a4 Add deep link redirect for OAuth callbacks (#5608)
After OAuth authentication completes, the browser now redirects to
eagle0://auth/complete to bring the app back to foreground. This
provides a better UX than asking users to manually close the browser.

- Unity client registers for deep link events and focuses window
- DeepLinkPostProcessor adds URL scheme to Info.plist on macOS/iOS
- All OAuth handlers (Google/Discord, Apple, Steam) redirect via deep link

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:10:58 -08:00
bbe301bc04 Unity layout adjustments (#5606)
* Unity layout adjustments

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Additional Unity layout adjustments

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 22:12:51 -08:00
138e7d89f3 Refactor Shardok map to use RectTransform for sizing (#5605)
- Change HexMetrics to accept RectTransform instead of Canvas
- Rename mapCanvas to mapArea in HexGrid (RectTransform type)
- Constrain map area to exclude sidebar for proper sizing at
  different aspect ratios

This allows the map to be properly sized based on the available
space, accounting for the sidebar at various screen resolutions.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 21:53:36 -08:00
d858f8243d Add three-tier layout mode (Wide/Medium/Narrow) for command panels (#5603)
* Add three-tier layout mode (Wide/Medium/Narrow) for command panels

- Add LayoutMode enum: Wide (>2.0), Medium (>=1.35), Narrow (<1.35)
- EagleGameController exposes CurrentLayoutMode property
- CommandSelector base class receives layout mode from CommandPanelController
- Adds IsNarrowLayout helper for selectors to check narrow mode

In narrow mode (iPad-like 4:3 aspect ratios), selectors can hide
hero/battalion pickers since users can select via the Resident
Heroes and Battalions panels instead.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add heroesColumn and battalionsColumn fields to March/Defend selectors

These GameObject fields allow hiding the hero and battalion selection
columns in narrow layout mode. Wire them up in Unity editor to the
appropriate column GameObjects.

Also fixes DefendCommandSelector import: UnityEditor -> UnityEngine

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Hide hero/battalion columns in narrow layout mode

In SetUpUI(), hide heroesColumn and battalionsColumn when IsNarrowLayout
is true. Users can still select heroes and battalions via the Resident
Heroes and Battalions side panels.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Wire up heroesColumn and battalionsColumn in Unity

Connect the column GameObjects to MarchCommandSelector and
DefendCommandSelector so they can be hidden in narrow layout mode.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 19:35:59 -08:00
5572a806af Update splash screen and app icon (#5604)
- Add new icon.png for app icon
- Remove old unused image asset
- Update ProjectSettings for splash screen

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 19:27:54 -08:00
1b153fbad6 Change default LLM provider to Gemini (#5602)
Gemini offers better cost/performance for standard LLM requests.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 14:04:21 -08:00
e53b899d7c Add turn mismatch detection with auto-reconnection (#5600)
* Add turn mismatch detection with auto-reconnection

Detects when server reports YourTurn but client has no available
commands. After a 3-second grace period (to avoid false positives
during normal update processing), logs an error via ErrorHandler
and triggers ForceReconnect() to recover state.

This addresses intermittent bugs where exceptions or token mismatches
leave the connection status showing "Your Turn" but the UI doesn't
display any turn actions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Read ServerGameStatus from GameUpdate level, add BattleInProgress display

- Move ServerGameStatus reading from ActionResultResponse to GameUpdate
  level so status updates work for all update types including Shardok
- Add "Battle in progress" display for the new BattleInProgress status

This fixes the turn mismatch false positives during Shardok battles -
the server now sends BattleInProgress instead of YourTurn during battles.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:57:49 -08:00
89c8fea438 Add BATTLE_IN_PROGRESS status and report during Shardok battles (#5601)
- Add new BATTLE_IN_PROGRESS status to ServerGameStatus enum
- Add server_game_status field to GameUpdate so any update type can
  include status (not just ActionResultResponse)
- Report BATTLE_IN_PROGRESS when there are outstanding Shardok battles
  instead of YOUR_TURN
- Set serverGameStatus on both GameUpdate and ActionResultResponse
  for backwards compatibility

This fixes the issue where "Your Turn" was shown during battles,
including observed battles where the player isn't participating.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:42:26 -08:00
7e19476167 Fix March command panel layout (#5599)
Adjust layout to prevent overflow on narrow aspect ratios.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 10:28:47 -08:00
54cd089106 Fix command panel layout for narrow aspect ratios (#5598)
Adjust layout to prevent overflow on iPad (4:3) and similar resolutions.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 07:47:39 -08:00
a6a0d58d20 Refactor widescreen layout to use enable/disable instead of reparenting (#5597)
* Refactor widescreen layout to use enable/disable instead of reparenting

Instead of moving UI panels between containers at runtime, use duplicate
panels positioned in Unity and enable/disable the appropriate version
based on aspect ratio.

Changes:
- Add separate narrow/wide versions of FactionsTable, MovingArmiesTable,
  and DominionPanel
- ArrangeLayout() now just enables/disables panels instead of reparenting
- Remove unused container GameObjects (factionsAndMovingArmiesRow, etc.)
- Both panel copies are kept in sync with model data

This approach is simpler to maintain and allows layouts to be fully
configured in the Unity editor.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update Unity scene with layout adjustments

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 20:31:41 -08:00
c2f1064742 Trim map textures to remove excess water border (#5596)
* Trim map textures to remove excess water border

Crop all map textures (map_bw_labels.png and province textures 1-43.png)
from 4096x2064 to 3786x1834, removing:
- Left: 200px
- Top: 130px
- Right: 110px
- Bottom: 100px

This leaves a small water margin around the landmass edges.

Also update newMapPrefab.prefab to set AnchoredPosition and SizeDelta to 0
instead of previous offset values.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Trim rawGray.bytes to match trimmed map textures

Apply same crop (left 200, top 130, right 110, bottom 100) to the
province hit-test data so pixel lookups remain aligned with the
trimmed map images.

Dimensions: 4096x2064 → 3786x1834

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update texture import settings and RectTransforms for trimmed map

- Set Non-Power of 2 to "None" for map texture to preserve actual
  dimensions (3786x1834) instead of scaling to 4096x2048
- Update RectTransforms to remove offsets now that images are trimmed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 14:01:09 -08:00
7173cf8faa Re-wire Shardok buttons after ShardokGameController move (#5595)
After moving ShardokGameController from ShardokCanvas to ShardokContainer,
several button onClick events lost their target references. Re-wire:
- EndTurn button (Back to Eagle / End Turn)
- DisplayActionsButtonClicked (9 action buttons)
- WarningPanelCommitClicked
- WarningPanelCancelClicked

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 06:50:22 -08:00
44dde46e39 Add missing Unity .meta files and update .gitignore (#5594)
Add .meta files for:
- BuildInfo.cs (build info feature)
- Editor/ folder and its scripts
- TouchAwareTooltip.cs

Also add ServerData/ to .gitignore to prevent local data from being tracked.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 22:09:20 -08:00
4c74265447 Move ShardokGameController from ShardokCanvas to ShardokContainer (#5593)
* Move ShardokGameController from ShardokCanvas to ShardokContainer

When exiting a battle, ShardokGameController calls this.gameObject.SetActive(false).
Previously this deactivated ShardokCanvas, leaving it inactive for the next battle.
Since ShardokCanvas is now inside ShardokContainer, GetComponentInChildren couldn't
find the controller because the canvas was inactive.

Fix: Move ShardokGameController to ShardokContainer so SetActive(false) deactivates
the whole container. Also update code to use GetComponent instead of GetComponentInChildren.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Use serialized eagleCommonTextures field instead of GetComponent

After moving ShardokGameController to ShardokContainer, EagleCommonTextures
was also moved. Replace runtime GetComponent calls with a serialized field
reference for better performance and clearer dependencies.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 22:09:07 -08:00
30fd79f4ff Add null checks with specific messages to GoToBattle (#5592)
Adds explicit null checks to identify exactly which reference is null:
- Model
- shardokContainer
- shardokController (from GetComponentInChildren)

This replaces generic NullReferenceException with specific error messages.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 21:31:04 -08:00
53e1dd623b Add git commit hash to exception logs (#5591)
- Add BuildInfo.cs with commit hash, short hash, and build timestamp
- Add BuildInfoGenerator.cs to auto-generate BuildInfo before builds
- Update ErrorHandler to include commit hash in error messages

This helps identify which exact build a production error came from.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 21:04:00 -08:00
1604343975 Link ShardokContainer in SettingsPanelController (#5590)
The ShardokContainer reference was missing (fileID: 0), causing a
NullReferenceException when clicking "Return to Lobby" in settings.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 20:33:05 -08:00
aa7d07d7c6 Fix GoToBattle button not hidden when Model becomes null (#5589)
Root cause: SwapModel() returns early when Model is null (line 504),
but goToBattleButton visibility is updated after that early return
(line 626). When StopAll() sets Model to null via SwapModel(), the
button was never hidden.

Fix: Hide the goToBattleButton in the early return path when Model
becomes null.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 20:27:14 -08:00
0042d56386 Recreate connection when gRPC channel is disposed (#5588)
When the underlying GrpcChannel is disposed (e.g., during environment
switch), the PersistentClientConnection now invokes an OnChannelDead
callback instead of futilely attempting to reconnect on the dead channel.

ConnectionHandler sets this callback to recreate the EagleConnection
and PersistentClientConnection, properly recovering from channel disposal.

Fixes ObjectDisposedException spam when channel dies.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 19:49:40 -08:00
0bb61da38c Fix overlay mesh position offset (#5586)
Adjust the overlay mesh local position to align correctly with the
hex grid on widescreen displays.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 19:29:24 -08:00
5dcf3b9c17 Fix Eagle UI layout on widescreen monitors (#5585)
Add HorizontalLayoutGroup to Improve Panel to prevent overlapping
elements on ultrawide displays (e.g., 3440x1440). The improvement
type buttons were covering the lock toggle and hero dropdown when
the screen was wider than the reference resolution.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 19:03:08 -08:00
8814f2a955 Render overlay text above 3D bridges (#5584)
Create separate Overlay Canvas with smaller plane distance so text
labels render in front of 3D bridge objects.

Changes:
- Add ShardokContainer parent to hold both canvases
- Rename shardokCanvas references to shardokContainer
- Add overlayContent RectTransform field to HexGrid for label parenting
- Update all scripts to use shardokContainer for activation

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 18:26:54 -08:00
c0b2e41212 Narrow iOS addressables workflow trigger paths (#5583)
Only trigger on changes to:
- AddressableAssetsData config
- Music assets (the addressable content)
- BuildScript.cs
- Build scripts

Previously triggered on any Unity file change, which was too broad.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 16:54:10 -08:00
902a65be08 Prefer bridge orientations that connect two land tiles (#5582)
When placing default bridges, check for opposite pairs of land tiles
and prefer orientations where both endpoints touch land, rather than
just orienting toward the first land tile found.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 16:48:10 -08:00
ac66d372f8 Fix iOS addressables building for wrong platform (#5581)
The -buildTarget iOS flag doesn't reliably switch the active build
target before editor scripts run. The addressables were being built
for StandaloneOSX instead of iOS.

Added BuildiOSAddressables() method that explicitly calls
EditorUserBuildSettings.SwitchActiveBuildTarget() before building.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 16:38:45 -08:00
14308026ea Update ASSET_AUDIT.md: mark Terrain Hexes and Discord logo as verified (#5580)
* Update ASSET_AUDIT.md: mark Terrain Hexes and Discord logo as verified

- Terrain Hexes: confirmed Unity Asset Store purchase
- Discord logo: complies with Discord brand guidelines
- Updated action items and recommendations to reflect remaining work

Remaining items:
- 6 music tracks (1 unknown source, 5 Audius non-CC license)
- 56 Shardok sound effects (unknown origin)
- 138 StrategyGameIcons (unknown source)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update ASSET_AUDIT.md: Medieval Victory Theme is CC0 Public Domain

Found source: RandomMind on Chosic, CC0 Public Domain license.
No attribution required, free for commercial use.

Remaining items:
- 5 Dima Koltsov/Audius tracks (non-CC license)
- 56 Shardok sound effects (unknown origin)
- 138 StrategyGameIcons (unknown source)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update ASSET_AUDIT.md: Dima Koltsov tracks mostly verified as CC BY 4.0

- No Time for Greatness: CC BY 4.0 (YouTube)
- Warriors of Demacia: CC BY 4.0 (YouTube)
- Valor: CC BY 4.0 (YouTube)
- Forest Queen Tale: Presumed CC BY 4.0 (not found on YouTube)
- Clouds: Presumed CC BY 4.0 (not found on YouTube)

Remaining items:
- 56 Shardok sound effects (unknown origin)
- 138 StrategyGameIcons (unknown source)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update ASSET_AUDIT.md: StrategyGameIcons verified as Asset Store purchase

Confirmed as "Strategy Game Icons" by REXARD from Unity Asset Store.

Remaining items:
- 56 Shardok sound effects (unknown origin)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update ASSET_AUDIT.md: identify 3 sounds from Zombie Monster Undead Collection

Verified from Unity Asset Store purchase:
- raise_undead.mp3
- undead_break_control.mp3
- undead_grew.wav

Also corrected count: 37 audio files (not 56 - was counting .meta files)

34 sound effects still unverified.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update ASSET_AUDIT.md: flag anybody.mp3 and runaway.mp3 for replacement

These two sound effects have licensing issues and must be replaced.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update ASSET_AUDIT.md: also flag burnination.mp3 for replacement

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update ASSET_AUDIT.md: mark remaining sound effects as presumed Asset Store

Owner believes 31 remaining sounds are from purchased Asset Store packs:
- Fantasy Interface Sounds
- Medieval Combat Sounds
- Magic Spells Sound Effects LITE
- Medieval Battle Sound Pack

Only 3 files need replacement: anybody.mp3, burnination.mp3, runaway.mp3

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Replace anybody.mp3 and burnination.mp3 with licensed alternatives

- anybody.mp3: replaced with Positive Effect 6.wav (Magic Spells Sound Effects LITE)
- burnination.mp3: replaced with Magic Element Fire 04.wav (Medieval Combat Sounds)

Still need replacements for:
- failure_horn.mp3
- runaway.mp3

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 15:44:53 -08:00
4b7a1791e0 Add bridge rotation based on builder position or adjacent land (#5577)
* Add bridge rotation based on builder position or adjacent land

Bridges now rotate to face meaningful directions:
- For bridges built by engineers: one end faces the builder's location
- For bridges present at battle start: one end faces adjacent land

Implementation:
- Add rotationDegrees parameter to HexGrid.SetCellModifier3D()
- Track bridge builder locations in _bridgeBuilderLocations dictionary
- Add GetHexNeighbors() to find adjacent hex cells
- Add CalculateBridgeRotation() to compute angle between cells
- Add CalculateBridgeRotationFromLand() to find land for initial bridges

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Simplify bridge rotation to 3-orientation lookup table

Replace complex trigonometry with simple lookup based on hex grid
direction. Only 3 possible orientations exist:
- Horizontal (0°): left ↔ right
- Diagonal up (124°): down-left ↔ up-right
- Diagonal down (56°): up-left ↔ down-right

Add BridgeRotationCalculator with unit tests for all 6 hex directions
from both even and odd rows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 15:34:36 -08:00
a6a3e26853 Add iOS addressables build workflow (#5579)
* Add iOS addressables build workflow

Creates a separate workflow to build and upload iOS addressables to CDN.
This is needed for iOS clients to load remote assets.

- Adds build_ios_addressables.sh script
- Adds ios_addressables_build.yml workflow
- Uploads to https://assets.eagle0.net/addressables/iOS/

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix iOS addressables build: add proto build step

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 13:18:07 -08:00
dc1c9e8284 Update ASSET_AUDIT.md with new AI-generated bridge icon (#5578)
Document that bridge.png was replaced with AI-generated wooden rope
bridge icon created with ChatGPT/DALL-E 3.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 13:17:23 -08:00
21eb5ff79a Replace bridge icon with AI-generated wooden rope bridge (#5576)
New 512x512 PNG icon featuring an isometric wooden rope bridge,
better suited for the fantasy game aesthetic. Generated with
ChatGPT/DALL-E, has transparent background.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:32:34 -08:00
2cd0583bb0 Add 3D bridge prefab support for hex map terrain modifiers (#5575)
Replace 2D bridge texture overlay with 3D bridge prefabs for more
visually appealing bridges on water tiles. The system now supports
an array of bridge prefabs for variety, selected deterministically
based on cell index.

- Add TerrainModifier3D field to HexGrid Cell struct
- Add SetCellModifier3D() and ClearCellModifier3Ds() methods
- Change ShardokGameController.bridgeImage to bridgePrefabs array
- Bridge positioned at true cell center using Geometry.AnchoredPosition
- Assign 6 bridge prefabs from TileableBridgePack in Gameplay scene

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:09:33 -08:00
e45ad8fc14 Silence log messages for LLM responses to deleted warmup games (#5573)
During blue-green deployments, warmup games may be invalidated while
LLM requests are in flight. The responses are correctly ignored, but
the log messages were noisy. Remove them since this is expected behavior.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 11:23:00 -08:00
d41535ffc0 Add missing TMP shader include files to Shaders directory (#5572)
The mobile shaders in TextMesh Pro/Shaders/ reference these include
files with relative paths but they only exist in Resources/Shaders/.
Copy them here to fix shader compilation errors.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 11:17:06 -08:00
c0eff139bf Add eagle-logs helper for tailing active instance logs (#5571)
Usage:
  eagle-logs           # Tail logs (follow mode)
  eagle-logs -n 100    # Show last 100 lines and follow
  eagle-logs --no-follow -n 50   # Last 50 lines without following

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 11:00:16 -08:00
4338600896 Replace fire icon with UXWing flame and consolidate duplicates (#5570)
* Replace fire icon with UXWing flame and consolidate duplicates

Replaced the startFire.png icon with a clean flat-style flame from UXWing
(free for commercial use, no attribution required). Consolidated from two
duplicate copies to a single canonical copy in Assets/Images/, updating all
Unity scene references.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update ASSET_AUDIT.md with UXWing flame icon source

Document that startFire.png was replaced with the flame icon from UXWing
(https://uxwing.com/flame-icon/) which is free for commercial use with no
attribution required. Also note the consolidation from two duplicate copies
to one canonical copy.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 10:59:51 -08:00
1ac7477597 Fix eagle-exec deployment and simplify implementation (#5569)
- Remove sudo symlink creation (deploy user lacks passwordless sudo)
- Simplify eagle-exec to read from /opt/eagle0/.active-instance file
- Update deploy-blue-green.sh to write active instance to file
- Keep fallback to checking running containers if file doesn't exist

Users can add their own alias:
  alias eagle-exec='/opt/eagle0/scripts/eagle-exec.sh'

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 10:47:16 -08:00
23cad2f383 Add iOS/mobile support with touch-aware tooltips (#5550)
* Add iOS/mobile support with touch-aware tooltips

- Add melee button to Shardok action bar (button 4 with [SHFT] label)
  - Changed MeleeAttackGroup from -1 to 4 to show melee commands as button
  - Updated Gameplay.unity to assign melee/charge commands to group 4
- Add touch-aware tooltip system using long-press pattern
  - Updated HoveringTooltipTextProvider to detect touch vs mouse input
  - On desktop: tooltips appear immediately on hover
  - On mobile: tooltips appear after 0.4s long-press
  - Created TouchAwareTooltip.cs as reusable component
- Add iOS build method to BuildScript.cs
  - Generates Xcode project for building/signing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add dedicated melee button (button9) for mobile/tablet support

- Add button9 and buttonText9 fields to ShardokGameController
- Change MeleeAttackGroup from 4 to 9 for dedicated button
- Change EndTurnCommandGroup from 9 to 12 (handled separately)
- Update melee/charge commands in Gameplay.unity to group 9
- Update End Turn command to group 12
- Add null checks for unassigned buttons in UpdateActionButtons

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix melee/charge command groups in Gameplay.unity

Set commandGroup to 9 for melee and charge commands so they
appear on the dedicated melee button and indicators work correctly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 10:45:29 -08:00
234 changed files with 19897 additions and 5960 deletions
+1
View File
@@ -10,3 +10,4 @@
src/main/csharp/**/GUI[[:space:]]Pro[[:space:]]Kit*/**/*.ttf !filter !diff !merge
src/main/csharp/**/Modern[[:space:]]UI[[:space:]]Pack/**/*.ttf !filter !diff !merge
*.herodata filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
+1 -3
View File
@@ -217,7 +217,7 @@ jobs:
# Copy files
scp -i ~/.ssh/deploy_key docker-compose.prod.yml deploy@"$DO_DROPLET_IP":/opt/eagle0/
scp -i ~/.ssh/deploy_key nginx/nginx.conf deploy@"$DO_DROPLET_IP":/opt/eagle0/nginx/
scp -i ~/.ssh/deploy_key scripts/deploy-blue-green.sh scripts/warmup-eagle.sh scripts/eagle-exec.sh deploy@"$DO_DROPLET_IP":/opt/eagle0/scripts/
scp -i ~/.ssh/deploy_key scripts/deploy-blue-green.sh scripts/warmup-eagle.sh scripts/eagle-exec.sh scripts/eagle-logs.sh deploy@"$DO_DROPLET_IP":/opt/eagle0/scripts/
scp -i ~/.ssh/deploy_key scripts/bin/warmup deploy@"$DO_DROPLET_IP":/opt/eagle0/scripts/bin/
- name: Deploy to production droplet
@@ -374,8 +374,6 @@ jobs:
# Note: Auth is deployed separately via auth_build.yml - do NOT touch auth here
chmod +x /opt/eagle0/scripts/*.sh
[ -f "/opt/eagle0/scripts/bin/warmup" ] && chmod +x /opt/eagle0/scripts/bin/warmup
# Create eagle-exec alias (symlink in /usr/local/bin)
sudo ln -sf /opt/eagle0/scripts/eagle-exec.sh /usr/local/bin/eagle-exec
GIT_SHA=\$(echo "\${EAGLE_IMAGE}" | sed 's/.*://')
/opt/eagle0/scripts/deploy-blue-green.sh "\${GIT_SHA}"
@@ -0,0 +1,66 @@
name: iOS Addressables Build
on:
push:
branches: [ "main" ]
paths:
- ".github/workflows/ios_addressables_build.yml"
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/AddressableAssetsData/**"
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Music/**"
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Editor/BuildScript.cs"
- "ci/github_actions/build_ios_addressables.sh"
- "ci/github_actions/upload_addressables.sh"
pull_request:
paths:
- ".github/workflows/ios_addressables_build.yml"
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/AddressableAssetsData/**"
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Music/**"
- "src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Editor/BuildScript.cs"
- "ci/github_actions/build_ios_addressables.sh"
- "ci/github_actions/upload_addressables.sh"
workflow_dispatch:
permissions:
contents: read
jobs:
build-ios-addressables:
runs-on: [self-hosted, macOS, unity-mac]
steps:
- uses: actions/checkout@v4
with:
lfs: true
clean: true
- name: Pull LFS files
run: git lfs pull
- name: Restore Library/
env:
UNITY_CACHE_PLATFORM: ios
run: ./ci/github_actions/restore_library.sh
- name: Build iOS Addressables
run: ./ci/github_actions/build_ios_addressables.sh "/tmp/eagle0/editor_ios_addressables.log"
- name: Persist Library/
if: success()
env:
UNITY_CACHE_PLATFORM: ios
run: ./ci/github_actions/persist_library.sh
- name: Upload Addressables to CDN
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
env:
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
run: ./ci/github_actions/upload_addressables.sh iOS
- name: Archive Build Log
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: editor_ios_addressables.log
path: /tmp/eagle0/editor_ios_addressables.log
retention-days: 5
+196
View File
@@ -0,0 +1,196 @@
name: iOS TestFlight
on:
workflow_dispatch:
inputs:
skip_upload:
description: 'Skip TestFlight upload (build and archive only)'
required: false
default: 'false'
type: boolean
permissions:
contents: read
jobs:
build-unity:
runs-on: [self-hosted, macOS, unity-mac]
outputs:
xcode_project_path: ${{ steps.build.outputs.xcode_project_path }}
steps:
- uses: actions/checkout@v4
with:
lfs: true
clean: true
fetch-depth: 0
- name: Pull LFS files
run: git lfs pull
- name: Restore Library/
env:
UNITY_CACHE_PLATFORM: ios
run: ./ci/github_actions/restore_library.sh
- name: Build iOS Unity Project
id: build
run: |
./ci/github_actions/build_unity_ios.sh "/tmp/eagle0/eagle0iOS"
echo "xcode_project_path=/tmp/eagle0/eagle0iOS" >> $GITHUB_OUTPUT
- name: Persist Library/
if: success()
env:
UNITY_CACHE_PLATFORM: ios
run: ./ci/github_actions/persist_library.sh
- name: Upload Addressables to CDN
if: success()
env:
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
run: ./ci/github_actions/upload_addressables.sh iOS
- name: Zip Xcode project for artifact
run: |
cd /tmp/eagle0
# Use tar for speed - Xcode projects have many small files
tar -czf eagle0iOS.tar.gz eagle0iOS
- name: Upload Xcode project
uses: actions/upload-artifact@v4
with:
name: xcode-project-${{ github.run_id }}
path: /tmp/eagle0/eagle0iOS.tar.gz
retention-days: 1
- name: Archive Build Log
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: editor_ios.log
path: /tmp/eagle0/editor_ios.log
retention-days: 5
archive-and-upload:
needs: build-unity
runs-on: [self-hosted, macOS, unity-mac]
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
ci
scripts
- name: Clean download directory
run: rm -rf /tmp/eagle0/eagle0iOS
- name: Download Xcode project
uses: actions/download-artifact@v4
with:
name: xcode-project-${{ github.run_id }}
path: /tmp/eagle0
- name: Extract Xcode project
run: |
cd /tmp/eagle0
tar -xzf eagle0iOS.tar.gz
rm eagle0iOS.tar.gz
ls -la eagle0iOS/
- name: Install Signing Certificate
env:
IOS_CERTIFICATE: ${{ secrets.IOS_CERTIFICATE }}
IOS_CERTIFICATE_PWD: ${{ secrets.IOS_CERTIFICATE_PWD }}
run: |
# Generate random keychain password
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV
# Decode certificate
echo "$IOS_CERTIFICATE" | base64 --decode > certificate.p12
# Delete any existing keychain from previous runs
security delete-keychain ios-build.keychain 2>/dev/null || true
# Create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" ios-build.keychain
security default-keychain -s ios-build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" ios-build.keychain
security set-keychain-settings -t 3600 -u ios-build.keychain
# Import certificate
security import certificate.p12 -k ios-build.keychain -P "$IOS_CERTIFICATE_PWD" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" ios-build.keychain
# Add keychain to search list
security list-keychains -d user -s ios-build.keychain login.keychain
rm certificate.p12
- name: Install Provisioning Profile
env:
IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }}
run: |
# Decode provisioning profile
echo "$IOS_PROVISIONING_PROFILE" | base64 --decode > profile.mobileprovision
# Extract UUID from provisioning profile
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print :UUID" /dev/stdin <<< $(security cms -D -i profile.mobileprovision))
echo "PROFILE_UUID=$PROFILE_UUID" >> $GITHUB_ENV
# Install to standard location
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE_UUID.mobileprovision
rm profile.mobileprovision
echo "Installed provisioning profile: $PROFILE_UUID"
- name: Archive and Export IPA
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
chmod +x ./ci/github_actions/archive_ios.sh
./ci/github_actions/archive_ios.sh "/tmp/eagle0/eagle0iOS" "/tmp/eagle0/archive" "$APPLE_TEAM_ID" "$PROFILE_UUID"
- name: Upload to TestFlight
if: ${{ github.event.inputs.skip_upload != 'true' }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
run: |
chmod +x ./ci/github_actions/upload_testflight.sh
./ci/github_actions/upload_testflight.sh "/tmp/eagle0/archive/eagle0.ipa"
- name: Upload IPA artifact
if: success()
uses: actions/upload-artifact@v4
with:
name: eagle0-ios-${{ github.run_id }}
path: /tmp/eagle0/archive/eagle0.ipa
retention-days: 7
- name: Cleanup Keychain
if: always()
run: |
security delete-keychain ios-build.keychain 2>/dev/null || true
cleanup:
needs: [build-unity, archive-and-upload]
if: always()
runs-on: ubuntu-latest
steps:
- name: Delete intermediate artifacts
env:
GH_TOKEN: ${{ github.token }}
run: |
artifact_name="xcode-project-${{ github.run_id }}"
echo "Deleting artifact: $artifact_name"
artifact_id=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" \
-q ".artifacts[] | select(.name == \"$artifact_name\") | .id")
if [ -n "$artifact_id" ]; then
gh api -X DELETE "repos/${{ github.repository }}/actions/artifacts/$artifact_id" || true
fi
+1
View File
@@ -122,6 +122,7 @@ use_repo(
"com_github_webview_webview_go",
"org_golang_google_grpc",
"org_golang_google_protobuf",
"org_golang_x_sys",
)
#
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# Archive and export iOS app for App Store / TestFlight
set -euxo pipefail
XCODE_PROJECT_PATH=${1:?Usage: archive_ios.sh <xcode_project_path> <output_path> <team_id> <profile_uuid>}
OUTPUT_PATH=${2:?Missing output path}
TEAM_ID=${3:?Missing team ID}
PROFILE_UUID=${4:?Missing provisioning profile UUID}
ARCHIVE_PATH="$OUTPUT_PATH/eagle0.xcarchive"
EXPORT_PATH="$OUTPUT_PATH"
echo "Archiving iOS app..."
echo " Xcode project: $XCODE_PROJECT_PATH"
echo " Archive path: $ARCHIVE_PATH"
echo " Team ID: $TEAM_ID"
mkdir -p "$OUTPUT_PATH"
# Find the .xcodeproj file
XCODEPROJ=$(find "$XCODE_PROJECT_PATH" -name "*.xcodeproj" -maxdepth 1 | head -1)
if [ -z "$XCODEPROJ" ]; then
echo "Error: No .xcodeproj found in $XCODE_PROJECT_PATH"
exit 1
fi
echo "Found Xcode project: $XCODEPROJ"
# Unity always generates "Unity-iPhone" as the main app scheme
SCHEME="Unity-iPhone"
echo "Using scheme: $SCHEME"
# Archive
xcodebuild archive \
-project "$XCODEPROJ" \
-scheme "$SCHEME" \
-archivePath "$ARCHIVE_PATH" \
-destination "generic/platform=iOS" \
-allowProvisioningUpdates \
CODE_SIGN_STYLE=Manual \
DEVELOPMENT_TEAM="$TEAM_ID" \
PROVISIONING_PROFILE_SPECIFIER="$PROFILE_UUID"
echo "Archive complete: $ARCHIVE_PATH"
# Create export options plist
EXPORT_OPTIONS_PLIST="$OUTPUT_PATH/ExportOptions.plist"
cat > "$EXPORT_OPTIONS_PLIST" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>teamID</key>
<string>$TEAM_ID</string>
<key>uploadSymbols</key>
<true/>
<key>signingStyle</key>
<string>manual</string>
<key>provisioningProfiles</key>
<dict>
<key>\$(PRODUCT_BUNDLE_IDENTIFIER)</key>
<string>$PROFILE_UUID</string>
</dict>
</dict>
</plist>
EOF
echo "Exporting IPA..."
# Export IPA
xcodebuild -exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportPath "$EXPORT_PATH" \
-exportOptionsPlist "$EXPORT_OPTIONS_PLIST" \
-allowProvisioningUpdates
# Find and rename the IPA to a consistent name
IPA_FILE=$(find "$EXPORT_PATH" -name "*.ipa" | head -1)
if [ -n "$IPA_FILE" ] && [ "$IPA_FILE" != "$EXPORT_PATH/eagle0.ipa" ]; then
mv "$IPA_FILE" "$EXPORT_PATH/eagle0.ipa"
fi
echo "Export complete: $EXPORT_PATH/eagle0.ipa"
ls -la "$EXPORT_PATH"
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Build iOS Addressables only (no player build)
# This switches Unity to iOS target and builds addressables for CDN upload
set -euxo pipefail
. ./ci/unity_version.sh
WORKSPACE=$(pwd)
echo "Building protos"
./scripts/build_protos.sh
UNITY_INSTALL_PATH="/Applications/Unity/Hub/Editor"
LOG_PATH=${1:-"/tmp/eagle0/editor_ios_addressables.log"}
echo "Building iOS Addressables"
mkdir -p "$(dirname "$LOG_PATH")"
# Build Addressables for iOS target
# Uses BuildiOSAddressables which explicitly switches build target
${UNITY_INSTALL_PATH}/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \
-nographics \
-batchmode \
-quit \
-executeMethod BuildScript.BuildiOSAddressables \
-logFile "$LOG_PATH" \
-projectPath "$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
echo "iOS Addressables build complete"
echo "Bundles should be in: $WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0/ServerData/iOS/"
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Build iOS Unity player (generates Xcode project)
set -euxo pipefail
. ./ci/unity_version.sh
WORKSPACE=$(pwd)
BUILD_PATH=${1:-"/tmp/eagle0/eagle0iOS"}
LOG_PATH="/tmp/eagle0/editor_ios.log"
echo "Building protos"
./scripts/build_protos.sh
UNITY_INSTALL_PATH="/Applications/Unity/Hub/Editor"
echo "Building iOS Unity player to: $BUILD_PATH"
mkdir -p "$(dirname "$LOG_PATH")"
mkdir -p "$BUILD_PATH"
# Build iOS player - this generates an Xcode project
${UNITY_INSTALL_PATH}/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \
-nographics \
-batchmode \
-quit \
-executeMethod BuildScript.BuildiOSPlayer \
-buildPath "$BUILD_PATH" \
-logFile "$LOG_PATH" \
-projectPath "$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
echo "iOS Unity build complete"
echo "Xcode project generated at: $BUILD_PATH"
ls -la "$BUILD_PATH"
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Upload IPA to TestFlight using Apple ID credentials (same as Mac notarization)
set -euxo pipefail
IPA_PATH=${1:?Usage: upload_testflight.sh <ipa_path>}
if [ ! -f "$IPA_PATH" ]; then
echo "Error: IPA file not found: $IPA_PATH"
exit 1
fi
echo "Uploading to TestFlight: $IPA_PATH"
# Uses same credentials as Mac notarization:
# - APPLE_ID: Your Apple ID email
# - APP_SPECIFIC_PASSWORD: App-specific password from appleid.apple.com
if [ -z "${APPLE_ID:-}" ]; then
echo "Error: APPLE_ID environment variable not set"
exit 1
fi
if [ -z "${APP_SPECIFIC_PASSWORD:-}" ]; then
echo "Error: APP_SPECIFIC_PASSWORD environment variable not set"
exit 1
fi
echo "Uploading with xcrun altool..."
xcrun altool --upload-app \
--type ios \
--file "$IPA_PATH" \
--username "$APPLE_ID" \
--password "$APP_SPECIFIC_PASSWORD"
echo "Upload complete! Check App Store Connect for processing status."
echo "The build should appear in TestFlight within 15-30 minutes after processing."
+107 -43
View File
@@ -115,14 +115,35 @@ All 26 tracks have CC licenses with proper attribution:
|-------|--------|---------|--------|
| Market Day | RandomMind | Free without attribution | [Chosic](https://www.chosic.com/download-audio/27016/) |
| Shopping List | Komiku | Free without attribution | [Chosic](https://www.chosic.com/download-audio/24714/) |
| Medieval: Victory Theme | Unknown | **NEEDS VERIFICATION** | Unknown |
| No Time for Greatness | Dima Koltsov | Audius Open Music License | [Audius](https://audius.co/dimathecomposer) |
| Warriors of Demacia | Dima Koltsov | Audius Open Music License | [Audius](https://audius.co/dimathecomposer) |
| Forest Queen Tale | Dima Koltsov | Audius Open Music License | [Audius](https://audius.co/dimathecomposer) |
| Valor | Dima Koltsov | Audius Open Music License | [Audius](https://audius.co/dimathecomposer) |
| Clouds | Dima Koltsov | Audius Open Music License | [Audius](https://audius.co/dimathecomposer) |
| Medieval: Victory Theme | RandomMind | CC0 Public Domain | [Chosic](https://www.chosic.com/download-audio/28492/) |
| No Time for Greatness | Dima Koltsov | CC BY 4.0 | [YouTube](https://www.youtube.com/watch?v=cQh0OWIFdgM) |
| Warriors of Demacia | Dima Koltsov | CC BY 4.0 | [YouTube](https://www.youtube.com/watch?v=yktSUMJn9ao) |
| Forest Queen Tale | Dima Koltsov | Presumed CC BY 4.0 | Not found on YouTube; other Dima Koltsov tracks are CC BY 4.0 |
| Valor | Dima Koltsov | CC BY 4.0 | [YouTube](https://www.youtube.com/watch?v=uoHYJRPcS2Y) |
| Clouds | Dima Koltsov | Presumed CC BY 4.0 | Not found on YouTube; other Dima Koltsov tracks are CC BY 4.0 |
**Note on Dima Koltsov tracks:** Audius uses an "Open Music License" by default, which is not the same as Creative Commons. These should eventually be replaced with CC-licensed alternatives.
**Note on Dima Koltsov tracks:** 3 of 5 tracks confirmed CC BY 4.0 via YouTube. 2 remaining tracks (Forest Queen Tale, Clouds) presumed same license but not verified.
---
## 2b. Creative Commons Sound Effects
**Location:** `Assets/Shardok/Sounds/`
| File | Description | Artist | License | Source |
|------|-------------|--------|---------|--------|
| `rain_loop.ogg` | Rain falling on clay roof tiles (loopable) | aesqe | CC BY 4.0 | [Freesound #37618](https://freesound.org/people/aesqe/sounds/37618/) |
| `blizzard_wind_loop.wav` | Wind draft loop (indoor recording, loops seamlessly) | nsstudios | CC BY 4.0 | [Freesound #651540](https://freesound.org/people/nsstudios/sounds/651540/) |
| `thunder_distant.mp3` | Distant thunder rumble | LittleRainySeasons | CC BY 4.0 | [Freesound #351526](https://freesound.org/people/LittleRainySeasons/sounds/351526/) |
| `thunder_loud.mp3` | Loud thunder clap | mokasza | CC BY 4.0 | [Freesound #810746](https://freesound.org/people/mokasza/sounds/810746/) |
| `thunder_crack.wav` | Thunder crack | OneSoundToRuleThemAll | CC BY 4.0 | [Freesound #238796](https://freesound.org/people/OneSoundToRuleThemAll/sounds/238796/) |
| `thunder_clap.wav` | Thunder clap | FreqMan | CC BY 4.0 | [Freesound #32544](https://freesound.org/people/FreqMan/sounds/32544/) |
**Location:** `Assets/Shardok/soundEffects/`
| File | Description | Artist | License | Source |
|------|-------------|--------|---------|--------|
| `runaway.mp3` | Medieval army running loop (gravel + metal/chain) | Yap_Audio_Production | CC BY 4.0 | [Freesound #218997](https://freesound.org/people/Yap_Audio_Production/sounds/218997/) |
---
@@ -138,32 +159,61 @@ All 26 tracks have CC licenses with proper attribution:
## 4. Potentially Problematic Assets (Review Needed)
### Clip Art (Unknown License)
| File | Concern |
|------|---------|
| `Assets/Shardok/commandImages/bridge.png` | Clip art style wooden bridge, unknown source - **needs replacement** |
| `Assets/Images/startFire.png` | Icon, unknown source - **needs verification or replacement** |
### ~~Clip Art (Unknown License)~~ RESOLVED
| File | Status |
|------|--------|
| ~~`Assets/Shardok/commandImages/bridge.png`~~ | **REPLACED** (2026-01-23) with AI-generated wooden rope bridge icon (ChatGPT/DALL-E 3, 512x512 PNG). No licensing restrictions - AI-generated for this project. |
| ~~`Assets/Images/startFire.png`~~ | **REPLACED** (2026-01-23) with "Flame Icon" from [UXWing](https://uxwing.com/flame-icon/) (free for commercial use, no attribution required). Consolidated duplicate removed. |
| ~~`Assets/Shardok/commandImages/startFire.png`~~ | **DELETED** (2026-01-23) - duplicate removed, all references updated to use `Assets/Images/startFire.png` |
### Shardok Sound Effects
- **Location:** `Assets/Shardok/soundEffects/`
- **Count:** 56 MP3 files
- **Count:** 37 audio files (was incorrectly counted as 56 including .meta files)
- **Contents:** Spell effects, movement, combat sounds
- **Status:** Unknown origin - may be custom or need verification
### Free Icons
- **Location:** `Assets/free_icons/`
- **Count:** 8 PNG weather icons
- **Status:** Verify "free" means commercially usable
**Verified from [Zombie Monster - Undead Collection](https://assetstore.unity.com/packages/audio/sound-fx/creatures/zombie-monster-undead-collection-70662) (Unity Asset Store):**
- `raise_undead.mp3`
- `undead_break_control.mp3`
- `undead_grew.wav`
### Terrain Hexes
**⚠️ MUST REPLACE (1 remaining):**
- ~~`anybody.mp3`~~ - **REPLACED** (2026-01-23) with `Positive Effect 6.wav` from Magic Spells Sound Effects LITE
- ~~`burnination.mp3`~~ - **REPLACED** (2026-01-23) with `Magic Element Fire 04.wav` from Medieval Combat Sounds
- `failure_horn.mp3` - licensing issue, no replacement found in purchased assets
- ~~`runaway.mp3`~~ - **REPLACED** (2026-01-27) with `MedievalArmyRunningLoop.mp3` from Freesound (CC BY 4.0)
**Presumed from Unity Asset Store purchases (31 files):**
Owner believes these are from: Fantasy Interface Sounds, Medieval Combat Sounds, Magic Spells Sound Effects LITE, and/or Medieval Battle Sound Pack.
- `archery.mp3`, `battle_shout.mp3`, `boo.mp3`, `braved_water.mp3`
- `build_bridge.mp3`, `build_bridge_failure.mp3`, `charge.mp3`
- `dismiss_unit.mp3`, `duel_challenged.mp3`, `failure_horn.mp3`, `fear.mp3`, `fear_failed.mp3`
- `fire_extinguish.mp3`, `fire_spread.mp3`, `fire_start.mp3`, `fire_start_failure.mp3`
- `freeze.mp3`, `holy_wave.mp3`, `holy_wave_damage.mp3`, `jail_door.mp3`, `lightning.mp3`
- `melee.mp3`, `meteor.mp3`, `mind_control.mp3`, `move.mp3`, `move 1.mp3`
- `raging_fire.mp3`, `reduce.mp3`, `repair.mp3`, `repair_failed.mp3`, `splash.mp3`
### ~~Free Icons~~ RESOLVED
- **Location:** `Assets/free_icons/` - **DELETED** (2026-01-27)
- **Resolution:** All icons replaced with equivalents from purchased Asset Store packs:
- `blizzard.png``16_blizzard_nobg.png` (4000_Fantasy_Icons)
- `rain.png``12_Magic_rain_nobg.png` (4000_Fantasy_Icons)
- `thunderstorm.png``27_Storm_nobg.png` (4000_Fantasy_Icons)
- `wind.png``23_Light_blow_nobg.png` (4000_Fantasy_Icons)
- `thermometer.png``startFire.png` (existing licensed asset)
- `snow.png``16_blizzard_nobg.png` (4000_Fantasy_Icons)
- `cloud.png`, `sun.png` → deleted (unused)
### ~~Terrain Hexes~~ VERIFIED
- **Location:** `Assets/Terrain Hexes/`
- **Count:** 85 PNG files
- **Status:** Unknown source - verify licensing
- **Status:** ✓ Confirmed Unity Asset Store purchase (2026-01-23)
### StrategyGameIcons
### ~~StrategyGameIcons~~ VERIFIED
- **Location:** `Assets/StrategyGameIcons/`
- **Count:** 138 PNG files
- **Status:** Unknown source - verify licensing
- **Publisher:** REXARD
- **Asset Store Link:** https://assetstore.unity.com/packages/2d/gui/icons/strategy-game-icons-64816
- **Status:** ✓ Confirmed Unity Asset Store purchase (2026-01-23)
---
@@ -193,28 +243,31 @@ NuGet packages in `Assets/Packages/` all include LICENSE.TXT files:
## Action Items
### Must Verify Before Opening Public Access:
### Must Replace Before Opening Public Access:
1. **Clip art images** - Unknown license, need replacement with properly licensed alternatives:
- `Assets/Shardok/commandImages/bridge.png` - wooden bridge icon
- `Assets/Images/startFire.png` - fire icon
1. ~~**Clip art images**~~ - **RESOLVED** (2026-01-23): Replaced with properly licensed alternatives
2. **Shardok sound effects** - 56 MP3 files of unknown origin. Either:
- Document their source
- Replace with known-licensed alternatives
- Confirm they were custom-created
2. ~~**Shardok sound effects**~~ - **ALL RESOLVED**:
- ~~`anybody.mp3`~~ - **REPLACED** (2026-01-23)
- ~~`burnination.mp3`~~ - **REPLACED** (2026-01-23)
- ~~`failure_horn.mp3`~~ - **REPLACED** (2026-01-27) with `Negative Effect 04.wav` from Magic Spells Sound Effects LITE
- ~~`runaway.mp3`~~ - **REPLACED** (2026-01-27) with Freesound CC BY 4.0
3. **Terrain Hexes** - 85 hex tiles of unknown source
- **TODO:** Confirm if this is a Unity Asset Store purchase (owner believes it is)
### Low Priority (Verify):
4. **StrategyGameIcons** - 138 icons of unknown source
- **TODO:** Investigate origin - check Unity Asset Store purchase history
3. **Dima Koltsov tracks** - 2 of 5 not verified: `Forest Queen Tale`, `Clouds` (presumed CC BY 4.0 like his other tracks)
5. **Medieval: Victory Theme** - Cannot find source or license. Either verify origin or replace.
### Already Resolved:
6. **Dima Koltsov (Audius) tracks** - 5 tracks under Audius "Open Music License" (not Creative Commons). Replace with CC-licensed alternatives eventually.
4. ~~**Free Icons**~~ - **RESOLVED** (2026-01-27): All replaced with Asset Store equivalents, folder deleted
7. **Discord logo** (`Eagle/Discord-Logo-Blurple.png`) - Likely fine for "Login with Discord" button per Discord brand guidelines, but verify usage complies with their terms
5. ~~**Terrain Hexes**~~ - **VERIFIED** (2026-01-23): Confirmed Unity Asset Store purchase
6. ~~**StrategyGameIcons**~~ - **VERIFIED** (2026-01-23): Unity Asset Store purchase (REXARD)
7. ~~**Medieval: Victory Theme**~~ - **VERIFIED** (2026-01-23): CC0 Public Domain by RandomMind ([Chosic](https://www.chosic.com/download-audio/28492/))
8. ~~**Discord logo**~~ - **OK** (2026-01-23): Usage complies with Discord brand guidelines for "Login with Discord" button
### Already Safe:
@@ -228,12 +281,23 @@ NuGet packages in `Assets/Packages/` all include LICENSE.TXT files:
## Recommendation
Before public release:
**Remaining before public release:**
1. Replace clip art images (`bridge.png`, `startFire.png`) with properly licensed alternatives (e.g., from [game-icons.net](https://game-icons.net) CC BY 3.0)
2. Verify source of `Assets/Shardok/soundEffects/` MP3s
3. Verify source of `Assets/Terrain Hexes/` and `Assets/StrategyGameIcons/`
4. Replace Dima Koltsov Audius tracks with CC-licensed alternatives
5. Find source of Medieval: Victory Theme or replace
None! All required items resolved.
**Low priority:**
2. Verify 2 Dima Koltsov tracks (`Forest Queen Tale`, `Clouds`) - presumed CC BY 4.0
**Already resolved:**
- ~~Clip art images~~ **DONE** - replaced with properly licensed alternatives
- ~~Terrain Hexes~~ **DONE** - confirmed Asset Store purchase
- ~~StrategyGameIcons~~ **DONE** - Unity Asset Store (REXARD)
- ~~Medieval: Victory Theme~~ **DONE** - CC0 Public Domain
- ~~3 other Dima Koltsov tracks~~ **DONE** - confirmed CC BY 4.0
- ~~anybody.mp3, burnination.mp3~~ **DONE** - replaced
- ~~free_icons~~ **DONE** - replaced with Asset Store equivalents
- ~~failure_horn.mp3~~ **DONE** - replaced with Negative Effect 04.wav
The bulk of your assets (95%+) are properly licensed Asset Store purchases or CC content.
+1
View File
@@ -12,6 +12,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
golang.org/x/sys v0.28.0
google.golang.org/grpc v1.68.0
google.golang.org/protobuf v1.36.3
)
+2
View File
@@ -43,6 +43,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+5
View File
@@ -35,6 +35,7 @@ WARMUP_SCRIPT="${SCRIPT_DIR}/warmup-eagle.sh"
SAVES_DIR="${APP_DIR}/saves"
FLUSH_MARKER="${SAVES_DIR}/.flush_complete"
DEPLOYMENT_IN_PROGRESS="${SAVES_DIR}/.deployment_in_progress"
ACTIVE_INSTANCE_FILE="${APP_DIR}/.active-instance"
# Colors for output
RED='\033[0;31m'
@@ -298,6 +299,10 @@ main() {
create_flush_marker "${deploy_id}" "eagle-${staging}"
log_info "[DEPLOY:${deploy_id}] Flush marker created - server will auto-invalidate stale cache"
# Write active instance file for eagle-exec helper
echo "eagle-${staging}" > "${ACTIVE_INSTANCE_FILE}"
log_info "[DEPLOY:${deploy_id}] Active instance file updated: eagle-${staging}"
# Update .env for admin service
local env_file="${APP_DIR}/.env"
if [ "$staging" = "green" ]; then
+17 -25
View File
@@ -1,43 +1,35 @@
#!/bin/bash
#
# Helper to run docker exec against the active Eagle instance.
# Automatically determines whether blue or green is active based on nginx config.
# Reads the active instance from /opt/eagle0/.active-instance (set by deploy-blue-green.sh).
#
# Usage:
# ./scripts/eagle-exec.sh printenv GEMINI_API_KEY
# ./scripts/eagle-exec.sh jcmd 1 VM.flags
# ./scripts/eagle-exec.sh sh # Get a shell
# eagle-exec printenv GEMINI_API_KEY
# eagle-exec jcmd 1 VM.flags
# eagle-exec sh # Get a shell
#
# Or create an alias:
# To create an alias, add to ~/.bashrc:
# alias eagle-exec='/opt/eagle0/scripts/eagle-exec.sh'
#
set -euo pipefail
APP_DIR="${APP_DIR:-/opt/eagle0}"
ACTIVE_FILE="${APP_DIR}/.active-instance"
# Determine active instance from nginx config
get_active_instance() {
local backend
backend=$(docker exec nginx grep -o 'eagle-[a-z]*:40032' /etc/nginx/nginx.conf 2>/dev/null | head -1 || echo "")
if [ "$backend" = "eagle-blue:40032" ]; then
echo "eagle-blue"
elif [ "$backend" = "eagle-green:40032" ]; then
echo "eagle-green"
# Read active instance from file, with fallback
if [ -f "$ACTIVE_FILE" ]; then
ACTIVE=$(cat "$ACTIVE_FILE")
else
# Fallback: check which container is actually running
if docker inspect --format='{{.State.Running}}' eagle-blue 2>/dev/null | grep -q true; then
ACTIVE="eagle-blue"
elif docker inspect --format='{{.State.Running}}' eagle-green 2>/dev/null | grep -q true; then
ACTIVE="eagle-green"
else
# Fallback: check which container is actually running
if docker inspect --format='{{.State.Running}}' eagle-blue 2>/dev/null | grep -q true; then
echo "eagle-blue"
elif docker inspect --format='{{.State.Running}}' eagle-green 2>/dev/null | grep -q true; then
echo "eagle-green"
else
echo ""
fi
ACTIVE=""
fi
}
ACTIVE=$(get_active_instance)
fi
if [ -z "$ACTIVE" ]; then
echo "Error: No active Eagle instance found" >&2
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
#
# Helper to tail logs from the active Eagle instance.
# Reads the active instance from /opt/eagle0/.active-instance (set by deploy-blue-green.sh).
#
# Usage:
# eagle-logs # Tail logs (follow mode)
# eagle-logs -n 100 # Show last 100 lines and follow
# eagle-logs --no-follow -n 50 # Show last 50 lines without following
#
# To create an alias, add to ~/.bashrc:
# alias eagle-logs='/opt/eagle0/scripts/eagle-logs.sh'
#
set -euo pipefail
APP_DIR="${APP_DIR:-/opt/eagle0}"
ACTIVE_FILE="${APP_DIR}/.active-instance"
# Read active instance from file, with fallback
if [ -f "$ACTIVE_FILE" ]; then
ACTIVE=$(cat "$ACTIVE_FILE")
else
# Fallback: check which container is actually running
if docker inspect --format='{{.State.Running}}' eagle-blue 2>/dev/null | grep -q true; then
ACTIVE="eagle-blue"
elif docker inspect --format='{{.State.Running}}' eagle-green 2>/dev/null | grep -q true; then
ACTIVE="eagle-green"
else
ACTIVE=""
fi
fi
if [ -z "$ACTIVE" ]; then
echo "Error: No active Eagle instance found" >&2
exit 1
fi
# Default to follow mode if no args provided
if [ $# -eq 0 ]; then
exec docker logs -f "$ACTIVE"
else
exec docker logs "$@" "$ACTIVE"
fi
@@ -37,4 +37,7 @@ sysinfo.txt
*.apk
*.unitypackage
mono_crash.*
mono_crash.*
# Local server data
ServerData/
@@ -26,7 +26,7 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>Temp\Bin\Debug\Assembly-CSharp\</OutputPath>
<DefineConstants>UNITY_6000_3_0;UNITY_6000_3;UNITY_6000;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;UNITY_2019_4_OR_NEWER;UNITY_2020_1_OR_NEWER;UNITY_2020_2_OR_NEWER;UNITY_2020_3_OR_NEWER;UNITY_2021_1_OR_NEWER;UNITY_2021_2_OR_NEWER;UNITY_2021_3_OR_NEWER;UNITY_2022_1_OR_NEWER;UNITY_2022_2_OR_NEWER;UNITY_2022_3_OR_NEWER;UNITY_2023_1_OR_NEWER;UNITY_2023_2_OR_NEWER;UNITY_2023_3_OR_NEWER;UNITY_6000_0_OR_NEWER;UNITY_6000_1_OR_NEWER;UNITY_6000_2_OR_NEWER;UNITY_6000_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;UNITY_ANALYTICS;ENABLE_AR;ENABLE_AUDIO;ENABLE_AUDIO_SCRIPTABLE_PIPELINE;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_VIRTUALTEXTURING;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_UNITY_CONSENT;ENABLE_UNITY_CLOUD_IDENTIFIERS;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_NATIVE_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_EDITOR_GAME_SERVICES;ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;ENABLE_MANAGED_UNITYTLS;INCLUDE_DYNAMIC_GI;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_MARSHALLING_TESTS;ENABLE_VIDEO;ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK;ENABLE_ACCELERATOR_CLIENT_DEBUGGING;ENABLE_ACCESSIBILITY_SCREEN_READER;TEXTCORE_1_0_OR_NEWER;EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED;PLATFORM_STANDALONE_OSX;PLATFORM_STANDALONE;UNITY_STANDALONE_OSX;UNITY_STANDALONE;ENABLE_GAMECENTER;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLOUD_SERVICES_ENGINE_DIAGNOSTICS;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;PLATFORM_HAS_CUSTOM_MUTEX;PLATFORM_UPDATES_TIME_OUTSIDE_OF_PLAYER_LOOP;PLATFORM_SUPPORTS_SPLIT_GRAPHICS_JOBS;ENABLE_MONO;NET_STANDARD_2_0;NET_STANDARD;NET_STANDARD_2_1;NETSTANDARD;NETSTANDARD2_1;ENABLE_PROFILER;DEBUG;TRACE;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_OSX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;TEXTCORE_FONT_ENGINE_1_5_OR_NEWER;TEXTCORE_TEXT_ENGINE_1_5_OR_NEWER;TEXTCORE_FONT_ENGINE_1_6_OR_NEWER;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER</DefineConstants>
<DefineConstants>UNITY_6000_3_0;UNITY_6000_3;UNITY_6000;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;UNITY_2019_4_OR_NEWER;UNITY_2020_1_OR_NEWER;UNITY_2020_2_OR_NEWER;UNITY_2020_3_OR_NEWER;UNITY_2021_1_OR_NEWER;UNITY_2021_2_OR_NEWER;UNITY_2021_3_OR_NEWER;UNITY_2022_1_OR_NEWER;UNITY_2022_2_OR_NEWER;UNITY_2022_3_OR_NEWER;UNITY_2023_1_OR_NEWER;UNITY_2023_2_OR_NEWER;UNITY_2023_3_OR_NEWER;UNITY_6000_0_OR_NEWER;UNITY_6000_1_OR_NEWER;UNITY_6000_2_OR_NEWER;UNITY_6000_3_OR_NEWER;UNITY_INCLUDE_TESTS;UNITY_ANALYTICS;ENABLE_AR;ENABLE_AUDIO;ENABLE_AUDIO_SCRIPTABLE_PIPELINE;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_UNITY_CONSENT;ENABLE_UNITY_CLOUD_IDENTIFIERS;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_EDITOR_GAME_SERVICES;ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;ENABLE_ENGINE_CODE_STRIPPING;ENABLE_ONSCREEN_KEYBOARD;ENABLE_MANAGED_UNITYTLS;INCLUDE_DYNAMIC_GI;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;ENABLE_MARSHALLING_TESTS;ENABLE_VIDEO;ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK;ENABLE_ACCELERATOR_CLIENT_DEBUGGING;ENABLE_ACCESSIBILITY_SCREEN_READER;ENABLE_ACCESSIBILITY_SETTINGS;TEXTCORE_1_0_OR_NEWER;EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED;ENABLE_RUNTIME_GI;ENABLE_GAMECENTER;ENABLE_NETWORK;ENABLE_IOS_ON_DEMAND_RESOURCES;ENABLE_IOS_APP_SLICING;PLAYERCONNECTION_LISTENS_FIXED_PORT;DEBUGGER_LISTENS_FIXED_PORT;PLATFORM_SUPPORTS_ADS_ID;PLATFORM_SUPPORTS_INSIGHTS_IDFA;PLATFORM_SUPPORTS_INSIGHTS_DEVICE_INFO;SUPPORT_ENVIRONMENT_VARIABLES;PLATFORM_SUPPORTS_PROFILER;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_ETC_COMPRESSION;UNITY_IPHONE_API;PLATFORM_SUPPORTS_SPLIT_GRAPHICS_JOBS;ENABLE_INSIGHTS_PLATFORM_SPECIFIC_RESOURCES;ENABLE_UNITYADS_RUNTIME;UNITY_UNITYADS_API;PLATFORM_IOS;UNITY_IOS;PLATFORM_IPHONE;UNITY_IPHONE;UNITY_HAS_GOOGLEVR;ENABLE_SPATIALTRACKING;ENABLE_CLOUD_SERVICES_ENGINE_DIAGNOSTICS;ENABLE_FIREBASE_IDENTIFIERS;ENABLE_MONO;NET_4_6;NET_UNITY_4_8;ENABLE_PROFILER;DEBUG;TRACE;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_OSX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;TEXTCORE_FONT_ENGINE_1_5_OR_NEWER;TEXTCORE_TEXT_ENGINE_1_5_OR_NEWER;TEXTCORE_FONT_ENGINE_1_6_OR_NEWER;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoWarn>0169,0649</NoWarn>
@@ -146,6 +146,7 @@
<Compile Include="Assets/Modern UI Pack/Scripts/Slider/RadialSlider.cs" />
<Compile Include="Assets/Eagle/Notifications/WeatherForcedTurnBackNotificationGenerator.cs" />
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManager.cs" />
<Compile Include="Assets/common/GUIUtils/TouchAwareTooltip.cs" />
<Compile Include="Assets/Eagle/GeneratedTextUpdater.cs" />
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/OutlawApprehendedDetailsNotificationGenerator.cs" />
<Compile Include="Assets/Shardok/HexMetrics.cs" />
@@ -174,9 +175,9 @@
<Compile Include="Assets/Shardok/BattalionTypeManager.cs" />
<Compile Include="Assets/Eagle/CommandSelectors/DefendCommandSelector.cs" />
<Compile Include="Assets/Eagle/HeroesAndBattalionsPanelController.cs" />
<Compile Include="Assets/common/HexMapStorage.cs" />
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManagerBasic.cs" />
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/BreakAllianceAmbassadorImprisonedNotificationGenerator.cs" />
<Compile Include="Assets/common/HexMapStorage.cs" />
<Compile Include="Assets/Eagle/CommandSelectors/PleaseRecruitMeCommandSelector.cs" />
<Compile Include="Assets/Eagle/Notifications/RiotAvertedNotificationGenerator.cs" />
<Compile Include="Assets/Modern UI Pack/Scripts/Notification/NotificationStacking.cs" />
@@ -207,6 +208,7 @@
<Compile Include="Assets/Eagle/CommandSelectors/ExileVassalCommandSelector.cs" />
<Compile Include="Assets/Tutorial/TutorialState.cs" />
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManagerBasicWithIconEditor.cs" />
<Compile Include="Assets/Shardok/WeatherEffectAnimator.cs" />
<Compile Include="Assets/Modern UI Pack/Scripts/UI Manager/UIManagerProgressBarLoop.cs" />
<Compile Include="Assets/Eagle/MovingArmiesTableController.cs" />
<Compile Include="Assets/ConnectionHandler/ConnectionHandler.cs" />
@@ -276,7 +278,6 @@
<Compile Include="Assets/Shardok/MeteorAnimator.cs" />
<Compile Include="Assets/Eagle/CommandSelectors/ReturnCommandSelector.cs" />
<Compile Include="Assets/Eagle/CommandSelectors/ResolveTruceCommandSelector.cs" />
<Compile Include="Assets/Eagle/ClientPregeneratedText.cs" />
<Compile Include="Assets/Eagle/CommandSelectors/SwearBrotherhoodCommandSelector.cs" />
<Compile Include="Assets/Auth/TokenStorage.cs" />
<Compile Include="Assets/Eagle/Table Rows/TableRowController.cs" />
@@ -294,6 +295,7 @@
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManagerEditor.cs" />
<Compile Include="Assets/UI/Scripts/SceneMigrationHelper.cs" />
<Compile Include="Assets/Eagle/Table Rows/UnitSelectorHeroRowController.cs" />
<Compile Include="Assets/Shardok/BridgeRotationCalculator.cs" />
<Compile Include="Assets/ConnectionHandler/CustomBattleHandler.cs" />
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/RansomPaidDetailsNotificationGenerator.cs" />
<Compile Include="Assets/Shardok/ControlAnimator.cs" />
@@ -308,6 +310,7 @@
<Compile Include="Assets/Eagle/CommandSelectors/RecruitHeroesCommandSelector.cs" />
<Compile Include="Assets/Shardok/CommandTypeUIManager.cs" />
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/PrisonerReleasedDetailsNotificationGenerator.cs" />
<Compile Include="Assets/common/SingleInstanceEnforcer.cs" />
<Compile Include="Assets/common/GUIUtils/TableRowClickDetector.cs" />
<Compile Include="Assets/PlayerColors.cs" />
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/CapturedHeroReturnedDetailsNotificationGenerator.cs" />
@@ -326,6 +329,7 @@
<Compile Include="Assets/Shardok/ExtinguishAnimator.cs" />
<Compile Include="Assets/Eagle/PanelPositions.cs" />
<Compile Include="Assets/Eagle/Notifications/NotificationGenerator.cs" />
<Compile Include="Assets/BuildInfo.cs" />
<Compile Include="Assets/Eagle/Notifications/DynamicTextNotification.cs" />
<Compile Include="Assets/Modern UI Pack/Scripts/UI Manager/UIManagerToggle.cs" />
<Compile Include="Assets/Modern UI Pack/Scripts/UI Manager/UIManagerAnimatedIcon.cs" />
@@ -388,6 +392,7 @@
<Compile Include="Assets/common/GUIUtils/EventBasedTable.cs" />
<Compile Include="Assets/Modern UI Pack/Scripts/Window/WindowManagerEditor.cs" />
<Compile Include="Assets/Eagle/Notifications/FactionDestroyedNotificationGenerator.cs" />
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/VassalRisesDetailsNotificationGenerator.cs" />
<Compile Include="Assets/Modern UI Pack/Scripts/UI Manager/UIManagerNotification.cs" />
<Compile Include="Assets/Eagle/CommandSelectors/HandleCapturedHeroesCommandSelector.cs" />
<Compile Include="Assets/Tutorial/UI/TutorialOverlayController.cs" />
@@ -411,10 +416,12 @@
<None Include="Assets/Packages/Microsoft.Extensions.Options.8.0.0/lib/netstandard2.1/Microsoft.Extensions.Options.xml" />
<None Include="Assets/TextMesh Pro/Shaders/TMPro_Mobile.cginc" />
<None Include="Assets/TextMesh Pro/Shaders/TMP_SDF SSD.shader" />
<None Include="Assets/TextMesh Pro/Shaders/TMPro_Properties.cginc" />
<None Include="Assets/Shardok/Painted Medieval Fantasy Items 1/medieval_items1.txt" />
<None Include="Assets/TextMesh Pro/Resources/Shaders/TMP_SDF-Surface-Mobile.shader" />
<None Include="Assets/Eagle/maskShader.shader" />
<None Include="Assets/GUI Pro Kit Fantasy RPG/Extensions/UIParticle/hidden particle.shader" />
<None Include="Assets/TextMesh Pro/Shaders/TMPro.cginc" />
<None Include="Assets/Terrain Hexes/readme.txt" />
<None Include="Assets/Modern UI Pack/Read Me.txt" />
<None Include="Assets/TextMesh Pro/Shaders/SDFFunctions.hlsl" />
@@ -490,12 +497,6 @@
<Reference Include="UnityEngine.ClothModule">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/Managed/UnityEngine/UnityEngine.ClothModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ClusterInputModule">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/Managed/UnityEngine/UnityEngine.ClusterInputModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ClusterRendererModule">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/Managed/UnityEngine/UnityEngine.ClusterRendererModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ContentLoadModule">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/Managed/UnityEngine/UnityEngine.ContentLoadModule.dll</HintPath>
</Reference>
@@ -688,9 +689,6 @@
<Reference Include="UnityEngine.VideoModule">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/Managed/UnityEngine/UnityEngine.VideoModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VirtualTexturingModule">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/Managed/UnityEngine/UnityEngine.VirtualTexturingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.WindModule">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/Managed/UnityEngine/UnityEngine.WindModule.dll</HintPath>
</Reference>
@@ -865,9 +863,6 @@
<Reference Include="Microsoft.Extensions.Logging.Abstractions">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Packages/Microsoft.Extensions.Logging.Abstractions.8.0.0/lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Annotations">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Packages/System.ComponentModel.Annotations.5.0.0/lib/netstandard2.1/System.ComponentModel.Annotations.dll</HintPath>
</Reference>
<Reference Include="nunit.framework">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/PackageCache/com.unity.ext.nunit@d8c07649098d/net40/unity-custom/nunit.framework.dll</HintPath>
</Reference>
@@ -922,374 +917,392 @@
<Reference Include="Unity.Analytics.StandardEvents">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/PackageCache/com.unity.analytics@c9d14a6bdec6/AnalyticsStandardEvents/Unity.Analytics.StandardEvents.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.iOS.Extensions.Xcode">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/PlaybackEngines/iOSSupport/UnityEditor.iOS.Extensions.Xcode.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.iOS.Extensions.Common">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/PlaybackEngines/iOSSupport/UnityEditor.iOS.Extensions.Common.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.Apple.Extensions.Common">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/PlaybackEngines/iOSSupport/UnityEditor.Apple.Extensions.Common.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.iOS.Extensions.Xcode">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/PlaybackEngines/MacStandaloneSupport/UnityEditor.iOS.Extensions.Xcode.dll</HintPath>
</Reference>
<Reference Include="netstandard">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/ref/2.1.0/netstandard.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Win32.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/Microsoft.Win32.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.AppContext">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.AppContext.dll</HintPath>
</Reference>
<Reference Include="System.Buffers">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Concurrent">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Collections.Concurrent.dll</HintPath>
</Reference>
<Reference Include="System.Collections.NonGeneric">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Collections.NonGeneric.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Specialized">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Collections.Specialized.dll</HintPath>
</Reference>
<Reference Include="System.Collections">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Collections.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.EventBasedAsync">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.ComponentModel.EventBasedAsync.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.ComponentModel.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.TypeConverter">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.ComponentModel.TypeConverter.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.ComponentModel.dll</HintPath>
</Reference>
<Reference Include="System.Console">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Console.dll</HintPath>
</Reference>
<Reference Include="System.Data.Common">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Data.Common.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Contracts">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Diagnostics.Contracts.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Debug">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Diagnostics.Debug.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.FileVersionInfo">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Diagnostics.FileVersionInfo.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Process">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Diagnostics.Process.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.StackTrace">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Diagnostics.StackTrace.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.TextWriterTraceListener">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Diagnostics.TextWriterTraceListener.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Tools">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Diagnostics.Tools.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.TraceSource">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Diagnostics.TraceSource.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Tracing">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Diagnostics.Tracing.dll</HintPath>
</Reference>
<Reference Include="System.Drawing.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Drawing.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Dynamic.Runtime">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Dynamic.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Globalization.Calendars">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Globalization.Calendars.dll</HintPath>
</Reference>
<Reference Include="System.Globalization.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Globalization.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Globalization">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Globalization.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression.ZipFile">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.Compression.ZipFile.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.Compression.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.DriveInfo">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.FileSystem.DriveInfo.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.FileSystem.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.Watcher">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.FileSystem.Watcher.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.FileSystem.dll</HintPath>
</Reference>
<Reference Include="System.IO.IsolatedStorage">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.IsolatedStorage.dll</HintPath>
</Reference>
<Reference Include="System.IO.MemoryMappedFiles">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.MemoryMappedFiles.dll</HintPath>
</Reference>
<Reference Include="System.IO.Pipes">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.Pipes.dll</HintPath>
</Reference>
<Reference Include="System.IO.UnmanagedMemoryStream">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.UnmanagedMemoryStream.dll</HintPath>
</Reference>
<Reference Include="System.IO">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.IO.dll</HintPath>
</Reference>
<Reference Include="System.Linq.Expressions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Linq.Expressions.dll</HintPath>
</Reference>
<Reference Include="System.Linq.Parallel">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Linq.Parallel.dll</HintPath>
</Reference>
<Reference Include="System.Linq.Queryable">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Linq.Queryable.dll</HintPath>
</Reference>
<Reference Include="System.Linq">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Memory">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.Http.dll</HintPath>
</Reference>
<Reference Include="System.Net.NameResolution">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.NameResolution.dll</HintPath>
</Reference>
<Reference Include="System.Net.NetworkInformation">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.NetworkInformation.dll</HintPath>
</Reference>
<Reference Include="System.Net.Ping">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.Ping.dll</HintPath>
</Reference>
<Reference Include="System.Net.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Net.Requests">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.Requests.dll</HintPath>
</Reference>
<Reference Include="System.Net.Security">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.Security.dll</HintPath>
</Reference>
<Reference Include="System.Net.Sockets">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.Sockets.dll</HintPath>
</Reference>
<Reference Include="System.Net.WebHeaderCollection">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.WebHeaderCollection.dll</HintPath>
</Reference>
<Reference Include="System.Net.WebSockets.Client">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.WebSockets.Client.dll</HintPath>
</Reference>
<Reference Include="System.Net.WebSockets">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Net.WebSockets.dll</HintPath>
</Reference>
<Reference Include="System.Numerics.Vectors">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.ObjectModel">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.DispatchProxy">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Reflection.DispatchProxy.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Emit.ILGeneration">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Reflection.Emit.ILGeneration.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Emit.Lightweight">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Reflection.Emit.Lightweight.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Emit">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Reflection.Emit.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Reflection.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Reflection.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Reflection">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Reflection.dll</HintPath>
</Reference>
<Reference Include="System.Resources.Reader">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Resources.Reader.dll</HintPath>
</Reference>
<Reference Include="System.Resources.ResourceManager">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Resources.ResourceManager.dll</HintPath>
</Reference>
<Reference Include="System.Resources.Writer">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Resources.Writer.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.VisualC">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.CompilerServices.VisualC.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Handles">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.Handles.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices.RuntimeInformation">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.InteropServices.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Numerics">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.Numerics.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Formatters">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.Serialization.Formatters.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Json">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.Serialization.Json.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.Serialization.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Xml">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.Serialization.Xml.dll</HintPath>
</Reference>
<Reference Include="System.Runtime">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Security.Claims">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Security.Claims.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Algorithms">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Security.Cryptography.Algorithms.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Csp">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Security.Cryptography.Csp.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Encoding">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Security.Cryptography.Encoding.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Security.Cryptography.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.X509Certificates">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Security.Cryptography.X509Certificates.dll</HintPath>
</Reference>
<Reference Include="System.Security.Principal">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Security.Principal.dll</HintPath>
</Reference>
<Reference Include="System.Security.SecureString">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Security.SecureString.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Text.Encoding.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Text.Encoding.dll</HintPath>
</Reference>
<Reference Include="System.Text.RegularExpressions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Text.RegularExpressions.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Overlapped">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Threading.Overlapped.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Parallel">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Threading.Tasks.Parallel.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Threading.Tasks.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Thread">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Threading.Thread.dll</HintPath>
</Reference>
<Reference Include="System.Threading.ThreadPool">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Threading.ThreadPool.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Timer">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Threading.Timer.dll</HintPath>
</Reference>
<Reference Include="System.Threading">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Threading.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Xml.ReaderWriter">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Xml.ReaderWriter.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XDocument">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Xml.XDocument.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XPath.XDocument">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Xml.XPath.XDocument.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XPath">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Xml.XPath.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XmlDocument">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Xml.XmlDocument.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XmlSerializer">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netstandard/System.Xml.XmlSerializer.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices.WindowsRuntime">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/Extensions/2.0.0/System.Runtime.InteropServices.WindowsRuntime.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Composition">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.ComponentModel.Composition.dll</HintPath>
</Reference>
<Reference Include="System.Core">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Core.dll</HintPath>
</Reference>
<Reference Include="System.Data">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Data.dll</HintPath>
</Reference>
<Reference Include="System.Drawing">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Drawing.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression.FileSystem">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.IO.Compression.FileSystem.dll</HintPath>
</Reference>
<Reference Include="System.Net">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Net.dll</HintPath>
</Reference>
<Reference Include="System.Numerics">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Numerics.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Runtime.Serialization.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.Web">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.ServiceModel.Web.dll</HintPath>
</Reference>
<Reference Include="System.Transactions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Transactions.dll</HintPath>
</Reference>
<Reference Include="System.Web">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Web.dll</HintPath>
</Reference>
<Reference Include="System.Windows">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Windows.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Xml.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Serialization">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Xml.Serialization.dll</HintPath>
</Reference>
<Reference Include="System.Xml">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.Xml.dll</HintPath>
<Reference Include="mscorlib">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/mscorlib.dll</HintPath>
</Reference>
<Reference Include="System">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/System.dll</HintPath>
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.dll</HintPath>
</Reference>
<Reference Include="mscorlib">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/NetStandard/compat/2.1.0/shims/netfx/mscorlib.dll</HintPath>
<Reference Include="System.Core">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Core.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Runtime.Serialization.dll</HintPath>
</Reference>
<Reference Include="System.Xml">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Xml.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Xml.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Numerics">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Numerics.dll</HintPath>
</Reference>
<Reference Include="System.Numerics.Vectors">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Net.Http.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.IO.Compression.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Microsoft.CSharp.dll</HintPath>
</Reference>
<Reference Include="System.Data">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Data.dll</HintPath>
</Reference>
<Reference Include="System.Data.DataSetExtensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Data.DataSetExtensions.dll</HintPath>
</Reference>
<Reference Include="System.Drawing">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Drawing.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression.FileSystem">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.IO.Compression.FileSystem.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Composition">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.ComponentModel.Composition.dll</HintPath>
</Reference>
<Reference Include="System.Transactions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/System.Transactions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Win32.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/Microsoft.Win32.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.AppContext">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.AppContext.dll</HintPath>
</Reference>
<Reference Include="System.Buffers">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Concurrent">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Collections.Concurrent.dll</HintPath>
</Reference>
<Reference Include="System.Collections.NonGeneric">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Collections.NonGeneric.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Specialized">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Collections.Specialized.dll</HintPath>
</Reference>
<Reference Include="System.Collections">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Collections.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Annotations">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ComponentModel.Annotations.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.EventBasedAsync">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ComponentModel.EventBasedAsync.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ComponentModel.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.TypeConverter">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ComponentModel.TypeConverter.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ComponentModel.dll</HintPath>
</Reference>
<Reference Include="System.Console">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Console.dll</HintPath>
</Reference>
<Reference Include="System.Data.Common">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Data.Common.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Contracts">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Diagnostics.Contracts.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Debug">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Diagnostics.Debug.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.FileVersionInfo">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Diagnostics.FileVersionInfo.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Process">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Diagnostics.Process.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.StackTrace">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Diagnostics.StackTrace.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.TextWriterTraceListener">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Diagnostics.TextWriterTraceListener.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Tools">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Diagnostics.Tools.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.TraceSource">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Diagnostics.TraceSource.dll</HintPath>
</Reference>
<Reference Include="System.Drawing.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Drawing.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Dynamic.Runtime">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Dynamic.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Globalization.Calendars">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Globalization.Calendars.dll</HintPath>
</Reference>
<Reference Include="System.Globalization.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Globalization.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Globalization">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Globalization.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression.ZipFile">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.Compression.ZipFile.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.DriveInfo">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.FileSystem.DriveInfo.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.FileSystem.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.Watcher">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.FileSystem.Watcher.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.FileSystem.dll</HintPath>
</Reference>
<Reference Include="System.IO.IsolatedStorage">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.IsolatedStorage.dll</HintPath>
</Reference>
<Reference Include="System.IO.MemoryMappedFiles">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.MemoryMappedFiles.dll</HintPath>
</Reference>
<Reference Include="System.IO.Pipes">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.Pipes.dll</HintPath>
</Reference>
<Reference Include="System.IO.UnmanagedMemoryStream">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.UnmanagedMemoryStream.dll</HintPath>
</Reference>
<Reference Include="System.IO">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.IO.dll</HintPath>
</Reference>
<Reference Include="System.Linq.Expressions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Linq.Expressions.dll</HintPath>
</Reference>
<Reference Include="System.Linq.Parallel">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Linq.Parallel.dll</HintPath>
</Reference>
<Reference Include="System.Linq.Queryable">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Linq.Queryable.dll</HintPath>
</Reference>
<Reference Include="System.Linq">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Memory">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Rtc">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.Http.Rtc.dll</HintPath>
</Reference>
<Reference Include="System.Net.NameResolution">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.NameResolution.dll</HintPath>
</Reference>
<Reference Include="System.Net.NetworkInformation">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.NetworkInformation.dll</HintPath>
</Reference>
<Reference Include="System.Net.Ping">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.Ping.dll</HintPath>
</Reference>
<Reference Include="System.Net.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Net.Requests">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.Requests.dll</HintPath>
</Reference>
<Reference Include="System.Net.Security">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.Security.dll</HintPath>
</Reference>
<Reference Include="System.Net.Sockets">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.Sockets.dll</HintPath>
</Reference>
<Reference Include="System.Net.WebHeaderCollection">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.WebHeaderCollection.dll</HintPath>
</Reference>
<Reference Include="System.Net.WebSockets.Client">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.WebSockets.Client.dll</HintPath>
</Reference>
<Reference Include="System.Net.WebSockets">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Net.WebSockets.dll</HintPath>
</Reference>
<Reference Include="System.ObjectModel">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.DispatchProxy">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Reflection.DispatchProxy.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Emit.ILGeneration">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Reflection.Emit.ILGeneration.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Emit.Lightweight">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Reflection.Emit.Lightweight.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Emit">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Reflection.Emit.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Reflection.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Reflection.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Reflection">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Reflection.dll</HintPath>
</Reference>
<Reference Include="System.Resources.Reader">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Resources.Reader.dll</HintPath>
</Reference>
<Reference Include="System.Resources.ResourceManager">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Resources.ResourceManager.dll</HintPath>
</Reference>
<Reference Include="System.Resources.Writer">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Resources.Writer.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.VisualC">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.CompilerServices.VisualC.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Handles">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.Handles.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices.RuntimeInformation">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices.WindowsRuntime">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.InteropServices.WindowsRuntime.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.InteropServices.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Numerics">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.Numerics.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Formatters">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.Serialization.Formatters.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Json">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.Serialization.Json.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.Serialization.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Xml">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.Serialization.Xml.dll</HintPath>
</Reference>
<Reference Include="System.Runtime">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Security.Claims">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Security.Claims.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Algorithms">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Security.Cryptography.Algorithms.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Csp">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Security.Cryptography.Csp.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Encoding">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Security.Cryptography.Encoding.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Security.Cryptography.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.X509Certificates">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Security.Cryptography.X509Certificates.dll</HintPath>
</Reference>
<Reference Include="System.Security.Principal">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Security.Principal.dll</HintPath>
</Reference>
<Reference Include="System.Security.SecureString">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Security.SecureString.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.Duplex">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ServiceModel.Duplex.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.Http">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ServiceModel.Http.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.NetTcp">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ServiceModel.NetTcp.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.Primitives">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ServiceModel.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.Security">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ServiceModel.Security.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Text.Encoding.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Text.Encoding.dll</HintPath>
</Reference>
<Reference Include="System.Text.RegularExpressions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Text.RegularExpressions.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Overlapped">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Threading.Overlapped.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Parallel">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Threading.Tasks.Parallel.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Threading.Tasks.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Thread">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Threading.Thread.dll</HintPath>
</Reference>
<Reference Include="System.Threading.ThreadPool">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Threading.ThreadPool.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Timer">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Threading.Timer.dll</HintPath>
</Reference>
<Reference Include="System.Threading">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Threading.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Xml.ReaderWriter">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Xml.ReaderWriter.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XDocument">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Xml.XDocument.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XPath.XDocument">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Xml.XPath.XDocument.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XPath">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Xml.XPath.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XmlDocument">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Xml.XmlDocument.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XmlSerializer">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/System.Xml.XmlSerializer.dll</HintPath>
</Reference>
<Reference Include="netstandard">
<HintPath>/Applications/Unity/Hub/Editor/6000.3.0f1/Unity.app/Contents/Resources/Scripting/UnityReferenceAssemblies/unity-4.8-api/Facades/netstandard.dll</HintPath>
</Reference>
<Reference Include="Analytics">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Analytics.dll</HintPath>
@@ -1354,9 +1367,6 @@
<Reference Include="Unity.AI.Navigation.Updater">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.AI.Navigation.Updater.dll</HintPath>
</Reference>
<Reference Include="Unity.Multiplayer.Center.Editor">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Multiplayer.Center.Editor.dll</HintPath>
</Reference>
<Reference Include="Unity.ScriptableBuildPipeline.Editor">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.ScriptableBuildPipeline.Editor.dll</HintPath>
</Reference>
@@ -1366,6 +1376,9 @@
<Reference Include="Unity.Addressables">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Addressables.dll</HintPath>
</Reference>
<Reference Include="Unity.Multiplayer.Center.Editor">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Multiplayer.Center.Editor.dll</HintPath>
</Reference>
<Reference Include="Unity.Services.Core.Components">
<HintPath>/Users/dancrosby/CodingProjects/github/eagle0-unity-client/src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ScriptAssemblies/Unity.Services.Core.Components.dll</HintPath>
</Reference>
@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: 4458439bbfafa478b905417eff3c5741
m_currentHash:
serializedVersion: 2
Hash: 00000000000000000000000000000000
Hash: 0e8900f37a8b7b1c88049de57e371339
m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 1
m_CatalogRequestsTimeout: 0
@@ -24,11 +24,11 @@ MonoBehaviour:
m_InternalBundleIdMode: 1
m_AssetLoadMode: 0
m_BundledAssetProviderType:
m_AssemblyName:
m_ClassName:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_AssetBundleProviderType:
m_AssemblyName:
m_ClassName:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_IgnoreUnsupportedFilesInBuild: 0
m_UniqueBundleIds: 0
m_EnableJsonCatalog: 0
@@ -53,7 +53,7 @@ MonoBehaviour:
m_RemoteCatalogBuildPath:
m_Id: 2af7f42f684fd4296a199c9e60624f6f
m_RemoteCatalogLoadPath:
m_Id: 55b13501972b540abb7332a5bad98342
m_Id: 55b13501972b540abb7332a5bad98342
m_ContentStateBuildPathProfileVariableName:
m_CustomContentStateBuildPath:
m_ContentStateBuildPath:
@@ -77,7 +77,7 @@ MonoBehaviour:
- m_Id: 32a6cb134d6b249acba4c1423e9667a4
m_Value: '[UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]'
- m_Id: 55b13501972b540abb7332a5bad98342
m_Value: https://assets.eagle0.net/addressables/[BuildTarget]
m_Value: 'https://assets.eagle0.net/addressables/[BuildTarget]'
- m_Id: b347880d4285c4a25a539211b563a545
m_Value: '[UnityEditor.EditorUserBuildSettings.activeBuildTarget]'
m_ProfileEntryNames:
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0ebe438313ec24b6894ce84cf4c89605
guid: 568267bd7b6fa4581adaa40faa984594
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a1b5efbbd8a2c4df59c9f7d542172b18
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -187,6 +187,23 @@ namespace Auth {
}
}
/// <summary>
/// Exchange a session transfer code for tokens.
/// Used when user clicks the deep link from the invitation download page.
/// Routed to Go auth service.
/// </summary>
public async Task<ExchangeSessionTransferCodeResponse> ExchangeSessionTransferCodeAsync(
string code) {
Debug.Log("[AuthClient] Exchanging session transfer code via gRPC...");
var request = new ExchangeSessionTransferCodeRequest { Code = code };
var response = await _authServiceClient.ExchangeSessionTransferCodeAsync(request);
Debug.Log(
$"[AuthClient] Session transfer successful: userId={response.User?.UserId}, displayName={response.User?.DisplayName}");
return response;
}
public void Dispose() {
_authServiceChannel?.Dispose();
_eagleChannel?.Dispose();
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using common;
using Grpc.Core;
using Net.Eagle0.Eagle.Api.Auth;
using UnityEngine;
@@ -20,6 +21,8 @@ namespace Auth {
private string _currentAuthServiceUrl;
private string _currentEagleUrl;
private OAuthProvider _currentLoginProvider; // Track provider during login
private string _pendingSessionTransferCode; // Queued if deep link arrives before auth
// client ready
// Events for UI updates
public event Action<UserInfo> OnLoginSuccess;
@@ -45,6 +48,90 @@ namespace Auth {
TokenStorage.InitializeCache();
// AuthClient is created lazily when SetServerUrls is called
// Register deep link handler for OAuth callback
Application.deepLinkActivated += OnDeepLinkActivated;
// On Windows, listen for deep links from other instances via named pipe
SingleInstanceEnforcer.OnDeepLinkReceived += OnDeepLinkActivated;
// Check if app was launched via deep link
if (!string.IsNullOrEmpty(Application.absoluteURL)) {
OnDeepLinkActivated(Application.absoluteURL);
}
// On Windows, URL scheme launches pass the URL as a command line argument
CheckCommandLineForDeepLink();
}
private void CheckCommandLineForDeepLink() {
var args = System.Environment.GetCommandLineArgs();
// Args[0] is the executable path, check remaining args for eagle0:// URLs
for (int i = 1; i < args.Length; i++) {
if (args[i].StartsWith("eagle0://")) {
Debug.Log($"[OAuthManager] Found deep link in command line: {args[i]}");
OnDeepLinkActivated(args[i]);
break;
}
}
}
private void OnDeepLinkActivated(string url) {
Debug.Log($"[OAuthManager] Deep link received: {url}");
// Handle session transfer deep link: eagle0://auth/session/{code}
const string sessionTransferPrefix = "eagle0://auth/session/";
if (url.StartsWith(sessionTransferPrefix)) {
var code = url.Substring(sessionTransferPrefix.Length).TrimEnd('/');
Debug.Log(
$"[OAuthManager] Session transfer deep link received, code length: {code.Length}");
WindowFocusManager.BringToForeground();
_ = HandleSessionTransferAsync(code);
return;
}
// Handle eagle0://auth/complete - brings app to foreground after OAuth
if (url.StartsWith("eagle0://auth")) {
Debug.Log("[OAuthManager] OAuth callback deep link - bringing app to foreground");
WindowFocusManager.BringToForeground();
}
}
/// <summary>
/// Handle session transfer code from invitation deep link.
/// Exchanges the one-time code for tokens and logs the user in.
/// </summary>
private async Task HandleSessionTransferAsync(string code) {
if (_authClient == null) {
// Auth client not ready yet - queue the code for later processing
Debug.Log(
"[OAuthManager] Auth client not ready, queuing session transfer code for later");
_pendingSessionTransferCode = code;
return;
}
try {
Debug.Log("[OAuthManager] Exchanging session transfer code for tokens...");
var response = await _authClient.ExchangeSessionTransferCodeAsync(code);
// Store tokens with actual provider from response
var providerName = response.User.Provider.ToString().ToLowerInvariant();
TokenStorage.StoreTokens(
response.AccessToken,
response.RefreshToken,
response.ExpiresAt,
response.User.UserId,
response.User.DisplayName,
providerName);
Debug.Log(
$"[OAuthManager] Session transfer successful for {response.User.DisplayName}");
OnLoginSuccess?.Invoke(response.User);
} catch (Exception ex) {
Debug.LogWarning($"[OAuthManager] Session transfer failed: {ex.Message}");
OnLoginFailed?.Invoke($"Sign-in link expired or invalid. Please sign in manually.");
}
}
/// <summary>
@@ -64,9 +151,19 @@ namespace Auth {
_currentEagleUrl = eagleUrl;
_authClient = new AuthClient(authServiceUrl, eagleUrl);
Debug.Log($"[OAuthManager] Configured: authService={authServiceUrl}, eagle={eagleUrl}");
// Process any pending session transfer that arrived before auth client was ready
if (!string.IsNullOrEmpty(_pendingSessionTransferCode)) {
var code = _pendingSessionTransferCode;
_pendingSessionTransferCode = null;
Debug.Log("[OAuthManager] Processing queued session transfer code");
_ = HandleSessionTransferAsync(code);
}
}
private void OnDestroy() {
Application.deepLinkActivated -= OnDeepLinkActivated;
SingleInstanceEnforcer.OnDeepLinkReceived -= OnDeepLinkActivated;
_authClient?.Dispose();
if (Instance == this) { Instance = null; }
}
@@ -124,7 +221,7 @@ namespace Auth {
// Bring game back to foreground even on failure
WindowFocusManager.BringToForeground();
Debug.LogError($"[OAuthManager] Login failed: {ex.Message}");
Debug.LogWarning($"[OAuthManager] Login failed: {ex.Message}");
OnLoginFailed?.Invoke(ex.Message);
throw;
}
@@ -184,6 +281,13 @@ namespace Auth {
Debug.Log($"[OAuthManager] Session restored for {response.User.DisplayName}");
OnLoginSuccess?.Invoke(response.User);
return true;
} catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound) {
// User no longer exists on server - remove the invalid account
var accountKey = TokenStorage.CurrentAccount?.AccountKey;
Debug.LogWarning(
$"[OAuthManager] User not found on server, removing invalid account: {accountKey}");
if (accountKey != null) { TokenStorage.RemoveAccount(accountKey); }
return false;
} catch (Exception ex) {
Debug.LogWarning($"[OAuthManager] Session validation failed: {ex.Message}");
TokenStorage.Clear();
@@ -274,6 +378,13 @@ namespace Auth {
$"[OAuthManager] Connected with stored account: {response.User.DisplayName}");
OnLoginSuccess?.Invoke(response.User);
return true;
} catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound) {
// User no longer exists on server - remove the invalid account
Debug.LogWarning(
$"[OAuthManager] User not found on server, removing account: {account.AccountKey}");
TokenStorage.RemoveAccount(account.AccountKey);
OnLoginFailed?.Invoke("Account no longer exists. Please sign in again.");
return false;
} catch (Exception ex) {
Debug.LogWarning($"[OAuthManager] Session validation failed: {ex.Message}");
OnLoginFailed?.Invoke("Session invalid. Please sign in again.");
@@ -0,0 +1,9 @@
/// <summary>
/// Build information generated at build time.
/// This file is auto-generated by BuildInfoGenerator - do not edit manually.
/// </summary>
public static class BuildInfo {
public const string GitCommitHash = "development";
public const string GitCommitShort = "dev";
public const string BuildTimestamp = "";
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9f6bcf450bd4543d39dbba8e09b7605c
@@ -94,8 +94,8 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 30
m_fontSizeBase: 30
m_fontSize: 18
m_fontSizeBase: 18
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
@@ -152,11 +152,11 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: 450
m_MinWidth: 240
m_MinHeight: 60
m_PreferredWidth: 450
m_PreferredWidth: 240
m_PreferredHeight: 60
m_FlexibleWidth: -1
m_FlexibleWidth: 0
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &3769343953609136136
@@ -253,8 +253,8 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 36
m_fontSizeBase: 36
m_fontSize: 18
m_fontSizeBase: 18
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
@@ -311,9 +311,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: 180
m_MinWidth: 200
m_MinHeight: -1
m_PreferredWidth: 180
m_PreferredWidth: 200
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
@@ -540,7 +540,7 @@ MonoBehaviour:
m_MinHeight: -1
m_PreferredWidth: 200
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleWidth: 0
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &4995675505853930853
@@ -636,8 +636,8 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 36
m_fontSizeBase: 36
m_fontSize: 18
m_fontSizeBase: 18
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
@@ -1285,9 +1285,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: 700
m_MinWidth: 300
m_MinHeight: -1
m_PreferredWidth: 700
m_PreferredWidth: 300
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: -1
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using eagle;
using Eagle0.Tutorial;
using Net.Eagle0.Eagle.Api;
@@ -73,6 +74,11 @@ public class AvailableGameItem : MonoBehaviour {
});
}
// Pass pre-resolved names from AvailableLeader.Name so names display
// immediately without needing to look them up via ClientTextProvider
heroDropdownController.PreResolvedNames =
leaders.Where(l => !string.IsNullOrEmpty(l.Name))
.ToDictionary(l => l.NameTextId, l => l.Name);
heroDropdownController.FallbackText = "Random";
heroDropdownController.AvailableHeroes = heroOptions;
}
@@ -136,7 +136,7 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
public Canvas connectionCanvas;
public Canvas eagleCanvas;
public Canvas shardokCanvas;
public GameObject shardokContainer;
public CustomBattleHandler customBattleHandler;
List<GameInfo> fetchedRunningGames;
@@ -150,8 +150,10 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
private readonly Object pendingReplyLock = new Object();
private int _connectedEnvironmentIndex = -1; // -1 means not connected
private List<StoredAccountButton> _storedAccountButtons = new();
public ClientPregeneratedText clientPregeneratedText;
private bool _exitedFullscreenForNewUser = false; // Track if we exited fullscreen for login
private FullScreenMode _previousFullScreenMode; // Store mode to restore after login
private int _previousWidth; // Store resolution to restore after login
private int _previousHeight;
const String BaseDomain = "eagle0.net";
const String EnvironmentKey = "environmentKey";
@@ -192,10 +194,6 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
_handleLobbyResponse(lobbyResponse);
}
public void ReceivePregeneratedTextUpdate(PregeneratedTextResponse response) {
clientPregeneratedText.Load(response.PregeneratedText);
}
void Start() {
var currentResolution = Screen.currentResolution;
var resolutions = Screen.resolutions;
@@ -206,7 +204,7 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
connectionCanvas.gameObject.SetActive(true);
eagleCanvas.gameObject.SetActive(false);
shardokCanvas.gameObject.SetActive(false);
shardokContainer.SetActive(false);
connectionCanvas.enabled = true;
connectionPanel.gameObject.SetActive(true);
@@ -301,6 +299,24 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
var accounts = OAuthManager.Instance?.GetStoredAccounts();
if (accounts == null || accounts.Count == 0) {
TutorialManager.Instance?.OnGameEvent("auth_panel_shown");
// Show hint for users coming from invitation flow
if (oauthStatusText != null) {
oauthStatusText.text =
"Came from an invitation? Check your browser for a 'Complete Sign-In' button.";
}
// Exit fullscreen so user can see browser with the sign-in link
if (Screen.fullScreen) {
_exitedFullscreenForNewUser = true;
_previousFullScreenMode = Screen.fullScreenMode;
_previousWidth = Screen.width;
_previousHeight = Screen.height;
Screen.fullScreen = false;
Debug.Log(
$"[ConnectionHandler] Exited fullscreen for new user sign-in flow (was {_previousWidth}x{_previousHeight})");
}
} else {
// Clear any previous hint
if (oauthStatusText != null) { oauthStatusText.text = ""; }
}
}
@@ -499,7 +515,7 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
await OAuthManager.Instance.LoginAsync(provider);
// OnLoginSuccess or OnNewUserNeedsDisplayName will be called
} catch (Exception ex) {
Debug.LogError($"OAuth login failed: {ex.Message}");
Debug.LogWarning($"OAuth login failed: {ex.Message}");
if (oauthStatusText != null) { oauthStatusText.text = $"Login failed: {ex.Message}"; }
}
}
@@ -507,6 +523,13 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
private void OnOAuthLoginSuccess(UserInfo user) {
MainQueue.Q.Enqueue(() => {
if (oauthStatusText != null) { oauthStatusText.text = $"Welcome, {user.DisplayName}!"; }
// Restore fullscreen if we exited it for the new user sign-in flow
if (_exitedFullscreenForNewUser) {
_exitedFullscreenForNewUser = false;
Screen.SetResolution(_previousWidth, _previousHeight, _previousFullScreenMode);
Debug.Log(
$"[ConnectionHandler] Restored fullscreen: {_previousWidth}x{_previousHeight} {_previousFullScreenMode}");
}
ConnectWithOAuth();
});
}
@@ -603,7 +626,7 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
public void CancelCustomBattle() {
customBattlePanel.SetActive(false);
shardokCanvas.gameObject.SetActive(false);
shardokContainer.SetActive(false);
connectionCanvas.gameObject.SetActive(true);
gameSelectionPanel.SetActive(true);
StartListeningForLobbyUpdates();
@@ -820,6 +843,13 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
eagleConnection.EagleGrpcClient,
eagleConnection.credentials,
EagleConnection.EagleCancellationToken);
_persistentClientConnection.OnChannelDead = () => {
// Queue to main thread to avoid threading issues
MainQueue.Q.Enqueue(() => {
Debug.Log("[ConnectionHandler] Channel dead, recreating connection");
_createConnection();
});
};
_persistentClientConnection.Connect();
errorHandler.PersistentClientConnection = _persistentClientConnection;
@@ -860,13 +890,13 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
_createConnection();
// If connection creation failed (e.g., no valid token), don't proceed
if (_persistentClientConnection == null) {
Debug.LogError("[ConnectionHandler] Connection creation failed, cannot proceed");
return;
}
try {
_persistentClientConnection.SendUpdateStreamRequestAsync(new UpdateStreamRequest {
PregeneratedTextRequest =
new PregeneratedTextRequest {
KnownPregeneratedTextHash = clientPregeneratedText.Hash
}
});
RequestMaps();
StartListeningForLobbyUpdates();
} catch (RpcException e) {
@@ -72,6 +72,11 @@ public class CreateGameItem : MonoBehaviour {
});
}
// Pass pre-resolved names from AvailableLeader.Name so names display
// immediately without needing to look them up via ClientTextProvider
heroDropdownController.PreResolvedNames =
leaders.Where(l => !string.IsNullOrEmpty(l.Name))
.ToDictionary(l => l.NameTextId, l => l.Name);
heroDropdownController.FallbackText = "Random";
heroDropdownController.AvailableHeroes = heroOptions;
heroDropdownController.HeadshotImage.gameObject.SetActive(false);
@@ -22,7 +22,7 @@ using VictoryCondition = Net.Eagle0.Common.VictoryCondition;
public class CustomBattleHandler : MonoBehaviour, IClientConnectionSubscriber {
public GameObject yourArmiesArea;
public GameObject aiArmiesArea;
public Canvas shardokCanvas;
public GameObject shardokContainer;
public Toggle defenderToggle;
public TMP_Dropdown mapDropdown;
@@ -103,8 +103,8 @@ public class CustomBattleHandler : MonoBehaviour, IClientConnectionSubscriber {
heroImages: _heroImages,
battalionNames: new Dictionary<int, string> { { 0, "no name" } });
shardokCanvas.gameObject.SetActive(true);
shardokCanvas.GetComponent<ShardokGameController>().SetUpGame(_shardokModel);
shardokContainer.SetActive(true);
shardokContainer.GetComponent<ShardokGameController>().SetUpGame(_shardokModel);
GameId = newGameResponse.EagleGameId;
Register();
@@ -30,8 +30,14 @@ public class RunningGameItem : MonoBehaviour {
this.gameId = gameId;
gameIdField.text = string.Format("{0:X}", gameId);
var textEntry = ClientTextProvider.Provider.GetTextEntry(leader.NameTextId);
var leaderName = textEntry != null ? textEntry.Text : "Hero";
// Use pre-resolved name from server if available, otherwise fall back to text lookup
string leaderName;
if (!string.IsNullOrEmpty(leader.Name)) {
leaderName = leader.Name;
} else {
var textEntry = ClientTextProvider.Provider.GetTextEntry(leader.NameTextId);
leaderName = textEntry != null ? textEntry.Text : "Hero";
}
leaderField.text = string.Format(
"{0} ({1})",
leaderName,
@@ -29,9 +29,9 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3084837499067662000}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -101,24 +101,27 @@ MonoBehaviour:
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
@@ -149,9 +152,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: 700
m_MinWidth: 300
m_MinHeight: -1
m_PreferredWidth: 700
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: -1
@@ -185,9 +188,9 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3084837499067662000}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -261,20 +264,23 @@ MonoBehaviour:
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
@@ -305,11 +311,11 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: 180
m_MinWidth: 300
m_MinHeight: -1
m_PreferredWidth: 180
m_PreferredWidth: 300
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleWidth: 0
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &1722990544386167487
@@ -322,8 +328,6 @@ GameObject:
m_Component:
- component: {fileID: 5405233146667072049}
- component: {fileID: 9089165643261525487}
- component: {fileID: 3309908850111668078}
- component: {fileID: 7352302768073002882}
- component: {fileID: 28101565677326836}
- component: {fileID: 4394564974352927053}
- component: {fileID: 1180739972031081654}
@@ -344,10 +348,10 @@ RectTransform:
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 933970514342940104}
m_Father: {fileID: 3084837499067662000}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -362,74 +366,6 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1722990544386167487}
m_CullTransparentMesh: 0
--- !u!114 &3309908850111668078
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1722990544386167487}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1a12ddbc47b17cd478cb447d1113a22b, type: 3}
m_Name:
m_EditorClassIdentifier:
buttonText: waiting...
buttonEvent:
m_PersistentCalls:
m_Calls: []
hoverSound: {fileID: 0}
clickSound: {fileID: 0}
normalText: {fileID: 1528463102076488116}
soundSource: {fileID: 0}
useCustomContent: 0
enableButtonSounds: 0
useHoverSound: 1
useClickSound: 1
--- !u!114 &7352302768073002882
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1722990544386167487}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a9d2f9067277ade4e9b47012ea4d9e17, type: 3}
m_Name:
m_EditorClassIdentifier:
_effectGradient:
serializedVersion: 2
key0: {r: 0, g: 0.5568628, b: 1, a: 1}
key1: {r: 0.23529412, g: 0.85457677, b: 0.972549, a: 1}
key2: {r: 0, g: 0, b: 0, a: 0}
key3: {r: 0, g: 0, b: 0, a: 0}
key4: {r: 0, g: 0, b: 0, a: 0}
key5: {r: 0, g: 0, b: 0, a: 0}
key6: {r: 0, g: 0, b: 0, a: 0}
key7: {r: 0, g: 0, b: 0, a: 0}
ctime0: 0
ctime1: 65535
ctime2: 0
ctime3: 0
ctime4: 0
ctime5: 0
ctime6: 0
ctime7: 0
atime0: 0
atime1: 65535
atime2: 0
atime3: 0
atime4: 0
atime5: 0
atime6: 0
atime7: 0
m_Mode: 0
m_NumColorKeys: 2
m_NumAlphaKeys: 2
_gradientType: 0
_blendMode: 2
_offset: 0
--- !u!114 &28101565677326836
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -474,7 +410,7 @@ MonoBehaviour:
m_OnClick:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 0}
- m_Target: {fileID: 7494010136792472269}
m_TargetAssemblyTypeName:
m_MethodName: JoinClicked
m_Mode: 1
@@ -529,11 +465,11 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: 200
m_MinWidth: 120
m_MinHeight: -1
m_PreferredWidth: 200
m_PreferredWidth: 120
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleWidth: 0
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &3410015936189372563
@@ -565,9 +501,9 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3084837499067662000}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -630,8 +566,8 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 30
m_fontSizeBase: 30
m_fontSize: 18
m_fontSizeBase: 18
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
@@ -641,20 +577,23 @@ MonoBehaviour:
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
@@ -685,11 +624,11 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: 450
m_MinWidth: 240
m_MinHeight: 60
m_PreferredWidth: 450
m_PreferredWidth: 240
m_PreferredHeight: 60
m_FlexibleWidth: -1
m_FlexibleWidth: 0
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &7494010136792472269
@@ -722,13 +661,13 @@ RectTransform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4435729753412350698}
- {fileID: 590678445898122332}
- {fileID: 9118077588552391645}
- {fileID: 5405233146667072049}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -843,9 +782,9 @@ RectTransform:
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5405233146667072049}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
@@ -908,31 +847,34 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 24
m_fontSizeBase: 24
m_fontSize: 18
m_fontSizeBase: 18
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
@@ -16,8 +16,14 @@ public class WaitingGameItem : MonoBehaviour {
gameIdField.text = string.Format("{0:X}", gameId);
playerCountField.text = playersString;
var textEntry = ClientTextProvider.Provider.GetTextEntry(leader.NameTextId);
var leaderName = textEntry != null ? textEntry.Text : "Hero";
// Use pre-resolved name from server if available, otherwise fall back to text lookup
string leaderName;
if (!string.IsNullOrEmpty(leader.Name)) {
leaderName = leader.Name;
} else {
var textEntry = ClientTextProvider.Provider.GetTextEntry(leader.NameTextId);
leaderName = textEntry != null ? textEntry.Text : "Hero";
}
leaderField.text = string.Format(
"{0} ({1})",
leaderName,
@@ -44,7 +44,13 @@ namespace eagle {
var wasEmpty = _entries.Count == 0;
_entries = value != null ? value.ToList() : new List<ChronicleEntry>();
if (_entries.Count == 0) return;
if (_entries.Count == 0) {
// Hide canvas if entries are cleared while visible (e.g., during state resync).
// Without this, user could click dismiss while entries are empty, causing
// crash.
if (gameObject.activeSelf) { gameObject.SetActive(false); }
return;
}
// Jump to the last entry when first populating, or if not currently viewing
if (wasEmpty || !gameObject.activeSelf) {
@@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Google.Protobuf;
using Net.Eagle0.Eagle.Api;
using UnityEngine;
namespace eagle {
public class ClientPregeneratedText : MonoBehaviour {
public static ClientPregeneratedText Provider = null;
public void Load(PregeneratedText incomingText) {
var directoryPath = Path.GetDirectoryName(_localPregeneratedTextPath);
Directory.CreateDirectory(directoryPath);
_pregeneratedTexts = incomingText.Entries.ToDictionary(x => x.TextId, x => x.Text);
Hash = incomingText.Hash;
File.WriteAllBytes(_localPregeneratedTextPath, incomingText.ToByteArray());
}
public void Awake() {
_localPregeneratedTextPath = Path.Combine(
Application.persistentDataPath,
"eagle0",
"Resources",
"pregenerated_text.txt");
if (File.Exists(_localPregeneratedTextPath)) {
var data = File.ReadAllBytes(_localPregeneratedTextPath);
var fromFile = PregeneratedText.Parser.ParseFrom(data);
_pregeneratedTexts = fromFile.Entries.ToDictionary(x => x.TextId, x => x.Text);
Hash = fromFile.Hash;
} else {
_pregeneratedTexts = new();
Hash = 0;
}
Provider = this;
}
public Int64 Hash { get; private set; }
public bool TryGetText(string textId, out string text) =>
_pregeneratedTexts.TryGetValue(textId, out text);
private string _localPregeneratedTextPath;
private Dictionary<string, string> _pregeneratedTexts = new();
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 347714dfb2a64d76ac4b4d87060e6159
timeCreated: 1717958518
@@ -104,10 +104,6 @@ namespace eagle {
public TextEntry GetTextEntry(string streamId) {
if (String.IsNullOrEmpty(streamId)) return new TextEntry("", false);
if (ClientPregeneratedText.Provider.TryGetText(streamId, out var text)) {
return new TextEntry(text, true);
}
lock (_lock) {
_streamingTexts.TryGetValue(streamId, out var entry);
return entry;
@@ -58,6 +58,8 @@ namespace eagle {
return;
}
// Pass layout mode to selector before setting up UI
Selector.CurrentLayoutMode = mainController.CurrentLayoutMode;
Selector.UpdateAvailableCommand(Model, AvailableCommand);
headerLabel.text = Selector.HeaderString;
} catch (Exception e) { errorHandler.Add(e); }
@@ -14,6 +14,19 @@ namespace eagle {
public abstract class CommandSelector : MonoBehaviour {
public Texture ButtonImage;
/// <summary>
/// Current layout mode. Set by CommandPanelController before SetUpUI is called.
/// Selectors can use this to adjust their UI for narrow screens.
/// </summary>
public LayoutMode CurrentLayoutMode { get; set; } = LayoutMode.Medium;
/// <summary>
/// True when in narrow layout mode (iPad-like 4:3 aspect ratios).
/// In narrow mode, selectors should hide hero/battalion pickers since
/// users can select via the Resident Heroes and Battalions panels.
/// </summary>
protected bool IsNarrowLayout => CurrentLayoutMode == LayoutMode.Narrow;
public abstract AvailableCommand.SealedValueOneofCase CommandType { get; }
public virtual bool IsAppropriateFor(AvailableCommand cmd) => cmd.SealedValueCase
== CommandType;
@@ -5,7 +5,7 @@ using Net.Eagle0.Eagle.Api;
using Net.Eagle0.Eagle.Api.Command.Util;
using Net.Eagle0.Eagle.Common;
using TMPro;
using UnityEditor;
using UnityEngine;
namespace eagle {
using BattalionId = Int32;
@@ -25,6 +25,10 @@ namespace eagle {
public EventBasedUnitSelector unitSelector;
public TMP_Dropdown rallyPointDropdown;
// Columns to hide in narrow layout mode (users select via side panels instead)
public GameObject heroesColumn;
public GameObject battalionsColumn;
public override AvailableCommand.SealedValueOneofCase CommandType =>
AvailableCommand.SealedValueOneofCase.DefendCommand;
public override string HeaderString => "Defend";
@@ -128,6 +132,10 @@ namespace eagle {
}
protected override void SetUpUI() {
// Hide hero/battalion columns in narrow mode - users select via side panels
if (heroesColumn != null) heroesColumn.SetActive(!IsNarrowLayout);
if (battalionsColumn != null) battalionsColumn.SetActive(!IsNarrowLayout);
if (DefendCommand.AvailableFleeProvinceIds.Count > 0) {
var availableRallyPointProvinceNames =
DefendCommand.AvailableFleeProvinceIds
@@ -7,6 +7,7 @@ using Net.Eagle0.Eagle.Api.Command.Util;
using Net.Eagle0.Eagle.Common;
using Net.Eagle0.Eagle.Views;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace eagle {
@@ -38,6 +39,10 @@ namespace eagle {
public EventBasedUnitSelector unitSelector;
public TMP_Dropdown fromDropdown;
public TMP_Dropdown toDropdown;
// Columns to hide in narrow layout mode (users select via side panels instead)
public GameObject heroesColumn;
public GameObject battalionsColumn;
public Slider goldSlider;
public Slider foodSlider;
public TMP_Text goldTakenLabel;
@@ -121,9 +126,12 @@ namespace eagle {
DestinationProvince is { RulingFactionId : not null } &&
DestinationProvince.RulingFactionId.Value != _playerId;
// Only warn about river crossing if destination is NOT owned by the same faction
// (friendly territory doesn't require assault)
private bool RequiresRiverCrossing => SelectedDestinationProvinceIndex is {}
idx && _sortedAvailableDestinations != null && idx < _sortedAvailableDestinations.Count &&
_sortedAvailableDestinations[idx].RequiresRiverCrossing;
_sortedAvailableDestinations[idx].RequiresRiverCrossing &&
DestinationRulingFactionId != _playerId;
public override bool WarnOnCommitButton =>
WouldAbandonProvince || NotEnoughFood || MarchingOnTrucePartner ||
@@ -294,6 +302,10 @@ namespace eagle {
}
protected override void SetUpUI() {
// Hide hero/battalion columns in narrow mode - users select via side panels
if (heroesColumn != null) heroesColumn.SetActive(!IsNarrowLayout);
if (battalionsColumn != null) battalionsColumn.SetActive(!IsNarrowLayout);
_sortedOneProvinceCommands =
MarchAvailableCommand.OneProvinceCommands
.OrderBy(cmd => _model.Provinces[cmd.OriginProvinceId].Name)
@@ -20,6 +20,13 @@ namespace eagle {
public accessor ConditionAccessor = hero => hero.Vigor;
public string ConditionName = "VIG";
/// <summary>
/// Optional dictionary of pre-resolved names keyed by NameTextId.
/// Set this before setting AvailableHeroes to use resolved names directly
/// instead of looking them up via ClientTextProvider.
/// </summary>
public Dictionary<string, string> PreResolvedNames { get; set; } = new();
private List<HeroView> _availableHeroes;
public List<HeroView> AvailableHeroes {
get => _availableHeroes;
@@ -32,17 +39,28 @@ namespace eagle {
listeners = new List<SingleEntryListener>();
for (int i = 0; i < _availableHeroes.Count; i++) {
HeroView hero = _availableHeroes[i];
var listener = new SingleEntryListener(
hero.NameTextId,
"",
var suffix =
showCondition
? $" ({GUIUtils.ConditionString(ConditionAccessor(hero), ConditionName)})"
: "",
i,
SetGeneratedText);
: "";
listeners.Add(listener);
ClientTextProvider.Provider.AddListener(listener);
// If we have a pre-resolved name, use it directly
if (!string.IsNullOrEmpty(hero.NameTextId) &&
PreResolvedNames.TryGetValue(hero.NameTextId, out var resolvedName) &&
!string.IsNullOrEmpty(resolvedName)) {
SetGeneratedText(i, resolvedName + suffix);
} else {
// Fall back to listener-based resolution
var listener = new SingleEntryListener(
hero.NameTextId,
"",
suffix,
i,
SetGeneratedText);
listeners.Add(listener);
ClientTextProvider.Provider.AddListener(listener);
}
}
SetHeadshot();
@@ -155,6 +155,8 @@ namespace eagle {
"<color=green>●</color> Generating...",
ServerGameStatus.Types.Status.ProcessingAction =>
"<color=green>●</color> Processing...",
ServerGameStatus.Types.Status.BattleInProgress =>
"<color=green>●</color> Battle in progress",
_ => "<color=green>●</color> Connected"
};
}
@@ -19,6 +19,14 @@ namespace eagle {
using FactionId = Int32;
using ProvinceId = Int32;
/// <summary>
/// Layout mode based on aspect ratio:
/// - Wide: Ultra-wide screens (ratio > 2.0), factions/armies column on right
/// - Medium: Standard widescreen (ratio >= 1.35), default layout
/// - Narrow: iPad-like 4:3 (ratio < 1.35), compact command panels
/// </summary>
public enum LayoutMode { Wide, Medium, Narrow }
public class EagleGameController : MonoBehaviour {
public SoundManager soundManager;
@@ -26,21 +34,26 @@ namespace eagle {
public TextMeshProUGUI connectionStatusLabel;
private bool _connectionStatusUIInitialized = false;
public GameObject alwaysOnLeftColumn;
public GameObject factionsAndMovingArmiesRow;
public GameObject wideScreenRightColumn;
public GameObject wideScreenBottomRow;
public ProvinceInfoPanelController provinceInfoPanelController;
public FreeHeroesTableController freeHeroesTableController;
public FactionsTableController factionsTableController;
public MovingArmiesTableController movingArmiesTableController;
// Containers for factions & moving armies panels (enable/disable these)
public GameObject factionsAndMovingArmiesNarrow; // Row beneath Free Heroes
public GameObject factionsAndMovingArmiesWide; // Column to right of Province Info
// Controller references for updating data (panels inside containers)
public FactionsTableController factionsTableControllerNarrow;
public MovingArmiesTableController movingArmiesTableControllerNarrow;
public FactionsTableController factionsTableControllerWide;
public MovingArmiesTableController movingArmiesTableControllerWide;
public HeroesAndBattalionsPanelController heroesAndBattalionsPanelController;
public MapController mapController;
public CommandButtonPanelController commandButtonPanelController;
public DominionPanelController hideableDominionPanelController;
public DominionPanelController wideDominionPanelController;
private DominionPanelController _dominionPanelController;
// Narrow: toggleable above Province Info; Wide: always-on below Free Heroes
public DominionPanelController dominionPanelControllerNarrow;
public DominionPanelController dominionPanelControllerWide;
public ChronicleCanvasController chronicleCanvasController;
public ProvinceId? SelectedProvince => mapController.SelectedProvinceId;
@@ -70,7 +83,22 @@ namespace eagle {
public CommandWarningPanelController commandWarningPanelController;
private double _aspectRatio;
private bool IsWideScreen => _aspectRatio > 2.0;
private const double WideScreenThreshold = 2.0;
private const double NarrowScreenThreshold = 1.35;
private bool IsWideScreen => _aspectRatio > WideScreenThreshold;
private bool IsNarrowScreen => _aspectRatio < NarrowScreenThreshold;
/// <summary>
/// Current layout mode based on aspect ratio. Accessible to CommandSelectors.
/// </summary>
public LayoutMode CurrentLayoutMode {
get {
if (IsWideScreen) return LayoutMode.Wide;
if (IsNarrowScreen) return LayoutMode.Narrow;
return LayoutMode.Medium;
}
}
private int _lastWidth;
private int _lastHeight;
@@ -81,7 +109,7 @@ namespace eagle {
private DynamicFactionTextUpdater factionTextUpdater = new DynamicFactionTextUpdater();
private IGameModel _newModel = null;
public Canvas shardokCanvas;
public GameObject shardokContainer;
private KeyValuePair<ProvinceId, OneProvinceAvailableCommands>? NextActiveProvinceKeyPair =>
Model == null || Model.AvailableCommandsByProvince.Count == 0
@@ -94,8 +122,7 @@ namespace eagle {
mapController.targetedProvincesChangedCallback = MapControllerChangedTarget;
mapController.hoveredProvinceChangedCallback = MapControllerChangedHover;
popupCanvas.SetActive(true);
_dominionPanelController = hideableDominionPanelController;
_dominionPanelController.gameObject.SetActive(false);
dominionPanelControllerNarrow.gameObject.SetActive(false);
showChronicleButton.gameObject.SetActive(false);
errorHandler.gameObject.SetActive(true);
@@ -125,31 +152,19 @@ namespace eagle {
_aspectRatio = (float)newWidth / (float)newHeight;
if (IsWideScreen) {
factionsTableController.gameObject.transform.SetParent(
wideScreenRightColumn.transform);
factionsTableController.GetComponent<LayoutElement>().flexibleWidth = 1;
movingArmiesTableController.gameObject.transform.SetParent(
wideScreenRightColumn.transform);
movingArmiesTableController.GetComponent<LayoutElement>().flexibleWidth = 1;
wideDominionPanelController.gameObject.SetActive(true);
hideableDominionPanelController.gameObject.SetActive(false);
_dominionPanelController = wideDominionPanelController;
_dominionPanelController.Model = Model;
factionsAndMovingArmiesRow.SetActive(false);
// Show wide layout containers, hide narrow layout containers
factionsAndMovingArmiesWide.SetActive(true);
factionsAndMovingArmiesNarrow.SetActive(false);
dominionPanelControllerWide.gameObject.SetActive(true);
dominionPanelControllerNarrow.gameObject.SetActive(false);
provinceInfoPanelController.WideScreen = true;
} else {
factionsTableController.gameObject.transform.SetParent(
factionsAndMovingArmiesRow.transform);
factionsTableController.GetComponent<LayoutElement>().flexibleWidth = 0;
movingArmiesTableController.gameObject.transform.SetParent(
factionsAndMovingArmiesRow.transform);
movingArmiesTableController.GetComponent<LayoutElement>().flexibleWidth = 0;
wideDominionPanelController.gameObject.SetActive(false);
_dominionPanelController = hideableDominionPanelController;
_dominionPanelController.Model = Model;
factionsAndMovingArmiesRow.SetActive(true);
// Show narrow layout containers, hide wide layout containers
factionsAndMovingArmiesWide.SetActive(false);
factionsAndMovingArmiesNarrow.SetActive(true);
dominionPanelControllerWide.gameObject.SetActive(false);
// Narrow dominion panel visibility controlled by user toggle, don't change here
provinceInfoPanelController.WideScreen = false;
}
@@ -176,16 +191,21 @@ namespace eagle {
if (_commandPanelController.Selector != null) {
_commandPanelController.Selector.TargetedProvinces = newTarget;
freeHeroesTableController.UpdateTables();
movingArmiesTableController.UpdateTables();
factionsTableController.UpdateTables();
movingArmiesTableControllerNarrow.UpdateTables();
movingArmiesTableControllerWide.UpdateTables();
factionsTableControllerNarrow.UpdateTables();
factionsTableControllerWide.UpdateTables();
heroesAndBattalionsPanelController.UpdateTables();
}
}
void MapControllerChangedHover(ProvinceId? newHover) {
_dominionPanelController.ProvinceHovered(newHover);
movingArmiesTableController.ProvinceHovered(newHover);
factionsTableController.ProvinceHovered(newHover);
dominionPanelControllerNarrow.ProvinceHovered(newHover);
dominionPanelControllerWide.ProvinceHovered(newHover);
movingArmiesTableControllerNarrow.ProvinceHovered(newHover);
movingArmiesTableControllerWide.ProvinceHovered(newHover);
factionsTableControllerNarrow.ProvinceHovered(newHover);
factionsTableControllerWide.ProvinceHovered(newHover);
}
void Update() {
@@ -224,7 +244,8 @@ namespace eagle {
public void ReloadFactionDisplaySetting() {
SetHeaderString();
mapController.ReloadFactionDisplaySetting();
factionsTableController.UpdateTables();
factionsTableControllerNarrow.UpdateTables();
factionsTableControllerWide.UpdateTables();
_commandPanelController.RefreshUI();
}
@@ -321,8 +342,10 @@ namespace eagle {
_commandPanelController.SetAvailableCommandAndSelector(cmd, selector);
mapController.ProvincesWithCommands = new List<ProvinceId>();
movingArmiesTableController.UpdateTables();
factionsTableController.UpdateTables();
movingArmiesTableControllerNarrow.UpdateTables();
movingArmiesTableControllerWide.UpdateTables();
factionsTableControllerNarrow.UpdateTables();
factionsTableControllerWide.UpdateTables();
heroesAndBattalionsPanelController.UpdateTables();
freeHeroesTableController.UpdateUnaffiliatedHeroSelections();
}
@@ -352,21 +375,26 @@ namespace eagle {
var currentProvince = Model.Provinces[pid.Value];
provinceInfoPanelController.Province = currentProvince;
freeHeroesTableController.CurrentProvince = currentProvince;
movingArmiesTableController.CurrentProvince = currentProvince;
factionsTableController.CurrentProvince = currentProvince;
movingArmiesTableControllerNarrow.CurrentProvince = currentProvince;
movingArmiesTableControllerWide.CurrentProvince = currentProvince;
factionsTableControllerNarrow.CurrentProvince = currentProvince;
factionsTableControllerWide.CurrentProvince = currentProvince;
heroesAndBattalionsPanelController.CurrentProvince = currentProvince;
} else {
SelectRecommendedCommand(null);
provinceInfoPanelController.Province = null;
freeHeroesTableController.CurrentProvince = null;
movingArmiesTableController.CurrentProvince = null;
factionsTableController.CurrentProvince = null;
movingArmiesTableControllerNarrow.CurrentProvince = null;
movingArmiesTableControllerWide.CurrentProvince = null;
factionsTableControllerNarrow.CurrentProvince = null;
factionsTableControllerWide.CurrentProvince = null;
heroesAndBattalionsPanelController.CurrentProvince = null;
}
UpdateButtons();
SetNextActiveProvinceButton();
_dominionPanelController.ForceUpdate();
dominionPanelControllerNarrow.ForceUpdate();
dominionPanelControllerWide.ForceUpdate();
SetMusic();
// Notify tutorial system of province selection
@@ -494,14 +522,20 @@ namespace eagle {
provinceInfoPanelController.Model = _newModel;
freeHeroesTableController.Model = _newModel;
movingArmiesTableController.Model = _newModel;
factionsTableController.Model = _newModel;
movingArmiesTableControllerNarrow.Model = _newModel;
movingArmiesTableControllerWide.Model = _newModel;
factionsTableControllerNarrow.Model = _newModel;
factionsTableControllerWide.Model = _newModel;
heroesAndBattalionsPanelController.Model = _newModel;
_commandPanelController.Model = _newModel;
_dominionPanelController.Model = _newModel;
dominionPanelControllerNarrow.Model = _newModel;
dominionPanelControllerWide.Model = _newModel;
mapController.Model = _newModel;
if (Model == null) { return; }
if (Model == null) {
goToBattleButton.gameObject.SetActive(false);
return;
}
// Start onboarding after first model update (UI panels are now populated)
if (oldModel == null && TutorialManager.Instance != null &&
@@ -616,8 +650,10 @@ namespace eagle {
}
freeHeroesTableController.UpdateTables();
movingArmiesTableController.UpdateTables();
factionsTableController.UpdateTables();
movingArmiesTableControllerNarrow.UpdateTables();
movingArmiesTableControllerWide.UpdateTables();
factionsTableControllerNarrow.UpdateTables();
factionsTableControllerWide.UpdateTables();
heroesAndBattalionsPanelController.UpdateTables();
Canvas.ForceUpdateCanvases();
@@ -664,8 +700,10 @@ namespace eagle {
mapController.TargetedProvinces = new List<ProvinceId>();
commandButtonPanelController.ClearButtonContents();
freeHeroesTableController.UpdateTables();
movingArmiesTableController.UpdateTables();
factionsTableController.UpdateTables();
movingArmiesTableControllerNarrow.UpdateTables();
movingArmiesTableControllerWide.UpdateTables();
factionsTableControllerNarrow.UpdateTables();
factionsTableControllerWide.UpdateTables();
heroesAndBattalionsPanelController.UpdateTables();
}
@@ -682,6 +720,12 @@ namespace eagle {
// Hide the button now, so if we get an exception below we can at least recover
goToBattleButton.gameObject.SetActive(false);
if (Model == null) { throw new InvalidOperationException("GoToBattle: Model is null"); }
if (shardokContainer == null) {
throw new InvalidOperationException("GoToBattle: shardokContainer is null");
}
ShardokGameModel selectedModel;
var runningBattles = Model.RunningShardokGameModels.Values;
@@ -691,14 +735,19 @@ namespace eagle {
} else if (runningBattles.Any()) {
selectedModel = runningBattles.First();
} else {
throw new ApplicationException(
"Go to Battle button clicked, but there are no battles");
throw new InvalidOperationException(
"GoToBattle: No battles in RunningShardokGameModels");
}
var shardokModel = Model.ShardokGameModels[selectedModel.ShardokGameId];
shardokCanvas.gameObject.SetActive(true);
var shardokController = shardokCanvas.GetComponent<ShardokGameController>();
shardokContainer.SetActive(true);
var shardokController = shardokContainer.GetComponent<ShardokGameController>();
if (shardokController == null) {
throw new InvalidOperationException(
"GoToBattle: shardokContainer missing ShardokGameController component");
}
shardokController.SetUpGame(shardokModel);
shardokController.SetConnection(errorHandler.PersistentClientConnection);
gameObject.SetActive(false);
@@ -138,6 +138,11 @@ namespace eagle {
private DateTime? _commandSubmittedTime;
private const double ProcessingDisplayDelayMs = 500.0;
// Turn mismatch detection: server says YourTurn but client has no commands
// Track when mismatch was first detected to avoid false positives during normal updates
private DateTime? _turnMismatchDetectedTime;
private const double TurnMismatchGracePeriodSeconds = 3.0;
// Track which Shardok games need full state resync after connection drop
// Thread-safe: accessed from both connection thread and Unity main thread
private readonly ConcurrentDictionary<string, bool> _shardokNeedsResync =
@@ -292,18 +297,18 @@ namespace eagle {
public void ReceiveGameUpdate(GameUpdate updateItem) {
if (updateItem.StartingState != null) { HandleStartingState(updateItem.StartingState); }
// Store server-reported game status for UI (available on any update type)
if (updateItem.ServerGameStatus != null) {
ServerStatus = updateItem.ServerGameStatus;
_connectionLogger.LogLine(
$"[UPDATE] ServerGameStatus updated: {ServerStatus.Status}");
}
switch (updateItem.GameUpdateDetailsCase) {
case GameUpdate.GameUpdateDetailsOneofCase.ActionResultResponse:
// Clear processing state - we received a response from the server
_commandSubmittedTime = null;
// Store server-reported game status for UI
if (updateItem.ActionResultResponse.ServerGameStatus != null) {
ServerStatus = updateItem.ActionResultResponse.ServerGameStatus;
_connectionLogger.LogLine(
$"[UPDATE] ServerGameStatus updated: {ServerStatus.Status}");
}
// Note: _lastUnfilteredResultCount is updated on the gRPC thread in
// UpdateResultCounts() before enqueueing. We don't update it here to avoid
// race conditions where a backlogged MainQueue update overwrites a newer count.
@@ -332,6 +337,9 @@ namespace eagle {
"[UPDATE] SKIPPED - no results, has commands, token matches");
}
// Check for turn state mismatch after processing update
CheckForTurnMismatch();
break;
case GameUpdate.GameUpdateDetailsOneofCase.ShardokActionResultResponse:
@@ -530,11 +538,18 @@ namespace eagle {
_currentModel.LastPostedToken = -1;
_currentModel.AvailableCommandsByProvince.Clear();
_commandSubmittedTime = null;
_turnMismatchDetectedTime = null;
_currentModel.GsView = startingState;
_currentModel.BattalionTypes =
startingState.BattalionTypes.ToDictionary(bt => bt.TypeId, bt => bt);
_currentModel.ChronicleEntries = startingState.ChronicleEntries.ToList();
// Don't overwrite existing chronicle entries with an empty list.
// Chronicle entries are historical records that accumulate over time.
// A stale StartingState (e.g., from early in the game before chronicles existed)
// arriving after a current state could otherwise wipe out existing entries.
if (startingState.ChronicleEntries.Any() || _currentModel.ChronicleEntries == null) {
_currentModel.ChronicleEntries = startingState.ChronicleEntries.ToList();
}
// For any outstanding battles in the new state, create models and mark for resync.
bool needsResubscribe = false;
@@ -925,6 +940,69 @@ namespace eagle {
(DateTime.UtcNow - _commandSubmittedTime.Value).TotalMilliseconds >
ProcessingDisplayDelayMs;
#endregion
#region Turn Mismatch Detection
/// <summary>
/// Checks for turn state mismatch: server reports YourTurn but client has no commands.
/// If mismatch persists beyond the grace period, logs an error and triggers reconnection.
/// </summary>
private void CheckForTurnMismatch() {
// Only check if we have server status
if (ServerStatus == null) {
_turnMismatchDetectedTime = null;
return;
}
bool serverSaysYourTurn = ServerStatus.Status == ServerGameStatus.Types.Status.YourTurn;
bool clientHasNoCommands = _currentModel.AvailableCommandsByProvince.Count == 0 &&
!_currentModel.CommandToken.HasValue;
bool isProcessingCommand = _commandSubmittedTime.HasValue;
// Mismatch: server says it's our turn but we have no commands to act on
// Exclude cases where we just submitted a command (waiting for response)
bool hasMismatch = serverSaysYourTurn && clientHasNoCommands && !isProcessingCommand;
if (!hasMismatch) {
// No mismatch - reset detection state
_turnMismatchDetectedTime = null;
return;
}
// Mismatch detected - track when it started
if (!_turnMismatchDetectedTime.HasValue) {
_turnMismatchDetectedTime = DateTime.UtcNow;
_connectionLogger.LogLine(
"[TURN_MISMATCH] Detected: server says YourTurn but client has no commands. " +
"Waiting for grace period...");
return;
}
// Check if grace period has elapsed
var elapsedSeconds = (DateTime.UtcNow - _turnMismatchDetectedTime.Value).TotalSeconds;
if (elapsedSeconds < TurnMismatchGracePeriodSeconds) { return; }
// Grace period exceeded - this is a real mismatch, trigger recovery
_connectionLogger.LogLine(
$"[TURN_MISMATCH] ERROR: Turn mismatch persisted for {elapsedSeconds:F1}s. " +
$"ServerStatus={ServerStatus.Status}, " +
$"AvailableCommands={_currentModel.AvailableCommandsByProvince.Count}, " +
$"CommandToken={_currentModel.CommandTokenString}, " +
$"LastPostedToken={_currentModel.LastPostedToken}. " +
"Triggering reconnection to recover state.");
// Throw exception for ErrorHandler to capture (provides stack trace for debugging)
var errorMessage =
$"Turn mismatch detected: server reports YourTurn but client has no commands " +
$"after {elapsedSeconds:F1}s. Reconnecting to recover state.";
ErrorHandler?.Add(new InvalidOperationException(errorMessage));
// Reset detection state and trigger reconnection
_turnMismatchDetectedTime = null;
PersistentConnection.ForceReconnect();
}
#endregion
}
}
@@ -56,6 +56,7 @@ namespace eagle.Notifications {
{ AllianceRejectedDetails, AllianceRejectedDetailsNotificationGenerator.Generator },
{ BreakAllianceAcceptedDetails, BreakAllianceAcceptedNotificationGenerator.Generator },
{ VassalExiledDetails, VassalExiledDetailsNotificationGenerator.Generator },
{ VassalRisesDetails, VassalRisesDetailsNotificationGenerator.Generator },
{ WithdrewForTruce, WithdrewForTruceDetailsNotificationGenerator.Generator },
{ ProfessionGainedDetails, ProfessionGainedDetailsNotificationGenerator.Generator },
{ NewFactionHeadDetails, NewFactionHeadDetailsNotificationGenerator.Generator }
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using Net.Eagle0.Eagle.Common;
using Net.Eagle0.Eagle.Views;
namespace eagle.Notifications.ARNNotifications {
using ProvinceId = Int32;
using HeroId = Int32;
public static class VassalRisesDetailsNotificationGenerator {
public static readonly ARNNotificationGenerator Generator = GenerateNotifications;
private static readonly Random Random = new();
private static readonly List<string> NotificationTitles = new() {
"A Vassal Rises",
"From Servant to Leader",
"Unexpected Succession",
"Rise of the Unsung",
"Fate's Choice",
"The Loyal Promoted",
"New Dawn"
};
private static string GetNotificationTitle() {
return NotificationTitles[Random.Next(NotificationTitles.Count)];
}
private static ProvinceId? FindProvinceForHero(HeroId heroId, IGameModel model) {
foreach (var province in model.Provinces.Values) {
if (province.FullInfo?.RulingFactionHeroIds.Contains(heroId) == true) {
return province.Id;
}
}
return null;
}
private static IEnumerable<Notification> GenerateNotifications(
Net.Eagle0.Eagle.Common.Notification notification,
IGameModel currentModel) {
var details = notification.Details.VassalRisesDetails;
var risingHero = currentModel.Heroes[details.RisingHeroId];
string textTemplate;
List<ProvinceId> affectedProvinces;
if (details.FactionId == currentModel.PlayerId) {
// Player's own faction - a vassal has risen to lead
if (details.IsOnlyLeader) {
textTemplate =
"With all your faction's leaders fallen or imprisoned, your loyal vassal {RisingHero} has risen to lead your forces.\n\n";
} else {
textTemplate =
"Your vassal {RisingHero} has risen to join the leadership of your faction.\n\n";
}
var heroProvinceId = FindProvinceForHero(details.RisingHeroId, currentModel);
affectedProvinces = heroProvinceId.HasValue
? new List<ProvinceId> { heroProvinceId.Value }
: currentModel.ProvincesForFaction(details.FactionId);
} else {
// Another faction
var factionName = currentModel.FactionName(details.FactionId);
if (details.IsOnlyLeader) {
textTemplate =
$"With all of {factionName}'s leaders fallen or imprisoned, {{RisingHero}} has risen from obscurity to lead their forces.\n\n";
} else {
textTemplate =
$"{{RisingHero}} has risen to join the leadership of {factionName}.\n\n";
}
affectedProvinces = currentModel.ProvincesForFaction(details.FactionId);
}
var heroPlaceholders = new Dictionary<string, (string nameTextId, string fallback)> {
{ "RisingHero", (risingHero.NameTextId, "A vassal") }
};
var displayedHeroes = new List<HeroView> { risingHero };
yield return DynamicTextNotification.StreamingDynamicNotification(
title: GetNotificationTitle(),
textTemplate: textTemplate,
heroPlaceholders: heroPlaceholders,
llmId: notification.LlmId,
provinceIds: affectedProvinces,
displayedHeroes: displayedHeroes);
}
}
}
@@ -26,7 +26,6 @@ namespace eagle {
public interface ILobbySubscriber {
void ReceiveLobbyUpdate(LobbyResponse lobbyResponse);
void ReceivePregeneratedTextUpdate(PregeneratedTextResponse response);
void HandleJoinGameResponse(JoinGameResponse response);
void HandleCreateGameResponse(CreateGameResponse response);
void HandleCustomBattleResponse(CustomBattleResponse response);
@@ -174,6 +173,12 @@ namespace eagle {
private List<PostCommandRequest> _pendingCommands = new();
/// <summary>
/// Callback invoked when the underlying gRPC channel is disposed and cannot be recovered.
/// The caller should create a new EagleConnection and PersistentClientConnection.
/// </summary>
public Action OnChannelDead { get; set; }
// Pending subscription acknowledgments - used to wait for server confirmation
private readonly Dictionary<EagleGameId, TaskCompletionSource<SubscriptionAck>>
_pendingSubscriptionAcks = new();
@@ -447,6 +452,13 @@ namespace eagle {
StartHeartbeatTimer();
await TryPendingCommands();
} catch (ObjectDisposedException e) {
// Channel is permanently disposed - notify caller to recreate connection
LogConnectionEvent("connect_failed", "ChannelDisposed");
_remoteEagleClientLogger.LogLine(
$"Channel disposed, triggering recreation: {e.Message}");
_currentState = ConnectionState.Disconnected;
OnChannelDead?.Invoke();
} catch (Exception e) {
_circuitBreaker.RecordFailure();
LogConnectionEvent("connect_failed", e.GetType().Name);
@@ -1062,19 +1074,6 @@ namespace eagle {
break;
}
case UpdateStreamResponse.ResponseDetailsOneofCase
.PregeneratedTextResponse: {
var lobbySubscriber = _lobbySubscriber;
if (lobbySubscriber != null) {
MainQueue.Q.Enqueue(() => {
lobbySubscriber.ReceivePregeneratedTextUpdate(
current.PregeneratedTextResponse);
});
}
break;
}
case UpdateStreamResponse.ResponseDetailsOneofCase.JoinGameResponse: {
var lobbySubscriber = _lobbySubscriber;
if (lobbySubscriber != null) {
@@ -382,7 +382,7 @@ namespace eagle {
public void DominionViewToggleClicked(bool value) {
dominionPanelController.gameObject.SetActive(value);
this.gameObject.SetActive(!value);
panel.SetActive(!value); // Hide panel content, not the entire controller
}
void Start() { panel.SetActive(false); }
@@ -3405,7 +3405,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Texture: {fileID: 2800000, guid: e4bb37b7e56df4d1ab5e894387c9eb26, type: 3}
m_Texture: {fileID: 2800000, guid: 2189208f745b6864c918942b534d0648, type: 3}
m_UVRect:
serializedVersion: 2
x: 0
@@ -1077,7 +1077,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Texture: {fileID: 2800000, guid: e4bb37b7e56df4d1ab5e894387c9eb26, type: 3}
m_Texture: {fileID: 2800000, guid: 2189208f745b6864c918942b534d0648, type: 3}
m_UVRect:
serializedVersion: 2
x: 0
@@ -32,7 +32,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5052182454111779026}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -106,20 +105,23 @@ MonoBehaviour:
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
@@ -194,7 +196,6 @@ RectTransform:
- {fileID: 9069939320221121289}
- {fileID: 5052182454111779026}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -342,7 +343,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1882366691182306769}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -416,20 +416,23 @@ MonoBehaviour:
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
@@ -565,7 +568,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1882366691182306769}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -639,20 +641,23 @@ MonoBehaviour:
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
@@ -787,7 +792,6 @@ RectTransform:
- {fileID: 1882366691182306769}
- {fileID: 5592936718364466473}
m_Father: {fileID: 2503366170477545538}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -811,7 +815,7 @@ MonoBehaviour:
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleWidth: 0
m_FlexibleHeight: 1
m_LayoutPriority: 1
--- !u!114 &8708491926376413125
@@ -873,7 +877,6 @@ RectTransform:
- {fileID: 2711202350830977002}
- {fileID: 3975726904536594838}
m_Father: {fileID: 2503366170477545538}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -959,7 +962,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 2422493811866013984}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -1086,7 +1088,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 2503366170477545538}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -1156,24 +1157,27 @@ MonoBehaviour:
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_HorizontalAlignment: 1
m_VerticalAlignment: 256
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
@@ -1276,7 +1280,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1882366691182306769}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -1404,7 +1407,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1882366691182306769}
m_RootOrder: 4
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -1478,20 +1480,23 @@ MonoBehaviour:
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
@@ -1630,7 +1635,6 @@ RectTransform:
- {fileID: 3419049079105268355}
- {fileID: 2304069784222831115}
m_Father: {fileID: 5052182454111779026}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -1716,7 +1720,6 @@ RectTransform:
- {fileID: 8657408901204124934}
- {fileID: 8841003191731364264}
m_Father: {fileID: 2422493811866013984}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -1802,7 +1805,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1882366691182306769}
m_RootOrder: 5
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -1929,7 +1931,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3975726904536594838}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -2056,7 +2057,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1882366691182306769}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -2183,7 +2183,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3975726904536594838}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -3,7 +3,7 @@ guid: 11ec0a81b8b14448d9522513ff1bcb55
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
@@ -20,9 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@@ -31,13 +34,13 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
@@ -54,11 +57,17 @@ TextureImporter:
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- serializedVersion: 3
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 4096
resizeAlgorithm: 0
@@ -68,9 +77,10 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 4096
resizeAlgorithm: 0
@@ -80,10 +90,11 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 4096
resizeAlgorithm: 0
textureFormat: -1
@@ -92,9 +103,10 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 4096
resizeAlgorithm: 0
@@ -104,12 +116,14 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
@@ -119,9 +133,11 @@ TextureImporter:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
@@ -34,8 +34,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.4, y: 0.4}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 5, y: -55}
m_SizeDelta: {x: -5, y: -60}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!222 &2303342583019532587
CanvasRenderer:

Some files were not shown because too many files have changed in this diff Show More