Compare commits

...
Author SHA1 Message Date
admin e9d59a4ca2 Distinguish unlisted games from missing saves 2026-07-15 13:29:06 -07:00
admin 811761ca19 Recover orphaned Postgres games in admin 2026-07-15 13:20:34 -07:00
adminandGitHub c49c4d26e1 Include client texts in save downloads (#8617)
* Include client texts in save downloads

* Fix admin service client text dependencies
2026-07-15 13:16:58 -07:00
adminandGitHub 68906df5b9 Clarify diplomacy with departing heroes (#8618) 2026-07-15 13:15:52 -07:00
adminandGitHub dc54584fa3 Limit support deadline tutorial to first year (#8616)
* Limit support deadline tutorial to first year

* Use the actual tutorial start year
2026-07-15 13:11:37 -07:00
adminandGitHub d665eb53f3 Remove tutorial phase from game state view (#8615) 2026-07-15 13:06:01 -07:00
adminandGitHub 59143a2e04 Add tutorial dialogue for battle conditions (#8613)
* Add tutorial dialogue for battle conditions

* Mention castle morale recovery in tutorial

* Mention Holy Wave battle recovery
2026-07-15 13:01:58 -07:00
adminandGitHub f9825861e3 Order generated hero names before backstories (#8614) 2026-07-15 13:01:09 -07:00
adminandGitHub 9b5dcc40a0 Keep editor OAuth flow in polling mode (#8612) 2026-07-15 13:00:32 -07:00
adminandGitHub 44514fb2f2 Activate unused-hero warning in tutorial battle (#8609) 2026-07-15 12:08:40 -07:00
adminandGitHub 3014602331 Retry transient OAuth polling failures (#8611) 2026-07-15 12:08:10 -07:00
adminandGitHub bb182efc3e Add dormant tutorial end-turn warning support (#8608) 2026-07-15 11:57:29 -07:00
adminandGitHub 3a04d865b6 Add OAuth login to text client (#8610) 2026-07-15 11:56:42 -07:00
adminandGitHub f2f81d1cda Add tutorial unused-hero warning protocol (#8607) 2026-07-15 11:54:11 -07:00
adminandGitHub 72dc8ba9fb Fix command title vertical centering (#8605) 2026-07-15 11:38:51 -07:00
adminandGitHub 7adc38fe12 Report and isolate game loading failures (#8604) 2026-07-15 11:35:19 -07:00
adminandGitHub 67dbf928d6 Isolate running game serialization failures (#8603) 2026-07-15 11:26:58 -07:00
adminandGitHub efad456bef Add tutorial dialogue for duel outcomes (#8601)
* Add tutorial dialogue for duel outcomes

* Distinguish lost and inconclusive duels

* Simplify duel outcome consequences
2026-07-15 11:21:18 -07:00
adminandGitHub 9ebc517c9f Reject unauthenticated game requests (#8602)
* Preserve authentication context for game streams

* Reject unauthenticated game requests
2026-07-15 11:19:11 -07:00
adminandGitHub 49020af5e9 Serialize unit selector touch targets (#8600) 2026-07-15 11:04:15 -07:00
adminandGitHub e5e5dd9fdc Serialize March touch targets (#8598)
* Serialize March touch targets

* Preserve March slider handle aspect
2026-07-15 10:56:25 -07:00
adminandGitHub 0dd780fc63 Add tutorial attack readiness guidance (#8599) 2026-07-15 10:53:39 -07:00
adminandGitHub ae4c441427 Clarify province abandonment before tutorial march (#8597) 2026-07-15 10:38:06 -07:00
adminandGitHub f01a2ee378 Show game over state in text client (#8595) 2026-07-15 10:34:49 -07:00
adminandGitHub e4901b6414 Clarify Retreat and Flee outcomes (#8596) 2026-07-15 10:31:51 -07:00
adminandGitHub f0b2fd5f5e Add attack preparation tutorial sequence (#8591)
* Add attack preparation tutorial sequence

* Emphasize hiring troops in attack tutorial

* Recommend Champions for troop training
2026-07-15 10:27:46 -07:00
adminandGitHub a28a0ef8d5 Serialize Battle Aftermath touch targets (#8594) 2026-07-15 10:26:22 -07:00
adminandGitHub 88ff742bb1 Serialize Attack Decision touch targets (#8593) 2026-07-15 10:21:12 -07:00
adminandGitHub d549c0642b Prevent duplicate gRPC stream closure (#8592) 2026-07-15 10:19:39 -07:00
adminandGitHub 00c677d12a Serialize Improve selector touch targets (#8590) 2026-07-15 10:15:06 -07:00
adminandGitHub 6c47019e8e Explain hero-only March units (#8588) 2026-07-15 09:33:11 -07:00
adminandGitHub f7932c6681 Serialize organize troops touch targets (#8589) 2026-07-15 09:32:44 -07:00
adminandGitHub 13e734d9ae Serialize popup Next button touch sizes (#8587) 2026-07-15 08:28:03 -07:00
adminandGitHub a4f934105f Fix text consistency across game rewinds (#8585) 2026-07-15 07:59:14 -07:00
adminandGitHub 1300ec9ab9 Serialize Eagle play-area touch targets (#8586) 2026-07-15 07:58:36 -07:00
adminandGitHub bfba377c57 Serialize shared UI touch target sizes (#8584) 2026-07-15 07:40:09 -07:00
adminandGitHub 56564c1bee Prevent disposed connections from reconnecting (#8583)
* Prevent disposed connections from reconnecting

* Disambiguate lifecycle test timer
2026-07-15 07:32:29 -07:00
adminandGitHub 1728d719a4 Preserve tutorial games across Eagle deploys (#8578)
* Restore tutorial battle config after deploy

* Invalidate warmed game index after deploy
2026-07-15 07:31:55 -07:00
adminandGitHub 3314ef191f Show valid trade command payloads (#8580) 2026-07-15 07:17:05 -07:00
adminandGitHub 1fbd2e8438 Balance command panel header layout (#8581) 2026-07-15 07:16:40 -07:00
adminandGitHub 7f50b61419 Rewire status UI after connection replacement (#8579) 2026-07-15 07:04:55 -07:00
adminandGitHub c7dea02861 Defer strategic tutorial dialogue during battles (#8572) 2026-07-15 06:54:50 -07:00
adminandGitHub afd8028665 Report malformed command protos as command errors (#8577) 2026-07-15 06:53:41 -07:00
adminandGitHub 2263977668 Preserve pending tutorial dialogue on reconnect (#8574)
* Preserve pending tutorial dialogue on reconnect

* Fix hero departure regression test calls

* Reset tutorial state when switching games
2026-07-15 06:52:59 -07:00
adminandGitHub 4cfec68ef8 Fix stale tutorial battle reinforcements (#8576)
* Fix stale tutorial battle reinforcements

* Limit fix to future tutorial battles
2026-07-15 06:40:23 -07:00
adminandGitHub 432276f550 Show attack decision payloads in text client (#8575)
* Show attack decision command payloads

* Expand complex command templates
2026-07-15 05:31:17 -07:00
adminandGitHub ef179e67ef Improve organize troops text client template (#8573) 2026-07-14 22:28:59 -07:00
adminandGitHub 7ee9fbb8da Recommend tutorial battle starting positions (#8571) 2026-07-14 21:48:25 -07:00
adminandGitHub 3d46bee7dd Prevent game loading lock inversion (#8570) 2026-07-14 21:39:14 -07:00
adminandGitHub c6dc5477f5 Remove client-driven tutorial content (#8569) 2026-07-14 21:38:34 -07:00
adminandGitHub ab7499ca7a Migrate remaining strategic tutorial dialogue to server (#8564)
* Migrate remaining strategic tutorial dialogue

* Restore province replacement in tutorial dialogue
2026-07-14 19:08:44 -07:00
adminandGitHub 7d95c81a00 Resolve province tutorial replacements in text client (#8566) 2026-07-14 18:38:39 -07:00
adminandGitHub e523ebf96e Resolve province tutorial replacements in Unity (#8568) 2026-07-14 18:38:13 -07:00
adminandGitHub b740e1ad59 Refresh same-token strategic updates (#8567) 2026-07-14 17:01:44 -07:00
adminandGitHub 3a612426f6 Add province tutorial replacement schema (#8565) 2026-07-14 16:46:00 -07:00
adminandGitHub 452d0cac5a Show under attack alert in command panel (#8563) 2026-07-14 15:08:04 -07:00
adminandGitHub bdb7b324c4 Use layout groups for command panel (#8562) 2026-07-14 14:53:38 -07:00
adminandGitHub 145f7e9f9c Prepare game schema before client text (#8561) 2026-07-14 14:22:20 -07:00
adminandGitHub 09e5f4801d Explain Shardok command mechanics (#8560) 2026-07-14 14:20:55 -07:00
adminandGitHub 2aa5fa181a Use recommended Shardok starting positions (#8557)
* Use recommended Shardok starting positions

* Snapshot reserves before applying recommendations
2026-07-14 14:20:16 -07:00
adminandGitHub f438c4bbe2 Expose recommended Shardok placements (#8559) 2026-07-14 14:19:44 -07:00
adminandGitHub 66ecc25075 Add Shardok map queries to text client (#8558) 2026-07-14 14:15:20 -07:00
adminandGitHub 562d32d296 Recommend Shardok starting positions (#8556)
* Recommend Shardok starting positions

* Require explicit AI time limits
2026-07-14 14:09:03 -07:00
adminandGitHub bdf9b09b26 Persist creation history in one batch (#8551) 2026-07-14 13:37:29 -07:00
adminandGitHub d2559ff67c Show tactical conditions in text client (#8555) 2026-07-14 13:36:51 -07:00
adminandGitHub 88433fbc18 Batch decision logging off the game thread (#8548) 2026-07-14 13:36:27 -07:00
adminandGitHub 9426488913 Use server-driven tactical tutorial dialogue (#8554) 2026-07-14 13:35:35 -07:00
adminandGitHub 81395a9467 Document Holy Wave mechanics (#8553) 2026-07-14 13:15:10 -07:00
adminandGitHub fb4e0c1bac Validate tactical tutorial dialogues before delivery (#8552) 2026-07-14 11:12:44 -07:00
adminandGitHub 94b273ff60 Validate tutorial dialogue references before delivery (#8544)
* Validate tutorial dialogue references before delivery

* Allow supported tactical tutorial focus
2026-07-14 10:54:12 -07:00
adminandGitHub b179e250dd Render tactical tutorial dialogue in text client (#8550) 2026-07-14 10:51:54 -07:00
adminandGitHub b3e35b60c0 Dispose replaced game model updater (#8545) 2026-07-14 10:47:05 -07:00
adminandGitHub 3a0fa9e59c Instrument AI command phases (#8547) 2026-07-14 10:42:13 -07:00
adminandGitHub cdf7c5649b Serve tactical tutorial dialogue (#8549) 2026-07-14 10:40:27 -07:00
adminandGitHub 743737555e Test tutorial server dialogue queue behavior (#8543) 2026-07-14 10:38:28 -07:00
adminandGitHub 94f2366af3 Reuse AI command availability snapshot (#8546) 2026-07-14 10:36:25 -07:00
adminandGitHub 5d4399cad6 Prefer server copies of tutorial dialogue (#8518)
* Prefer server copies of tutorial dialogue

* Preserve tutorial dialogue model during battle transition

* Retry tutorial dialogue when dependencies arrive

* Pass tutorial model directly to dialogue manager

* Ignore stale game model callbacks

* Ignore incomplete tutorial game identity

* Initialize provisional model game ID

* Persist new game histories with their game ID

* Revert "Persist new game histories with their game ID"

This reverts commit e23610a7c3152b4f4cb5623f1c31b60d1ad23756.

* Remove redundant dialogue text listeners
2026-07-14 09:46:06 -07:00
adminandGitHub 011562a2d6 Avoid Postgres waits during Shardok reconnects (#8542) 2026-07-14 09:44:04 -07:00
adminandGitHub 781c7a8c3d Dispatch initial LLM requests asynchronously (#8541) 2026-07-14 09:26:33 -07:00
adminandGitHub bce1ddaf81 Persist new game histories with their game ID (#8540) 2026-07-14 08:39:30 -07:00
adminandGitHub d734c591a8 Clarify mixed-border AI decision reasons (#8539) 2026-07-14 08:34:45 -07:00
adminandGitHub c5aca2666c Tune AI Rest priority for hero depth (#8537)
* Tune AI rest priority for hero depth

* Raise shallow AI rest threshold

* Consider feast for RestProvince quests
2026-07-14 08:08:42 -07:00
adminandGitHub 6e261ef7b5 Omit unrestricted client text visibility rows (#8538) 2026-07-14 08:02:51 -07:00
adminandGitHub 71240cd9ed Keep one PostgreSQL connection warm (#8536) 2026-07-14 07:05:48 -07:00
adminandGitHub 15d7d30ccf Defer schema pool refill after game creation (#8533) 2026-07-14 06:37:21 -07:00
adminandGitHub f52d54b7c2 Sync Xcode before TestFlight Unity builds (#8534) 2026-07-14 06:35:58 -07:00
6314d3e4e3 Fix water crossing flee scoring (#8527)
* Fix water crossing flee scoring

* Strengthen water crossing creator penalty

* Score crossing creators on one saturating distance scale

Replace the flat unavailable-creator penalty with a single loop over
every crossing-capable unit. Unavailable (fled/captured/destroyed) and
unplaced creators sit at the max distance sentinel, and live distances
(including IMPOSSIBLE) are clamped to it, so losing a crossing creator
can never score better than any placed position. This also removes the
formula discontinuity between the creator-alive and creator-lost
branches that the flee transition crossed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Name water crossing distance sentinel

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 21:37:41 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3f54abc3c0 Update actions/setup-node action to v7 (#8532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-13 21:12:54 -07:00
adminandGitHub 0e126faee2 Move navigation touch sizing into prefabs (#8529)
* Move navigation touch sizing into prefabs

* Increase Chronicle dismiss touch height
2026-07-13 21:10:10 -07:00
adminandGitHub 763ab63860 Preserve chronicle history on rejoin (#8531) 2026-07-13 18:18:08 -07:00
adminandGitHub 38a15fdf9f Enable PostgreSQL batched insert rewriting (#8530) 2026-07-13 15:11:34 -07:00
adminandGitHub b0446d99e4 Move command panel touch sizing into prefabs (#8528) 2026-07-13 14:32:26 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2f6a58255b Update dependency bazel to v9.2.0 (#8526)
* Update dependency bazel to v9.2.0

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 14:21:52 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
a4257efc0e Update aws-sdk-go-v2 monorepo (#8525)
* Update aws-sdk-go-v2 monorepo

* Update generated Go artifacts

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 14:21:38 -07:00
adminandGitHub b155494b4d Move Manage Prisoners touch sizing into prefab (#8459) 2026-07-13 14:19:51 -07:00
adminandGitHub 04a51c9fb6 Cap leader relocation marches (#8524) 2026-07-13 14:13:51 -07:00
adminandGitHub ee5cf07db3 Reserve incoming leader destinations (#8523) 2026-07-13 14:13:15 -07:00
adminandGitHub 5c4470b090 Allow left info panels to shrink for alerts (#8467)
* Allow left info panels to shrink for alerts

* Resize left panels only during attacks

* Preserve panel height under attack
2026-07-13 14:10:32 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>admin
86171cc1ae Lock file maintenance (#8514)
* Lock file maintenance

* Refresh Bazel lockfile for Cargo maintenance

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dan Crosby <dan@danielcrosby.net>
2026-07-13 11:41:44 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>admin
d5da95c5e3 Update dependency com.zaxxer:HikariCP to v7 (#8513)
* Update dependency com.zaxxer:HikariCP to v7

* Regenerate Maven lockfile after rebase

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dan Crosby <dan@danielcrosby.net>
2026-07-13 11:35:02 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
23c7385d8f Update actions/checkout action to v7 (#8512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-13 09:30:01 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>admin
cc5b6dbc75 Update dependency rules_rust to v0.71.3 (#8508)
* Update dependency rules_rust to v0.71.3

* Update generated dependency lockfiles

* Fix protobuf Rust crates for rules_rust 0.71

* Refresh rules_rust lockfile after rebase

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Dan Crosby <dan@danielcrosby.net>
2026-07-13 09:29:43 -07:00
adminandGitHub 025bcbbff6 Instrument create-game phases in JFR (#8521) 2026-07-13 08:56:33 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
fa6133f3ee Update module google.golang.org/grpc to v1.82.0 (#8511)
* Update module google.golang.org/grpc to v1.82.0

* Update generated Go artifacts

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:54:25 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
af35d42792 Update dependency rules_cc to v0.2.22 (#8499)
* Update dependency rules_cc to v0.2.22

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:53:59 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
d3f99ccada Update dependency org.scalameta:scalafmt-cli_2.13 to v3.11.3 (#8498)
* Update dependency org.scalameta:scalafmt-cli_2.13 to v3.11.3

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:53:38 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f4e3944716 Update dependency org.postgresql:postgresql to v42.7.13 (#8497)
* Update dependency org.postgresql:postgresql to v42.7.13

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:53:13 -07:00
adminandGitHub c67dc76ed5 Upgrade LLVM toolchain to 22.1.8 (#8520) 2026-07-13 08:40:23 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
8bd9342e68 Update module golang.org/x/sys to v0.47.0 (#8510)
* Update module golang.org/x/sys to v0.47.0

* Update generated Go artifacts

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:26:24 -07:00
adminandGitHub 55c55adf8a Automate Bazel and LLVM Renovate updates (#8519) 2026-07-13 08:23:23 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
3f6fee72b5 Update dependency rules_dotnet to v0.22.1 (#8507)
* Update dependency rules_dotnet to v0.22.1

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:22:19 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
35d441d7f6 Update dependency com.thesamet.scalapb:protoc-bridge_3 to v0.9.10 (#8496)
* Update dependency com.thesamet.scalapb:protoc-bridge_3 to v0.9.10

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:20:49 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ddb49f411b Update dependency io.sentry:sentry to v8.48.0 (#8506)
* Update dependency io.sentry:sentry to v8.48.0

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:20:27 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
130c0a5f37 Update dependency grpc-java to v1.82.0 (#8505)
* Update dependency grpc-java to v1.82.0

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:19:30 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
9c3eb2d6e7 Update dependency grpc to v1.82.0 (#8504)
* Update dependency grpc to v1.82.0

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:19:01 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
a84154bc74 Update dependency apple_support to v2.7.0 (#8503)
* Update dependency apple_support to v2.7.0

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:18:01 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
08a5dc6104 Update dependency rules_nodejs to v6.7.5 (#8500)
* Update dependency rules_nodejs to v6.7.5

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 08:11:25 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
33d963488b Update go toolchain directive to v1.26.5 (#8501)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-13 08:06:28 -07:00
adminandGitHub 2c3135a493 Render server-driven tutorial dialogue in Unity (#8487)
* Render server-driven tutorial dialogue in Unity

* Keep startup dialogue fallback available

* Revert "Keep startup dialogue fallback available"

This reverts commit 40ca3b35b3f29c29428f8a08b899ebda13f50dc0.

* Backfill dialogue text for tutorial games

* Restore tutorial runtime for split scenes
2026-07-13 07:38:15 -07:00
adminandGitHub b3b69474e4 Preprovision PostgreSQL game schemas (#8515) 2026-07-13 07:37:37 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ee74455b57 Update aws-sdk-go-v2 monorepo (#8495)
* Update aws-sdk-go-v2 monorepo

* Update generated Go artifacts

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 07:30:58 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
bd7378a971 Update eclipse_temurin_25_jre:26-jre Docker digest to a0053c4 (#8494)
* Update eclipse_temurin_25_jre:26-jre Docker digest to a0053c4

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 07:26:53 -07:00
adminandGitHub 588fd4217a Sync Xcode before Unity CI builds (#8517) 2026-07-13 07:18:10 -07:00
adminandGitHub 1a60af2cd2 Expand Renovate Monday update window (#8509) 2026-07-13 07:14:39 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
8398da469d Update Rust crate rand to v0.10.2 (#8502)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-13 07:14:22 -07:00
adminandGitHub f7280d37a6 Reset create button after returning to lobby (#8491)
* Reset create button after game entry

* Initialize create button label
2026-07-13 06:40:29 -07:00
adminandGitHub 1e98f4e3e8 Flush game creation acknowledgment before initialization (#8492) 2026-07-13 06:39:54 -07:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
3b203ac1b2 Update eclipse_temurin_25_jdk:26-jdk Docker digest to 8e04698 (#8493)
* Update eclipse_temurin_25_jdk:26-jdk Docker digest to 8e04698

* Update generated dependency lockfiles

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 06:35:06 -07:00
adminandGitHub 0ac3bd1e6e Acknowledge game creation before initialization (#8490) 2026-07-12 16:48:31 -07:00
adminandGitHub b56df59977 Batch marking generated texts requested (#8488) 2026-07-12 15:41:49 -07:00
adminandGitHub 441841ec41 Batch PostgreSQL schema initialization (#8486) 2026-07-12 14:34:15 -07:00
adminandGitHub b8ebd1d234 Batch PostgreSQL per-entity history writes (#8485) 2026-07-12 14:25:58 -07:00
adminandGitHub c1d9c53b30 Render tutorial dialogue in text client (#8458)
* Render server-driven tutorial dialogue in text client

* Render already-applied tutorial dialogue
2026-07-12 14:21:54 -07:00
adminandGitHub b4fbc7770a Show unknown battle troop counts (#8484) 2026-07-12 14:14:20 -07:00
adminandGitHub 065d5b466a Cache completed PostgreSQL client texts (#8483) 2026-07-12 14:11:49 -07:00
adminandGitHub 59a28f75e5 Separate bootstrap events from state updates (#8479) 2026-07-12 14:05:04 -07:00
adminandGitHub 86dc430ebc Batch resolved text visibility lookups (#8482) 2026-07-12 13:04:02 -07:00
adminandGitHub a062d6e8b2 Batch initial client text persistence (#8481) 2026-07-12 13:03:31 -07:00
adminandGitHub 04425edc34 Log failed resource fetch URLs (#8477) 2026-07-12 11:28:07 -07:00
adminandGitHub 5a622b276a Show drop confirmation on first click (#8480) 2026-07-12 11:27:34 -07:00
adminandGitHub 9c809d6453 Consider global quests from vassal provinces (#8478) 2026-07-12 11:25:06 -07:00
adminandGitHub 2838e8b280 Revert "Deliver game start during initial sync (#8461)" (#8476)
This reverts commit 6c219a6d43.
2026-07-12 10:49:13 -07:00
adminandGitHub 3b03f3384a Prioritize specific expansion quest targets (#8475)
* Prioritize specific expansion quest targets

* Use faction province helper for quest targets
2026-07-12 10:39:43 -07:00
adminandGitHub 6c219a6d43 Deliver game start during initial sync (#8461) 2026-07-12 10:35:12 -07:00
adminandGitHub 062c19dacf Keep province title clear of header controls (#8473)
* Keep province title clear of header controls

* Prevent province title wrapping

* Move province status icons beside support
2026-07-12 10:32:53 -07:00
adminandGitHub 82b9c0f7df Clear live province intelligence dates (#8474) 2026-07-12 07:39:15 -07:00
adminandGitHub 13cc93345a Make AI rest decisions account for hero depth (#8472) 2026-07-11 21:41:18 -07:00
adminandGitHub a5652e43f2 Prefer training over resting in development fallbacks (#8470) 2026-07-11 21:40:52 -07:00
290 changed files with 13883 additions and 12568 deletions
+1 -1
View File
@@ -1 +1 @@
9.1.1
9.2.0
+2 -2
View File
@@ -79,7 +79,7 @@ jobs:
rm -f .git/hooks/post-checkout .git/hooks/post-merge .git/hooks/pre-push
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig-no-lfs
GIT_CONFIG_NOSYSTEM: "1"
@@ -182,7 +182,7 @@ jobs:
FASTMAIL_FROM_NAME: ${{ secrets.FASTMAIL_FROM_NAME }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
@@ -51,7 +51,7 @@ jobs:
brew install git-lfs
git-lfs version
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
lfs: false
@@ -94,7 +94,7 @@ jobs:
brew install git-lfs
git-lfs version
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
lfs: false
@@ -109,7 +109,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
lfs: false
+3 -3
View File
@@ -74,14 +74,14 @@ jobs:
brew install git-lfs
git-lfs version
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
lfs: false
- name: Check BUILD.bazel dependencies
run: ./scripts/check_build_deps.sh --strict
- name: Set up Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v7
with:
node-version: 'lts/*'
- name: Check JavaScript syntax
@@ -116,7 +116,7 @@ jobs:
brew install git-lfs
git-lfs version
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
lfs: false
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
+2 -2
View File
@@ -116,7 +116,7 @@ jobs:
- name: Checkout repository
if: steps.check-latest.outputs.skip != 'true'
uses: actions/checkout@v6
uses: actions/checkout@v7
env:
GIT_LFS_SKIP_SMUDGE: 1
with:
@@ -325,7 +325,7 @@ jobs:
git-lfs --version
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
env:
GIT_LFS_SKIP_SMUDGE: 1
with:
+1 -1
View File
@@ -56,7 +56,7 @@ jobs:
rm -f .git/hooks/post-checkout .git/hooks/post-merge .git/hooks/pre-push
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig-no-lfs
GIT_CONFIG_NOSYSTEM: "1"
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
brew install git-lfs
git-lfs version
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
lfs: false
+8 -2
View File
@@ -138,7 +138,7 @@ jobs:
fi
git-lfs --version
- uses: actions/checkout@v6
- uses: actions/checkout@v7
env:
GIT_LFS_SKIP_SMUDGE: 1
with:
@@ -160,6 +160,12 @@ jobs:
- name: Ensure Unity version installed
run: ./ci/github_actions/ensure_unity_installed.sh ios
- name: Ensure Bazel installed
uses: ./.github/actions/setup-bazel
- name: Sync Bazel Xcode config
run: ./scripts/sync_bazel_xcode.sh
- name: Build iOS Unity Project
id: build
run: |
@@ -229,7 +235,7 @@ jobs:
fi
git-lfs --version
- uses: actions/checkout@v6
- uses: actions/checkout@v7
env:
GIT_LFS_SKIP_SMUDGE: 1
with:
+2 -2
View File
@@ -113,7 +113,7 @@ jobs:
fi
git-lfs --version
- uses: actions/checkout@v6
- uses: actions/checkout@v7
env:
GIT_LFS_SKIP_SMUDGE: 1
with:
@@ -287,7 +287,7 @@ jobs:
fi
git-lfs --version
- uses: actions/checkout@v6
- uses: actions/checkout@v7
env:
GIT_LFS_SKIP_SMUDGE: 1
with:
@@ -4,17 +4,20 @@ on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
paths:
- '.bazelversion'
- 'MODULE.bazel'
- 'MODULE.bazel.lock'
- 'maven_install.json'
- 'renovate.json'
- 'ci/github_actions/ensure_bazel_installed.sh'
- 'ci/github_actions/update_llvm_distributions.py'
- '.github/actions/setup-bazel/**'
- '.github/workflows/renovate_dependency_artifacts.yml'
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
paths:
- '.bazelversion'
- 'MODULE.bazel'
- 'MODULE.bazel.lock'
- 'maven_install.json'
@@ -22,6 +25,7 @@ on:
- 'go.sum'
- 'renovate.json'
- 'ci/github_actions/ensure_bazel_installed.sh'
- 'ci/github_actions/update_llvm_distributions.py'
- '.github/actions/setup-bazel/**'
- '.github/workflows/renovate_dependency_artifacts.yml'
@@ -58,11 +62,13 @@ jobs:
const changed = files.map((file) => file.filename);
const bazelPatterns = [
/^\.bazelversion$/,
/^MODULE\.bazel$/,
/^MODULE\.bazel\.lock$/,
/^maven_install\.json$/,
/^renovate\.json$/,
/^ci\/github_actions\/ensure_bazel_installed\.sh$/,
/^ci\/github_actions\/update_llvm_distributions\.py$/,
/^\.github\/actions\/setup-bazel\//,
/^\.github\/workflows\/renovate_dependency_artifacts\.yml$/,
];
@@ -87,7 +93,7 @@ jobs:
include.push({
artifact: 'bazel',
name: 'Bazel lockfiles',
changed_files: 'MODULE.bazel.lock maven_install.json',
changed_files: 'MODULE.bazel MODULE.bazel.lock maven_install.json',
commit_message: 'Update generated dependency lockfiles',
});
}
@@ -111,13 +117,18 @@ jobs:
steps:
- name: Checkout PR
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
lfs: false
- name: Ensure Bazel installed
uses: ./.github/actions/setup-bazel
- name: Refresh LLVM distribution metadata
env:
GITHUB_TOKEN: ${{ github.token }}
run: python3 ci/github_actions/update_llvm_distributions.py
- name: Update Maven lockfile
env:
REPIN: "1"
@@ -128,15 +139,16 @@ jobs:
- name: Verify generated lockfiles are current
run: |
if git diff --quiet -- MODULE.bazel.lock maven_install.json; then
if git diff --quiet -- MODULE.bazel MODULE.bazel.lock maven_install.json; then
echo "Generated dependency lockfiles are current"
exit 0
fi
echo "Generated dependency lockfiles are stale. Run the lockfile update commands and commit the result:"
echo " python3 ci/github_actions/update_llvm_distributions.py"
echo " REPIN=1 bazel run @maven//:pin"
echo " bazel build --nobuild //src/main/scala/net/eagle0/eagle:eagle_server //src/main/cpp/net/eagle0/shardok:shardok-server"
git diff -- MODULE.bazel.lock maven_install.json
git diff -- MODULE.bazel MODULE.bazel.lock maven_install.json
exit 1
update-artifacts:
@@ -173,13 +185,19 @@ jobs:
git-lfs version
- name: Checkout Renovate branch
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
token: ${{ secrets.RENOVATE_LOCKFILE_TOKEN || github.token }}
lfs: false
- name: Refresh LLVM distribution metadata
if: matrix.artifact == 'bazel'
env:
GITHUB_TOKEN: ${{ github.token }}
run: python3 ci/github_actions/update_llvm_distributions.py
- name: Ensure Bazel installed
if: matrix.artifact == 'bazel'
uses: ./.github/actions/setup-bazel
+1 -1
View File
@@ -143,7 +143,7 @@ jobs:
brew install git-lfs
git-lfs version
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
lfs: false
+1 -1
View File
@@ -57,7 +57,7 @@ jobs:
rm -f .git/hooks/post-checkout .git/hooks/post-merge .git/hooks/pre-push
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig-no-lfs
GIT_CONFIG_NOSYSTEM: "1"
+1 -1
View File
@@ -72,7 +72,7 @@ jobs:
brew install git-lfs
git-lfs version
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
persist-credentials: false
lfs: false
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
fi
rm -f .git/hooks/post-checkout .git/hooks/post-merge .git/hooks/pre-push
- uses: actions/checkout@v6
- uses: actions/checkout@v7
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig-no-lfs
GIT_CONFIG_NOSYSTEM: "1"
+8 -2
View File
@@ -14,6 +14,7 @@ on:
- "scripts/build_protos.sh"
- "scripts/build_plugins.sh"
- "scripts/build_windows_plugin.sh"
- "scripts/sync_bazel_xcode.sh"
- "ci/github_actions/build_unity.sh"
- "ci/github_actions/test_unity_editmode.sh"
- "ci/github_actions/ensure_bazel_installed.sh"
@@ -39,6 +40,7 @@ on:
- "scripts/build_protos.sh"
- "scripts/build_plugins.sh"
- "scripts/build_windows_plugin.sh"
- "scripts/sync_bazel_xcode.sh"
- "ci/github_actions/build_unity.sh"
- "ci/github_actions/test_unity_editmode.sh"
- "ci/github_actions/ensure_bazel_installed.sh"
@@ -94,7 +96,7 @@ jobs:
fi
git-lfs --version
- uses: actions/checkout@v6
- uses: actions/checkout@v7
env:
GIT_LFS_SKIP_SMUDGE: 1
with:
@@ -156,6 +158,8 @@ jobs:
dotnet-version: '8.0.x'
- name: Ensure Bazel installed
uses: ./.github/actions/setup-bazel
- name: Sync Bazel Xcode configuration
run: ./scripts/sync_bazel_xcode.sh
- name: Run Unity EditMode tests
run: ./ci/github_actions/test_unity_editmode.sh
@@ -209,7 +213,7 @@ jobs:
fi
git-lfs --version
- uses: actions/checkout@v6
- uses: actions/checkout@v7
env:
GIT_LFS_SKIP_SMUDGE: 1
with:
@@ -271,6 +275,8 @@ jobs:
dotnet-version: '8.0.x'
- name: Ensure Bazel installed
uses: ./.github/actions/setup-bazel
- name: Sync Bazel Xcode configuration
run: ./scripts/sync_bazel_xcode.sh
- name: Detect Addressables changes
id: addressables
+21 -19
View File
@@ -22,10 +22,10 @@ bazel_dep(name = "aspect_rules_esbuild", version = "0.26.0")
bazel_dep(name = "aspect_rules_js", version = "3.2.2")
bazel_dep(name = "bazel_jar_jar", version = "0.1.15", repo_name = "bazel_jar_jar")
bazel_dep(name = "platforms", version = "1.1.0")
bazel_dep(name = "rules_dotnet", version = "0.21.5")
bazel_dep(name = "rules_dotnet", version = "0.22.1")
bazel_dep(name = "rules_foreign_cc", version = "0.15.1")
bazel_dep(name = "rules_android", version = "0.7.3")
bazel_dep(name = "rules_nodejs", version = "6.7.4")
bazel_dep(name = "rules_nodejs", version = "6.7.5")
bazel_dep(name = "rules_pkg", version = "1.2.0")
bazel_dep(name = "rules_shell", version = "0.8.0")
@@ -74,12 +74,12 @@ bazel_dep(name = "toolchains_llvm", version = "1.8.0")
llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm")
LLVM_VERSION = "22.1.7"
LLVM_VERSION = "22.1.8"
LLVM_DISTRIBUTIONS = {
"LLVM-22.1.7-Linux-ARM64.tar.xz": "118ca2d3ad9da34367e05735317854e7977db45dc4c02a32af58da64c23b8789",
"LLVM-22.1.7-Linux-X64.tar.xz": "edb0522b41e261819c06ea437d249f9b8acfa413d3805bc9920eec6fb76ff830",
"LLVM-22.1.7-macOS-ARM64.tar.xz": "4177245188b0a30a6539c96b361dea56f253485756bfd8927a6a59e7301e7806",
"LLVM-22.1.8-Linux-ARM64.tar.xz": "805efad2bb91cb4967fa569e0881d10c0f69c04461cf671cccbae19f547acc34",
"LLVM-22.1.8-Linux-X64.tar.xz": "df0e1ecf16caf3489a272a5eea4eec9b0d82878f6477fa309504f918a0006384",
"LLVM-22.1.8-macOS-ARM64.tar.xz": "f260f4f7c0d430828a81ae8a3826a1d63fc0963ec2459489308cc23b1f7eab4f",
}
# Native toolchain (macOS -> macOS, Linux -> Linux)
@@ -169,7 +169,7 @@ use_repo(
# Language Support - Rust
#
bazel_dep(name = "rules_rust", version = "0.70.0")
bazel_dep(name = "rules_rust", version = "0.71.3")
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(edition = "2021")
@@ -177,7 +177,7 @@ use_repo(rust, "rust_toolchains")
register_toolchains("@rust_toolchains//:all")
crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate")
crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate")
crate.from_cargo(
name = "map_generator_crates",
cargo_lockfile = "//src/main/rust/net/eagle0/eagle/map_generator:Cargo.lock",
@@ -189,7 +189,7 @@ use_repo(crate, "map_generator_crates")
# Platform Support - Apple/iOS
#
bazel_dep(name = "apple_support", version = "2.6.1", repo_name = "build_bazel_apple_support")
bazel_dep(name = "apple_support", version = "2.7.0", repo_name = "build_bazel_apple_support")
bazel_dep(name = "rules_apple", version = "4.5.3", repo_name = "build_bazel_rules_apple")
multiple_version_override(
@@ -214,6 +214,8 @@ use_repo(apple_cc_configure, "local_config_apple_cc", "local_config_apple_cc_too
bazel_dep(name = "protobuf", version = "35.1", repo_name = "com_google_protobuf")
single_version_override(
module_name = "protobuf",
patch_strip = 1,
patches = ["//third_party/protobuf/patches:rust_crates_dev_dependency.patch"],
version = "35.1",
)
@@ -234,8 +236,8 @@ use_repo(
)
bazel_dep(name = "rules_proto", version = "7.1.0")
bazel_dep(name = "rules_cc", version = "0.2.19")
bazel_dep(name = "grpc", version = "1.81.1")
bazel_dep(name = "rules_cc", version = "0.2.22")
bazel_dep(name = "grpc", version = "1.82.0")
# gRPC 1.81.1's generated upb module map trips Clang's layering check in the
# Linux ARM64 cross-toolchain even though the normal Bazel deps are present.
@@ -245,7 +247,7 @@ single_version_override(
patches = ["//third_party/grpc/patches:disable_xds_client_layering_check.patch"],
)
bazel_dep(name = "grpc-java", version = "1.78.0.bcr.1")
bazel_dep(name = "grpc-java", version = "1.82.0")
bazel_dep(name = "rules_proto_grpc_csharp", version = "5.8.0")
bazel_dep(name = "flatbuffers", version = "25.12.19")
@@ -270,7 +272,7 @@ oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
# curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json" "https://registry-1.docker.io/v2/library/eclipse-temurin/manifests/25-jre" | python3 -c "import json,sys; m=json.load(sys.stdin); [print(p['digest']) for p in m.get('manifests',[]) if p['platform']['architecture']=='amd64']"
oci.pull(
name = "eclipse_temurin_25_jre",
digest = "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
digest = "sha256:a0053c48b1a74412b14a111d6fd547799f8a6a5c77e2318f698b996ec899b0ce",
image = "docker.io/library/eclipse-temurin",
platforms = ["linux/amd64"],
tag = "26-jre",
@@ -279,7 +281,7 @@ oci.pull(
# Base image for JFR sidecar (Java 25 JDK - includes jcmd for JFR dumps)
oci.pull(
name = "eclipse_temurin_25_jdk",
digest = "sha256:41c3a5b8a607d226c73e81538cd94d0e66f746f41f057561447eb47b40a91a65",
digest = "sha256:8e0469892385b62feab689f211fde7eeeb6537418c8fbe613147427b53870ec3",
image = "docker.io/library/eclipse-temurin",
platforms = ["linux/amd64"],
tag = "26-jdk",
@@ -334,7 +336,7 @@ maven.install(
"com.thesamet.scalapb:scalapb-runtime_3:%s" % SCALAPB_VERSION,
"com.thesamet.scalapb:scalapb-runtime-grpc_3:%s" % SCALAPB_VERSION,
"com.thesamet.scalapb:compilerplugin_3:%s" % SCALAPB_VERSION,
"com.thesamet.scalapb:protoc-bridge_3:0.9.9",
"com.thesamet.scalapb:protoc-bridge_3:0.9.10",
# JSON
"org.json4s:json4s-ast_3:4.1.0-M8",
@@ -345,7 +347,7 @@ maven.install(
"org.scalamock:scalamock_3:7.5.5",
# Developer tools
"org.scalameta:scalafmt-cli_2.13:3.11.1",
"org.scalameta:scalafmt-cli_2.13:3.11.3",
# AWS SDK
"software.amazon.awssdk:s3-transfer-manager:%s" % AWS_SDK_VERSION,
@@ -376,14 +378,14 @@ maven.install(
"com.nimbusds:nimbus-jose-jwt:10.9.1",
# Error tracking
"io.sentry:sentry:8.43.2",
"io.sentry:sentry:8.48.0",
# SQLite for client text storage
"org.xerial:sqlite-jdbc:3.53.2.0",
# Postgres for production history benchmarking and migration work
"com.zaxxer:HikariCP:6.3.3",
"org.postgresql:postgresql:42.7.11",
"com.zaxxer:HikariCP:7.1.0",
"org.postgresql:postgresql:42.7.13",
],
duplicate_version_warning = "error",
fail_if_repin_required = True,
+49 -2097
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""Update pinned LLVM release asset names and SHA-256 digests in MODULE.bazel."""
from __future__ import annotations
import argparse
import json
import os
import re
import sys
import urllib.request
from pathlib import Path
from typing import Any
LLVM_VERSION_PATTERN = re.compile(r'^LLVM_VERSION = "(?P<version>\d+\.\d+\.\d+)"$', re.MULTILINE)
LLVM_DISTRIBUTIONS_PATTERN = re.compile(
r"^LLVM_DISTRIBUTIONS = \{\n.*?^\}$",
re.MULTILINE | re.DOTALL,
)
LLVM_DISTRIBUTION_PLATFORMS = (
"Linux-ARM64",
"Linux-X64",
"macOS-ARM64",
)
def fetch_release(version: str, token: str | None) -> dict[str, Any]:
url = f"https://api.github.com/repos/llvm/llvm-project/releases/tags/llvmorg-{version}"
headers = {
"Accept": "application/vnd.github+json",
"User-Agent": "eagle0-llvm-distribution-updater",
"X-GitHub-Api-Version": "2022-11-28",
}
if token:
headers["Authorization"] = f"Bearer {token}"
request = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(request, timeout=30) as response:
return json.load(response)
def load_release(path: Path) -> dict[str, Any]:
with path.open(encoding="utf-8") as release_file:
return json.load(release_file)
def llvm_distributions(version: str, release: dict[str, Any]) -> dict[str, str]:
expected_tag = f"llvmorg-{version}"
if release.get("tag_name") != expected_tag:
raise ValueError(
f"Expected release tag {expected_tag}, got {release.get('tag_name')!r}"
)
assets = {asset["name"]: asset for asset in release.get("assets", [])}
distributions: dict[str, str] = {}
for platform in LLVM_DISTRIBUTION_PLATFORMS:
name = f"LLVM-{version}-{platform}.tar.xz"
asset = assets.get(name)
if asset is None:
raise ValueError(f"LLVM release {version} is missing required asset {name}")
digest = asset.get("digest")
if not isinstance(digest, str) or not digest.startswith("sha256:"):
raise ValueError(f"LLVM release asset {name} has no SHA-256 digest")
distributions[name] = digest.removeprefix("sha256:")
return distributions
def render_distributions(distributions: dict[str, str]) -> str:
lines = ["LLVM_DISTRIBUTIONS = {"]
lines.extend(f' "{name}": "{digest}",' for name, digest in distributions.items())
lines.append("}")
return "\n".join(lines)
def update_module(module_path: Path, release: dict[str, Any]) -> bool:
original = module_path.read_text(encoding="utf-8")
version_match = LLVM_VERSION_PATTERN.search(original)
if version_match is None:
raise ValueError(f"Could not find LLVM_VERSION in {module_path}")
distributions = llvm_distributions(version_match.group("version"), release)
replacement = render_distributions(distributions)
updated, replacements = LLVM_DISTRIBUTIONS_PATTERN.subn(replacement, original, count=1)
if replacements != 1:
raise ValueError(f"Could not find exactly one LLVM_DISTRIBUTIONS block in {module_path}")
if updated == original:
return False
module_path.write_text(updated, encoding="utf-8")
return True
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--module", type=Path, default=Path("MODULE.bazel"))
parser.add_argument(
"--release-json",
type=Path,
help="Read GitHub release JSON from a file instead of the GitHub API",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
module_text = args.module.read_text(encoding="utf-8")
version_match = LLVM_VERSION_PATTERN.search(module_text)
if version_match is None:
print(f"ERROR: Could not find LLVM_VERSION in {args.module}", file=sys.stderr)
return 1
version = version_match.group("version")
try:
release = (
load_release(args.release_json)
if args.release_json
else fetch_release(version, os.environ.get("GITHUB_TOKEN"))
)
changed = update_module(args.module, release)
except (OSError, ValueError) as error:
print(f"ERROR: {error}", file=sys.stderr)
return 1
if changed:
print(f"Updated LLVM {version} distribution metadata in {args.module}")
else:
print(f"LLVM {version} distribution metadata is current")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
import tempfile
import unittest
from pathlib import Path
import update_llvm_distributions
class UpdateLlvmDistributionsTest(unittest.TestCase):
def release(self, *, missing_digest: bool = False) -> dict:
assets = []
for index, platform in enumerate(
update_llvm_distributions.LLVM_DISTRIBUTION_PLATFORMS, start=1
):
asset = {
"name": f"LLVM-22.1.8-{platform}.tar.xz",
"digest": f"sha256:{index:064x}",
}
assets.append(asset)
if missing_digest:
assets[0]["digest"] = None
return {"tag_name": "llvmorg-22.1.8", "assets": assets}
def test_updates_asset_versions_and_digests(self) -> None:
with tempfile.TemporaryDirectory() as directory:
module = Path(directory) / "MODULE.bazel"
module.write_text(
'LLVM_VERSION = "22.1.8"\n\n'
"LLVM_DISTRIBUTIONS = {\n"
' "LLVM-22.1.7-Linux-ARM64.tar.xz": "old-arm",\n'
' "LLVM-22.1.7-Linux-X64.tar.xz": "old-x64",\n'
' "LLVM-22.1.7-macOS-ARM64.tar.xz": "old-mac",\n'
"}\n",
encoding="utf-8",
)
changed = update_llvm_distributions.update_module(module, self.release())
self.assertTrue(changed)
updated = module.read_text(encoding="utf-8")
self.assertNotIn("22.1.7", updated)
self.assertIn('"LLVM-22.1.8-Linux-ARM64.tar.xz": "' + "1".zfill(64), updated)
self.assertIn('"LLVM-22.1.8-macOS-ARM64.tar.xz": "' + "3".zfill(64), updated)
def test_rejects_asset_without_sha256_digest(self) -> None:
with self.assertRaisesRegex(ValueError, "has no SHA-256 digest"):
update_llvm_distributions.llvm_distributions(
"22.1.8", self.release(missing_digest=True)
)
if __name__ == "__main__":
unittest.main()
+33 -8
View File
@@ -4,20 +4,18 @@ This guide covers operating the Eagle text client against a running Eagle server
## Authentication And Startup
The current bearer-token file is:
```
/private/tmp/eagle0-text-client-token.txt
```
If that file is absent or authentication fails, stop and ask the user for a current bearer token. Do not invent a token or use another user's credentials.
The text client can request and refresh its own tokens. GitHub is the recommended OAuth provider:
Start the production client with:
```bash
bazel run //src/main/scala/net/eagle0/eagle/text_client:eagle_text_client -- --tls --host prod.eagle0.net --port 443 --bearer-token-file /private/tmp/eagle0-text-client-token.txt
bazel run //src/main/scala/net/eagle0/eagle/text_client:eagle_text_client -- --tls --host prod.eagle0.net --port 443 --oauth
```
On the first run, or when no usable refresh token remains, the client prints a GitHub login URL. Paste that URL to the user and wait for them to authenticate in their browser. The callback returns to a temporary loopback listener in the text client, not to the Unity client. Never open the URL or choose an account on the user's behalf.
The client stores the access and refresh tokens in `~/.eagle0/text-client/` with owner-only permissions and refreshes the access token automatically. For an existing token location, pass both `--bearer-token-file <path>` and `--refresh-token-file <path>`. Use `--oauth-provider <provider>` only if the account uses `google`, `discord`, `apple`, `steam`, or `twitch` instead of the recommended `github` provider.
At the `eagle0>` prompt, use `lobby` to list running games and available new-game leaders. To resume a game, use:
```text
@@ -133,6 +131,33 @@ Shardok update with no available commands clears the previous command list.
Use `shardok-commands-json` or `shardok-units-json` when the concise output omits a needed field.
During battle setup, `shardok-commands` prints any server-recommended unit placements in order and identifies the
ordinary current command index for the next recommendation:
```text
recommended-starting-positions (ordered):
[0] unit=12 target=(3,4)
[1] unit=15 target=(4,4)
use next: post-shardok <shardok-game-id> 17
```
The list is advice, not an automatic placement batch. Review it against `shardok-map`, choose whether to use the first
recommendation, and submit that one placement with the printed `post-shardok` command. After every placement, wait for
the updated state and run `shardok-commands` again; the legal command indices and remaining recommendations may change.
The client synchronizes Shardok map definitions when it connects. To inspect the
base terrain together with current tile modifiers and occupying units, use:
```text
shardok-map <shardok-game-id>
shardok-map-json <shardok-game-id>
```
The battle ID may be omitted when the client knows about exactly one Shardok
battle. The concise map prints every coordinate's terrain plus fire, castle,
bridge, ice, and snow state. The JSON form includes the complete base map,
dynamic tile modifiers, and current active units.
## Reconnects And Shutdown
The client automatically recreates a failed gRPC stream and re-subscribes to the active game. It retains its in-process `ClientTextStore`, so a reconnect should announce:
@@ -15,7 +15,7 @@ Source map for Shardok command requirements/costs:
- Charge: terrain-entry cost from BattalionType::GetCostToEnterTerrain(...); allowsMoveAfterCharge is true for Light Cavalry and Heavy Cavalry in src/main/resources/net/eagle0/shardok/battalionTypes.tsv; stun settings are baseStunChanceOnCharge and trainingDifferenceMultiplierForStunChanceOnCharge in the same file; src/main/cpp/net/eagle0/shardok/library/command_factories/ChargeCommandFactory.cpp.
- Move: per-hex terrain-entry costs, plus extraCostForZocMovement=1; src/main/cpp/net/eagle0/shardok/library/command_factories/MoveCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/MoveCommand.cpp.
- Lightning Bolt: lightningActionPointCost=5 and lightningRange=3; src/main/cpp/net/eagle0/shardok/library/command_factories/LightningBoltCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/LightningBoltCommand.cpp.
- Meteor: meteorStartActionPointCost=5, meteorTargetActionPointCost=0, meteorCancelActionPointCost=0, meteorCastActionPointCost=1, meteorCastVigorCost=20, and meteorRange=3; src/main/cpp/net/eagle0/shardok/library/command_factories/MeteorStartCommandFactory.cpp, MeteorTargetCommandFactory.cpp, src/main/cpp/net/eagle0/shardok/library/util/MeteorRangeCoords.cpp, src/main/cpp/net/eagle0/shardok/library/commands/MeteorStartCommand.cpp, MeteorTargetCommand.cpp, MeteorCancelCommand.cpp, and src/main/cpp/net/eagle0/shardok/library/actions/MeteorCastAction.cpp.
- Meteor: minimumVigorForMeteor=30, meteorStartActionPointCost=5, meteorTargetActionPointCost=0, meteorCancelActionPointCost=0, meteorCastActionPointCost=1, meteorCastVigorCost=20, meteorRange=3, meteorSplashFactor=0.4, and meteorCastWisdomXp=5; src/main/cpp/net/eagle0/shardok/library/command_factories/MeteorStartCommandFactory.cpp, MeteorTargetCommandFactory.cpp, src/main/cpp/net/eagle0/shardok/library/util/MeteorRangeCoords.cpp, src/main/cpp/net/eagle0/shardok/library/commands/MeteorStartCommand.cpp, MeteorTargetCommand.cpp, MeteorCancelCommand.cpp, and src/main/cpp/net/eagle0/shardok/library/actions/MeteorCastAction.cpp.
- Start Fire: startFireActionPointCost=5; hero start-fire thresholds are Mage or agility >= 70 and wisdom >= 70 in src/main/cpp/net/eagle0/shardok/ai_battle_simulator/AiBattleSimulator.cpp; src/main/cpp/net/eagle0/shardok/library/command_factories/StartFireCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/StartFireCommand.cpp.
- Extinguish Fire: extinguishActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/ExtinguishFireCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/ExtinguishFireCommand.cpp.
- Freeze Water: freezeWaterActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/FreezeWaterCommandFactory.cpp.
@@ -23,22 +23,22 @@ Source map for Shardok command requirements/costs:
- Blow Bridge: blowBridgeActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/BlowBridgeCommandFactory.cpp.
- Placement: no AP cost in setup commands; src/main/cpp/net/eagle0/shardok/library/actions/PlaceUnitCommand.cpp and PlaceHiddenUnitCommand.cpp.
- Scout: scoutActionPointCost=5 and scoutRange=4; src/main/cpp/net/eagle0/shardok/library/command_factories/ScoutCommandFactory.cpp.
- Raise Dead: raiseDeadActionPointCost=5 and raiseDeadRange=2; src/main/cpp/net/eagle0/shardok/library/command_factories/RaiseDeadCommandFactory.cpp.
- Dismiss Unit: requires remainingActionPoints > 0 in src/main/cpp/net/eagle0/shardok/library/command_factories/ControlCommandFactory.cpp; execution zeroes both units in src/main/cpp/net/eagle0/shardok/library/commands/DismissUnitCommand.cpp.
- Holy Wave: holyWaveActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/HolyWaveCommandFactory.cpp.
- Stop and Rest: stop posts no AP cost; rest zeroes AP in src/main/cpp/net/eagle0/shardok/library/commands/UnitRestCommand.cpp; stop implementation in UnitStopCommand.cpp.
- Challenge Duel: challengeDuelActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/ChallengeDuelCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/challenge_duel_command/ChallengeDuelCommand.cpp.
- Fear: fearActionPointCost=5 and fearRange=3; src/main/cpp/net/eagle0/shardok/library/command_factories/FearCommandFactory.cpp.
- Repair: repairActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/RepairCommandFactory.cpp.
- Reduce: reduceActionPointCost=5 and reduceRange=3; src/main/cpp/net/eagle0/shardok/library/command_factories/ReduceCommandFactory.cpp.
- Raise Dead: raiseDeadActionPointCost=5, raiseDeadRange=2, raiseDeadStartingSize=100, raiseDeadWisdomXp=1, and raiseDeadCharismaXp=2; src/main/cpp/net/eagle0/shardok/library/command_factories/RaiseDeadCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/RaiseDeadCommand.cpp.
- Dismiss Unit: dismissBaseOdds=60 and requires remainingActionPoints > 0 in src/main/cpp/net/eagle0/shardok/library/command_factories/ControlCommandFactory.cpp; execution zeroes both units in src/main/cpp/net/eagle0/shardok/library/commands/DismissUnitCommand.cpp.
- Holy Wave: holyWaveActionPointCost=5, holyWaveVigorCost=10, holyWaveInspireBonus=25, maxInspireOverBase=10, holyWaveVigorBuf=5, holyWaveDamagePercentage=0.7, and holyWaveCharismaXp=5; src/main/cpp/net/eagle0/shardok/library/command_factories/HolyWaveCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/HolyWaveCommand.cpp.
- Stop and Rest: stop posts no AP cost; actionCostToVigorCost=0.5 and rest zeroes AP in src/main/cpp/net/eagle0/shardok/library/commands/UnitRestCommand.cpp; availability and stop implementation are in ChargeCommandFactory.cpp and UnitStopCommand.cpp.
- Challenge Duel: challengeDuelActionPointCost=5, minimumVigorToAcceptDuel=30, duelDeclinedMoraleAdjustment=-15, duelWonMoraleBoost=15, and duelWinnerCharismaXp=20; src/main/cpp/net/eagle0/shardok/library/command_factories/ChallengeDuelCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/challenge_duel_command/ChallengeDuelCommand.cpp.
- Fear: fearActionPointCost=5, fearRange=3, fearDebuf=-25, fearFailedBuf=10, and fearCharismaXp=2; src/main/cpp/net/eagle0/shardok/library/command_factories/FearCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/FearCommand.cpp.
- Repair: repairActionPointCost=5 and repairBuf=25; src/main/cpp/net/eagle0/shardok/library/command_factories/RepairCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/RepairCommand.cpp.
- Reduce: reduceActionPointCost=5, reduceRange=3, and reduceForestBonus=50; src/main/cpp/net/eagle0/shardok/library/command_factories/ReduceCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/ReduceCommand.cpp.
- Flee and Retreat: fleeActionPointCost=5 and retreatActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/FleeCommandFactory.cpp and RetreatCommandFactory.cpp.
- Control: requires remainingActionPoints > 0 in ControlCommandFactory.cpp; execution zeroes acting unit in src/main/cpp/net/eagle0/shardok/library/commands/ControlCommand.cpp.
- Reinforce: reinforceActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/ReinforceCommandFactory.cpp.
- Brave Water: braveWaterActionPointCost=8; src/main/cpp/net/eagle0/shardok/library/command_factories/BraveWaterCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/BraveWaterCommand.cpp.
- Brave Water: braveWaterActionPointCost=8, braveWaterRangerBonus=20, and braveWaterMaxLoss=0.2; src/main/cpp/net/eagle0/shardok/library/command_factories/BraveWaterCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/BraveWaterCommand.cpp.
- Release Unit: requires remainingActionPoints > 0 in ControlCommandFactory.cpp; execution zeroes both units in src/main/cpp/net/eagle0/shardok/library/commands/ReleaseUnitCommand.cpp.
- End Turn and Setup: no command AP cost; end turn zeroes player unit AP in src/main/cpp/net/eagle0/shardok/library/commands/EndTurnCommand.cpp; setup end in src/main/cpp/net/eagle0/shardok/library/actions/EndPlayerSetupCommand.cpp.
- Hide: hideActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/HideCommandFactory.cpp.
- Fortify: fortifyActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/FortifyCommandFactory.cpp.
- Fortify: fortifyActionPointCost=5 and fortifyDamageTakenMultiplier=0.9; src/main/cpp/net/eagle0/shardok/library/command_factories/FortifyCommandFactory.cpp, src/main/cpp/net/eagle0/shardok/library/commands/FortifyCommand.cpp, and the commands/actions that clear fortified status.
- Become Outlaw: uses fleeActionPointCost=5 through src/main/cpp/net/eagle0/shardok/library/command_factories/FleeCommandFactory.cpp and src/main/cpp/net/eagle0/shardok/library/commands/BecomeOutlawCommand.cpp.
- Evacuate Prisoners: evacuatePrisonersActionPointCost=5; src/main/cpp/net/eagle0/shardok/library/command_factories/EvacuatePrisonersCommandFactory.cpp.
-->
@@ -127,13 +127,13 @@ The follow-up list is a planning aid, not a complete list of everything the unit
### Meteor
<b>Unit requirements:</b> [Mage](/heroes/#mage) attached to Light Infantry, Light Cavalry, or Longbowmen, with enough vigor to start the spell. Heavy units and Undead cannot cast.
<b>Unit requirements:</b> [Mage](/heroes/#mage) attached to Light Infantry, Light Cavalry, or Longbowmen, with at least 30 vigor. Heavy units and Undead cannot cast.
<b>Action Point cost:</b> start costs 5 AP and consumes all remaining AP; target and cancel cost 0 AP; the automatic cast step is configured as 1 AP but resolves by setting the caster to 0 AP.
<b>Available when:</b> the caster can begin the spell, choose or change a target within 3 hexes, cast the pending meteor, or cancel the pending meteor.
<b>Effect:</b> Begin a delayed meteor spell, choose where it will land, then resolve the strike when the cast step becomes available. You can cancel while the meteor is still pending.
<b>Effect:</b> Begin a delayed meteor spell, then choose a target within 3 hexes on the following round. When the meteor resolves, a unit on the target hex takes a fire attack based on the Mage's Wisdom. Units on the six adjacent hexes take 40% as much. The impact damages castles, bridges, ice, and snow; may start fires on the target and adjacent hexes; and removes fortification from units it hits. Destroying supporting ice or a bridge can also drop a unit into the water. The Mage spends 20 vigor, gains 5 Wisdom XP, and is stunned for 2 rounds after the strike. You can cancel while the meteor is still pending.
<b>Selection:</b> choose the caster, spell target, or cancel option depending on the current meteor step.
@@ -229,21 +229,21 @@ The follow-up list is a planning aid, not a complete list of everything the unit
<b>Available when:</b> there is an empty, undead-traversable target hex within 2 hexes.
<b>Effect:</b> Create or restore undead forces through necromantic action.
<b>Effect:</b> Make the displayed success roll. On success, create a battalion of 100 Undead on the target hex, controlled by the Necromancer. The new battalion starts with 50 morale, 0 training, and 0 armament. On failure, no unit is created. Either result spends the action; the Necromancer gains 1 Wisdom XP and 2 Charisma XP.
<b>Selection:</b> choose the necromancer unit and target.
### Dismiss Unit
<b>Unit requirements:</b> a unit that is allowed to be dismissed.
<b>Unit requirements:</b> a hero who currently controls a summoned unit, such as Undead raised by a Necromancer.
<b>Action Point cost:</b> requires the controlling unit to have more than 0 AP; execution sets both the controlling unit and dismissed unit to 0 AP.
<b>Available when:</b> dismissal is valid for the current tactical state.
<b>Available when:</b> the controlling hero has more than 0 AP and still has a controlled unit.
<b>Effect:</b> Remove the unit from the tactical battle.
<b>Effect:</b> Make the displayed success roll, based on the controlling hero's Charisma and Wisdom. Success disbands the controlled unit. Failure still ends the control relationship, but the unit remains on the battlefield uncontrolled and targets its former controller.
<b>Selection:</b> choose the unit to dismiss.
<b>Selection:</b> choose Dismiss Unit on the controlling hero; the controlled unit is the target.
### Holy Wave
@@ -251,33 +251,35 @@ The follow-up list is a planning aid, not a complete list of everything the unit
<b>Action Point cost:</b> 5 AP; consumes all remaining AP.
<b>Available when:</b> the paladin has a valid holy-wave action.
<b>Vigor cost:</b> 10 from the Paladin under normal circumstances.
<b>Effect:</b> Use a holy-area effect from the capable unit.
<b>Available when:</b> the Paladin unit has enough AP and meets the shared morale and hero-vigor requirements.
<b>Selection:</b> choose the paladin unit and command target, if one is required.
<b>Effect:</b> Emit a wave centered on the Paladin. The Paladin and every adjacent non-Undead unit controlled by the same player gain 25 morale, capped at 10 above base morale and at 100 overall; attached heroes regain 5 vigor up to starting vigor; and stun is removed. Every adjacent Undead unit, regardless of allegiance, loses 70% of its troops, becomes visible, and targets the Paladin. The Paladin gains 5 Charisma XP. The effect does not make a success roll.
<b>Selection:</b> choose the Paladin unit. Holy Wave does not require a target.
### Stop and Rest
<b>Unit requirements:</b> a unit that can stop or rest.
<b>Unit requirements:</b> a unit with an available charge.
<b>Action Point cost:</b> Stop costs 0 AP; Rest consumes all remaining AP.
<b>Available when:</b> the unit can end movement or spend its action resting.
<b>Available when:</b> Stop is offered outside an enemy zone of control; Rest is offered inside an enemy zone of control.
<b>Effect:</b> `UNIT_STOP_COMMAND` stops a unit. `UNIT_REST_COMMAND` has a unit spend its action resting instead of acting aggressively.
<b>Effect:</b> Stop declines the available charge without spending the unit's remaining AP. Rest declines the charge, ends the activation, and restores an attached hero's vigor by half of the AP given up. For example, resting with 4 AP restores 2 vigor.
<b>Selection:</b> choose stop or rest for the acting unit.
### Challenge Duel
<b>Unit requirements:</b> [Champion](/heroes/#champion).
<b>Unit requirements:</b> [Champion](/heroes/#champion) challenging an adjacent known enemy unit with an attached hero. A challenge cannot enter a castle.
<b>Action Point cost:</b> 5 AP; consumes all remaining AP.
<b>Available when:</b> an opposing unit can be challenged.
<b>Available when:</b> the target hero has at least 30 vigor, or the Champion does not know enough about the target to know otherwise.
<b>Effect:</b> Challenge an opposing unit to a duel. The tactical action emits accepted or declined outcomes directly.
<b>Effect:</b> The target's chance to accept depends on Bravery and its assessment of the two battalions and heroes. A target that declines normally loses 15 morale; a target known to have less than 30 vigor declines without that penalty. If accepted, the heroes exchange attacks until one falls or 30 exchanges pass. A winner's battalion gains 15 morale and the winning hero gains 20 Charisma XP. Participating heroes can also gain Strength and Agility XP.
<b>Selection:</b> choose the champion unit and duel target.
@@ -289,7 +291,7 @@ The follow-up list is a planning aid, not a complete list of everything the unit
<b>Available when:</b> a known enemy that uses morale is within 3 hexes.
<b>Effect:</b> Use a fear effect against the target.
<b>Effect:</b> Make the displayed success roll. The Necromancer's Charisma and Wisdom improve the chance, while a target hero's Charisma resists it. Success reduces the target battalion's morale by 25 and stuns it for 1 round. Failure instead increases its morale by 10. The Necromancer gains 2 Charisma XP either way.
<b>Selection:</b> choose the necromancer unit and target.
@@ -299,11 +301,11 @@ The follow-up list is a planning aid, not a complete list of everything the unit
<b>Action Point cost:</b> 5 AP; consumes all remaining AP.
<b>Available when:</b> a damaged unit or battlefield object can be repaired.
<b>Available when:</b> a damaged friendly or unoccupied bridge or castle is on the Engineer's hex or an adjacent hex. The Engineer cannot repair from water or repair a structure occupied by a known enemy.
<b>Effect:</b> Repair the damaged target.
<b>Effect:</b> Restore 25 integrity to the bridge or castle, up to its maximum of 100. Repair does not restore troops or hero vigor.
<b>Selection:</b> choose the engineer unit and repair target.
<b>Selection:</b> choose the Engineer unit and the bridge or castle to repair.
### Reduce
@@ -311,71 +313,83 @@ The follow-up list is a planning aid, not a complete list of everything the unit
<b>Action Point cost:</b> 5 AP; consumes all remaining AP.
<b>Available when:</b> an enemy or damaged castle is within 3 hexes and the line is not blocked by mountains.
<b>Available when:</b> a known enemy unit or a castle with remaining integrity is within 3 hexes and the line is not blocked by mountains. Friendly-occupied hexes and the Engineer's own hex cannot be targeted.
<b>Effect:</b> Reduce the target's defensive or structural value.
<b>Effect:</b> Bombard the target. The damage roll improves with the Engineer's Agility, Strength, and Wisdom, and gains a bonus when friendly forces have access to a forest. The result reduces castle or bridge integrity on the hex, damages any occupying enemy unit, and removes that unit's fortified status.
<b>Selection:</b> choose the engineer unit and target.
### Flee and Retreat
### Flee
<b>Unit requirements:</b> a unit that is allowed to leave or withdraw.
<b>Unit requirements:</b> a unit with an attached hero that is allowed to flee.
<b>Action Point cost:</b> 5 AP; consumes all remaining AP.
<b>Available when:</b> flight or retreat is valid for the current battlefield state.
<b>Available when:</b> the hero can leave the battle and has enough AP.
<b>Effect:</b> `FLEE_COMMAND` handles immediate flight from a tactical situation. `RETREAT_COMMAND` handles a more formal withdrawal command.
<b>Effect:</b> Attempt to withdraw from the province entirely. Make the displayed success roll: nearby visible friendly units improve the chance to escape, while nearby visible enemies and impassable terrain reduce it. Units 2 hexes away have half as much effect as adjacent units. VIP heroes escape automatically. On success, the unit leaves both the battle and the province and will escape even if the province is later captured. On failure, the unit is captured during the attempt.
<b>Selection:</b> choose the fleeing or retreating unit.
<b>Selection:</b> choose the fleeing unit.
### Retreat
<b>Unit requirements:</b> a defending unit with an attached hero.
<b>Action Point cost:</b> 5 AP; consumes all remaining AP.
<b>Available when:</b> the defending hero has enough AP. Attackers cannot use Retreat.
<b>Effect:</b> Withdraw the unit from the battle while remaining in the province. Retreat succeeds automatically and does not make the immediate capture roll used by Flee, but the hero will still be captured if their side loses the battle and the province is captured.
<b>Selection:</b> choose the retreating unit.
### Control
<b>Unit requirements:</b> a unit with access to a control effect.
<b>Unit requirements:</b> a hero who already has a controlled summoned unit, such as Undead raised by a Necromancer.
<b>Action Point cost:</b> requires more than 0 AP; execution sets the acting unit to 0 AP.
<b>Available when:</b> a valid target can be controlled.
<b>Available when:</b> the controlling hero has more than 0 AP and still has a controlled unit.
<b>Effect:</b> Take control of the target when the command is available.
<b>Effect:</b> Spend the controlling hero's remaining AP to command the linked unit this round. Control does not create a new controlled unit; Raise Dead establishes that relationship.
<b>Selection:</b> choose the acting unit and control target.
<b>Selection:</b> choose Control on the controlling hero; the linked unit is the target.
### Reinforce
<b>Unit requirements:</b> reinforcements available to enter the battle.
<b>Unit requirements:</b> an on-map unit with enough AP and a friendly reserve unit available to enter the battle.
<b>Action Point cost:</b> 5 AP; consumes all remaining AP.
<b>Available when:</b> the battle state allows reinforcement and there is a valid entry option.
<b>Available when:</b> fewer than 10 friendly on-map units have attached heroes, and the reserve unit has an empty, non-burning hex in its designated starting area.
<b>Effect:</b> Bring reinforcements into the battle.
<b>Effect:</b> Spend the acting unit's activation and move the selected reserve unit onto the chosen entry hex as a normal active unit.
<b>Selection:</b> choose the reinforcement option and entry placement.
### Brave Water
<b>Unit requirements:</b> a unit that can attempt brave-water movement. [Ranger](/heroes/#ranger) is not required, but Rangers get an odds bonus.
<b>Unit requirements:</b> a unit with an attached hero whose battalion type permits Brave Water. The hero needs at least 10 vigor. [Ranger](/heroes/#ranger) is not required, but Rangers receive a 20-point odds bonus.
<b>Action Point cost:</b> 8 AP; consumes all remaining AP.
<b>Available when:</b> the unit has a valid water path or water target.
<b>Available when:</b> the unit is on land and can reach a believed-empty, traversable land hex across water.
<b>Effect:</b> Move through water despite the usual danger or restriction.
<b>Effect:</b> Make the displayed crossing roll. Agility and Wisdom improve the chance; armament and bad weather reduce it. Success moves the unit across the water, unless a hidden occupant triggers an ambush on arrival. Failure leaves the unit in place. The attempt always stuns the unit for 1 round and can cost up to 20% of its troops, armament, and hero vigor; safer crossings reduce the possible losses.
<b>Selection:</b> choose the unit and water movement target.
### Release Unit
<b>Unit requirements:</b> a unit that can release a constrained target.
<b>Unit requirements:</b> a hero who currently controls a summoned unit, such as Undead raised by a Necromancer.
<b>Action Point cost:</b> requires more than 0 AP; execution sets both the controlling unit and released unit to 0 AP.
<b>Available when:</b> a controlled, held, or otherwise constrained unit can be released.
<b>Available when:</b> the controlling hero has more than 0 AP and still has a controlled unit.
<b>Effect:</b> Release the constrained unit.
<b>Effect:</b> End the control relationship without a roll. The released unit becomes uncontrolled, targets its former controller, and remains on the battlefield. Both units end their activations.
<b>Selection:</b> choose the acting unit and unit to release.
<b>Selection:</b> choose Release Unit on the controlling hero; the controlled unit is the target.
### End Turn and Setup
@@ -407,21 +421,21 @@ The follow-up list is a planning aid, not a complete list of everything the unit
<b>Action Point cost:</b> 5 AP; consumes all remaining AP.
<b>Available when:</b> the engineer can fortify its current position.
<b>Available when:</b> the Engineer is not already fortified, is standing on fortifiable terrain, and has enough AP.
<b>Effect:</b> Have the unit fortify its current position.
<b>Effect:</b> Mark the unit as fortified, reducing damage taken by 10%. Moving removes fortification; being hit by Reduce or Meteor also removes it, as does taking fire damage at the end of the unit's turn.
<b>Selection:</b> choose the engineer unit.
### Become Outlaw
<b>Unit requirements:</b> a unit or hero eligible for the outlaw consequence.
<b>Unit requirements:</b> a unit with an attached hero that is not allowed to flee normally.
<b>Action Point cost:</b> 5 AP through the flee/outlaw action.
<b>Available when:</b> the battle result or tactical state allows the outlaw transition.
<b>Available when:</b> the hero has enough AP, cannot flee, and the battle permits the outlaw outcome.
<b>Effect:</b> Mark the unit or hero as becoming an outlaw as a battle consequence.
<b>Effect:</b> Use the same displayed escape roll as Flee. Success removes the unit from battle and records the hero as an outlaw. Failure removes the unit as captured.
<b>Selection:</b> choose the outlaw command when offered.
@@ -431,8 +445,8 @@ The follow-up list is a planning aid, not a complete list of everything the unit
<b>Action Point cost:</b> 5 AP; consumes all remaining AP.
<b>Available when:</b> prisoners can be moved out of danger.
<b>Available when:</b> at least one hostile hero has already been captured, and the Warden is allowed to flee and has enough AP.
<b>Effect:</b> Move prisoners out of danger when the battlefield state allows prisoner evacuation.
<b>Effect:</b> Remove the Warden's unit from battle as evacuated and secure every captured hostile hero for the Warden's player. The evacuation succeeds automatically.
<b>Selection:</b> choose the warden unit and evacuation command.
+7 -7
View File
@@ -2,18 +2,18 @@ module github.com/nolen777/eagle0
go 1.25.0
toolchain go1.26.4
toolchain go1.26.5
require (
github.com/aws/aws-sdk-go-v2 v1.42.0
github.com/aws/aws-sdk-go-v2/config v1.32.25
github.com/aws/aws-sdk-go-v2/credentials v1.19.24
github.com/aws/aws-sdk-go-v2/service/s3 v1.103.3
github.com/aws/aws-sdk-go-v2 v1.42.1
github.com/aws/aws-sdk-go-v2/config v1.32.30
github.com/aws/aws-sdk-go-v2/credentials v1.19.29
github.com/aws/aws-sdk-go-v2/service/s3 v1.105.1
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
golang.org/x/sys v0.46.0
google.golang.org/grpc v1.81.1
golang.org/x/sys v0.47.0
google.golang.org/grpc v1.82.0
google.golang.org/protobuf v1.36.11
)
+58
View File
@@ -4,34 +4,54 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=
github.com/aws/aws-sdk-go-v2 v1.32.8 h1:cZV+NUS/eGxKXMtmyhtYPJ7Z4YLoI/V8bkTdRZfYhGo=
github.com/aws/aws-sdk-go-v2 v1.32.8/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA=
github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K0ek=
github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 h1:p1BBrg/Hhp6uK7zpejeI8QFXHJeC/mynzi04Sl03k9g=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13/go.mod h1:8cIfkE9MDhkRZGpQ22aV6/lkYeYSozpz16Smrs5x4Ls=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 h1:3IZY0XAJquT3aHzbkHfPzy4ACPcEjVG0x87KOwtpqGY=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14/go.mod h1:zwM6veDkhGgQFqkBy+uT28AAYpLu+uFMlPl+rCg/73E=
github.com/aws/aws-sdk-go-v2/config v1.28.10 h1:fKODZHfqQu06pCzR69KJ3GuttraRJkhlC8g80RZ0Dfg=
github.com/aws/aws-sdk-go-v2/config v1.28.10/go.mod h1:PvdxRYZ5Um9QMq9PQ0zHHNdtKK+he2NHtFCUFMXWXeg=
github.com/aws/aws-sdk-go-v2/config v1.32.25 h1:ACCejvStYoilgwrfegSt5ZntCbPrk52qfwyNcnl3omM=
github.com/aws/aws-sdk-go-v2/config v1.32.25/go.mod h1:LJyU8sDRbXUxFn8xMJIGP+v9QYYwveNLI8a/giAOiAs=
github.com/aws/aws-sdk-go-v2/config v1.32.29 h1:BcMHHnpiWKogf+gGfpj3K1w+Sktz29XDo/cPSAPO3FU=
github.com/aws/aws-sdk-go-v2/config v1.32.29/go.mod h1:+Kbhn8Es4kPUph3F/0W7avykytc+Jh2Ld9/msv9ljV4=
github.com/aws/aws-sdk-go-v2/config v1.32.30 h1:XwsEzpTJfQYJbFicz/QMLwAZdyeNVVoOEkbF7R3gPJk=
github.com/aws/aws-sdk-go-v2/config v1.32.30/go.mod h1:Ud32SuMc+/9BGxfpSVld7HrE2o05JwKmXY4M3jOQNZU=
github.com/aws/aws-sdk-go-v2/credentials v1.17.51 h1:F/9Sm6Y6k4LqDesZDPJCLxQGXNNHd/ZtJiWd0lCZKRk=
github.com/aws/aws-sdk-go-v2/credentials v1.17.51/go.mod h1:TKbzCHm43AoPyA+iLGGcruXd4AFhF8tOmLex2R9jWNQ=
github.com/aws/aws-sdk-go-v2/credentials v1.19.24 h1:2hQqYCV9yqyePQ9o6dCrZc/zO8U3TwPr9mIKlZnPu/I=
github.com/aws/aws-sdk-go-v2/credentials v1.19.24/go.mod h1:IDwpACtwqHLISdzfwUUNq4P9DsB/h5BLg4FwJPNfqFY=
github.com/aws/aws-sdk-go-v2/credentials v1.19.28 h1:zTXJSsNcoO91/mTXsZoYf0AK8dvNPiA58/VtyGXR+wM=
github.com/aws/aws-sdk-go-v2/credentials v1.19.28/go.mod h1:Kd9E0JzDBW/q1xbsHFrev/GnbAf5J0Ng8xoyc7HZ91Q=
github.com/aws/aws-sdk-go-v2/credentials v1.19.29 h1:WHZGssHH887cO0ox07SIQZsFx3MKD4ps6w0xUEmnKYQ=
github.com/aws/aws-sdk-go-v2/credentials v1.19.29/go.mod h1:Mhl0xR6zjguiuj00XRx2wMx22sAltk7oya39sT7fdg8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23 h1:IBAoD/1d8A8/1aA8g4MBVtTRHhXRiNAgwdbo/xRM2DI=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23/go.mod h1:vfENuCM7dofkgKpYzuzf1VT1UKkA/YL3qanfBn7HCaA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 h1:r6qZHbT+wxgWO/e9vYNUEtg7lv5+UN3pRqKhLXvnArg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29/go.mod h1:QRnaRcTVGKPGRy8w78HMQtKUGRYcnMZAANATkeVA6Mo=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30 h1:/hi1JADLEW9YYryEz1w4GQu0EtP23pP553Cf9KgsDV4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30/go.mod h1:/3AOgy4K17Dm4ucMZVC/MJkzy5kmfKUcINRHZyo0koQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 h1:jSJjSBzw8VDIbWv+mmvBSP8ezsztMYJGH+eKqi9AmNs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27/go.mod h1:/DAhLbFRgwhmvJdOfSm+WwikZrCuUJiA4WgJG0fTNSw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 h1:xM/Is9cKMHa8Jj8zkvWhvrFkZsXJV9E+BB4g0HW0duQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30/go.mod h1:WueJeNDZvK1fMYEWJIkcivBfEzUkTpBhzlrUKKY8EuA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 h1:l+X4K77Dui85pIj5foXDhPlnqcNRG2QUyvca300lXh8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27/go.mod h1:KvZXSFEXm6x84yE8qffKvT3x8J5clWnVFXphpohhzJ8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 h1:jn46zC9LdsVR/ZpMIJqMqb8hHv31BlLx3ulVqNspUOk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30/go.mod h1:1hTMsAgbdS/AtUi4bw8+gUuh1pceo+eXRLfpSuSQj3M=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
@@ -40,46 +60,78 @@ github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27 h1:AmB5QxnD+fBFrg9LcqzkgF/CaYv
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27/go.mod h1:Sai7P3xTiyv9ZUYO3IFxMnmiIP759/67iQbU4kdmkyU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 h1:VTGy885W5DKBxWRUJbym9hytNaYzsyaPkCHGRRMAOhU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30/go.mod h1:AS0HycUvJRFvTt613AYDOgO2jzw+00cVSMny8XB3yMY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31 h1:3GUprIsfmGcC5SACIyB0e7E0BM1O1b3Erl5CePYIAeQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31/go.mod h1:7PuV1yl5e2xnUbm+RqvVg5i2iBM8EyijZNoI9wsOoOc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 h1:ZD2+BSw9vFsNlKYIasSNt3uDbjqqXIBcM13UJv/Lx2k=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12/go.mod h1:Ms4zlcVBbXbiP7EVLhl+lgjvA/a7YphqQ3Ih3174EmI=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 h1:mbRIur/BiHK6SKPjoBIXSE/hJ6g6JGRLuxQy1jGjlN4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13/go.mod h1:ITg9em2KbJx1s0y4aqRX5OYWG6HBZ5TVR//OdpEZ2CQ=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.8 h1:iwYS40JnrBeA9e9aI5S6KKN4EB2zR4iUVYN0nwVivz4=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.8/go.mod h1:Fm9Mi+ApqmFiknZtGpohVcBGvpTu542VC4XO9YudRi0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 h1:V51LGlOq/1VsDsHUdoklAQi7rMmx4qQubvFYAlP2254=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22/go.mod h1:4Pzhyz8hJOm2bepgl+NjvRx8vlUFAIIvJnZ/MkcNPpU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.23 h1:9Fjh6fi/U5JEStVZijmaMpUwE/gvBJj7x2B/PjbO9To=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.23/go.mod h1:iMoT2f1tClxrWAAnKCXjZQ6LOmfLrMG14wmnWpM+F14=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8 h1:cWno7lefSH6Pp+mSznagKCgfDGeZRin66UvYUqAkyeA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8/go.mod h1:tPD+VjU3ABTBoEJ3nctu5Nyg4P4yjqSH5bJGGkY4+XE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 h1:DRebniUGZ2MqiiIVmQJ04vIXr918hubdHMnarSLEWyU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29/go.mod h1:LfRkPCD8YHDM2E5eTkos2UpwYeZnBcVarTa8L59bJHA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 h1:/Z5jmNrKsSD7EmDjzAPsm/3L9IuOkzaynklJZ1qX7S4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30/go.mod h1:lEzEZnOosE7zi8Z6royW1cFJTD9fpab4Ul1SBrllewk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.8 h1:/Mn7gTedG86nbpjT4QEKsN1D/fThiYe1qvq7WsBGNHg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.8/go.mod h1:Ae3va9LPmvjj231ukHB6UeT8nS7wTPfC3tMZSZMwNYg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29 h1:hiME6pBzC7OTl9LMtlyTWBuEl1f4QBcUmFDKC7MLXtc=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29/go.mod h1:G7RP+uhagpKtKhd1BM9N6JQqjCcGEU47K5lBVZQyRQw=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31 h1:uao4A3QZ5UmB326V6KF+qRpv9Tjz7IlnlnTbbANntlU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31/go.mod h1:I/1+z0VwL1GhQyLgkoHDlygpUZ+iTAwOQ/NsftiUL2I=
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2 h1:a7aQ3RW+ug4IbhoQp29NZdc7vqrzKZZfWZSaQAXOZvQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2/go.mod h1:xMekrnhmJ5aqmyxtmALs7mlvXw5xRh+eYjOjvrIIFJ4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.103.3 h1:JRseEu/vIDMaWis4bSw0QbXL+cvIGc1XnX076H5ZXLE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.103.3/go.mod h1:77ZAgynvx1txMvDG8gGWoWkO1augYDxkp9JElWFgjQU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0 h1:XptwLL+UHXgafYMIHTy59IRovLbhz3znkxY2uS/pbXU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0/go.mod h1:zdmCoFO/dSI7GlrwsPqFJI+WlFnSU4Tc8TJnlXrM1Do=
github.com/aws/aws-sdk-go-v2/service/s3 v1.105.1 h1:LkBKxAOE5WXjlFuFZqPG1rREnl6I6QCMElcXFDEidos=
github.com/aws/aws-sdk-go-v2/service/s3 v1.105.1/go.mod h1:zdmCoFO/dSI7GlrwsPqFJI+WlFnSU4Tc8TJnlXrM1Do=
github.com/aws/aws-sdk-go-v2/service/signin v1.2.0 h1:3nXpRcFwRCW8n7HgO2QGy0Dc20eQNfBuUemGQhpF8m8=
github.com/aws/aws-sdk-go-v2/service/signin v1.2.0/go.mod h1:LxYujSTLPRlp2vTtcUO/+1ilrew8ytt6SvQyOgejzFQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.4.0 h1:sLzmJGCMv+C8KqiJgEqDLB6vxaJGmobRh4rr//ZpA3w=
github.com/aws/aws-sdk-go-v2/service/signin v1.4.0/go.mod h1:mxC0nT/C8wMMS97DemZPzvUZxvIt+2Iq+eS3JdFZGgg=
github.com/aws/aws-sdk-go-v2/service/signin v1.4.1 h1:V7ZZ300WPXGjvkyore5DGe0ljVPOxCXie/thWdtSBXE=
github.com/aws/aws-sdk-go-v2/service/signin v1.4.1/go.mod h1:mxC0nT/C8wMMS97DemZPzvUZxvIt+2Iq+eS3JdFZGgg=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.9 h1:YqtxripbjWb2QLyzRK9pByfEDvgg95gpC2AyDq4hFE8=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.9/go.mod h1:lV8iQpg6OLOfBnqbGMBKYjilBlf633qwHnBEiMSPoHY=
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 h1:ey1XLTYXb9PcLt4535632o5kCGXNXEhNb620Dqwuylo=
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3/go.mod h1:Lk7PlmoTYryQmyBG0EXqj5BcUbj3whXdU2s3yGI3EAc=
github.com/aws/aws-sdk-go-v2/service/sso v1.32.0 h1:qjMmry/cBDee1E/2gyvel0uRYCi3mwRZ2hf6N+GAodo=
github.com/aws/aws-sdk-go-v2/service/sso v1.32.0/go.mod h1:u8af9Nqkmqnr96f7v9nHqzZT9XBwbXEkTiqT4ROuJSE=
github.com/aws/aws-sdk-go-v2/service/sso v1.32.1 h1:gYFYh4iLLcAOJRLNPY2aD2g9DIhKn4eof8UkIrr1rTk=
github.com/aws/aws-sdk-go-v2/service/sso v1.32.1/go.mod h1:u8af9Nqkmqnr96f7v9nHqzZT9XBwbXEkTiqT4ROuJSE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8 h1:6dBT1Lz8fK11m22R+AqfRsFn8320K0T5DTGxxOQBSMw=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8/go.mod h1:/kiBvRQXBc6xeJTYzhSdGvJ5vm1tjaDEjH+MSeRJnlY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 h1:yLr03zQE/5Eu5l3QU0Si+xMbLMbSDF2YXsigqXngs6g=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6/go.mod h1:Q5N6icH+KJZDLh+ESNwzdv6cZ6vLFF/egy3IOxWhmz4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.0 h1:fpOlDPI55HdszaxapEGk6HsGosOUaM2YPWJpjMgp8UI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.0/go.mod h1:DMPWJBjYs6+3+f/qhBFEFPPlQ6NlhWjai3dJNvipJ84=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1 h1:arjT9Cm3/WYbGmD5TUZHk4UQn4Lle1fUNZs5FC6CtF0=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1/go.mod h1:DMPWJBjYs6+3+f/qhBFEFPPlQ6NlhWjai3dJNvipJ84=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.6 h1:VwhTrsTuVn52an4mXx29PqRzs2Dvu921NpGk7y43tAM=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.6/go.mod h1:+8h7PZb3yY5ftmVLD7ocEoE98hdc8PoKS0H3wfx1dlc=
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 h1:VrIhKRCSK1umelSgB9RghvA9RTUYeQffyAS5ApXehNI=
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc=
github.com/aws/aws-sdk-go-v2/service/sts v1.44.0 h1:bLZ0PolJ8J+HkJHztcXORUpHXBye2U8298lCEMi6ZCU=
github.com/aws/aws-sdk-go-v2/service/sts v1.44.0/go.mod h1:9gdl4RrflIdpDb2TlXshWgR1F9TeCkvqDx77Vpr4Z/Q=
github.com/aws/aws-sdk-go-v2/service/sts v1.44.1 h1:RvfHDg+xvAeZ+5741vUEjpOVtYSIm93W2zhx10Xtydw=
github.com/aws/aws-sdk-go-v2/service/sts v1.44.1/go.mod h1:9gdl4RrflIdpDb2TlXshWgR1F9TeCkvqDx77Vpr4Z/Q=
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/aws/smithy-go v1.27.1 h1:4T340VFndXtADGF52gYa1POyL7s9E4Z1OeZ1hCscIw8=
github.com/aws/smithy-go v1.27.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY=
github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
@@ -121,6 +173,8 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ=
go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
@@ -149,6 +203,8 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
@@ -174,6 +230,8 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
+292 -130
View File
@@ -4,9 +4,9 @@
"com.amazonaws:aws-lambda-java-core": -179547809,
"com.amazonaws:aws-lambda-java-events": -1998571449,
"com.google.android:annotations": 1910180409,
"com.google.api.grpc:proto-google-common-protos": -2106826238,
"com.google.auth:google-auth-library-credentials": 1149797254,
"com.google.auth:google-auth-library-oauth2-http": -2102758232,
"com.google.api.grpc:proto-google-common-protos": 391189699,
"com.google.auth:google-auth-library-credentials": 43407369,
"com.google.auth:google-auth-library-oauth2-http": 1085819179,
"com.google.auto.value:auto-value": 1146221104,
"com.google.auto.value:auto-value-annotations": -1101276935,
"com.google.code.findbugs:jsr305": 495355163,
@@ -25,11 +25,11 @@
"com.squareup.okio:okio": 795722926,
"com.thesamet.scalapb:compilerplugin_3": 603908795,
"com.thesamet.scalapb:lenses_3": -1873974815,
"com.thesamet.scalapb:protoc-bridge_3": -1260006911,
"com.thesamet.scalapb:protoc-bridge_3": -1482619205,
"com.thesamet.scalapb:scalapb-json4s_3": -772830803,
"com.thesamet.scalapb:scalapb-runtime-grpc_3": -2026114331,
"com.thesamet.scalapb:scalapb-runtime_3": -1281968352,
"com.zaxxer:HikariCP": 1858991878,
"com.zaxxer:HikariCP": 1991920386,
"io.netty:netty-buffer": 732437843,
"io.netty:netty-codec": -1199307927,
"io.netty:netty-codec-http": -1456497900,
@@ -39,26 +39,27 @@
"io.netty:netty-handler": -1970529483,
"io.netty:netty-handler-proxy": -195328652,
"io.netty:netty-resolver": -548277043,
"io.netty:netty-tcnative-boringssl-static": 2033070843,
"io.netty:netty-tcnative-classes": 2023773715,
"io.netty:netty-tcnative-boringssl-static": -754344644,
"io.netty:netty-tcnative-classes": -763641772,
"io.netty:netty-transport": -1962486730,
"io.netty:netty-transport-native-epoll": -1215800119,
"io.netty:netty-transport-native-unix-common": -818773192,
"io.netty:netty-transport-native-epoll": 833393060,
"io.netty:netty-transport-native-unix-common": 1230419987,
"io.opencensus:opencensus-api": 2042364617,
"io.opencensus:opencensus-contrib-grpc-metrics": 317094357,
"io.perfmark:perfmark-api": 1019157794,
"io.sentry:sentry": 563160305,
"io.sentry:sentry": -511720268,
"javax.xml.bind:jaxb-api": -1735500680,
"junit:junit": -744267592,
"org.checkerframework:checker-qual": -819031361,
"org.codehaus.mojo:animal-sniffer-annotations": -1634689353,
"org.codehaus.mojo:animal-sniffer-annotations": 852823480,
"org.json4s:json4s-ast_3": 687176866,
"org.json4s:json4s-core_3": 977579605,
"org.json4s:json4s-native_3": -123383795,
"org.postgresql:postgresql": -1963352047,
"org.mockito:mockito-core": -1925148636,
"org.postgresql:postgresql": -1283293677,
"org.reactivestreams:reactive-streams": 509083534,
"org.scala-lang:scala3-staging_3": -360398695,
"org.scalameta:scalafmt-cli_2.13": 243143182,
"org.scalameta:scalafmt-cli_2.13": 923201552,
"org.scalamock:scalamock_3": -1047317697,
"org.slf4j:slf4j-api": -2005163028,
"org.slf4j:slf4j-simple": 1999565066,
@@ -85,8 +86,8 @@
"com.google.android:annotations": 769933135,
"com.google.api.grpc:proto-google-common-protos": 1506674904,
"com.google.api:api-common": 1729001290,
"com.google.auth:google-auth-library-credentials": 563614471,
"com.google.auth:google-auth-library-oauth2-http": -1252786723,
"com.google.auth:google-auth-library-credentials": -426926107,
"com.google.auth:google-auth-library-oauth2-http": -307063036,
"com.google.auto.value:auto-value": -1399392164,
"com.google.auto.value:auto-value-annotations": 1092025564,
"com.google.code.findbugs:jsr305": -1038659426,
@@ -95,8 +96,8 @@
"com.google.guava:failureaccess": -56291903,
"com.google.guava:guava": 2119270017,
"com.google.guava:listenablefuture": 1588902908,
"com.google.http-client:google-http-client": 1756875461,
"com.google.http-client:google-http-client-gson": 1267916085,
"com.google.http-client:google-http-client": -174274814,
"com.google.http-client:google-http-client-gson": 1658743026,
"com.google.j2objc:j2objc-annotations": -2074422376,
"com.google.protobuf:protobuf-java": -1065767575,
"com.google.re2j:re2j": -461460138,
@@ -110,15 +111,15 @@
"com.squareup.okhttp:okhttp": -1936180231,
"com.squareup.okio:okio": 1978550531,
"com.squareup.okio:okio-jvm": -1730532943,
"com.thesamet.scalapb:compilerplugin_3": -1073075945,
"com.thesamet.scalapb:lenses_3": 791427023,
"com.thesamet.scalapb:protoc-bridge_3": -1984150046,
"com.thesamet.scalapb:protoc-gen_3": 1860775213,
"com.thesamet.scalapb:scalapb-json4s_3": 1579843487,
"com.thesamet.scalapb:scalapb-runtime-grpc_3": 97373115,
"com.thesamet.scalapb:scalapb-runtime_3": 818778125,
"com.typesafe:config": -1791448454,
"com.zaxxer:HikariCP": -704610647,
"com.thesamet.scalapb:compilerplugin_3": -986481869,
"com.thesamet.scalapb:lenses_3": 1632980089,
"com.thesamet.scalapb:protoc-bridge_3": -1510160103,
"com.thesamet.scalapb:protoc-gen_3": 36781197,
"com.thesamet.scalapb:scalapb-json4s_3": 1449885529,
"com.thesamet.scalapb:scalapb-runtime-grpc_3": 1266714255,
"com.thesamet.scalapb:scalapb-runtime_3": 318411326,
"com.typesafe:config": 1779132647,
"com.zaxxer:HikariCP": 337938184,
"commons-codec:commons-codec": 1377992676,
"commons-io:commons-io": 669567296,
"dev.dirs:directories": 1716880629,
@@ -149,28 +150,30 @@
"io.netty:netty-handler": 1617793819,
"io.netty:netty-handler-proxy": 1503431664,
"io.netty:netty-resolver": -533324791,
"io.netty:netty-tcnative-boringssl-static": -1922162184,
"io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64": -310918884,
"io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64": 1582797105,
"io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64": -1315559501,
"io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64": -95251834,
"io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64": -1202154110,
"io.netty:netty-tcnative-classes": -1020773440,
"io.netty:netty-tcnative-boringssl-static": 1274713091,
"io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64": 423479165,
"io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64": 1509601219,
"io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64": -1452923369,
"io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64": -1682600003,
"io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64": 741676114,
"io.netty:netty-tcnative-classes": -2096215842,
"io.netty:netty-transport": -1390736500,
"io.netty:netty-transport-classes-epoll": 704589083,
"io.netty:netty-transport-native-epoll:jar:linux-x86_64": -2050990961,
"io.netty:netty-transport-native-epoll:jar:linux-x86_64": -933296886,
"io.netty:netty-transport-native-unix-common": -2090924105,
"io.opencensus:opencensus-api": -1002511030,
"io.opencensus:opencensus-contrib-grpc-metrics": -1797408624,
"io.opencensus:opencensus-contrib-http-util": -541858674,
"io.perfmark:perfmark-api": -752483303,
"io.sentry:sentry": 1564972879,
"io.sentry:sentry": -1910119764,
"javax.activation:javax.activation-api": -217704356,
"javax.annotation:javax.annotation-api": 193132517,
"javax.inject:javax.inject": -1284481446,
"javax.xml.bind:jaxb-api": 1600326587,
"joda-time:joda-time": 230482511,
"junit:junit": 712711302,
"net.bytebuddy:byte-buddy": 105401824,
"net.bytebuddy:byte-buddy-agent": -1761476423,
"net.java.dev.jna:jna": 943627808,
"net.java.dev.jna:jna-platform": 1004830334,
"org.apache.commons:commons-compress": -308222379,
@@ -181,7 +184,7 @@
"org.apache.httpcomponents:httpclient": -504016800,
"org.apache.httpcomponents:httpcore": -353060362,
"org.apache.xbean:xbean-reflect": -1764161877,
"org.checkerframework:checker-qual": -53559553,
"org.checkerframework:checker-qual": 1032417002,
"org.codehaus.mojo:animal-sniffer-annotations": -1570976252,
"org.codehaus.plexus:plexus-archiver": 766678324,
"org.codehaus.plexus:plexus-classworlds": -543968237,
@@ -194,45 +197,47 @@
"org.jetbrains.kotlin:kotlin-stdlib-common": 11605,
"org.jetbrains:annotations": 116138949,
"org.jline:jline:jar:jdk8": -1133032957,
"org.json4s:json4s-ast_3": 689482233,
"org.json4s:json4s-core_3": 481951064,
"org.json4s:json4s-jackson-core_3": 813804735,
"org.json4s:json4s-native-core_3": -289663551,
"org.json4s:json4s-native_3": -2093359247,
"org.json4s:json4s-ast_3": 218697247,
"org.json4s:json4s-core_3": 888852031,
"org.json4s:json4s-jackson-core_3": -1708586056,
"org.json4s:json4s-native-core_3": -1556364554,
"org.json4s:json4s-native_3": 1810593063,
"org.jspecify:jspecify": -1402924792,
"org.mockito:mockito-core": 534163387,
"org.objenesis:objenesis": 684097820,
"org.ow2.asm:asm": 1177470613,
"org.postgresql:postgresql": -570711868,
"org.postgresql:postgresql": -1200274402,
"org.reactivestreams:reactive-streams": -1996658890,
"org.scala-lang.modules:scala-collection-compat_2.13": -2012838353,
"org.scala-lang.modules:scala-collection-compat_3": -915569507,
"org.scala-lang.modules:scala-collection-compat_3": 2001700859,
"org.scala-lang.modules:scala-xml_2.13": -1867291781,
"org.scala-lang:scala-compiler": 993782407,
"org.scala-lang:scala-library": -479379026,
"org.scala-lang:scala-reflect": 2030778053,
"org.scala-lang:scala3-library_3": -340570223,
"org.scala-lang:scala3-library_3": -1714704820,
"org.scala-lang:scala3-staging_3": 1767551804,
"org.scalameta:common2_2.13": 183869992,
"org.scalameta:common_2.13": 1613859331,
"org.scalameta:io_2.13": 1078868274,
"org.scalameta:common2_2.13": 1775009973,
"org.scalameta:common_2.13": -1607493593,
"org.scalameta:io_2.13": 1529358668,
"org.scalameta:mdoc-parser_2.13": 1998129208,
"org.scalameta:metaconfig-core_2.13": 1560822216,
"org.scalameta:metaconfig-pprint_2.13": -13676725,
"org.scalameta:metaconfig-typesafe-config_2.13": -1923428474,
"org.scalameta:munit-diff_2.13": 612923316,
"org.scalameta:parsers_2.13": -2012847908,
"org.scalameta:scalafmt-cli_2.13": -788023559,
"org.scalameta:scalafmt-config_2.13": 761717294,
"org.scalameta:scalafmt-core_2.13": 1878102965,
"org.scalameta:scalafmt-dynamic-core_2.13": -1900839476,
"org.scalameta:scalafmt-dynamic_2.13": 860328364,
"org.scalameta:scalafmt-interfaces": 651108583,
"org.scalameta:scalafmt-macros_2.13": -1771055183,
"org.scalameta:scalafmt-sysops_2.13": -1263777532,
"org.scalameta:scalameta_2.13": -428808011,
"org.scalameta:metaconfig-typesafe-config_2.13": 389481839,
"org.scalameta:munit-diff_2.13": 667211464,
"org.scalameta:parsers_2.13": -732548017,
"org.scalameta:scalafmt-cli_2.13": -872730967,
"org.scalameta:scalafmt-config_2.13": -1220516276,
"org.scalameta:scalafmt-core_2.13": -518465330,
"org.scalameta:scalafmt-dynamic-core_2.13": 1088145997,
"org.scalameta:scalafmt-dynamic_2.13": -193332653,
"org.scalameta:scalafmt-interfaces": 2029532209,
"org.scalameta:scalafmt-macros_2.13": 1019057318,
"org.scalameta:scalafmt-sysops_2.13": -919350628,
"org.scalameta:scalameta_2.13": 494629884,
"org.scalameta:svm-subs": -181782616,
"org.scalameta:trees2_2.13": -1248113756,
"org.scalameta:trees_2.13": -1233441989,
"org.scalamock:scalamock_3": -9538025,
"org.scalameta:trees2_2.13": 1085082114,
"org.scalameta:trees_2.13": -1981634150,
"org.scalamock:scalamock_3": 23905729,
"org.slf4j:slf4j-api": -1249720338,
"org.slf4j:slf4j-simple": -1378006603,
"org.tukaani:xz": -1726944826,
@@ -274,12 +279,11 @@
"software.amazon.eventstream:eventstream": 1951806649
},
"conflict_resolution": {
"com.google.api.grpc:proto-google-common-protos:2.63.1": "com.google.api.grpc:proto-google-common-protos:2.64.1",
"com.google.guava:failureaccess:1.0.1": "com.google.guava:failureaccess:1.0.3",
"com.google.j2objc:j2objc-annotations:2.8": "com.google.j2objc:j2objc-annotations:3.1",
"io.netty:netty-transport-native-unix-common:4.1.127.Final": "io.netty:netty-transport-native-unix-common:4.1.135.Final",
"io.netty:netty-transport-native-unix-common:4.1.133.Final": "io.netty:netty-transport-native-unix-common:4.1.135.Final",
"io.opencensus:opencensus-api:0.31.0": "io.opencensus:opencensus-api:0.31.1",
"org.codehaus.mojo:animal-sniffer-annotations:1.26": "org.codehaus.mojo:animal-sniffer-annotations:1.27"
"org.checkerframework:checker-qual:3.49.5": "org.checkerframework:checker-qual:3.55.1"
},
"artifacts": {
"com.amazonaws:aws-lambda-java-core": {
@@ -356,15 +360,15 @@
},
"com.google.auth:google-auth-library-credentials": {
"shasums": {
"jar": "dd7e39b8f5c366b09f8c2d400b7b63c925499df813b0f0d78044abd72098e159"
"jar": "d29934610c3e9eb6c60e95ad05ca34ce486c3867c2f3689548b4aa2c2dd6ec52"
},
"version": "1.40.0"
"version": "1.42.1"
},
"com.google.auth:google-auth-library-oauth2-http": {
"shasums": {
"jar": "67fe2c9df9780d8725c1dff61d2ffbbcb93fdcf15e91c44a5e610a4a69afd151"
"jar": "fb54ada3c0223ffb4470fb6d8fbd55f764588dbd244dc7c05caaf4ab62977898"
},
"version": "1.40.0"
"version": "1.42.1"
},
"com.google.auto.value:auto-value": {
"shasums": {
@@ -416,15 +420,15 @@
},
"com.google.http-client:google-http-client": {
"shasums": {
"jar": "97a0666340956dbc96893bc8cf843a5b57367543f218feddad3ff433fbfdaa68"
"jar": "20aa0948f6ceb5cfa9b2b3adddd9b7e990ab40230e084081642cbb81235d3eef"
},
"version": "2.0.2"
"version": "2.1.0"
},
"com.google.http-client:google-http-client-gson": {
"shasums": {
"jar": "37c8fd5c7869bc4abf3b3219c7e79cb566afa745559772e969c21595199600cd"
"jar": "3cf72c7b88375a34bf721c664f42edc8e096e2370e4126c9b8933c3c92e591f3"
},
"version": "2.0.2"
"version": "2.1.0"
},
"com.google.j2objc:j2objc-annotations": {
"shasums": {
@@ -518,9 +522,9 @@
},
"com.thesamet.scalapb:protoc-bridge_3": {
"shasums": {
"jar": "e7e2f1862f54076b6870bd034a7c16aae7b88cfee3d00b69dbb6b1175108560c"
"jar": "019cee53ec9e4eb6be0f8b72be4c9a97b56ddd780da5128a4db97af4af32e186"
},
"version": "0.9.9"
"version": "0.9.10"
},
"com.thesamet.scalapb:protoc-gen_3": {
"shasums": {
@@ -548,15 +552,15 @@
},
"com.typesafe:config": {
"shasums": {
"jar": "dd6b9fb5da5bc49d31b581703f57050e1e7d403814f62f3832181a7226b36256"
"jar": "47d13e3196c84f2a8ccd014ae563738c3da5b7406b38e03505ec0601a2f4fc37"
},
"version": "1.4.6"
"version": "1.4.9"
},
"com.zaxxer:HikariCP": {
"shasums": {
"jar": "709f378c05756280939ce50fc1b1f1a53bb8e1899dc1b249f21f12703640b48b"
"jar": "2be45c641b841e81df87292e181f2c203500c5f0e9a1676f135927387f3cf40b"
},
"version": "6.3.3"
"version": "7.1.0"
},
"commons-codec:commons-codec": {
"shasums": {
@@ -740,20 +744,20 @@
},
"io.netty:netty-tcnative-boringssl-static": {
"shasums": {
"jar": "ec3b14ceff74c5d7e24a378e64074744cc5e7035c49cf6ca0a4e23dff9713d1f",
"linux-aarch_64": "11dfee82cfcb5c4f596271fe1d56a70fe77b4034e54162d0bd211623b540857f",
"linux-x86_64": "39ac6b1eb4ffc18d5fe9412cc6253a8954bafc8e3ca10109bc370490f73a3673",
"osx-aarch_64": "eef3fc7cdc5e2dc8c5cb50bae11443052bf1ee2f6e8b64378565746d1529f05e",
"osx-x86_64": "e3beaa3ebdb136569528c59aa826d771efadc0ac9db484c6c0e457fa5efd0f07",
"windows-x86_64": "5458b4b502763d49cd2e16c434312687178afac9c164cabf75c4a8bcecf7ac50"
"jar": "ffb1fd42e5952863159f10a370e8c2dcfa3f40615ec4caefaa9ac8b1b1790233",
"linux-aarch_64": "cba3868793eb7ad32963275f32eb3e8932a3d50192557af8abd2516dab8b8956",
"linux-x86_64": "8e4bf375ae8b82fe657f54d142beb35d115d8294ec5d23a9c09afc8a93dd4928",
"osx-aarch_64": "10c6dd7eee84c099ea9e40ccbd933b501c665738d610627c5390d729fc978a47",
"osx-x86_64": "b5a5048cff2a64641cbbe9fcfffbf032ef506b4d2836c8ba9e689b3c461b24fc",
"windows-x86_64": "dee808e0dd5b4c14b4e8bd8d116dff347163b32574b2fb11a2ade476f7d03628"
},
"version": "2.0.74.Final"
"version": "2.0.75.Final"
},
"io.netty:netty-tcnative-classes": {
"shasums": {
"jar": "194874cf723794dd409fd1e728cd91ffbcff0584d73b4d8a1ad0d69d04acf9b3"
"jar": "2d5a6a8be78bba7782eec385ba178b12f5bfc6837771fd3bb8c1d7cd18b6911c"
},
"version": "2.0.74.Final"
"version": "2.0.75.Final"
},
"io.netty:netty-transport": {
"shasums": {
@@ -769,9 +773,9 @@
},
"io.netty:netty-transport-native-epoll": {
"shasums": {
"linux-x86_64": "3d03f27eea1cd23357a56a96e1eeedfeb3d74fa0ba4d5c36a862bd035056275b"
"linux-x86_64": "fdc76fe46e7218ef54547c1002aff69c4597291cc3ef34322d6f1a2f3090c880"
},
"version": "4.1.127.Final"
"version": "4.1.133.Final"
},
"io.netty:netty-transport-native-unix-common": {
"shasums": {
@@ -805,9 +809,9 @@
},
"io.sentry:sentry": {
"shasums": {
"jar": "8f3d16c54f013694fd987689a2b37fa6ed7767b95de79d1949a0e24a1d62cc1a"
"jar": "a5477fab0018a68c335565c034302b12cff3b0cb56e4a0ffa5f8f47840746d83"
},
"version": "8.43.2"
"version": "8.48.0"
},
"javax.activation:javax.activation-api": {
"shasums": {
@@ -845,6 +849,18 @@
},
"version": "4.13.2"
},
"net.bytebuddy:byte-buddy": {
"shasums": {
"jar": "42ba43dcccd8d9d77bfe8776a83e72b67f1fa52c4038a98629e7d288b648da4e"
},
"version": "1.12.8"
},
"net.bytebuddy:byte-buddy-agent": {
"shasums": {
"jar": "18faf4f33893c3e883a3220adfdab25d6ceff9e46b1ad760a5b6f83df9373307"
},
"version": "1.12.8"
},
"net.java.dev.jna:jna": {
"shasums": {
"jar": "0c8eb7acf67261656d79005191debaba3b6bf5dd60a43735a245429381dbecff"
@@ -907,9 +923,9 @@
},
"org.checkerframework:checker-qual": {
"shasums": {
"jar": "508c83c62c344f6f7ee28f47b88a8797d6116d043bfd1ca0576c828dd1df2880"
"jar": "857658c8bdcbff794949a8d9922f88d63ee3751c203c7cd7d21ea97f9e745288"
},
"version": "3.49.5"
"version": "3.55.1"
},
"org.codehaus.mojo:animal-sniffer-annotations": {
"shasums": {
@@ -1019,6 +1035,18 @@
},
"version": "1.0.0"
},
"org.mockito:mockito-core": {
"shasums": {
"jar": "9a5f59ad06d0bb7d0fd2d4a74eb4d66d561ee0327f0febacb7f9cfb5be4abbfb"
},
"version": "4.4.0"
},
"org.objenesis:objenesis": {
"shasums": {
"jar": "03d960bd5aef03c653eb000413ada15eb77cdd2b8e4448886edf5692805e35f3"
},
"version": "3.2"
},
"org.ow2.asm:asm": {
"shasums": {
"jar": "876eab6a83daecad5ca67eb9fcabb063c97b5aeb8cf1fca7a989ecde17522051"
@@ -1027,9 +1055,9 @@
},
"org.postgresql:postgresql": {
"shasums": {
"jar": "1981b31d3993c58702783c1cddf10a34e48c1f413d70ff1cb6def0a143484647"
"jar": "6e0e4cc2d8cae902084f8a2b18728b073a6fd9d1f87c9d8bff8f298c18185b93"
},
"version": "42.7.11"
"version": "42.7.13"
},
"org.reactivestreams:reactive-streams": {
"shasums": {
@@ -1075,9 +1103,9 @@
},
"org.scala-lang:scala3-library_3": {
"shasums": {
"jar": "a16e046f4a9df9e0d0e0bc54bf6298c94d7d867457f7b3b68291a88c568b92d0"
"jar": "2c6eace9158df8ee4485a06128a479d968c1ebfc83ae9b1a394b58429f0d1492"
},
"version": "3.3.7"
"version": "3.3.8"
},
"org.scala-lang:scala3-staging_3": {
"shasums": {
@@ -1087,21 +1115,21 @@
},
"org.scalameta:common2_2.13": {
"shasums": {
"jar": "42a4fae02d82dc7450dddd69ce1d8b8f159119132898af56169f77b8c9a623bd"
"jar": "dc17dfd2a4923f49c85bd69be85f820ba5d57f50a4425b27a8635b592a0c1471"
},
"version": "4.17.0"
"version": "4.17.2"
},
"org.scalameta:common_2.13": {
"shasums": {
"jar": "3b5b1f67809daf6044113737af8aa4cc5c3b36f071950cfcf7d02f4fe6b1d19a"
"jar": "70fd266cd4e6ab3909d329ef051264865fbadeeb86b598672a65439e51cdadd6"
},
"version": "4.17.0"
"version": "4.17.2"
},
"org.scalameta:io_2.13": {
"shasums": {
"jar": "d09dac9887efb0aaf7bbcc5b5e20f7e7e4947800592a44359e2f31b2b6c9699d"
"jar": "5e6edc6b5aa96a595382ffb9e4cd55a6cb302064d388db7fa2168334fd1cb90e"
},
"version": "4.17.0"
"version": "4.17.2"
},
"org.scalameta:mdoc-parser_2.13": {
"shasums": {
@@ -1129,69 +1157,69 @@
},
"org.scalameta:munit-diff_2.13": {
"shasums": {
"jar": "789f02dfc17e62626560d48522d16bc550990a4d722d27b38d215a39dd8c9a5d"
"jar": "79d52c3007ae90ee2c6e156e3b2a5c0cfeb5a0dd67e570a36cb9a9e84f6e1521"
},
"version": "1.3.0"
"version": "1.3.4"
},
"org.scalameta:parsers_2.13": {
"shasums": {
"jar": "c0669f252bf96db52ac32c92bf54f2853b8f192287a827626c3efe51b20f94b3"
"jar": "c789f3cb81b93ddc8d46cf8b36dfd40715d52fe3c1ef2cb4f14bd4b1fc8543be"
},
"version": "4.17.0"
"version": "4.17.2"
},
"org.scalameta:scalafmt-cli_2.13": {
"shasums": {
"jar": "1da5a56393f5fcf01d65c86ca91adc5f8d5448447a75bf9d8b229db5a0a84009"
"jar": "da5379c790d39a52e4c059eba04fdb0e97402d6eb9b5318b9370c7b2f9c08318"
},
"version": "3.11.1"
"version": "3.11.3"
},
"org.scalameta:scalafmt-config_2.13": {
"shasums": {
"jar": "c058aefc9f874c78072fca15a25e72a4d3ab31bc2872266c42cc6e2651f6cbf1"
"jar": "4cb7fd5ac2768203e5ea09e7670d3a5012f5176a573ccf6a92114fcb79f55ab5"
},
"version": "3.11.1"
"version": "3.11.3"
},
"org.scalameta:scalafmt-core_2.13": {
"shasums": {
"jar": "f5f0e48927f0d5a32763f6b82a8427b40231db65a908c48b4438b000562c32d2"
"jar": "dd176a03c2716223977e8c0cd29cef4b7b7dc0a4e3102a8a9bc70bae6e917751"
},
"version": "3.11.1"
"version": "3.11.3"
},
"org.scalameta:scalafmt-dynamic-core_2.13": {
"shasums": {
"jar": "d4efc029f83f7c3ac6f3374b33636c3d38ef24bbb4d04ba300996d0e63991f2b"
"jar": "3c38c49a717f1be67757d1192707ec38442fc84014408010bde746db94bca92a"
},
"version": "3.11.1"
"version": "3.11.3"
},
"org.scalameta:scalafmt-dynamic_2.13": {
"shasums": {
"jar": "101275030c12c0cc8baa9b6b015b93a4500915990a088d964f41fe482366b890"
"jar": "f78b5eb890132266ae0ab0ff05f57caaf07d53d206b6528e9a5a868d14917591"
},
"version": "3.11.1"
"version": "3.11.3"
},
"org.scalameta:scalafmt-interfaces": {
"shasums": {
"jar": "ec8ac6769c5960e8ee04d4fee15a4b82ba6032b3df3f4d725221ef94dd1dad90"
"jar": "e217de8cbc3324f5e2dea7015a25c91f1b155f665a2a8c3e9c981a0a0f8735b0"
},
"version": "3.11.1"
"version": "3.11.3"
},
"org.scalameta:scalafmt-macros_2.13": {
"shasums": {
"jar": "49a4ab5745cc0b54acdb74b3fd6017451d5699d5d327ca1bea0747bc622edc9f"
"jar": "90f69e31e837e393b252f07504f240c142449876f1444e01c727625f1b63acdd"
},
"version": "3.11.1"
"version": "3.11.3"
},
"org.scalameta:scalafmt-sysops_2.13": {
"shasums": {
"jar": "a862c2bd21ba6b83d403a8aca33af541de356f7d39e88c1656bd2cf6a3b28a80"
"jar": "8bdf5ef43760eaba9e7ef5023db7a9714af2e136899e19cb8cf1ecf0355fef0c"
},
"version": "3.11.1"
"version": "3.11.3"
},
"org.scalameta:scalameta_2.13": {
"shasums": {
"jar": "7606f691512cac7e52e1c28146cc95001212aeb3737b44d5ea6f4ec51b9a454c"
"jar": "38123610b842b10010ab3a62cff36f9c1f197a8ace3d84903258580aca4acd55"
},
"version": "4.17.0"
"version": "4.17.2"
},
"org.scalameta:svm-subs": {
"shasums": {
@@ -1201,15 +1229,15 @@
},
"org.scalameta:trees2_2.13": {
"shasums": {
"jar": "c1ad0ac4c5617ee25ad97b72fb2d03c48d5ee89770e7cc70b0e3e984689296ce"
"jar": "0e75bfede5171c07f8a0d8f0ef581a7af27c9b5f6953182783553aa67fe0fb83"
},
"version": "4.17.0"
"version": "4.17.2"
},
"org.scalameta:trees_2.13": {
"shasums": {
"jar": "b45d46d995e166889cbda015f541ca79afd94ada860513438e6c0ab5f4f84591"
"jar": "638b5500b8815094a25936b57188fc616358add563a9337f525522eac16ddf24"
},
"version": "4.17.0"
"version": "4.17.2"
},
"org.scalamock:scalamock_3": {
"shasums": {
@@ -1850,6 +1878,14 @@
"org.json4s:json4s-native-core_3",
"org.scala-lang:scala3-library_3"
],
"org.mockito:mockito-core": [
"net.bytebuddy:byte-buddy",
"net.bytebuddy:byte-buddy-agent",
"org.objenesis:objenesis"
],
"org.postgresql:postgresql": [
"org.checkerframework:checker-qual"
],
"org.scala-lang.modules:scala-collection-compat_2.13": [
"org.scala-lang:scala-library"
],
@@ -2932,6 +2968,50 @@
"org.junit.runners.parameterized",
"org.junit.validator"
],
"net.bytebuddy:byte-buddy": [
"net.bytebuddy",
"net.bytebuddy.agent.builder",
"net.bytebuddy.asm",
"net.bytebuddy.build",
"net.bytebuddy.description",
"net.bytebuddy.description.annotation",
"net.bytebuddy.description.enumeration",
"net.bytebuddy.description.field",
"net.bytebuddy.description.method",
"net.bytebuddy.description.modifier",
"net.bytebuddy.description.type",
"net.bytebuddy.dynamic",
"net.bytebuddy.dynamic.loading",
"net.bytebuddy.dynamic.scaffold",
"net.bytebuddy.dynamic.scaffold.inline",
"net.bytebuddy.dynamic.scaffold.subclass",
"net.bytebuddy.implementation",
"net.bytebuddy.implementation.attribute",
"net.bytebuddy.implementation.auxiliary",
"net.bytebuddy.implementation.bind",
"net.bytebuddy.implementation.bind.annotation",
"net.bytebuddy.implementation.bytecode",
"net.bytebuddy.implementation.bytecode.assign",
"net.bytebuddy.implementation.bytecode.assign.primitive",
"net.bytebuddy.implementation.bytecode.assign.reference",
"net.bytebuddy.implementation.bytecode.collection",
"net.bytebuddy.implementation.bytecode.constant",
"net.bytebuddy.implementation.bytecode.member",
"net.bytebuddy.jar.asm",
"net.bytebuddy.jar.asm.commons",
"net.bytebuddy.jar.asm.signature",
"net.bytebuddy.matcher",
"net.bytebuddy.pool",
"net.bytebuddy.utility",
"net.bytebuddy.utility.dispatcher",
"net.bytebuddy.utility.nullability",
"net.bytebuddy.utility.privilege",
"net.bytebuddy.utility.visitor"
],
"net.bytebuddy:byte-buddy-agent": [
"net.bytebuddy.agent",
"net.bytebuddy.agent.utility.nullability"
],
"net.java.dev.jna:jna": [
"com.sun.jna",
"com.sun.jna.ptr",
@@ -3385,6 +3465,84 @@
"org.jspecify:jspecify": [
"org.jspecify.annotations"
],
"org.mockito:mockito-core": [
"org.mockito",
"org.mockito.codegen",
"org.mockito.configuration",
"org.mockito.creation.instance",
"org.mockito.exceptions.base",
"org.mockito.exceptions.misusing",
"org.mockito.exceptions.stacktrace",
"org.mockito.exceptions.verification",
"org.mockito.exceptions.verification.junit",
"org.mockito.exceptions.verification.opentest4j",
"org.mockito.hamcrest",
"org.mockito.internal",
"org.mockito.internal.configuration",
"org.mockito.internal.configuration.injection",
"org.mockito.internal.configuration.injection.filter",
"org.mockito.internal.configuration.injection.scanner",
"org.mockito.internal.configuration.plugins",
"org.mockito.internal.creation",
"org.mockito.internal.creation.bytebuddy",
"org.mockito.internal.creation.instance",
"org.mockito.internal.creation.proxy",
"org.mockito.internal.creation.settings",
"org.mockito.internal.creation.util",
"org.mockito.internal.debugging",
"org.mockito.internal.exceptions",
"org.mockito.internal.exceptions.stacktrace",
"org.mockito.internal.exceptions.util",
"org.mockito.internal.framework",
"org.mockito.internal.hamcrest",
"org.mockito.internal.handler",
"org.mockito.internal.invocation",
"org.mockito.internal.invocation.finder",
"org.mockito.internal.invocation.mockref",
"org.mockito.internal.junit",
"org.mockito.internal.listeners",
"org.mockito.internal.matchers",
"org.mockito.internal.matchers.apachecommons",
"org.mockito.internal.matchers.text",
"org.mockito.internal.progress",
"org.mockito.internal.reporting",
"org.mockito.internal.runners",
"org.mockito.internal.runners.util",
"org.mockito.internal.session",
"org.mockito.internal.stubbing",
"org.mockito.internal.stubbing.answers",
"org.mockito.internal.stubbing.defaultanswers",
"org.mockito.internal.util",
"org.mockito.internal.util.collections",
"org.mockito.internal.util.concurrent",
"org.mockito.internal.util.io",
"org.mockito.internal.util.reflection",
"org.mockito.internal.verification",
"org.mockito.internal.verification.api",
"org.mockito.internal.verification.argumentmatching",
"org.mockito.internal.verification.checkers",
"org.mockito.invocation",
"org.mockito.junit",
"org.mockito.listeners",
"org.mockito.mock",
"org.mockito.plugins",
"org.mockito.quality",
"org.mockito.session",
"org.mockito.stubbing",
"org.mockito.verification"
],
"org.objenesis:objenesis": [
"org.objenesis",
"org.objenesis.instantiator",
"org.objenesis.instantiator.android",
"org.objenesis.instantiator.annotations",
"org.objenesis.instantiator.basic",
"org.objenesis.instantiator.gcj",
"org.objenesis.instantiator.perc",
"org.objenesis.instantiator.sun",
"org.objenesis.instantiator.util",
"org.objenesis.strategy"
],
"org.ow2.asm:asm": [
"org.objectweb.asm",
"org.objectweb.asm.signature"
@@ -4174,6 +4332,8 @@
"javax.xml.bind:jaxb-api",
"joda-time:joda-time",
"junit:junit",
"net.bytebuddy:byte-buddy",
"net.bytebuddy:byte-buddy-agent",
"net.java.dev.jna:jna",
"net.java.dev.jna:jna-platform",
"org.apache.commons:commons-compress",
@@ -4203,6 +4363,8 @@
"org.json4s:json4s-native-core_3",
"org.json4s:json4s-native_3",
"org.jspecify:jspecify",
"org.mockito:mockito-core",
"org.objenesis:objenesis",
"org.ow2.asm:asm",
"org.postgresql:postgresql",
"org.reactivestreams:reactive-streams",
+24 -4
View File
@@ -6,7 +6,9 @@
"dependencyDashboard": true,
"enabledManagers": [
"bazel-module",
"bazelisk",
"cargo",
"custom.regex",
"github-actions",
"gomod",
"swift"
@@ -18,7 +20,9 @@
{
"description": "Bazel module artifact updates run in GitHub Actions so hosted Renovate does not need unsafe bazel mod deps execution",
"matchManagers": [
"bazel-module"
"bazel-module",
"bazelisk",
"custom.regex"
],
"skipArtifactsUpdate": true
},
@@ -30,17 +34,33 @@
"skipArtifactsUpdate": true
}
],
"customManagers": [
{
"customType": "regex",
"description": "Update the LLVM and Clang toolchain release",
"managerFilePatterns": [
"/^MODULE\\.bazel$/"
],
"matchStrings": [
"LLVM_VERSION = \"(?<currentValue>\\d+\\.\\d+\\.\\d+)\""
],
"depNameTemplate": "llvm/llvm-project",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^llvmorg-(?<version>\\d+\\.\\d+\\.\\d+)$",
"versioningTemplate": "semver"
}
],
"lockFileMaintenance": {
"enabled": true,
"schedule": [
"before 6am on monday"
"* * * * 1"
]
},
"prConcurrentLimit": 3,
"prHourlyLimit": 1,
"prHourlyLimit": 3,
"rebaseWhen": "behind-base-branch",
"schedule": [
"before 6am on monday"
"* * * * 1"
],
"separateMajorMinor": true,
"timezone": "America/Los_Angeles"
@@ -4,6 +4,8 @@
#include "AIWaterCrossingCalculator.hpp"
#include <algorithm>
#include "AIMinimumDistanceAndTarget.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/HexMapHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/HexMapHasher.hpp"
@@ -12,11 +14,27 @@
namespace shardok {
namespace {
constexpr int kMaxWaterCrossingDistance = 1000;
constexpr double kCrossingCreatorDistanceMultiplier = 100.0;
auto RecomputeModifierHash(HexMap *hexMap) -> MapId {
hexMap->mutate_modifier_hash(GetModifierHash(hexMap));
return ActionPointDistancesCache::GetMapId(hexMap);
}
auto CanCreateWaterCrossing(
const net::eagle0::shardok::storage::fb::Unit *unit,
const BattalionTypeGetter &battalionTypeGetter) -> bool {
if (!unit->has_attached_hero()) return false;
const auto profession = unit->attached_hero().profession_info().profession();
const auto &battalionType = battalionTypeGetter(unit->battalion().type());
return profession == net::eagle0::shardok::storage::fb::Profession_ENGINEER ||
(profession == net::eagle0::shardok::storage::fb::Profession_MAGE &&
battalionType->allowsCasting);
}
} // namespace
auto UnitIdsRequiringWaterCrossing(
@@ -94,14 +112,8 @@ auto UnitIdsToCreateWaterCrossing(
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT)
continue;
if (!unit->has_attached_hero()) continue;
const auto profession = unit->attached_hero().profession_info().profession();
const auto &battalionType = battalionTypeGetter(unit->battalion().type());
if (profession == net::eagle0::shardok::storage::fb::Profession_ENGINEER ||
(profession == net::eagle0::shardok::storage::fb::Profession_MAGE &&
battalionType->allowsCasting)) {
if (CanCreateWaterCrossing(unit, battalionTypeGetter)) {
unitIds.push_back(unit->unit_id());
}
}
@@ -314,6 +326,31 @@ auto WaterCrossingScore(
const auto mapId = ActionPointDistancesCache::GetMapId(gameState->hex_map());
// Put a big penalty on the distance for every unit that could create a crossing, on a single
// saturating scale: unavailable (fled/captured/destroyed) and unplaced creators sit at the
// maximum, and live distances (including IMPOSSIBLE) are clamped to it. Losing a crossing
// creator therefore never scores better than any placed position.
for (const auto *unit : *gameState->units()) {
if (unit->player_id() != playerId) continue;
if (!CanCreateWaterCrossing(unit, battalionTypeGetter)) continue;
const auto status = unit->status();
const bool available =
status == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT ||
status == net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT;
int thisDistance = kMaxWaterCrossingDistance;
if (available && unit->location().row() >= 0) {
const auto &battalionType = battalionTypeGetter(unit->battalion().type());
const auto *apd = apdCache->GetRaw(gameState->hex_map(), mapId, battalionType, false);
thisDistance = std::min(
MinimumDistance(apd, unit->location(), startCrossingFrom),
kMaxWaterCrossingDistance);
}
totalScore -= thisDistance * kCrossingCreatorDistanceMultiplier;
}
if (unitIdsCreatingCrossing.empty()) {
for (const UnitId uid : unitIdsRequiringCrossing) {
const Unit *unit = gameState->units()->Get(uid);
@@ -321,31 +358,15 @@ auto WaterCrossingScore(
const Coords location = unit->location();
const auto *apd = apdCache->GetRaw(gameState->hex_map(), mapId, battalionType, false);
const int thisDistance =
location.row() < 0 ? 1000 : MinimumDistance(apd, location, startCrossingFrom);
const int thisDistance = location.row() < 0
? kMaxWaterCrossingDistance
: MinimumDistance(apd, location, startCrossingFrom);
totalScore -= thisDistance;
}
return totalScore;
}
// First put a big penalty on the distance for units that can create a crossing
for (const UnitId uid : unitIdsCreatingCrossing) {
const Unit *unit = gameState->units()->Get(uid);
const auto &battalionType = battalionTypeGetter(unit->battalion().type());
Coords location = unit->location();
int thisDistance;
if (location.row() < 0) thisDistance = 1000;
else {
const auto *apd = apdCache->GetRaw(gameState->hex_map(), mapId, battalionType, false);
thisDistance = MinimumDistance(apd, location, startCrossingFrom);
}
totalScore -= thisDistance * 100.0;
}
// Now a smaller penalty for distance for units that need to cross, except if they block -- then
// a large penalty
for (const UnitId uid : unitIdsRequiringCrossing) {
@@ -358,7 +379,7 @@ auto WaterCrossingScore(
const auto *apd = apdCache->GetRaw(gameState->hex_map(), mapId, battalionType, false);
int thisDistance;
if (location.row() < 0) thisDistance = 1000;
if (location.row() < 0) thisDistance = kMaxWaterCrossingDistance;
else { thisDistance = MinimumDistance(apd, location, startCrossingFrom); }
bool targetBlocks = false;
@@ -4,132 +4,12 @@
#include "AIWaterCrossingCommandChooser.hpp"
#include <algorithm>
#include <ranges>
#include "AIMinimumDistanceAndTarget.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIWaterCrossingCalculator.hpp"
namespace shardok {
using Unit = net::eagle0::shardok::storage::fb::Unit;
constexpr ScoreValue kNoRequiredCrossingScore = std::numeric_limits<ScoreValue>::max();
[[nodiscard]] auto AIWaterCrossingCommandChooser::WaterCrossingScore(
const BattalionTypeGetter &battalionTypeGetter,
const GameStateW &gameState,
const CoordsSet &castleCoords,
const CoordsSet &startCrossingFrom) const -> ScoreValue {
uint32_t castleClaimCount = 0;
for (const auto *unit : *gameState->units()) {
if (unit->player_id() != playerId) continue;
const auto status = unit->status();
if (status != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
status != net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT)
continue;
if (!unit->has_attached_hero()) continue;
++castleClaimCount;
}
CoordsSet destinations = castleCoords;
if (castleClaimCount < castleCoords.size()) {
destinations = CoordsSet(gameState->hex_map());
for (const auto *enemyUnit : *gameState->units()) {
if (enemyUnit->player_id() == playerId) continue;
const auto status = enemyUnit->status();
if (status != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
AssertValid(enemyUnit->location(), gameState->hex_map());
destinations.Add(enemyUnit->location());
}
}
const auto unitIdsRequiringCrossing = UnitIdsRequiringWaterCrossing(
gameState,
playerId,
castleCoords,
apdCache,
battalionTypeGetter);
if (unitIdsRequiringCrossing.empty()) return kNoRequiredCrossingScore;
const auto unitIdsCreatingCrossing =
UnitIdsToCreateWaterCrossing(gameState, playerId, battalionTypeGetter);
ScoreValue totalScore = 0;
const auto mapId = ActionPointDistancesCache::GetMapId(gameState->hex_map());
if (unitIdsCreatingCrossing.empty()) {
for (const UnitId uid : unitIdsRequiringCrossing) {
const Unit *unit = gameState->units()->Get(uid);
const auto &battalionType = battalionTypeGetter(unit->battalion().type());
const Coords location = unit->location();
const auto *apd = apdCache->GetRaw(gameState->hex_map(), mapId, battalionType, false);
const int thisDistance =
location.row() < 0 ? 1000 : MinimumDistance(apd, location, startCrossingFrom);
totalScore -= thisDistance;
}
return totalScore;
}
// First put a big penalty on the distance for units that can create a crossing
for (const UnitId uid : unitIdsCreatingCrossing) {
const Unit *unit = gameState->units()->Get(uid);
const auto &battalionType = battalionTypeGetter(unit->battalion().type());
Coords location = unit->location();
int thisDistance;
if (location.row() < 0) thisDistance = 1000;
else {
const auto *apd = apdCache->GetRaw(gameState->hex_map(), mapId, battalionType, false);
thisDistance = MinimumDistance(apd, location, startCrossingFrom);
}
totalScore -= thisDistance * 100.0;
}
// Now a smaller penalty for distance for units that need to cross, except if they block -- then
// a large penalty
for (const UnitId uid : unitIdsRequiringCrossing) {
// If this unit ID can also create a crossing, we already handled it
if (std::ranges::contains(unitIdsCreatingCrossing, uid)) continue;
const Unit *unit = gameState->units()->Get(uid);
const auto &battalionType = battalionTypeGetter(unit->battalion().type());
Coords location = unit->location();
const auto *apd = apdCache->GetRaw(gameState->hex_map(), mapId, battalionType, false);
int thisDistance;
if (location.row() < 0) thisDistance = 1000;
else { thisDistance = MinimumDistance(apd, location, startCrossingFrom); }
bool targetBlocks = false;
// If we're not capable of creating a crossing, don't get in the way of somebody that is.
for (const UnitId crossingUid : unitIdsCreatingCrossing) {
const auto *crossingCapableUnit = gameState->units()->Get(crossingUid);
// Don't check for units that aren't yet placed
if (crossingCapableUnit->location().row() < 0) continue;
AssertValid(crossingCapableUnit->location(), gameState->hex_map());
if (thisDistance <
MinimumDistance(apd, crossingCapableUnit->location(), startCrossingFrom)) {
targetBlocks = true;
break;
}
}
if (targetBlocks) continue;
totalScore -= thisDistance;
}
return totalScore;
}
auto AIWaterCrossingCommandChooser::StartCrossingFrom(
const BattalionTypeGetter &battalionTypeGetter,
const GameStateW &gameState,
@@ -18,7 +18,6 @@ namespace shardok {
using GameState = net::eagle0::shardok::storage::fb::GameState;
using Unit = net::eagle0::shardok::storage::fb::Unit;
using ScoreValue = double;
class AIWaterCrossingCommandChooser {
private:
@@ -34,12 +33,6 @@ public:
const BattalionTypeGetter &battalionTypeGetter,
const GameStateW &gameState,
const CoordsSet &castleCoords) const -> CoordsSet;
[[nodiscard]] auto WaterCrossingScore(
const BattalionTypeGetter &battalionTypeGetter,
const GameStateW &gameState,
const CoordsSet &castleCoords,
const CoordsSet &startCrossingFrom) const -> ScoreValue;
};
} // namespace shardok
@@ -404,3 +404,19 @@ cc_library(
"@com_google_protobuf//:protobuf",
],
)
cc_library(
name = "starting_position_recommender",
srcs = ["StartingPositionRecommender.cpp"],
hdrs = ["StartingPositionRecommender.hpp"],
copts = COPTS,
visibility = ["//visibility:public"],
deps = [
":ai_config",
":shardok_ai_client",
"//src/main/cpp/net/eagle0/shardok/library:engine",
"//src/main/cpp/net/eagle0/shardok/library:shardok_c_types",
"//src/main/protobuf/net/eagle0/shardok/api:placement_command_cc_proto",
"//src/main/protobuf/net/eagle0/shardok/common:command_type_cc_proto",
],
)
@@ -137,15 +137,20 @@ void CheckCommand(const CommandSPtr &realCommand, const CommandSPtr &guessedComm
auto ShardokAIClient::StandardChooseCommandIndex(
const GameSettingsSPtr &settings,
const GameStateW &guessedState,
const CommandListSPtr &realAvailableCommands) const -> CommandChoiceResults {
const CommandListSPtr &realAvailableCommands,
const std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults {
const auto settingsGetter = settings->GetGetter();
const auto guessedEngine = ShardokEngine(settings, guessedState);
const auto guessedCommands = guessedEngine.GetAvailableCommandsForAIPlayer(playerId);
const auto commandCount = guessedCommands->size();
// Calculate time budget based on game situation using new dynamic per-command settings
const auto timeBudget =
auto timeBudget =
CalculateTimeBudget(playerId, settings, guessedState, commandCount, isAllAiBattle);
if (timeLimit.has_value()) {
timeBudget.remainingBudget = *timeLimit;
timeBudget.minDepthRequired = 1;
}
// Configure MCTS based on proximity to enemy
// When far from enemy: use AVERAGING with maxPlayerFlips=0 (single-player lookahead)
@@ -398,7 +403,8 @@ auto ShardokAIClient::StandardChooseCommandIndex(
auto ShardokAIClient::LateRoundAttackerChooseCommandIndex(
const GameSettingsSPtr &settings,
const GameStateW &guessedState,
const CommandListSPtr &realAvailableCommands) const -> CommandChoiceResults {
const CommandListSPtr &realAvailableCommands,
const std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults {
if (const auto dismissCommand = std::ranges::find_if(
*realAvailableCommands,
[](const CommandSPtr &cmd) {
@@ -406,7 +412,7 @@ auto ShardokAIClient::LateRoundAttackerChooseCommandIndex(
net::eagle0::shardok::common::DISMISS_UNIT_COMMAND;
});
dismissCommand == realAvailableCommands->end()) {
return StandardChooseCommandIndex(settings, guessedState, realAvailableCommands);
return StandardChooseCommandIndex(settings, guessedState, realAvailableCommands, timeLimit);
} else {
CommandChoiceResults results{};
results.chosenIndex =
@@ -423,14 +429,19 @@ auto ShardokAIClient::LateRoundAttackerChooseCommandIndex(
auto ShardokAIClient::FinalRoundAttackerChooseCommandIndex(
const GameSettingsSPtr &settings,
const GameStateW &guessedState,
const CommandListSPtr &realAvailableCommands) const -> CommandChoiceResults {
const CommandListSPtr &realAvailableCommands,
const std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults {
const auto fleeCommand =
std::ranges::find_if(*realAvailableCommands, [](const CommandSPtr &cmd) {
return cmd->GetCommandType() == net::eagle0::shardok::common::FLEE_COMMAND;
});
if (fleeCommand == realAvailableCommands->end()) {
return LateRoundAttackerChooseCommandIndex(settings, guessedState, realAvailableCommands);
return LateRoundAttackerChooseCommandIndex(
settings,
guessedState,
realAvailableCommands,
timeLimit);
}
// Extract values directly from settings for flee decision evaluation
@@ -460,14 +471,15 @@ auto ShardokAIClient::FinalRoundAttackerChooseCommandIndex(
return results;
} else {
// Fight instead of flee
return StandardChooseCommandIndex(settings, guessedState, realAvailableCommands);
return StandardChooseCommandIndex(settings, guessedState, realAvailableCommands, timeLimit);
}
}
auto ShardokAIClient::ChooseCommandIndex(
const GameSettingsSPtr &settings,
const GameStateView &gsv,
const CommandListSPtr &realAvailableCommands) const -> CommandChoiceResults {
const CommandListSPtr &realAvailableCommands,
const std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults {
static std::array<std::atomic<int>, net::eagle0::shardok::common::CommandType_MAX + 1>
typeChosenCount;
static std::atomic<int> totalChoices = 0;
@@ -479,13 +491,23 @@ auto ShardokAIClient::ChooseCommandIndex(
if (const int roundsRemaining = RoundsRemaining(settings, gsv);
!isDefender && roundsRemaining <= 1) {
results =
FinalRoundAttackerChooseCommandIndex(settings, guessedState, realAvailableCommands);
results = FinalRoundAttackerChooseCommandIndex(
settings,
guessedState,
realAvailableCommands,
timeLimit);
} else if (!isDefender && roundsRemaining <= 3) {
results =
LateRoundAttackerChooseCommandIndex(settings, guessedState, realAvailableCommands);
results = LateRoundAttackerChooseCommandIndex(
settings,
guessedState,
realAvailableCommands,
timeLimit);
} else {
results = StandardChooseCommandIndex(settings, guessedState, realAvailableCommands);
results = StandardChooseCommandIndex(
settings,
guessedState,
realAvailableCommands,
timeLimit);
}
const auto chosenType = (*realAvailableCommands)[results.chosenIndex]->GetCommandType();
@@ -526,7 +548,7 @@ auto ShardokAIClient::ChooseCommandIndex(const ShardokEngine &engine) const
const auto &settings = engine.GetGameSettings();
const auto &gsv = engine.GetGameStateView(GetPlayerId());
const auto results = ChooseCommandIndex(settings, gsv, availableCommands);
const auto results = ChooseCommandIndex(settings, gsv, availableCommands, std::nullopt);
apdCache->ConsolidateThreadLocalCache_Racy();
return results;
}
@@ -9,6 +9,8 @@
#ifndef EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
#define EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
#include <chrono>
#include <optional>
#include <vector>
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
@@ -56,15 +58,18 @@ private:
[[nodiscard]] auto StandardChooseCommandIndex(
const GameSettingsSPtr& settings,
const GameStateW& guessedState,
const CommandListSPtr& realAvailableCommands) const -> CommandChoiceResults;
const CommandListSPtr& realAvailableCommands,
std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults;
[[nodiscard]] auto LateRoundAttackerChooseCommandIndex(
const GameSettingsSPtr& settings,
const GameStateW& guessedState,
const CommandListSPtr& realAvailableCommands) const -> CommandChoiceResults;
const CommandListSPtr& realAvailableCommands,
std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults;
[[nodiscard]] auto FinalRoundAttackerChooseCommandIndex(
const GameSettingsSPtr& settings,
const GameStateW& guessedState,
const CommandListSPtr& realAvailableCommands) const -> CommandChoiceResults;
const CommandListSPtr& realAvailableCommands,
std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults;
public:
explicit ShardokAIClient(
@@ -87,7 +92,8 @@ public:
[[nodiscard]] auto ChooseCommandIndex(
const GameSettingsSPtr& settings,
const net::eagle0::shardok::api::GameStateView& gsv,
const CommandListSPtr& realAvailableCommands) const -> CommandChoiceResults;
const CommandListSPtr& realAvailableCommands,
std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults;
// MCTS configuration methods (only relevant when using MCTS algorithm)
[[nodiscard]] auto GetMCTSConfig() const -> const mcts::MCTSConfig& { return mctsConfig; }
@@ -0,0 +1,174 @@
//
// Created by Codex on 7/14/26.
//
#include "StartingPositionRecommender.hpp"
#include <algorithm>
#include <array>
#include <optional>
#include <ranges>
#include "AIConfig.hpp"
#include "ShardokAIClient.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"
namespace shardok {
using net::eagle0::shardok::api::PlacementCommand;
using net::eagle0::shardok::common::CommandType;
namespace {
struct TutorialPlacement {
UnitId unitId;
int row;
int column;
};
auto RecommendTutorialStartingPositions(const ShardokEngine& engine, const PlayerId playerId)
-> std::optional<std::vector<PlacementCommand>> {
const auto* units = engine.GetCurrentGameState()->units();
const auto isUnplacedPlayerUnit = [playerId](const auto* unit) {
return unit->player_id() == playerId &&
unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT;
};
if (std::ranges::count_if(*units, isUnplacedPlayerUnit) != 3) return std::nullopt;
const auto longbowmen = std::ranges::find_if(*units, [playerId](const auto* unit) {
return unit->player_id() == playerId &&
unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT &&
unit->battalion().type() ==
net::eagle0::shardok::storage::fb::BattalionTypeId_LONGBOWMEN;
});
const auto sadar = std::ranges::find_if(*units, [playerId](const auto* unit) {
return unit->player_id() == playerId &&
unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT &&
unit->attached_hero().profession_info().profession() ==
net::eagle0::shardok::storage::fb::Profession_CHAMPION;
});
const auto marek =
std::ranges::find_if(*units, [playerId, &units, &longbowmen, &sadar](const auto* unit) {
return unit->player_id() == playerId &&
unit->status() ==
net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT &&
(longbowmen == units->end() ||
unit->unit_id() != (*longbowmen)->unit_id()) &&
(sadar == units->end() || unit->unit_id() != (*sadar)->unit_id());
});
if (longbowmen == units->end() || sadar == units->end() || marek == units->end()) {
return std::nullopt;
}
if ((*longbowmen)->unit_id() == (*sadar)->unit_id()) return std::nullopt;
const std::array desiredPlacements{
TutorialPlacement{(*longbowmen)->unit_id(), 6, 12},
TutorialPlacement{(*sadar)->unit_id(), 8, 11},
TutorialPlacement{(*marek)->unit_id(), 10, 11}};
ShardokEngine simulatedEngine(engine, false);
std::vector<PlacementCommand> recommendations;
recommendations.reserve(desiredPlacements.size());
for (const auto& placement : desiredPlacements) {
const auto commands = simulatedEngine.GetAvailableCommandsForAIPlayer(playerId);
const auto command = std::ranges::find_if(*commands, [&placement](const auto& candidate) {
return candidate->GetActorUnitId() == placement.unitId &&
candidate->GetTargetRow() == placement.row &&
candidate->GetTargetColumn() == placement.column &&
(candidate->GetCommandType() ==
net::eagle0::shardok::common::PLACE_UNIT_COMMAND ||
candidate->GetCommandType() ==
net::eagle0::shardok::common::PLACE_HIDDEN_UNIT_COMMAND);
});
if (command == commands->end()) return std::nullopt;
PlacementCommand recommendation;
recommendation.set_unit_id(placement.unitId);
recommendation.set_row(placement.row);
recommendation.set_column(placement.column);
recommendations.push_back(recommendation);
simulatedEngine.PostCommand(playerId, std::distance(commands->begin(), command));
}
return recommendations;
}
} // namespace
auto RecommendStartingPositions(
const ShardokEngine& engine,
const PlayerId playerId,
const std::chrono::milliseconds timePerChoice) -> std::vector<PlacementCommand> {
if (engine.GetGameStatus()->state() !=
net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP ||
engine.GetCurrentPlayerId() != playerId) {
return {};
}
const auto playerInfos = engine.GetPlayerInfos();
const auto playerInfo = std::ranges::find_if(playerInfos, [playerId](const auto& info) {
return info.player_id() == playerId;
});
if (playerInfo == playerInfos.end()) return {};
if (engine.IsTutorialBattleEnabled() && playerInfo->is_defender()) {
if (const auto tutorialRecommendations =
RecommendTutorialStartingPositions(engine, playerId)) {
return *tutorialRecommendations;
}
}
ShardokEngine simulatedEngine(engine, false);
mcts::MCTSConfig mctsConfig;
mctsConfig.backpropagationPolicy = mcts::MCTSBackpropagationPolicy::MINIMAX;
mctsConfig.maxPlayerFlips = 1;
mctsConfig.maxSimulationFlips = 1;
mctsConfig.simulationPolicy = mcts::MCTSSimulationPolicy::WEIGHTED_HEURISTIC;
ShardokAIClient ai(
playerId,
playerInfo->is_defender(),
false,
simulatedEngine.GetCurrentGameState()->hex_map(),
simulatedEngine.GetGameSettings()->GetGetter(),
AIAlgorithmType::ITERATIVE_DEEPENING,
ScoringCalculatorType::STANDARD,
mctsConfig);
std::vector<PlacementCommand> recommendations;
while (simulatedEngine.GetGameStatus()->state() ==
net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP &&
simulatedEngine.GetCurrentPlayerId() == playerId) {
const auto commands = simulatedEngine.GetAvailableCommandsForAIPlayer(playerId);
if (commands->empty()) break;
const auto gameStateView = simulatedEngine.GetGameStateView(playerId);
const auto choice = ai.ChooseCommandIndex(
simulatedEngine.GetGameSettings(),
gameStateView,
commands,
timePerChoice);
const auto& command = (*commands)[choice.chosenIndex];
const auto commandType = command->GetCommandType();
if (commandType == net::eagle0::shardok::common::END_PLAYER_SETUP_COMMAND) break;
if (commandType != net::eagle0::shardok::common::PLACE_UNIT_COMMAND &&
commandType != net::eagle0::shardok::common::PLACE_HIDDEN_UNIT_COMMAND) {
break;
}
PlacementCommand recommendation;
recommendation.set_unit_id(command->GetActorUnitId());
recommendation.set_row(command->GetTargetRow());
recommendation.set_column(command->GetTargetColumn());
recommendations.push_back(recommendation);
simulatedEngine.PostCommand(playerId, static_cast<int64_t>(choice.chosenIndex));
}
return recommendations;
}
} // namespace shardok
@@ -0,0 +1,26 @@
//
// Created by Codex on 7/14/26.
//
#ifndef EAGLE0_SHARDOK_AI_STARTING_POSITION_RECOMMENDER_HPP
#define EAGLE0_SHARDOK_AI_STARTING_POSITION_RECOMMENDER_HPP
#include <chrono>
#include <vector>
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
#include "src/main/protobuf/net/eagle0/shardok/api/placement_command.pb.h"
namespace shardok {
class ShardokEngine;
[[nodiscard]] auto RecommendStartingPositions(
const ShardokEngine& engine,
PlayerId playerId,
std::chrono::milliseconds timePerChoice = std::chrono::milliseconds(500))
-> std::vector<net::eagle0::shardok::api::PlacementCommand>;
} // namespace shardok
#endif // EAGLE0_SHARDOK_AI_STARTING_POSITION_RECOMMENDER_HPP
@@ -15,6 +15,7 @@ cc_library(
":game_update_receiver",
"//src/main/cpp/net/eagle0/common:shardok_latency_trace",
"//src/main/cpp/net/eagle0/shardok/ai:shardok_ai_client",
"//src/main/cpp/net/eagle0/shardok/ai:starting_position_recommender",
"//src/main/cpp/net/eagle0/shardok/library:engine",
"//src/main/cpp/net/eagle0/shardok/library:shardok_c_types",
"//src/main/cpp/net/eagle0/shardok/library:unit_placement_info",
@@ -20,6 +20,7 @@
#include "src/main/cpp/net/eagle0/common/ShardokLatencyTrace.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/StartingPositionRecommender.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/protobuf/net/eagle0/shardok/api/game_state_view.pb.h"
@@ -237,7 +238,7 @@ void ShardokGameController::DoAIThread() {
{{"player_id", std::to_string(playerId)},
{"round", std::to_string(gsv.current_round())},
{"available_command_count", std::to_string(availableCommands->size())}});
return aiClient->ChooseCommandIndex(settings, gsv, availableCommands);
return aiClient->ChooseCommandIndex(settings, gsv, availableCommands, std::nullopt);
}();
chosenIndex = results.chosenIndex;
@@ -455,6 +456,10 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
cachedGameId,
{{"starting_action_id", std::to_string(startingActionId)}});
AllUpdates updates{};
std::unique_ptr<ShardokEngine> recommendationEngine;
std::shared_ptr<AvailableCommands> recommendationCommands;
PlayerId recommendationPlayerId = -1;
std::pair<int64_t, PlayerId> recommendationCacheKey;
incomingRegistrations++;
std::vector<net::eagle0::shardok::storage::ShardokActionWithResultingState> awrs;
@@ -527,6 +532,15 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
*acs->mutable_preview_command() = {
std::begin(previewCommands),
std::end(previewCommands)};
if (engine->GetGameStatus()->state() ==
net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP &&
engine->GetCurrentPlayerId() == pi.player_id() && !availableCommands.empty()) {
recommendationEngine = std::make_unique<ShardokEngine>(*engine, false);
recommendationCommands = acs;
recommendationPlayerId = pi.player_id();
recommendationCacheKey = {updates.newUnfilteredCount, recommendationPlayerId};
}
}
updates.filteredResults.emplace_back(fid, actionResultViews, acs);
@@ -592,6 +606,25 @@ auto ShardokGameController::GetUpdates(const int64_t startingActionId) -> AllUpd
}
}
if (recommendationEngine) {
std::scoped_lock<std::mutex> recommendationGuard(recommendationLock);
auto cached = recommendedStartingPositionsCache.find(recommendationCacheKey);
if (cached == recommendedStartingPositionsCache.end()) {
cached = recommendedStartingPositionsCache
.emplace(
recommendationCacheKey,
RecommendStartingPositions(
*recommendationEngine,
recommendationPlayerId))
.first;
}
recommendationCommands->mutable_recommended_starting_positions()->Reserve(
cached->second.size());
*recommendationCommands->mutable_recommended_starting_positions() = {
cached->second.begin(),
cached->second.end()};
}
return updates;
}
@@ -13,6 +13,7 @@
#include <condition_variable>
#include <ctime>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
@@ -27,6 +28,7 @@
#include "src/main/cpp/net/eagle0/shardok/library/UnitPlacementInfo.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/GameStateHelpers.hpp"
#include "src/main/protobuf/net/eagle0/shardok/api/game_state_view.pb.h"
#include "src/main/protobuf/net/eagle0/shardok/api/placement_command.pb.h"
namespace shardok {
using net::eagle0::shardok::api::ActionResultView;
@@ -121,6 +123,10 @@ private:
std::vector<std::shared_ptr<ShardokAIClient>> aiClients;
std::thread aiThread;
std::mutex recommendationLock{};
std::map<std::pair<int64_t, PlayerId>, std::vector<net::eagle0::shardok::api::PlacementCommand>>
recommendedStartingPositionsCache;
// Each entry maps an Eagle faction id of a non-participant watcher to the
// Shardok pids of participants whose allies list named this watcher. Used
// when generating per-watcher filtered views in GetUpdates.
@@ -12,6 +12,7 @@
#include <optional>
#include <ranges>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -566,9 +567,81 @@ void ShardokEngine::PostCommand(
const CommandSPtr command = (*cachedAvailableCommands)[commandIndex];
cachedAvailableCommands = nullptr;
if (MaybeInterceptTutorialEndTurn(command)) { return; }
PostActionUnchecked(command, randomGenerator, roll);
}
auto ShardokEngine::MaybeInterceptTutorialEndTurn(const CommandSPtr &command) -> bool {
using net::eagle0::shardok::common::ActionType;
using net::eagle0::shardok::common::CommandType;
if (command->GetCommandType() != CommandType::END_TURN_COMMAND ||
!tutorialController_.IsEnabled()) {
return false;
}
const auto configuredHeroIds = tutorialController_.EndTurnWarningEagleHeroIds();
if (configuredHeroIds.empty()) { return false; }
std::unordered_set<int32_t> warnedHeroIds;
for (const auto &entry : actionHistory) {
const auto &result = entry.action_result();
if (result.type() != ActionType::TUTORIAL_UNUSED_HERO_WARNING) { continue; }
warnedHeroIds.insert(
result.tutorial_warning_eagle_hero_ids().begin(),
result.tutorial_warning_eagle_hero_ids().end());
}
std::unordered_set<UnitId> actedUnitIds;
for (auto entry = actionHistory.rbegin(); entry != actionHistory.rend(); ++entry) {
const auto &result = entry->action_result();
if (result.type() == ActionType::PLAYER_TURN_START && result.has_next_player() &&
result.next_player().value() == command->GetPlayerId()) {
break;
}
if (result.has_actor()) { actedUnitIds.insert(result.actor().value()); }
}
const auto availableCommands = availableCommandsFactory->GetAvailableCommands(
gameState,
command->GetPlayerId(),
/* includeFollowUps=*/true);
std::unordered_set<UnitId> unitsAbleToAct;
for (const auto &availableCommand : *availableCommands) {
const auto proto = availableCommand->GetCommandProto();
if (proto.has_actor()) { unitsAbleToAct.insert(proto.actor().value()); }
}
const std::unordered_set<int32_t> configuredHeroIdSet(
configuredHeroIds.begin(),
configuredHeroIds.end());
std::vector<int32_t> newlyWarnedHeroIds;
for (const auto *unit : *gameState->units()) {
if (unit->player_id() != command->GetPlayerId() || !unit->has_attached_hero() ||
unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT ||
actedUnitIds.contains(unit->unit_id()) || !unitsAbleToAct.contains(unit->unit_id())) {
continue;
}
const auto eagleHeroId = unit->attached_hero().eagle_hero_id();
if (configuredHeroIdSet.contains(eagleHeroId) && !warnedHeroIds.contains(eagleHeroId)) {
newlyWarnedHeroIds.push_back(eagleHeroId);
}
}
if (newlyWarnedHeroIds.empty()) { return false; }
ActionResultProto warningResult;
warningResult.set_type(ActionType::TUTORIAL_UNUSED_HERO_WARNING);
warningResult.mutable_player()->set_value(command->GetPlayerId());
warningResult.mutable_tutorial_warning_eagle_hero_ids()->Add(
newlyWarnedHeroIds.begin(),
newlyWarnedHeroIds.end());
ApplyAndAddActionResult(warningResult);
return true;
}
void ShardokEngine::PostActionUnchecked(
const CommandSPtr &command,
const std::shared_ptr<RandomGenerator> &randomGenerator,
@@ -82,6 +82,8 @@ private:
const std::shared_ptr<RandomGenerator> &randomGenerator,
std::optional<int32_t> roll);
[[nodiscard]] auto MaybeInterceptTutorialEndTurn(const CommandSPtr &command) -> bool;
public:
// Constructor using existing game state
ShardokEngine(
@@ -56,6 +56,11 @@ public:
/// Returns true if the controller is enabled for this battle.
[[nodiscard]] auto IsEnabled() const -> bool { return enabled_; }
[[nodiscard]] auto EndTurnWarningEagleHeroIds() const -> std::vector<int32_t> {
return {config_.end_turn_warning_eagle_hero_ids().begin(),
config_.end_turn_warning_eagle_hero_ids().end()};
}
/// Check all events at end of round, return actions for triggered events.
/// Events fire in config order - first triggered event executes, then next.
/// Each event fires at most once.
@@ -131,6 +131,12 @@ auto TargetIsHiddenFromOpponents(const ActionResultType type) -> bool {
resultView.clear_target_unit();
}
if (actorIsSelfOrAlly) {
resultView.mutable_tutorial_warning_eagle_hero_ids()->Add(
result.tutorial_warning_eagle_hero_ids().begin(),
result.tutorial_warning_eagle_hero_ids().end());
}
if (result.has_target_coords() &&
(actorIsSelfOrAlly || !TargetIsHiddenFromOpponents(result.type()))) {
*resultView.mutable_target_coords() = result.target_coords();
@@ -56,9 +56,13 @@ namespace Auth {
/// Routed to Go auth service.
/// </summary>
/// <param name="provider">OAuth provider (Discord, Google, GitHub, Apple)</param>
/// <returns>Tuple of (URL to open in browser, state token for polling)</returns>
public async Task<(string authUrl, string state)> GetOAuthUrlAsync(OAuthProvider provider) {
var request = new GetOAuthUrlRequest { Provider = provider };
/// <param name="pollOnly">Whether the client will poll instead of accepting a deep
/// link</param> <returns>Tuple of (URL to open in browser, state token for
/// polling)</returns>
public async Task<(string authUrl, string state)> GetOAuthUrlAsync(
OAuthProvider provider,
bool pollOnly = false) {
var request = new GetOAuthUrlRequest { Provider = provider, PollOnly = pollOnly };
var response = await _authServiceClient.GetOAuthUrlAsync(request);
Debug.Log($"[AuthClient] Got OAuth URL for {provider}, state={response.State}");
@@ -281,8 +281,13 @@ namespace Auth {
_currentLoginProvider = provider; // Track for later storage
try {
#if UNITY_EDITOR
const bool pollOnly = true;
#else
const bool pollOnly = false;
#endif
// Get OAuth URL and state from server
var (authUrl, state) = await _authClient.GetOAuthUrlAsync(provider);
var (authUrl, state) = await _authClient.GetOAuthUrlAsync(provider, pollOnly);
// Open system browser
Debug.Log($"[OAuthManager] Opening browser for OAuth: {authUrl}");
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using eagle;
using Eagle0.Tutorial;
using Net.Eagle0.Eagle.Api;
using Net.Eagle0.Eagle.Common;
using Net.Eagle0.Eagle.Views;
@@ -38,11 +37,7 @@ public class AvailableGameItem : MonoBehaviour {
}
}
public void JoinClicked() {
// Track multiplayer status for tutorial system
TutorialManager.IsMultiplayerGame = maxHumanPlayerCount > 1;
JoinCallback(gameId, SelectedLeaderTextId);
}
public void JoinClicked() { JoinCallback(gameId, SelectedLeaderTextId); }
public void DropClicked() { DropCallback?.Invoke(gameId); }
@@ -12,7 +12,6 @@ using common;
using common.GUIUtils;
using common.WhatsNew;
using eagle;
using Eagle0.Tutorial;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Net.Eagle0.Eagle.Api;
@@ -144,6 +143,8 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
List<GameInfo> fetchedRunningGames;
List<AvailableLeader> fetchedNewGameLeaders;
private Task<EagleGameController> _eagleScenePreloadTask;
private long _acceptedGameId;
private EagleConnection eagleConnection;
private PersistentClientConnection _persistentClientConnection;
private HttpClient _httpClient;
@@ -301,10 +302,9 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
// Refresh stored account buttons
RefreshStoredAccountButtons();
// Show sign-in tutorial for new users who have no stored accounts
// Show a sign-in hint for new users who have no stored accounts.
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
SetOAuthStatusText(InvitationSignInHint);
// Exit fullscreen so user can see browser with the sign-in link
@@ -854,9 +854,6 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
// Clear loading indicator now that lobby is populated
if (lobbyStatusText != null) { lobbyStatusText.text = ""; }
// Trigger lobby tutorial for users who haven't seen it
TutorialManager.Instance?.OnGameEvent("lobby_entered");
});
}
@@ -984,9 +981,6 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
private void ConfirmDropGame(long gameId) { _ = _internalDropGame(gameId); }
private void CreateGame(string leaderNameTextId, int humanPlayerCount, int totalPlayerCount) {
// Track multiplayer status for tutorial system
TutorialManager.IsMultiplayerGame = humanPlayerCount > 1;
TutorialManager.IsTutorialGame = false;
var game = _internalCreateGame(leaderNameTextId, humanPlayerCount, totalPlayerCount);
}
@@ -998,8 +992,6 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
}
public void CreateTutorialGame(TutorialPhase startPhase) {
TutorialManager.IsMultiplayerGame = false;
TutorialManager.IsTutorialGame = true;
var game = _internalCreateGame(
desiredLeaderTextId: "",
humanPlayerCount: 1,
@@ -1026,11 +1018,20 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
switch (response.Result) {
case JoinGameResult.SuccessJoinGameResult: SelectEagleGame(response.GameId); break;
default: Debug.LogError($"Game creation failed: {response.Result}"); break;
default:
Debug.LogError($"Game creation failed: {response.Result}");
_ = ResetGameCreation();
break;
}
} catch (Exception e) { Console.WriteLine(e.ToString()); }
}
public void HandleCreateGameAcceptedResponse(CreateGameAcceptedResponse response) {
_acceptedGameId = response.GameId;
createGameItem.SetCreating(true);
if (_eagleScenePreloadTask == null) { _eagleScenePreloadTask = PreloadEagleScene(); }
}
public void HandleCustomBattleResponse(CustomBattleResponse response) {
try {
customBattleHandler.HandleCustomBattleResponse(response);
@@ -1164,9 +1165,13 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
SetLobbyActive(false);
var selectedPersistentConnection = _persistentClientConnection;
var selectedEnvironmentName = ConnectedEnvironmentName;
var eagleController = GameSceneManager.Instance == null
var eagleController = _acceptedGameId == gameId && _eagleScenePreloadTask != null
? await _eagleScenePreloadTask
: GameSceneManager.Instance == null
? eagleCanvas?.GetComponent<EagleGameController>()
: await GameSceneManager.Instance.TransitionToEagle();
_eagleScenePreloadTask = null;
_acceptedGameId = 0;
if (eagleController == null) {
Debug.LogError(
"Cannot select Eagle game because the Eagle scene did not provide an " +
@@ -1192,6 +1197,31 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
selectedPersistentConnection,
selectedEnvironmentName);
ShowEaglePanel(eagleCanvas);
createGameItem.SetCreating(false);
}
}
private async Task<EagleGameController> PreloadEagleScene() {
var eagleController = GameSceneManager.Instance == null
? eagleCanvas?.GetComponent<EagleGameController>()
: await GameSceneManager.Instance.TransitionToEagle();
var loadedCanvas = eagleController?.GetComponent<Canvas>();
if (loadedCanvas != null) { loadedCanvas.gameObject.SetActive(false); }
return eagleController;
}
private async Task ResetGameCreation() {
createGameItem.SetCreating(false);
_acceptedGameId = 0;
var preloadTask = _eagleScenePreloadTask;
_eagleScenePreloadTask = null;
if (preloadTask != null) {
try {
await preloadTask;
} catch (Exception e) { Debug.LogException(e); }
}
if (GameSceneManager.Instance != null) {
await GameSceneManager.Instance.UnloadEagleScene();
}
}
}
@@ -22,6 +22,8 @@ public class CreateGameItem : MonoBehaviour {
private List<AvailableLeader> availableLeaders;
private void Awake() { SetCreating(false); }
public string SelectedLeaderTextId {
get {
if (heroDropdownController.SelectedHeroId == 0) {
@@ -37,6 +39,11 @@ public class CreateGameItem : MonoBehaviour {
CreateCallback(SelectedLeaderTextId, totalHumansDropdown.value + 1, TotalPlayerCount);
}
public void SetCreating(bool creating) {
createButton.interactable = !creating;
buttonText.text = creating ? "CREATING…" : "CREATE";
}
public void SetHumanPlayersDropdown() {
var currentValue = totalHumansDropdown.value;
totalHumansDropdown.ClearOptions();
@@ -49,8 +56,6 @@ public class CreateGameItem : MonoBehaviour {
public void SetCreationOptions(List<AvailableLeader> leaders) {
this.availableLeaders = leaders;
buttonText.text = "CREATE";
List<HeroView> heroOptions =
new List<HeroView> { new() { Id = 0, Profession = Profession.NoProfession } };
for (int i = 0; i < leaders.Count; i++) {
@@ -9,8 +9,6 @@ public class DropGameConfirmationPanel : MonoBehaviour {
private long _pendingGameId;
void Awake() { gameObject.SetActive(false); }
public void Show(long gameId, string message) {
_pendingGameId = gameId;
if (messageText != null) { messageText.text = message; }
@@ -61,6 +61,8 @@ namespace eagle {
thermometer.defenderLabel.text = battle.DefenderTroopCount.Value.ToString();
} else {
thermometer.UpdateBalance(battle.DefenderRatio, defColor, atkColors);
thermometer.attackerLabel.text = "???";
thermometer.defenderLabel.text = "???";
}
actionButton.onClick.RemoveAllListeners();
@@ -37,7 +37,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 60, y: 35}
m_SizeDelta: {x: 80, y: 30}
m_SizeDelta: {x: 80, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &42355368
MonoBehaviour:
@@ -618,7 +618,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 160, y: 35}
m_SizeDelta: {x: 80, y: 30}
m_SizeDelta: {x: 80, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &710430062
GameObject:
@@ -1618,7 +1618,7 @@ RectTransform:
m_AnchorMin: {x: 1, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: -100, y: 35}
m_SizeDelta: {x: 160, y: 30}
m_SizeDelta: {x: 160, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1531553116
MonoBehaviour:
@@ -22,10 +22,6 @@ namespace eagle {
private ChronicleEntry CurrentEntry => _entries[_currentIndex];
private void Awake() {
TouchTargetUtils.EnsureMinimumTouchTargets(previousButton, nextButton);
}
public void OnTextUpdate(string text, bool completed) { SetUp(); }
public string TextId() { return _entries.Count > 0 ? CurrentEntry.GeneratedTextId : null; }
@@ -137,8 +137,8 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 85
m_PreferredHeight: 85
m_FlexibleWidth: -1
@@ -21,7 +21,6 @@ namespace eagle {
for (int i = 0; i < contentsCount; i++) {
int buttonIndex = i;
var newButton = Instantiate(buttonPrefab, buttonsContainer.transform);
TouchTargetUtils.EnsureMinimumTouchTarget(newButton);
newButton.GetComponentInChildren<RawImage>().texture = contents[buttonIndex].image;
newButton.onClick.AddListener(() => gameController.SelectButtonIndex(buttonIndex));
File diff suppressed because it is too large Load Diff
@@ -243,6 +243,8 @@ namespace eagle {
public Button commitCommandButton;
public TextMeshProUGUI commitButtonText;
public Button infoButton;
public TextMeshProUGUI underAttackText;
public Transform specializationLayout;
public IGameModel Model { get; set; }
public ErrorHandler errorHandler;
public CommandWarningPanelController commandWarningPanelController;
@@ -368,7 +370,7 @@ namespace eagle {
public void Awake() {
commandSelectors = new List<CommandSelector>();
foreach (Transform child in gameObject.transform) {
foreach (Transform child in specializationLayout) {
var sel = child.gameObject.GetComponent<CommandSelector>();
if (sel != null) { commandSelectors.Add(sel); }
}
@@ -381,8 +383,6 @@ namespace eagle {
public void Start() {
foreach (var kvp in commandSelectors) { kvp.gameObject.SetActive(false); }
TouchTargetUtils.EnsureMinimumTouchTarget(commitCommandButton);
TouchTargetUtils.EnsureMinimumTouchTarget(infoButton);
}
public void Update() {
@@ -2115,10 +2115,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 125
m_PreferredHeight: 25
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -1161,9 +1161,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinHeight: 48
m_PreferredWidth: 150
m_PreferredHeight: 32
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -2155,10 +2155,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 225
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -2963,10 +2963,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 225
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -3071,10 +3071,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 225
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -3998,10 +3998,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 225
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -5822,9 +5822,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinHeight: 48
m_PreferredWidth: 150
m_PreferredHeight: 32
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -555,9 +555,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinHeight: 48
m_PreferredWidth: 150
m_PreferredHeight: 32
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -2307,9 +2307,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinHeight: 48
m_PreferredWidth: 150
m_PreferredHeight: 32
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -2576,10 +2576,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 225
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -3345,10 +3345,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 225
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -3917,9 +3917,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinHeight: 48
m_PreferredWidth: 200
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: 0
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -5707,10 +5707,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 125
m_PreferredHeight: 25
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -4402,10 +4402,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 150
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -572,6 +572,7 @@ GameObject:
m_Component:
- component: {fileID: 9063570085238334375}
- component: {fileID: 7255705380449941913}
- component: {fileID: 9100000000000000201}
m_Layer: 0
m_Name: Lock
m_TagString: Untagged
@@ -796,6 +797,7 @@ GameObject:
- component: {fileID: 1927873372456662095}
- component: {fileID: 579296918478550819}
- component: {fileID: 4587657740126355967}
- component: {fileID: 9100000000000000202}
m_Layer: 0
m_Name: Devastation Toggle
m_TagString: Untagged
@@ -1200,6 +1202,7 @@ GameObject:
m_Component:
- component: {fileID: 5270310487800380897}
- component: {fileID: 2327285081336772738}
- component: {fileID: 9100000000000000203}
m_Layer: 0
m_Name: Lock
m_TagString: Untagged
@@ -1945,6 +1948,7 @@ GameObject:
- component: {fileID: 3117163427893374906}
- component: {fileID: 4817183911828687387}
- component: {fileID: 4307944766613959021}
- component: {fileID: 9100000000000000204}
m_Layer: 0
m_Name: Economy Toggle
m_TagString: Untagged
@@ -2535,6 +2539,7 @@ GameObject:
- component: {fileID: 4943719427635584319}
- component: {fileID: 1624135427163740082}
- component: {fileID: 3649755510827586979}
- component: {fileID: 9100000000000000205}
m_Layer: 0
m_Name: Agriculture Toggle
m_TagString: Untagged
@@ -2729,6 +2734,7 @@ GameObject:
m_Component:
- component: {fileID: 7980102133610567528}
- component: {fileID: 7826769704341099561}
- component: {fileID: 9100000000000000206}
m_Layer: 0
m_Name: Lock
m_TagString: Untagged
@@ -2889,6 +2895,7 @@ GameObject:
m_Component:
- component: {fileID: 33654934627540799}
- component: {fileID: 4759915228927732221}
- component: {fileID: 9100000000000000207}
m_Layer: 0
m_Name: Lock
m_TagString: Untagged
@@ -4002,6 +4009,7 @@ GameObject:
- component: {fileID: 4103879905621038639}
- component: {fileID: 5294436050907953632}
- component: {fileID: 7680661694278740015}
- component: {fileID: 9100000000000000208}
m_Layer: 0
m_Name: Infrastructure Toggle
m_TagString: Untagged
@@ -4497,3 +4505,163 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: bd19585e1f7d445a3b2c459d2c8535eb, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &9100000000000000201
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1382658920502641030}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &9100000000000000202
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1575326401059928320}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &9100000000000000203
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2449159195461577259}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &9100000000000000204
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3953852845382787669}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &9100000000000000205
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5165403539164238665}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &9100000000000000206
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5492998529588505869}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &9100000000000000207
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5591836015222019972}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &9100000000000000208
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7994236895301891522}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -407,7 +407,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 338.31927, y: -12.923078}
m_SizeDelta: {x: 96.75219, y: 44}
m_SizeDelta: {x: 96.75219, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &7706371227584479729
MonoBehaviour:
@@ -1294,7 +1294,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 59.126343, y: -12.923078}
m_SizeDelta: {x: 118.25268, y: 44}
m_SizeDelta: {x: 118.25268, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &3005629370892248399
MonoBehaviour:
@@ -2192,7 +2192,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 204.09793, y: -12.923078}
m_SizeDelta: {x: 131.69049, y: 44}
m_SizeDelta: {x: 131.69049, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1248166195846916077
MonoBehaviour:
@@ -3622,7 +3622,7 @@ MonoBehaviour:
m_ChildForceExpandWidth: 0
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
@@ -4258,7 +4258,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 756.95197, y: -12.923078}
m_SizeDelta: {x: 107.50243, y: 44}
m_SizeDelta: {x: 107.50243, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &50070872754773658
CanvasRenderer:
@@ -4476,7 +4476,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 465.82172, y: -12.923078}
m_SizeDelta: {x: 118.25268, y: 44}
m_SizeDelta: {x: 118.25268, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &6791613139375434283
MonoBehaviour:
@@ -5008,7 +5008,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 604.0744, y: -12.923078}
m_SizeDelta: {x: 118.25268, y: 44}
m_SizeDelta: {x: 118.25268, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &159881109546823439
MonoBehaviour:
@@ -1797,9 +1797,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 170
m_MinHeight: 40
m_MinHeight: 48
m_PreferredWidth: 170
m_PreferredHeight: 40
m_PreferredHeight: 48
m_FlexibleWidth: 0
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -4191,10 +4191,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 18
m_PreferredHeight: 18
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 48
m_PreferredHeight: 48
m_FlexibleWidth: 0
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -4560,9 +4560,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: 20
m_PreferredHeight: 48
m_FlexibleWidth: 1
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -7825,8 +7825,8 @@ RectTransform:
m_Children: []
m_Father: {fileID: 906553694472734689}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 1}
m_AnchorMin: {x: 0, y: 0.5}
m_AnchorMax: {x: 0, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 32, y: 32}
m_Pivot: {x: 0.5, y: 0.5}
@@ -7860,7 +7860,7 @@ MonoBehaviour:
m_Calls: []
m_Sprite: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 0
m_PreserveAspect: 0
m_PreserveAspect: 1
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
@@ -8631,9 +8631,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 100
m_MinHeight: 44
m_MinHeight: 48
m_PreferredWidth: 200
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: 0
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -9193,8 +9193,8 @@ RectTransform:
m_Children: []
m_Father: {fileID: 5006087206053140860}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 1}
m_AnchorMin: {x: 0, y: 0.5}
m_AnchorMax: {x: 0, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 32, y: 32}
m_Pivot: {x: 0.5, y: 0.5}
@@ -9228,7 +9228,7 @@ MonoBehaviour:
m_Calls: []
m_Sprite: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 0
m_PreserveAspect: 0
m_PreserveAspect: 1
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
@@ -9383,10 +9383,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 18
m_PreferredHeight: 18
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 48
m_PreferredHeight: 48
m_FlexibleWidth: 0
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -9537,10 +9537,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 18
m_PreferredHeight: 18
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 48
m_PreferredHeight: 48
m_FlexibleWidth: 0
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -10282,9 +10282,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 100
m_MinHeight: 44
m_MinHeight: 48
m_PreferredWidth: 200
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: 0
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -10405,9 +10405,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: 20
m_PreferredHeight: 48
m_FlexibleWidth: 1
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -10670,10 +10670,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 18
m_PreferredHeight: 18
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 48
m_PreferredHeight: 48
m_FlexibleWidth: 0
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -1425,9 +1425,9 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 135
m_MinHeight: 44
m_MinHeight: 48
m_PreferredWidth: 150
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: 0
m_FlexibleHeight: 0
m_LayoutPriority: 1
@@ -3986,8 +3986,8 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 0
m_MinHeight: -1
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 80
m_PreferredHeight: 48
m_FlexibleWidth: -1
@@ -1102,10 +1102,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 120
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -1120,7 +1120,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 75, y: -15}
m_SizeDelta: {x: 150, y: 44}
m_SizeDelta: {x: 150, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7054265081857237224
CanvasRenderer:
@@ -1230,9 +1230,9 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: 44
m_MinHeight: 48
m_PreferredWidth: 150
m_PreferredHeight: 44
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -324,7 +324,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 215, y: 24}
m_SizeDelta: {x: 250, y: 44}
m_SizeDelta: {x: 250, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1949439835697595086
MonoBehaviour:
@@ -1434,7 +1434,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 194, y: -42}
m_SizeDelta: {x: 160, y: 44}
m_SizeDelta: {x: 160, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7346251710853780603
CanvasRenderer:
@@ -3907,10 +3907,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 125
m_PreferredHeight: 25
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -93,17 +93,7 @@ namespace eagle {
new AttackDecisionSelectedCommand { Decision = SelectedDecision }
};
private void EnsureTouchTargets() {
TouchTargetUtils.EnsureMinimumTouchTargets(
advanceToggle,
demandTributeToggle,
withdrawToggle,
safePassageToggle);
TouchTargetUtils.EnsureMinimumTouchHeights(goldSlider, foodSlider);
}
protected override void SetUpUI() {
EnsureTouchTargets();
unitsTable.RowCount = AttackDecisionCommand.ActingUnits.Count;
AttackDecisionCommand.ActingUnits.Each((cu, idx) => {
@@ -243,7 +233,6 @@ namespace eagle {
private void ConfigureToggle(Toggle toggle, bool available) {
if (toggle == null) return;
TouchTargetUtils.EnsureMinimumTouchTarget(toggle);
toggle.gameObject.SetActive(true);
toggle.interactable = available;
@@ -100,14 +100,7 @@ namespace eagle {
}
}
private void EnsureTouchTargets() {
TouchTargetUtils.EnsureMinimumTouchTargets(keepProvinceToggle, withdrawToggle);
TouchTargetUtils.EnsureMinimumTouchHeight(destinationDropdown);
TouchTargetUtils.EnsureMinimumTouchHeights(goldSlider, foodSlider);
}
protected override void SetUpUI() {
EnsureTouchTargets();
unitsTable.RowCount = BattleAftermathCommand.SurvivingUnits.Count;
BattleAftermathCommand.SurvivingUnits.Each((cu, idx) => {
CombatUnitRowController row = unitsTable.ComponentAt<CombatUnitRowController>(idx);
@@ -181,7 +174,6 @@ namespace eagle {
private void ConfigureToggle(Toggle toggle, bool available) {
if (toggle == null) return;
TouchTargetUtils.EnsureMinimumTouchTarget(toggle);
toggle.gameObject.SetActive(true);
toggle.interactable = available;
toggle.group = null;
@@ -135,18 +135,6 @@ namespace eagle {
}
}
private void EnsureTouchTargets() {
TouchTargetUtils.EnsureMinimumTouchTargets(
economyToggle,
agricultureToggle,
infrastructureToggle,
devastationToggle,
economyLockToggle,
agricultureLockToggle,
infrastructureLockToggle,
devastationLockToggle);
}
private void UpdateLockToggleVisibility() {
var selectedType = SelectedType;
var types = new[] {
@@ -193,7 +181,6 @@ namespace eagle {
if (toggle == null) return;
TouchTargetUtils.EnsureMinimumTouchTarget(toggle);
// Always show the toggle, but disable if not available
toggle.gameObject.SetActive(true);
toggle.interactable = available;
@@ -236,7 +223,6 @@ namespace eagle {
}
protected override void SetUpUI() {
EnsureTouchTargets();
var improveCommand = ImproveAvailableCommand;
orderedHeroes = ImproveAvailableCommand.AvailableHeroIds.Select(id => _model.Heroes[id])
@@ -67,17 +67,6 @@ namespace eagle {
_availableCommand.ManagePrisonerCommand;
private ProvinceId ActingProvinceId => ManagePrisonersCommand.ActingProvinceId;
private void EnsureTouchTargets() {
TouchTargetUtils.EnsureMinimumTouchTargets(
ReleaseToggle,
MoveToggle,
ExileToggle,
ExecuteToggle,
ReturnToggle,
NextHeroButton);
TouchTargetUtils.EnsureMinimumTouchHeight(moveToDropdown);
}
public override bool WarnOnCommitButton => false;
public override string CommitWarningText =>
$"This will create a treaty with {_model.FactionName(SelectedHero.FactionId.Value)}";
@@ -138,7 +127,6 @@ namespace eagle {
}
protected override void SetUpUI() {
EnsureTouchTargets();
MoveToggle.onValueChanged.RemoveListener(MoveToggled);
MoveToggle.onValueChanged.AddListener(MoveToggled);
if (_selectedHeroIndex >= ManagePrisonersCommand.Prisoners.Count)
@@ -321,7 +309,6 @@ namespace eagle {
private void ConfigureToggle(Toggle toggle, bool available) {
if (toggle == null) return;
TouchTargetUtils.EnsureMinimumTouchTarget(toggle);
toggle.gameObject.SetActive(true);
toggle.interactable = available;
@@ -29,7 +29,6 @@ namespace eagle {
private string cachedWarningText = "";
private const string NoProvinceSelectedText = "Select...";
private const float NarrowStepInactiveAlpha = 0.55f;
private const float NarrowSliderHandleSize = 32f;
private bool HasNoneOption => toDropdown.options.Last().text == NoProvinceSelectedText;
@@ -298,7 +297,6 @@ namespace eagle {
.Where(batt => availableBattalionIds.Contains(batt.Id))
.ToList();
unitSelector.includeRecommendedConfigButtonInTouchTargets = !UsesNarrowTabs;
unitSelector.SetUpUnits(
_model,
SelectedOneProvinceCommand.AvailableHeroIds.ToList(),
@@ -442,10 +440,6 @@ namespace eagle {
NarrowUnitsTab.SetActive(unitsStep);
UpdateRecommendedButtonState();
if (routeStep) { EnsureNarrowRouteTouchTargets(); }
if (unitsStep) {
unitSelector.EnsureTouchTargets(includeRecommendedConfigButton: false);
}
UpdateNarrowTabVisuals();
}
@@ -456,18 +450,6 @@ namespace eagle {
private GameObject NarrowUnitsTab => selectedUnitsColumn.transform.parent.gameObject;
private void EnsureNarrowRouteTouchTargets() {
TouchTargetUtils.EnsureMinimumTouchHeight(fromDropdown);
TouchTargetUtils.EnsureMinimumTouchHeight(toDropdown);
TouchTargetUtils.EnsureMinimumTouchHeight(goldSlider);
TouchTargetUtils.EnsureMinimumTouchHeight(foodSlider);
KeepSliderHandlesSquare(goldSlider);
KeepSliderHandlesSquare(foodSlider);
foreach (var button in routeAndSuppliesColumn.GetComponentsInChildren<Button>(true)) {
TouchTargetUtils.EnsureMinimumTouchTarget(button);
}
}
private void UpdateNarrowTabVisuals() {
if (!IsNarrowLayout) return;
@@ -496,19 +478,6 @@ namespace eagle {
layoutElement.flexibleWidth = flexibleWidth;
}
private static void KeepSliderHandlesSquare(Slider slider) {
var handle = slider.handleRect;
handle.anchorMin = new Vector2(handle.anchorMin.x, 0.5f);
handle.anchorMax = new Vector2(handle.anchorMax.x, 0.5f);
handle.sizeDelta = new Vector2(NarrowSliderHandleSize, NarrowSliderHandleSize);
}
private void LateUpdate() {
if (_availableCommand == null || !IsNarrowLayout) return;
KeepSliderHandlesSquare(goldSlider);
KeepSliderHandlesSquare(foodSlider);
}
private void ClearWarningTextListener() {
if (warningTextListener != null) {
ClientTextProvider.Provider.RemoveListener(warningTextListener);
@@ -58,7 +58,6 @@ namespace eagle {
}
protected override void SetUpUI() {
TouchTargetUtils.EnsureMinimumTouchTarget(nextHeroButton);
if (_selectedHeroIndex >= HeroCount) { _selectedHeroIndex = 0; }
DisplayHero();
nextHeroButton.gameObject.SetActive(HeroCount > 1);
@@ -54,7 +54,6 @@ namespace eagle {
}
protected override void SetUpUI() {
TouchTargetUtils.EnsureMinimumTouchTargets(payToggle, nextFactionButton);
_demands = new List<TributeAndFaction>(ResolveTributeAvailableCommand.Demands);
_demandIndex = 0;
DisplayDemand();
@@ -23,7 +23,6 @@ namespace eagle {
public BattalionRowController availableBattalionsRow;
public GameObject selectedUnitRow;
public Button recommendedConfigButton;
public bool includeRecommendedConfigButtonInTouchTargets = true;
public UnityEvent selectionChanged;
@@ -122,26 +121,6 @@ namespace eagle {
SetUpTables();
}
public void EnsureTouchTargets(bool includeRecommendedConfigButton = true) {
if (includeRecommendedConfigButton && includeRecommendedConfigButtonInTouchTargets) {
TouchTargetUtils.EnsureMinimumTouchTarget(recommendedConfigButton);
}
EnsureContentTouchTargets(availableHeroesContent);
EnsureContentTouchTargets(availableBattalionsContent);
EnsureContentTouchTargets(availableBattalionsContentNarrow);
EnsureContentTouchTargets(selectedUnitsContent);
}
private static void EnsureContentTouchTargets(GameObject content) {
if (content == null) return;
foreach (var row in content.GetComponentsInChildren<TableRowController>(true)) {
TouchTargetUtils.EnsureMinimumTouchHeight(row);
}
foreach (var button in content.GetComponentsInChildren<Button>(true)) {
TouchTargetUtils.EnsureMinimumTouchTarget(button);
}
}
private IGameModel _model;
List<HeroId> AvailableHeroIds = new();
List<BattalionView> AvailableBattalions = new();
@@ -178,7 +157,6 @@ namespace eagle {
var newRow = instantiateInTable(availableHeroRow, parentTransform)
.GetComponent<UnitSelectorHeroRowController>();
newRow.Hero = hero;
TouchTargetUtils.EnsureMinimumTouchHeight(newRow);
var index = i;
newRow.GetComponentInChildren<TableRowClickDetector>().onClick.AddListener(
@@ -195,7 +173,6 @@ namespace eagle {
for (int i = 0; i < idsCount; i++) {
var newRow = instantiateInTable(selectedUnitRow, parentTransform)
.GetComponent<SelectedUnitRowController>();
TouchTargetUtils.EnsureMinimumTouchHeight(newRow);
var heroId = SelectedIds[i].heroId;
var battalion = SelectedIds[i].battalion;
@@ -228,7 +205,6 @@ namespace eagle {
}
heroesAndBattalionsPanelController.UpdateTables();
EnsureTouchTargets();
selectionChanged.Invoke();
}
@@ -243,7 +219,6 @@ namespace eagle {
var newRow = instantiateInTable(availableBattalionsRow.gameObject, parentTransform)
.GetComponent<BattalionRowController>();
newRow.Battalion = battalion;
TouchTargetUtils.EnsureMinimumTouchHeight(newRow);
var index = i;
@@ -59,7 +59,6 @@ namespace eagle {
}
if (retryButton != null) {
TouchTargetUtils.EnsureMinimumTouchTarget(retryButton);
retryButton.onClick.AddListener(OnRetryClicked);
retryButton.gameObject.SetActive(false);
}
@@ -139,11 +139,6 @@ namespace eagle {
}
void Start() {
TouchTargetUtils.EnsureMinimumTouchTargets(
showChronicleButton,
commitCommandButton,
gameIdButton,
nextActiveProvinceButton);
gameIdButton.GetComponentInChildren<Text>().text = "";
mapController.targetedProvincesChangedCallback = MapControllerChangedTarget;
mapController.hoveredProvinceChangedCallback = MapControllerChangedHover;
@@ -204,12 +199,7 @@ namespace eagle {
public void StopAll() {
_newModel = null;
if (ModelUpdater != null) {
ModelUpdater.StopListeningForUpdates();
ModelUpdater.UpdateAction = null;
ModelUpdater.ActionResultAction = null;
ModelUpdater = null;
}
TearDownModelUpdater();
SwapModel();
Model = null;
@@ -312,10 +302,14 @@ namespace eagle {
int? playerId,
PersistentClientConnection persistentClientConnection,
string environmentName = null) {
ModelUpdater = new GameModelUpdater(gameId, playerId, persistentClientConnection) {
UpdateAction = ModelUpdated,
NoteRecipient = notification => notificationPanel.AddNote(notification)
};
TearDownModelUpdater();
var newModelUpdater =
new GameModelUpdater(gameId, playerId, persistentClientConnection) {
NoteRecipient = notification => notificationPanel.AddNote(notification)
};
newModelUpdater.UpdateAction = model => ModelUpdated(newModelUpdater, model);
ModelUpdater = newModelUpdater;
commitCommandButton.gameObject.SetActive(false);
battleProgressPanel.SetGoToBattleAction(GoToBattle);
@@ -468,12 +462,6 @@ namespace eagle {
dominionPanelControllerNarrow.ForceUpdate();
dominionPanelControllerWide.ForceUpdate();
SetMusic();
// Notify tutorial system of province selection
if (pid.HasValue) {
TutorialManager.Instance?.TriggerRegistry?.OnProvinceSelected(
Model.Provinces[pid.Value]);
}
}
private void PrefetchHeadshotForHeroes(List<HeroView> heroViews) {
@@ -588,13 +576,18 @@ namespace eagle {
_newModel != null && _newModel.AvailableCommandsByProvince.Any();
var commandTokenChanged = Model != null && _newModel != null &&
Model.CommandToken != _newModel.CommandToken;
var strategicUpdateChanged =
Model != null && _newModel != null &&
Model.StrategicUpdateRevision != _newModel.StrategicUpdateRevision;
var availableCommandsChanged =
Model != null && _newModel != null && !SameAvailableCommands(Model, _newModel);
// If we already had commands, and we still have the same command token, don't
// actually swap the model. But always update battle effects — battles can start
// or end between command updates, and skipping cleanup leaves stale animations on
// the map. Replacement command tokens must flow through so stale selectors are
// cleared and rebound to the new available commands.
if (hasExistingCommands && hasIncomingCommands && !commandTokenChanged) {
// Preserve the user's selection across non-strategic refreshes. A result-bearing
// strategic update must still flow through even when its command token is unchanged:
// automatic results can change visible state and command availability without
// advancing the posted-command token.
if (hasExistingCommands && hasIncomingCommands && !commandTokenChanged &&
!strategicUpdateChanged) {
mapController.UpdateBattleEffects(_newModel);
UpdateBattleProgressDisplay(_newModel);
return;
@@ -620,31 +613,22 @@ namespace eagle {
return;
}
// On first model, sync IsTutorialGame from the server-reported GameType.
// This covers joining an existing game (where ConnectionHandler doesn't know the type).
if (oldModel == null) {
TutorialManager.IsTutorialGame = Model.GameType == GameType.Tutorial;
// Initialize province ID map from server-provided dimensions and hash
if (Model.EagleMap != null) { provinceIDLoader.Initialize(Model.EagleMap); }
}
// Notify tutorial system after IsTutorialGame is synced from the first model.
// Give server-authored tutorial dialogue the latest model for entity resolution.
TutorialManager.Instance?.OnModelUpdated(Model, oldModel);
// Start onboarding after first model update (UI panels are now populated)
if (oldModel == null && TutorialManager.Instance != null &&
!TutorialManager.Instance.State.OnboardingCompleted) {
TutorialManager.Instance.StartOnboarding();
}
PrefetchHeadshots(Model);
SetMusic();
if ((oldModel == null || oldModel.CommandToken != _newModel.CommandToken) &&
if ((oldModel == null || commandTokenChanged || availableCommandsChanged) &&
Model.AvailableCommandsByProvince.Count > 0) {
if (oldModel != null && oldModel.AvailableCommandsByProvince.Count > 0) {
if (oldModel != null && oldModel.AvailableCommandsByProvince.Count > 0 &&
(commandTokenChanged || availableCommandsChanged)) {
string str = "We got replacement commands, but we already had commands. " +
oldModel.CommandToken + " " + _newModel.CommandToken;
@@ -766,6 +750,21 @@ namespace eagle {
}
}
private static bool SameAvailableCommands(IGameModel a, IGameModel b) {
if (a.AvailableCommandsByProvince.Count != b.AvailableCommandsByProvince.Count) {
return false;
}
foreach (var kv in a.AvailableCommandsByProvince) {
if (!b.AvailableCommandsByProvince.TryGetValue(kv.Key, out var commands) ||
!kv.Value.Equals(commands)) {
return false;
}
}
return true;
}
private void UpdateBattleProgressDisplay(IGameModel model) {
if (model.ShardokBattles.Any()) {
battleProgressPanel.gameObject.SetActive(true);
@@ -775,14 +774,14 @@ namespace eagle {
}
}
void ModelUpdated(IGameModel nm) {
MainQueue.Q.Enqueue(() => { _newModel = nm; });
void ModelUpdated(GameModelUpdater modelUpdater, IGameModel nm) {
MainQueue.Q.Enqueue(() => {
if (ModelUpdater != modelUpdater) { return; }
_newModel = nm;
});
}
private void PostCommittedCommand(ProvinceId provinceId, SelectedCommand selectedCommand) {
// Notify tutorial system of command
TutorialManager.Instance?.TriggerRegistry?.OnCommandIssued(selectedCommand);
ModelUpdater.PostCommand(provinceId: provinceId, command: selectedCommand)
.ContinueWith(response => {
if (response.IsFaulted) { errorHandler.Add(response.Exception); }
@@ -882,16 +881,15 @@ namespace eagle {
}
private void OnDestroy() {
if (ModelUpdater != null) {
ModelUpdater.StopListeningForUpdates();
ModelUpdater.UpdateAction = null;
ModelUpdater.ActionResultAction = null;
ModelUpdater.NoteRecipient = null;
ModelUpdater.ErrorHandler = null;
ModelUpdater = null;
}
TearDownModelUpdater();
factionTextUpdater?.Dispose();
}
private void TearDownModelUpdater() {
var modelUpdater = ModelUpdater;
ModelUpdater = null;
modelUpdater?.Dispose();
}
}
}
@@ -43,6 +43,7 @@ namespace eagle {
TokenId? CommandToken { get; }
TokenId LastPostedToken { get; }
long StrategicUpdateRevision { get; }
IList<ShardokBattleView> ShardokBattles { get; }
int BattleDay { get; }
@@ -71,7 +72,8 @@ namespace eagle {
EagleMapInfo EagleMap { get; }
}
public class GameModelUpdater : IClientConnectionSubscriber, IGameStateProvider {
public class GameModelUpdater : IClientConnectionSubscriber, IGameStateProvider, IDisposable {
private bool _disposed;
private FactionId? PlayerId => _currentModel.PlayerId;
public Int64? CurrentEagleToken => _currentModel.CommandToken;
public Int64? CurrentShardokToken(string shardokGameId) {
@@ -192,6 +194,7 @@ namespace eagle {
public TokenId? CommandToken { get; set; }
public TokenId LastPostedToken { get; set; }
public long StrategicUpdateRevision { get; set; }
public String CommandTokenString =>
CommandToken.HasValue ? CommandToken.Value.ToString() : "{null}";
@@ -265,7 +268,7 @@ namespace eagle {
_currentModel.AvailableCommandsByProvince =
new Dictionary<ProvinceId, OneProvinceAvailableCommands>();
_currentModel.GsView = new GameStateView();
_currentModel.GsView = new GameStateView { GameId = eagleGameId };
_currentModel.ShardokGameModels =
new ConcurrentDictionary<ShardokGameId, ShardokGameModel>();
@@ -394,6 +397,12 @@ namespace eagle {
: -1;
var tokenMatches = hasCommands && incomingToken == _currentModel.CommandToken;
if (updateItem.ActionResultResponse.AlreadyAppliedActionResultViews.Count > 0) {
TutorialManager.Instance?.OnAlreadyAppliedStrategicActionResults(
GameId,
updateItem.ActionResultResponse.AlreadyAppliedActionResultViews);
}
_connectionLogger.LogLine(
$"[UPDATE] hasResults={hasResults}, hasCommands={hasCommands}, " +
$"incomingToken={incomingToken}, currentToken={_currentModel.CommandTokenString}, " +
@@ -404,6 +413,7 @@ namespace eagle {
updateItem.ActionResultResponse.ActionResultViews.ToList(),
updateItem.ActionResultResponse.UnfilteredResultCountAfter);
HandleAvailableCommands(updateItem.ActionResultResponse.AvailableCommands);
_currentModel.StrategicUpdateRevision++;
if (UpdateAction != null) UpdateAction.Invoke(_currentModel);
_connectionLogger.LogLine(
@@ -470,6 +480,12 @@ namespace eagle {
shardokGameModel.HandleServerStatus(oneResponse.ShardokServerStatus);
}
if (oneResponse.TutorialDialogues.Count > 0) {
TutorialManager.Instance?.OnTacticalServerDialogues(
GameId,
oneResponse.TutorialDialogues);
}
// Clear resync flags after successfully receiving updates
ClearShardokResyncFlag(oneResponse.ShardokGameId);
shardokGameModel.SkipAutoPlacement = false;
@@ -501,6 +517,11 @@ namespace eagle {
out var resetModel)) {
resetModel.ResetForNewBattle();
}
if (updateItem.ShardokBattleResetResponse.TutorialDialogues.Count > 0) {
TutorialManager.Instance?.OnTacticalServerDialogues(
GameId,
updateItem.ShardokBattleResetResponse.TutorialDialogues);
}
_shardokResultCounts.TryRemove(resetGameId, out _);
if (UpdateAction != null) UpdateAction.Invoke(_currentModel);
break;
@@ -564,6 +585,18 @@ namespace eagle {
public void StopListeningForUpdates() { PersistentConnection.Unsubscribe(this); }
public void Dispose() {
if (_disposed) return;
_disposed = true;
StopListeningForUpdates();
UpdateAction = null;
ActionResultAction = null;
NoteRecipient = null;
ErrorHandler = null;
HasPendingCommandWithToken = null;
}
/// <summary>
/// Mark a Shardok game for full state resync on next connection.
/// Used after connection drops to ensure state consistency.
@@ -652,16 +685,18 @@ namespace eagle {
_modelCorrupted = false;
}
var existingChronicleEntries =
_currentModel.ChronicleEntries ?? new List<ChronicleEntry>();
_currentModel.GsView = startingState;
_currentModel.BattalionTypes =
startingState.BattalionTypes.ToDictionary(bt => bt.TypeId, bt => bt);
// 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();
}
// Chronicle entries are historical records that accumulate over time. Merge the
// StartingState into cached history so a stale snapshot containing only the initial
// entry cannot wipe out annual chronicles restored from disk. Entries from the
// server still replace cached entries for the same date.
_currentModel.ChronicleEntries = existingChronicleEntries;
foreach (var entry in startingState.ChronicleEntries) { UpdateChronicleEntry(entry); }
// For any outstanding battles in the new state, create models and mark for resync.
bool needsResubscribe = false;
@@ -823,12 +858,6 @@ namespace eagle {
_connectionLogger.LogLine(
$"Existing commands: {_currentModel.AvailableCommandsByProvince}");
_connectionLogger.LogLine($"New commands: {availableCommands.CommandsByProvince}");
} else if (
_currentModel.CommandToken.HasValue &&
_currentModel.AvailableCommandsByProvince.Count > 0) {
// If we're in here, we already have commands and the tokens match, so there's
// nothing to update.
return;
}
_currentModel.CommandToken = availableCommands.Token;
@@ -1031,7 +1060,11 @@ namespace eagle {
}
}
if (cp.AsOf != null) { province.AsOf = cp.AsOf; }
if (cp.ClearAsOfDate) {
province.AsOf = null;
} else if (cp.AsOf != null) {
province.AsOf = cp.AsOf;
}
}
private void UpdateChronicleEntry(ChronicleEntry newEntry) {
@@ -1281,6 +1281,7 @@ GameObject:
- component: {fileID: 3636629843065580977}
- component: {fileID: 4078066562855709738}
- component: {fileID: 4336842109283128186}
- component: {fileID: 9100000000000000101}
m_Layer: 0
m_Name: Game ID Button
m_TagString: Untagged
@@ -1402,6 +1403,26 @@ MonoBehaviour:
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &9100000000000000101
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1303915697410641120}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &1309384294125923286
GameObject:
m_ObjectHideFlags: 0
@@ -4240,6 +4261,7 @@ GameObject:
- component: {fileID: 965102285360037418}
- component: {fileID: 3663693121331330979}
- component: {fileID: 4533358996739107876}
- component: {fileID: 9100000000000000102}
m_Layer: 0
m_Name: Confirm Command Button
m_TagString: Untagged
@@ -4361,6 +4383,26 @@ MonoBehaviour:
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &9100000000000000102
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3872014544231032350}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!114 &4533358996739107876
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -5405,7 +5447,7 @@ MonoBehaviour:
m_PreferredWidth: -1
m_PreferredHeight: 216
m_FlexibleWidth: 1
m_FlexibleHeight: -1
m_FlexibleHeight: 1
m_LayoutPriority: 1
--- !u!114 &5241770508748684138
MonoBehaviour:
@@ -10394,10 +10436,10 @@ RectTransform:
- {fileID: 56111272101208694}
m_Father: {fileID: 6016649767628985964}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 318.33334, y: -35}
m_SizeDelta: {x: 85, y: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7040993406008513325
CanvasRenderer:
@@ -11414,6 +11456,7 @@ GameObject:
- component: {fileID: 2244481629370632549}
- component: {fileID: 869261839825346708}
- component: {fileID: 297135253675020023}
- component: {fileID: 9100000000000000103}
m_Layer: 0
m_Name: Next Active Province Button
m_TagString: Untagged
@@ -11535,6 +11578,26 @@ MonoBehaviour:
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &9100000000000000103
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8119061345753752109}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &8181948710335746175
GameObject:
m_ObjectHideFlags: 0
@@ -13908,5 +13971,8 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
provinceInfoPanelController: {fileID: 5448146937309441222}
commandPanelController: {fileID: 8023117543167758928}
alertPanel: {fileID: 900000000000000001}
alertText: {fileID: 900000000000000003}
freeHeroesPanel: {fileID: 6624731758075026335}
factionsAndMovingArmiesPanel: {fileID: 1268776338142197082}
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using common;
using Eagle0.Tutorial;
using Net.Eagle0.Eagle.Views;
using UnityEngine;
@@ -104,11 +103,6 @@ namespace eagle {
var newRow = unaffiliatedHeroesTable.ComponentAt<UnaffiliatedHeroRowController>(i);
newRow.SetUnaffiliatedHero(uh, Model);
SetUnaffiliatedHeroRowSelections(newRow, uh);
// Check if this hero's profession should trigger a tutorial
if (Model.Heroes.TryGetValue(uh.HeroId, out var hero)) {
TutorialManager.Instance?.TriggerRegistry?.CheckProfessionTutorial(hero);
}
});
}
@@ -133,4 +127,4 @@ namespace eagle {
.ToList()
: new List<UnaffiliatedHeroBasics>();
}
}
}
@@ -153,9 +153,6 @@ namespace eagle {
var row = heroesTable.ComponentAt<HeroRowController>(i);
row.Hero = hero;
SetHeroRowSelections(row, hero);
// Check if this hero's profession should trigger a tutorial
TutorialManager.Instance?.TriggerRegistry?.CheckProfessionTutorial(hero);
});
// Register hero rows with tutorial system for highlighting
@@ -221,4 +218,4 @@ namespace eagle {
HasProvinceInfo ? ProvinceUtils.OrderedBattalions(CurrentProvince)
: new List<BattalionView>();
}
}
}
@@ -29,6 +29,7 @@ namespace eagle {
void ReceiveLobbyUpdate(LobbyResponse lobbyResponse);
void HandleJoinGameResponse(JoinGameResponse response);
void HandleCreateGameResponse(CreateGameResponse response);
void HandleCreateGameAcceptedResponse(CreateGameAcceptedResponse response);
void HandleCustomBattleResponse(CustomBattleResponse response);
}
@@ -64,6 +65,7 @@ namespace eagle {
private volatile bool _isConnecting = false;
private volatile bool _isExpectedReconnect = false;
private volatile bool _disposed = false;
private Timer _idleCheckTimer = null;
private Timer _heartbeatTimer = null;
private const double HeartbeatIntervalSeconds = 10.0;
@@ -99,9 +101,18 @@ namespace eagle {
/// Force an immediate reconnection attempt, bypassing the circuit breaker timeout.
/// </summary>
public void ForceReconnect() {
_circuitBreaker.ForceReconnect();
_retryTimer?.Dispose();
NextReconnectAttempt = null;
lock (this) {
if (_disposed) {
LogFlow("ForceReconnect() SKIPPED disposed=true");
return;
}
_circuitBreaker.ForceReconnect();
_retryTimer?.Dispose();
_retryTimer = null;
NextReconnectAttempt = null;
}
LogConnectionEvent("force_reconnect", "User requested immediate reconnect");
RunConnectAsync();
}
@@ -111,8 +122,11 @@ namespace eagle {
/// rather than silently swallowed by Task.Run().
/// </summary>
private void RunConnectAsync() {
if (_disposed) return;
_ = Task.Run(async () => {
try {
if (_disposed) return;
await Connect();
} catch (Exception e) {
// Log but don't rethrow - this is fire-and-forget
@@ -159,25 +173,32 @@ namespace eagle {
}
private void ScheduleReconnect(string reason) {
double backoffSeconds = GetBackoffSeconds();
_consecutiveFailures++;
// Stay quiet during the first retry after a disconnect. Occasional
// disconnects are normal and usually recover on the first attempt, so we
// suppress the "Reconnecting..." UX until a retry has itself failed.
if (_consecutiveFailures > 1) { _currentState = ConnectionState.Reconnecting; }
NextReconnectAttempt = DateTime.UtcNow.AddSeconds(backoffSeconds);
lock (this) {
if (_disposed) {
LogFlow($"SCHEDULE_RECONNECT skipped reason={reason} disposed=true");
return;
}
LogFlow($"SCHEDULE_RECONNECT reason={reason} backoff={backoffSeconds:F1}s attempt={_consecutiveFailures}");
LogConnectionEvent(
"schedule_reconnect",
$"{reason}, backoff={backoffSeconds:F1}s, attempt={_consecutiveFailures}");
double backoffSeconds = GetBackoffSeconds();
_consecutiveFailures++;
// Stay quiet during the first retry after a disconnect. Occasional
// disconnects are normal and usually recover on the first attempt, so we
// suppress the "Reconnecting..." UX until a retry has itself failed.
if (_consecutiveFailures > 1) { _currentState = ConnectionState.Reconnecting; }
NextReconnectAttempt = DateTime.UtcNow.AddSeconds(backoffSeconds);
_retryTimer?.Dispose();
_retryTimer = new Timer();
_retryTimer.AutoReset = false;
_retryTimer.Interval = backoffSeconds * 1000;
_retryTimer.Elapsed += (sender, args) => RunConnectAsync();
_retryTimer.Enabled = true;
LogFlow($"SCHEDULE_RECONNECT reason={reason} backoff={backoffSeconds:F1}s attempt={_consecutiveFailures}");
LogConnectionEvent(
"schedule_reconnect",
$"{reason}, backoff={backoffSeconds:F1}s, attempt={_consecutiveFailures}");
_retryTimer?.Dispose();
_retryTimer = new Timer { AutoReset = false, Interval = backoffSeconds * 1000 };
_retryTimer.Elapsed += (sender, args) => {
if (!_disposed) { RunConnectAsync(); }
};
_retryTimer.Enabled = true;
}
}
private readonly Eagle.EagleClient _grpcClient;
@@ -397,6 +418,11 @@ namespace eagle {
}
public async Task Connect() {
if (_disposed) {
LogFlow("Connect() SKIPPED disposed=true");
return;
}
// Prevent concurrent connection attempts
if (_isConnecting) {
LogFlow($"Connect() SKIPPED already_connecting=true");
@@ -495,28 +521,32 @@ namespace eagle {
return;
}
_lastSuccessfulConnect = DateTime.UtcNow;
_consecutiveFailures = 0; // Reset backoff on successful connection
_currentState = ConnectionState.Connected;
LogFlow($"State -> {_currentState}");
_circuitBreaker.RecordSuccess();
LogConnectionEvent("connect_success");
lock (this) {
if (_disposed) return;
// CRITICAL: Cancel any pending scheduled reconnect to prevent it from
// firing and killing this working connection
if (_retryTimer != null) {
_retryTimer.Enabled = false;
_retryTimer.Dispose();
_retryTimer = null;
_lastSuccessfulConnect = DateTime.UtcNow;
_consecutiveFailures = 0; // Reset backoff on successful connection
_currentState = ConnectionState.Connected;
LogFlow($"State -> {_currentState}");
_circuitBreaker.RecordSuccess();
LogConnectionEvent("connect_success");
// CRITICAL: Cancel any pending scheduled reconnect to prevent it from
// firing and killing this working connection
if (_retryTimer != null) {
_retryTimer.Enabled = false;
_retryTimer.Dispose();
_retryTimer = null;
}
// CRITICAL: Reset idle timer baseline before starting idle check
// Otherwise, on reconnect, _lastResponseReceived still has the old
// timestamp and CheckForIdleTimeout() will immediately kill this
// new connection thinking it's been idle for minutes
_lastResponseReceived = DateTime.UtcNow;
_lastIdleWarningLevel = 0;
}
// CRITICAL: Reset idle timer baseline before starting idle check
// Otherwise, on reconnect, _lastResponseReceived still has the old
// timestamp and CheckForIdleTimeout() will immediately kill this
// new connection thinking it's been idle for minutes
_lastResponseReceived = DateTime.UtcNow;
_lastIdleWarningLevel = 0;
// Start monitoring for idle timeout (relies on HTTP/2 keepalive)
StartIdleCheckTimer();
@@ -529,9 +559,19 @@ namespace eagle {
LogConnectionEvent("connect_failed", "ChannelDisposed");
_remoteEagleClientLogger.LogLine(
$"Channel disposed, triggering recreation: {e.Message}");
_currentState = ConnectionState.Disconnected;
OnChannelDead?.Invoke();
Action channelDeadHandler;
lock (this) {
if (_disposed) return;
_currentState = ConnectionState.Disconnected;
channelDeadHandler = OnChannelDead;
}
channelDeadHandler?.Invoke();
} catch (Exception e) {
if (_disposed) {
LogFlow($"Connect failure ignored after disposal: {e.GetType().Name}");
return;
}
_circuitBreaker.RecordFailure();
LogConnectionEvent("connect_failed", e.GetType().Name);
_remoteEagleClientLogger.LogLine($"Got exception {e} in Connect()");
@@ -557,6 +597,8 @@ namespace eagle {
}
private async Task<bool> TryPendingCommands() {
if (_disposed) return true;
// Try-acquire-or-skip: if another invocation is already draining the queue,
// bail out immediately. The running invocation will process whatever is in
// _pendingCommands at the moment it copied the list; anything that arrives
@@ -580,6 +622,8 @@ namespace eagle {
_remoteEagleClientLogger.LogLine($"preparing to post pending commands");
while (pendingCommands.Any()) {
if (_disposed) return true;
var nextCommand = pendingCommands.Dequeue();
if (_subscribers.TryGetValue(nextCommand.GameId, out var subscriber)) {
switch (nextCommand.Command.SealedValueCase) {
@@ -691,9 +735,11 @@ namespace eagle {
return true;
} catch (Exception e) {
_remoteEagleClientLogger.LogLine($"Got exception {e} in TryPendingCommands()");
lock (this) {
while (pendingCommands.Any()) {
_pendingCommands.Add(pendingCommands.Dequeue());
if (!_disposed) {
lock (this) {
while (pendingCommands.Any()) {
_pendingCommands.Add(pendingCommands.Dequeue());
}
}
}
@@ -702,6 +748,8 @@ namespace eagle {
}
public void Disconnect() {
if (_disposed) return;
_awaitingCommandResponse = false;
lock (this) {
_lastDisconnect = DateTime.UtcNow;
@@ -718,6 +766,10 @@ namespace eagle {
CancellationTokenSource tokenSourceToDispose = null;
lock (this) {
if (_disposed) return;
_disposed = true;
_awaitingCommandResponse = false;
// Dispose timers first to stop any pending callbacks
StopIdleCheckTimer();
StopHeartbeatTimer();
@@ -739,6 +791,9 @@ namespace eagle {
// Clear pending commands
_pendingCommands.Clear();
OnChannelDead = null;
OnCommandError = null;
// Cancel thread cancellation token (but don't dispose yet)
_threadCancellationTokenSource?.Cancel();
@@ -758,7 +813,9 @@ namespace eagle {
}
tokenSourceToDispose?.Dispose();
_tryPendingCommandsSemaphore.Dispose();
// An async command drain can still be unwinding after disposal. SemaphoreSlim has no
// resources to release unless its wait handle is requested, so leave it undisposed
// rather than racing WaitAsync()/Release() on the retired connection.
}
public void SetLobbySubscriber(ILobbySubscriber subscriber) {
@@ -1275,6 +1332,19 @@ namespace eagle {
break;
}
case UpdateStreamResponse.ResponseDetailsOneofCase
.CreateGameAcceptedResponse: {
var lobbySubscriber = _lobbySubscriber;
if (lobbySubscriber != null) {
MainQueue.Q.Enqueue(() => {
lobbySubscriber.HandleCreateGameAcceptedResponse(
current.CreateGameAcceptedResponse);
});
}
break;
}
case UpdateStreamResponse.ResponseDetailsOneofCase
.CustomBattleResponse: {
var lobbySubscriber = _lobbySubscriber;
@@ -1389,7 +1459,7 @@ namespace eagle {
// 1. App is not shutting down (main token not cancelled)
// 2. Thread token was NOT cancelled (if it was, something else like
// SyncMismatch already disposed the call and scheduled reconnect)
if (!_cancellationToken.IsCancellationRequested && !tokenCancelled) {
if (!_disposed && !_cancellationToken.IsCancellationRequested && !tokenCancelled) {
lock (this) {
_lastDisconnect = DateTime.UtcNow;
MarkAllShardokGamesForResync();
@@ -1403,6 +1473,11 @@ namespace eagle {
}
} catch (RpcException e) {
lock (this) {
if (_disposed) {
LogFlow($"RpcException: skipping reconnect (disposed, status={e.StatusCode})");
return;
}
_lastDisconnect = DateTime.UtcNow;
_lastDisconnectReason = e.StatusCode;
@@ -1463,6 +1538,11 @@ namespace eagle {
}
}
} catch (ObjectDisposedException e) {
if (_disposed) {
LogFlow("ObjectDisposed: skipping reconnect (connection disposed)");
return;
}
_lastDisconnect = DateTime.UtcNow;
LogConnectionEvent("disconnect", "ObjectDisposed");
_remoteEagleClientLogger.LogLine($"Got ObjectDisposedException {e}");
@@ -1480,6 +1560,11 @@ namespace eagle {
ScheduleReconnect("ObjectDisposed");
} catch (Exception e) {
if (_disposed) {
LogFlow($"UnexpectedException: skipping reconnect (connection disposed, type={e.GetType().Name})");
return;
}
// Catch-all for any unexpected exceptions. Without this, exceptions in an
// async void method can crash the process or be silently lost, leaving the
// connection dead with no recovery.
@@ -1534,11 +1619,13 @@ namespace eagle {
private void StartIdleCheckTimer() {
StopIdleCheckTimer();
_idleCheckTimer = new Timer {
var timer = new Timer {
AutoReset = true,
Interval = 5000 // Check every 5 seconds
};
_idleCheckTimer.Elapsed += (sender, args) => {
timer.Elapsed += (sender, args) => {
if (_disposed) return;
try {
// Log BEFORE callback to confirm timer is firing
Console.WriteLine($"[IDLE_TIMER] Elapsed at {DateTime.UtcNow:HH:mm:ss.fff}");
@@ -1549,7 +1636,15 @@ namespace eagle {
_remoteEagleClientLogger.LogLine($"Exception in idle check timer: {e}");
}
};
_idleCheckTimer.Enabled = true;
lock (this) {
if (_disposed) {
timer.Dispose();
return;
}
_idleCheckTimer = timer;
_idleCheckTimer.Enabled = true;
}
}
private void StopIdleCheckTimer() {
@@ -1561,6 +1656,8 @@ namespace eagle {
}
private void CheckForIdleTimeout() {
if (_disposed) return;
var idleTime = (DateTime.UtcNow - _lastResponseReceived).TotalSeconds;
// Log every check when idle > 5s to diagnose timer issues
@@ -1634,15 +1731,17 @@ namespace eagle {
private void StartHeartbeatTimer() {
StopHeartbeatTimer();
_heartbeatTimer =
new Timer { AutoReset = true, Interval = HeartbeatIntervalSeconds * 1000 };
_heartbeatTimer.Elapsed += (sender, args) => {
var timer = new Timer { AutoReset = true, Interval = HeartbeatIntervalSeconds * 1000 };
timer.Elapsed += (sender, args) => {
if (_disposed) return;
try {
// Log BEFORE Task.Run to confirm timer is firing
Console.WriteLine(
$"[HEARTBEAT_TIMER] Elapsed at {DateTime.UtcNow:HH:mm:ss.fff}");
_ = Task.Run(async () => {
try {
if (_disposed) return;
await SendHeartbeat();
} catch (Exception e) {
// Log but don't rethrow - exceptions in Task.Run are silently lost
@@ -1656,7 +1755,15 @@ namespace eagle {
_remoteEagleClientLogger.LogLine($"Exception in heartbeat timer: {e}");
}
};
_heartbeatTimer.Enabled = true;
lock (this) {
if (_disposed) {
timer.Dispose();
return;
}
_heartbeatTimer = timer;
_heartbeatTimer.Enabled = true;
}
}
private void StopHeartbeatTimer() {
@@ -1668,6 +1775,8 @@ namespace eagle {
}
private async Task SendHeartbeat() {
if (_disposed) return;
LogFlow($"HEARTBEAT_START state={_currentState}");
if (_cancellationToken.IsCancellationRequested) {
@@ -58,10 +58,7 @@ namespace eagle {
protected int DisplayedIndex { get; private set; }
void Start() {
TouchTargetUtils.EnsureMinimumTouchTarget(nextButton);
SetUpPanel();
}
void Start() { SetUpPanel(); }
void OnDisable() {
if (_popupInfos.Count == 0) { canvas.SetChildActive(false); }
@@ -7684,7 +7684,7 @@ MonoBehaviour:
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleWidth: 0
m_FlexibleHeight: 1
m_LayoutPriority: 1
--- !u!1 &1686216885
@@ -8592,7 +8592,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_SizeDelta: {x: -200, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1849727105
MonoBehaviour:
@@ -8687,7 +8687,7 @@ MonoBehaviour:
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_TextWrappingMode: 0
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
@@ -227,7 +227,6 @@ namespace eagle {
SetUnderAttack(underAttack);
if (fullInfo.ProvinceOrders != ProvinceOrderType.UnknownOrders) {
AsOfField.gameObject.SetActive(false);
switch (Province.FullInfo.ProvinceOrders) {
case ProvinceOrderType.Entrust: entrustImage.gameObject.SetActive(true); break;
case ProvinceOrderType.Develop: developImage.gameObject.SetActive(true); break;
@@ -236,7 +235,9 @@ namespace eagle {
mobilizeImage.gameObject.SetActive(true);
break;
}
} else if (Province.AsOf != null) {
}
if (Province.AsOf != null) {
AsOfField.text = $"As of {DisplayNames.DateString(Province.AsOf)}";
AsOfField.gameObject.SetActive(true);
}
@@ -1,5 +1,6 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace eagle {
public class ProvinceUnderAttackAlertController : MonoBehaviour {
@@ -9,11 +10,33 @@ namespace eagle {
[SerializeField]
private ProvinceInfoPanelController provinceInfoPanelController;
[SerializeField]
private CommandPanelController commandPanelController;
[SerializeField]
private GameObject alertPanel;
[SerializeField]
private TextMeshProUGUI alertText;
[SerializeField]
private RectTransform freeHeroesPanel;
[SerializeField]
private RectTransform factionsAndMovingArmiesPanel;
private LayoutElement freeHeroesLayoutElement;
private LayoutElement factionsAndMovingArmiesLayoutElement;
private float freeHeroesMinimumHeight;
private float factionsAndMovingArmiesMinimumHeight;
private float alertHeightPerFlexiblePanel;
private void Awake() {
freeHeroesLayoutElement = freeHeroesPanel.GetComponent<LayoutElement>();
factionsAndMovingArmiesLayoutElement =
factionsAndMovingArmiesPanel.GetComponent<LayoutElement>();
freeHeroesMinimumHeight = freeHeroesLayoutElement.minHeight;
factionsAndMovingArmiesMinimumHeight = factionsAndMovingArmiesLayoutElement.minHeight;
var alertLayoutElement = alertPanel.GetComponent<LayoutElement>();
var columnLayoutGroup = alertPanel.transform.parent.GetComponent<VerticalLayoutGroup>();
alertHeightPerFlexiblePanel =
(alertLayoutElement.preferredHeight + columnLayoutGroup.spacing) / 2f;
provinceInfoPanelController.UnderAttackChanged += SetUnderAttack;
SetUnderAttack(provinceInfoPanelController.UnderAttack);
}
@@ -26,12 +49,24 @@ namespace eagle {
if (!alertPanel.activeSelf) { return; }
float pulse = (Mathf.Sin(Time.unscaledTime * Mathf.PI * 2f / 1.2f) + 1f) * 0.5f;
alertText.color = Color.Lerp(UnderAttackTextColor, UnderAttackTextPulseColor, pulse);
SetAlertTextColor(Color.Lerp(UnderAttackTextColor, UnderAttackTextPulseColor, pulse));
}
private void SetUnderAttack(bool underAttack) {
alertPanel.SetActive(underAttack);
if (underAttack) { alertText.color = UnderAttackTextColor; }
commandPanelController.underAttackText.gameObject.SetActive(underAttack);
freeHeroesLayoutElement.minHeight =
underAttack ? freeHeroesMinimumHeight - alertHeightPerFlexiblePanel
: freeHeroesMinimumHeight;
factionsAndMovingArmiesLayoutElement.minHeight =
underAttack ? factionsAndMovingArmiesMinimumHeight - alertHeightPerFlexiblePanel
: factionsAndMovingArmiesMinimumHeight;
if (underAttack) { SetAlertTextColor(UnderAttackTextColor); }
}
private void SetAlertTextColor(Color color) {
alertText.color = color;
commandPanelController.underAttackText.color = color;
}
}
}
@@ -121,7 +121,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
@@ -321,10 +321,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 25
m_PreferredHeight: 25
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 48
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -958,10 +958,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 25
m_PreferredHeight: 25
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 48
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -1183,10 +1183,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 25
m_PreferredHeight: 25
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 48
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -1912,10 +1912,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 25
m_PreferredHeight: 25
m_MinWidth: 48
m_MinHeight: 48
m_PreferredWidth: 48
m_PreferredHeight: 48
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -53,13 +53,6 @@ namespace eagle {
}
}
private void Awake() {
TouchTargetUtils.EnsureMinimumTouchTarget(plusButton);
TouchTargetUtils.EnsureMinimumTouchTarget(minusButton);
TouchTargetUtils.EnsureMinimumTouchTarget(maxButton);
TouchTargetUtils.EnsureMinimumTouchTarget(dismissButton);
}
private void DrawComponents() {
var resultHeight = resultRow.GetComponent<LayoutElement>().minHeight;
layoutElement.minHeight = resultHeight;
@@ -280,7 +280,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
@@ -743,7 +743,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_MinHeight: 48
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
@@ -0,0 +1,110 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using eagle;
using eagle.Notifications;
using Grpc.Core;
using Net.Eagle0.Eagle.Api;
using Net.Eagle0.Eagle.Views;
using NUnit.Framework;
using ActionResultType = Net.Eagle0.Eagle.Common.ActionResultType;
namespace eagle0.Tests {
public class GameModelUpdaterTests {
[Test]
public void DisposeUnsubscribesAndReleasesCallbacks() {
const long gameId = 7123;
var connection =
new PersistentClientConnection(null, new Metadata(), CancellationToken.None);
var updater = new GameModelUpdater(gameId, null, connection) {
UpdateAction =
_ => {},
ActionResultAction =
_ => {},
NoteRecipient = (Notification _) => {},
HasPendingCommandWithToken =
_ => true
};
var subscribers = (IDictionary) typeof(PersistentClientConnection)
.GetField(
"_subscribers",
BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(connection);
subscribers.Add(gameId, updater);
updater.Dispose();
Assert.IsFalse(subscribers.Contains(gameId));
Assert.IsNull(updater.UpdateAction);
Assert.IsNull(updater.ActionResultAction);
Assert.IsNull(updater.NoteRecipient);
Assert.IsNull(updater.HasPendingCommandWithToken);
Assert.DoesNotThrow(updater.Dispose);
}
[Test]
public void ResultBearingUpdateReplacesCommandsWhenTokenIsUnchanged() {
const long gameId = 7124;
const long token = 17;
const int provinceId = 34;
var models = new List<IGameModel>();
var updater = new GameModelUpdater(gameId, null, null) { UpdateAction = models.Add };
updater.ReceiveGameUpdate(new GameUpdate {
GameId = gameId,
StartingState = new GameStateView { GameId = gameId },
ActionResultResponse =
new ActionResultResponse {
AvailableCommands = AvailableCommandsWith(
token,
provinceId,
new AvailableCommand {
ReturnCommand =
new ReturnAvailableCommand {
ActingProvinceId = provinceId
}
})
}
});
var questFulfilled = new ActionResultResponse {
UnfilteredResultCountAfter = 1,
AvailableCommands = AvailableCommandsWith(
token,
provinceId,
new AvailableCommand {
RecruitHeroesCommand =
new RecruitHeroesAvailableCommand {
ActingProvinceId = provinceId
}
})
};
questFulfilled.ActionResultViews.Add(
new ActionResultView { Type = ActionResultType.QuestFulfilled });
updater.ReceiveGameUpdate(
new GameUpdate { GameId = gameId, ActionResultResponse = questFulfilled });
Assert.AreEqual(2, models.Count);
Assert.AreEqual(token, models[1].CommandToken);
Assert.Greater(models[1].StrategicUpdateRevision, models[0].StrategicUpdateRevision);
var command = models[1].AvailableCommandsForProvince(provinceId).Single();
Assert.AreEqual(
AvailableCommand.SealedValueOneofCase.RecruitHeroesCommand,
command.SealedValueCase);
}
private static AvailableCommands
AvailableCommandsWith(long token, int provinceId, AvailableCommand command) {
var result = new AvailableCommands { Token = token, SuggestedProvinceId = provinceId };
result.CommandsByProvince.Add(
provinceId,
new OneProvinceAvailableCommands {
ProvinceId = provinceId,
Commands = { command }
});
return result;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c9ac45b897a646889599e86c16cc32b7
@@ -0,0 +1,65 @@
using System.Reflection;
using System.Threading;
using eagle;
using Grpc.Core;
using NUnit.Framework;
using Timer = System.Timers.Timer;
namespace eagle0.Tests {
[TestFixture]
public class PersistentClientConnectionLifecycleTests {
[Test]
public void DisposeStopsAndPreventsAllConnectionTimers() {
var connection =
new PersistentClientConnection(null, new Metadata(), CancellationToken.None);
SetPrivateField(connection, "_retryTimer", new Timer());
SetPrivateField(connection, "_idleCheckTimer", new Timer());
SetPrivateField(connection, "_heartbeatTimer", new Timer());
connection.OnChannelDead = () => {};
connection.OnCommandError =
_ => {};
connection.Dispose();
Assert.IsNull(GetPrivateField<Timer>(connection, "_retryTimer"));
Assert.IsNull(GetPrivateField<Timer>(connection, "_idleCheckTimer"));
Assert.IsNull(GetPrivateField<Timer>(connection, "_heartbeatTimer"));
Assert.IsNull(connection.OnChannelDead);
Assert.IsNull(connection.OnCommandError);
InvokePrivate(connection, "ScheduleReconnect", "test-after-dispose");
InvokePrivate(connection, "StartIdleCheckTimer");
InvokePrivate(connection, "StartHeartbeatTimer");
connection.ForceReconnect();
Assert.IsNull(GetPrivateField<Timer>(connection, "_retryTimer"));
Assert.IsNull(GetPrivateField<Timer>(connection, "_idleCheckTimer"));
Assert.IsNull(GetPrivateField<Timer>(connection, "_heartbeatTimer"));
Assert.DoesNotThrow(connection.Dispose);
}
private static T GetPrivateField<T>(object target, string fieldName) {
var field = target.GetType().GetField(
fieldName,
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(field, $"Missing private field '{fieldName}'");
return (T)field.GetValue(target);
}
private static void SetPrivateField<T>(object target, string fieldName, T value) {
var field = target.GetType().GetField(
fieldName,
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(field, $"Missing private field '{fieldName}'");
field.SetValue(target, value);
}
private static void InvokePrivate(object target, string methodName, params object[] args) {
var method = target.GetType().GetMethod(
methodName,
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(method, $"Missing private method '{methodName}'");
method.Invoke(target, args);
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 370b3269832442e196d1b432bdae2f1e
guid: da873f267348474f90a5fe62ea51011a
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,55 @@
using System.Reflection;
using common;
using eagle;
using NUnit.Framework;
using UnityEngine;
namespace eagle0.Tests {
[TestFixture]
public class PersistentUIManagerTests {
[Test]
public void RewiresStatusUiWhenCurrentConnectionIsReplaced() {
var previousConnection = PersistentClientConnection.Current;
var managerObject = new GameObject("PersistentUIManager test");
var statusObject = new GameObject("ConnectionStatusUI test");
try {
var manager = managerObject.AddComponent<PersistentUIManager>();
var statusUi = statusObject.AddComponent<ConnectionStatusUI>();
manager.connectionStatusUI = statusUi;
var firstConnection = new PersistentClientConnection(null, null, default);
var replacementConnection = new PersistentClientConnection(null, null, default);
PersistentClientConnection.Current = firstConnection;
InvokeTryWireConnection(manager);
Assert.AreSame(firstConnection, GetMonitoredConnection(statusUi));
PersistentClientConnection.Current = replacementConnection;
InvokeTryWireConnection(manager);
Assert.AreSame(replacementConnection, GetMonitoredConnection(statusUi));
} finally {
PersistentClientConnection.Current = previousConnection;
Object.DestroyImmediate(managerObject);
Object.DestroyImmediate(statusObject);
}
}
private static void InvokeTryWireConnection(PersistentUIManager manager) {
var method = typeof(PersistentUIManager)
.GetMethod(
"TryWireConnection",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(method);
method.Invoke(manager, null);
}
private static PersistentClientConnection GetMonitoredConnection(
ConnectionStatusUI statusUi) {
var field = typeof(ConnectionStatusUI)
.GetField(
"_connection",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(field);
return (PersistentClientConnection)field.GetValue(statusUi);
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 98467fdbaf584fbb928a373340b542d7
guid: 8c8140b065a6469994d836c8f7e0a5c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2cd70b162309940b1bf97c3077e07322
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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