mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 10:35:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9d59a4ca2 | ||
|
|
811761ca19 | ||
|
|
c49c4d26e1 | ||
|
|
68906df5b9 | ||
|
|
dc54584fa3 | ||
|
|
d665eb53f3 | ||
|
|
59143a2e04 | ||
|
|
f9825861e3 | ||
|
|
9b5dcc40a0 | ||
|
|
44514fb2f2 | ||
|
|
3014602331 | ||
|
|
bb182efc3e | ||
|
|
3a04d865b6 | ||
|
|
f2f81d1cda | ||
|
|
72dc8ba9fb | ||
|
|
7adc38fe12 | ||
|
|
67dbf928d6 | ||
|
|
efad456bef | ||
|
|
9ebc517c9f | ||
|
|
49020af5e9 | ||
|
|
e5e5dd9fdc | ||
|
|
0dd780fc63 | ||
|
|
ae4c441427 | ||
|
|
f01a2ee378 | ||
|
|
e4901b6414 | ||
|
|
f0b2fd5f5e | ||
|
|
a28a0ef8d5 | ||
|
|
88ff742bb1 | ||
|
|
d549c0642b | ||
|
|
00c677d12a | ||
|
|
6c47019e8e | ||
|
|
f7932c6681 | ||
|
|
13e734d9ae | ||
|
|
a4f934105f | ||
|
|
1300ec9ab9 | ||
|
|
bfba377c57 | ||
|
|
56564c1bee | ||
|
|
1728d719a4 | ||
|
|
3314ef191f | ||
|
|
1fbd2e8438 | ||
|
|
7f50b61419 | ||
|
|
c7dea02861 | ||
|
|
afd8028665 | ||
|
|
2263977668 | ||
|
|
4cfec68ef8 | ||
|
|
432276f550 | ||
|
|
ef179e67ef | ||
|
|
7ee9fbb8da | ||
|
|
3d46bee7dd | ||
|
|
c6dc5477f5 | ||
|
|
ab7499ca7a | ||
|
|
7d95c81a00 | ||
|
|
e523ebf96e | ||
|
|
b740e1ad59 | ||
|
|
3a612426f6 | ||
|
|
452d0cac5a | ||
|
|
bdb7b324c4 | ||
|
|
145f7e9f9c | ||
|
|
09e5f4801d | ||
|
|
2aa5fa181a | ||
|
|
f438c4bbe2 | ||
|
|
66ecc25075 | ||
|
|
562d32d296 | ||
|
|
bdf9b09b26 | ||
|
|
d2559ff67c | ||
|
|
88433fbc18 | ||
|
|
9426488913 | ||
|
|
81395a9467 | ||
|
|
fb4e0c1bac | ||
|
|
94b273ff60 | ||
|
|
b179e250dd | ||
|
|
b3e35b60c0 | ||
|
|
3a0fa9e59c | ||
|
|
cdf7c5649b | ||
|
|
743737555e | ||
|
|
94f2366af3 | ||
|
|
5d4399cad6 | ||
|
|
011562a2d6 | ||
|
|
781c7a8c3d | ||
|
|
bce1ddaf81 | ||
|
|
d734c591a8 | ||
|
|
c5aca2666c | ||
|
|
6e261ef7b5 | ||
|
|
71240cd9ed | ||
|
|
15d7d30ccf | ||
|
|
f52d54b7c2 | ||
|
|
6314d3e4e3 | ||
|
|
3f54abc3c0 | ||
|
|
0e126faee2 | ||
|
|
763ab63860 | ||
|
|
38a15fdf9f | ||
|
|
b0446d99e4 | ||
|
|
2f6a58255b | ||
|
|
a4257efc0e | ||
|
|
b155494b4d | ||
|
|
04a51c9fb6 | ||
|
|
ee5cf07db3 | ||
|
|
5c4470b090 | ||
|
|
86171cc1ae | ||
|
|
d5da95c5e3 | ||
|
|
23c7385d8f | ||
|
|
cc5b6dbc75 | ||
|
|
025bcbbff6 | ||
|
|
fa6133f3ee | ||
|
|
af35d42792 | ||
|
|
d3f99ccada | ||
|
|
f4e3944716 | ||
|
|
c67dc76ed5 | ||
|
|
8bd9342e68 | ||
|
|
55c55adf8a | ||
|
|
3f6fee72b5 | ||
|
|
35d441d7f6 | ||
|
|
ddb49f411b | ||
|
|
130c0a5f37 | ||
|
|
9c3eb2d6e7 | ||
|
|
a84154bc74 | ||
|
|
08a5dc6104 | ||
|
|
33d963488b | ||
|
|
2c3135a493 | ||
|
|
b3b69474e4 | ||
|
|
ee74455b57 | ||
|
|
bd7378a971 | ||
|
|
588fd4217a | ||
|
|
1a60af2cd2 | ||
|
|
8398da469d | ||
|
|
f7280d37a6 | ||
|
|
1e98f4e3e8 | ||
|
|
3b203ac1b2 | ||
|
|
0ac3bd1e6e | ||
|
|
b56df59977 | ||
|
|
441841ec41 | ||
|
|
b8ebd1d234 | ||
|
|
c1d9c53b30 | ||
|
|
b4fbc7770a | ||
|
|
065d5b466a | ||
|
|
59a28f75e5 | ||
|
|
86dc430ebc | ||
|
|
a062d6e8b2 | ||
|
|
04425edc34 | ||
|
|
5a622b276a | ||
|
|
9c809d6453 | ||
|
|
2838e8b280 | ||
|
|
3b03f3384a | ||
|
|
6c219a6d43 | ||
|
|
062c19dacf | ||
|
|
82b9c0f7df | ||
|
|
13cc93345a | ||
|
|
a5652e43f2 | ||
|
|
09237eb9b1 | ||
|
|
b15a9f0878 | ||
|
|
a478906805 | ||
|
|
9619cb8076 | ||
|
|
588bcbd81f | ||
|
|
16cacb9ee8 | ||
|
|
150970fcae | ||
|
|
de6ccb4345 | ||
|
|
ab64052fa7 | ||
|
|
38129c2a8e | ||
|
|
2fd16d962c | ||
|
|
4dcb4bacdb | ||
|
|
e1dd455af3 | ||
|
|
1cd7474864 | ||
|
|
c1c60c406a | ||
|
|
b20c8e55db | ||
|
|
ca485e2c05 | ||
|
|
1f44105ac4 | ||
|
|
b200d6d631 | ||
|
|
5ca66b9090 | ||
|
|
a714271ef2 | ||
|
|
21a311e9eb | ||
|
|
4b7a26b12b |
+1
-1
@@ -1 +1 @@
|
||||
9.1.1
|
||||
9.2.0
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Generated
+49
-2097
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()
|
||||
@@ -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
|
||||
@@ -37,7 +35,7 @@ commands
|
||||
commands-json
|
||||
```
|
||||
|
||||
`state` is the concise map and owned-province view. `state-json` prints the complete view. `commands-json` includes the exact fields, IDs, resource amounts, and command choices currently accepted by the server.
|
||||
`state` is the concise map and owned-province view. `state-json` prints the complete view. `commands-json` includes the exact fields, IDs, resource amounts, and command choices currently accepted by the server. Each entry also includes a `post-json` template for its corresponding selected command.
|
||||
|
||||
Generated text is stored locally during the client process. Use `text <text-id>` for a text ID without spaces, such as `text hn_105`. Province leaders in `state` are resolved through this store when their names have been received.
|
||||
|
||||
@@ -49,6 +47,12 @@ Use the available-command data from `commands-json` to select a command, then po
|
||||
post-json <province-id> <SelectedCommandType> <JSON>
|
||||
```
|
||||
|
||||
Templates contain the accepted JSON field names and default values. Replace zero
|
||||
IDs, `UNKNOWN` enum values, empty collections, and other placeholders with a legal
|
||||
choice from the available-command payload before posting. Where a selected command
|
||||
must echo server data, such as a ransom offer, the client copies that data into the
|
||||
template automatically.
|
||||
|
||||
For example:
|
||||
|
||||
```text
|
||||
@@ -57,7 +61,7 @@ post-rest 13
|
||||
post-feast 13
|
||||
```
|
||||
|
||||
The client has shortcuts for `post-rest`, `post-return`, `post-feast`, riot decisions, and battle aftermath. Other selected-command JSON schemas are defined in:
|
||||
The client has shortcuts for `post-rest`, `post-return`, `post-feast`, riot decisions, and battle aftermath. The authoritative selected-command schemas are defined in:
|
||||
|
||||
```text
|
||||
src/main/protobuf/net/eagle0/eagle/api/selected_command.proto
|
||||
@@ -119,8 +123,41 @@ post-shardok <shardok-game-id> <index>
|
||||
post-shardok <shardok-game-id> <index> <integer-roll>
|
||||
```
|
||||
|
||||
The battle ID may be omitted only when exactly one outstanding battle currently
|
||||
has postable commands. If multiple battles have commands, the client refuses the
|
||||
ambiguous shorthand; use the battle ID printed by `shardok-commands`. Battles
|
||||
removed from Eagle's outstanding-battle list are no longer postable, and a
|
||||
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:
|
||||
|
||||
@@ -20,9 +20,14 @@ export default defineConfig({
|
||||
label: "Concepts",
|
||||
items: [
|
||||
{ label: "Game Concepts", slug: "concepts" },
|
||||
{ label: "Victory and Defeat", slug: "concepts/victory-and-defeat" },
|
||||
{ label: "Who You Command", slug: "concepts/command-authority" },
|
||||
{ label: "Eagle Round Flow", slug: "concepts/eagle-round-flow" },
|
||||
{ label: "March and Battle Timeline", slug: "concepts/march-timeline" },
|
||||
{ label: "Information and Recon", slug: "concepts/information-and-recon" },
|
||||
{ label: "From Eagle to Shardok", slug: "concepts/eagle-to-shardok" },
|
||||
{ label: "Shardok Battles", slug: "concepts/shardok-battles" },
|
||||
{ label: "Shardok Combat Fundamentals", slug: "concepts/shardok-combat" },
|
||||
{
|
||||
label: "Shardok Victory Conditions",
|
||||
slug: "concepts/shardok-victory-conditions",
|
||||
@@ -32,6 +37,7 @@ export default defineConfig({
|
||||
{ label: "Eagle Commands", slug: "commands" },
|
||||
{ label: "Shardok Commands", slug: "shardok-commands" },
|
||||
{ label: "Heroes", slug: "heroes" },
|
||||
{ label: "Battalions", slug: "battalions" },
|
||||
{ label: "Factions", slug: "factions" },
|
||||
{ label: "Provinces", slug: "provinces" },
|
||||
{ label: "Integration Notes", slug: "integration" },
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Battalions
|
||||
description: Troop count, type, training, armament, morale, heroes, and the battalion lifecycle.
|
||||
---
|
||||
|
||||
Battalions are Eagle's organized troop formations and become units in [Shardok battles](/concepts/shardok-battles/) when attached to heroes.
|
||||
|
||||
## Reading a battalion
|
||||
|
||||
- **Troops** are its current soldiers. Casualties and starvation reduce this number; reaching zero destroys the battalion.
|
||||
- **Type** determines movement, damage, battlefield limits, and whether an attached hero can cast or use stealth.
|
||||
- **TRN (training)** increases the damage the battalion delivers. It is raised with [Train](/commands/#train).
|
||||
- **ARM (armament)** reduces normal damage the battalion takes. It is raised with [Arm Troops](/commands/#arm-troops) and cannot exceed the province's effective infrastructure.
|
||||
- **Morale** affects combat performance and gates many tactical actions. It changes through battle effects.
|
||||
|
||||
## How the combat factors work
|
||||
|
||||
These values do different jobs; they are not interchangeable ratings that simply add into one combat score.
|
||||
|
||||
- **Training** scales the battalion type's melee and missile damage bonuses. Higher training therefore means more damage per troop. It also affects some command-specific odds, including the chance to stun on a [Charge](/shardok-commands/#charge).
|
||||
- **Armament** scales the battalion type's resistances and reduces incoming normal damage. Each type and damage category has its own resistance profile, so the same ARM value does not protect every battalion equally. Penetrating damage bypasses this normal armament reduction. If the unit can use [Archery](/shardok-commands/#archery), armament also determines its starting volleys: currently one volley per 10 ARM, rounded down, plus any bonus from its battalion type.
|
||||
- **Morale** changes the battalion's damage swing. Most units also need at least 10 morale to use normal tactical commands.
|
||||
- **Hero vigor** is an action constraint and current energy reserve, not battalion armor. A hero-bearing unit normally needs at least 10 vigor to act, and special effects can spend or reduce it.
|
||||
- **Troop count** multiplies battalion damage: more surviving troops deliver more total battalion damage. It also determines how many soldiers remain available to absorb casualties.
|
||||
- **Battalion type** supplies the base damage, training bonus, resistance profile, armament benefit, movement costs, charge multiplier, and special capabilities.
|
||||
- **Hero stats** contribute separately. Charisma and bravery affect battalion combat swings, while a hero also contributes personal damage and enables stat- or profession-based commands.
|
||||
|
||||
Exact enemy training, armament, hero stats, or other details may be hidden. Use only the information your faction has revealed; the game does not promise that an unknown value is zero.
|
||||
|
||||
## Battalion types
|
||||
|
||||
### Light Infantry
|
||||
|
||||
The basic flexible foot formation. It is inexpensive, has the largest normal infantry capacity, can carry casting heroes, and allows [Ranger](/heroes/#ranger) stealth. Its protection gains from ARM are useful but lower than those of heavy formations.
|
||||
|
||||
### Heavy Infantry
|
||||
|
||||
Slower and more expensive to train and equip, with fewer troops per full battalion. Heavy Infantry starts with substantial physical resistance and generally gains more protection from ARM than light formations. It also delivers heavier melee damage, but cannot cast or hide.
|
||||
|
||||
### Light Cavalry
|
||||
|
||||
Fast on open terrain and especially effective when charging: its charge deals 1.2 times normal unit damage. A charge spends only the terrain-entry cost, so Light Cavalry may move or use another available command afterward when it retains AP.
|
||||
|
||||
### Heavy Cavalry
|
||||
|
||||
The most expensive normal formation, with the smallest capacity and highest food cost. It combines strong built-in resistance and armament scaling with the largest charge multiplier, 1.5. Like Light Cavalry, it can sometimes act again after charging if AP remains.
|
||||
|
||||
### Longbowmen
|
||||
|
||||
A ranged specialist that is always [archery](/shardok-commands/#archery)-capable and receives two bonus volleys in addition to those provided by ARM. It can also support casting and stealth-capable heroes. Longbow attacks include penetrating missile damage. Because penetrating damage bypasses normal armament reduction, trained Longbowmen can inflict heavy casualties even on well-armed troops. They are comparatively poorly protected by their own ARM.
|
||||
|
||||
### Undead
|
||||
|
||||
An exceptional formation created through necromantic effects rather than ordinary recruitment. Undead have high capacity, do not consume food or use normal morale, can cross ocean terrain, and are vulnerable to [Holy Wave](/shardok-commands/#holy-wave). Their combat and movement rules differ enough that they should not be evaluated like a normal recruitable battalion.
|
||||
|
||||
## Heroes and units
|
||||
|
||||
A Shardok unit normally combines one hero and one battalion. The hero supplies stats, vigor, profession commands, and special capabilities; the battalion supplies troops, type, training, armament, movement, morale, and damage profile. A profession alone may not be enough: casting requires a casting-capable battalion, and Ranger stealth requires a stealth-capable one.
|
||||
|
||||
## Creating and changing battalions
|
||||
|
||||
[**Organize Troops**](/commands/#organize-troops) creates, resizes, merges, transfers, or dismisses battalions. New troops cost gold and begin without training or armament. Transfers preserve weighted training and armament. Empty battalions are removed.
|
||||
|
||||
Use [**Train**](/commands/#train) to improve eligible battalions in the province and [**Arm Troops**](/commands/#arm-troops) while traveling to buy higher armament. Province economy and agriculture affect which types can be raised; effective infrastructure limits equipment.
|
||||
|
||||
## Movement, food, and battle
|
||||
|
||||
[March](/commands/#march) moves selected battalions with their heroes and supplies. Stationed and moving battalions consume food. A shortage spends the remaining food and can reduce battalions by up to 20 percent in one consumption event.
|
||||
|
||||
Battle results return surviving troop counts and statuses to Eagle. Battalions may survive with casualties, be destroyed, flee with their heroes, be captured/secured, or become the new garrison after conquest.
|
||||
|
||||
See [Organize Troops](/commands/#organize-troops), [Train](/commands/#train), [Arm Troops](/commands/#arm-troops), and [Shardok Battles](/concepts/shardok-battles/#units).
|
||||
@@ -94,7 +94,9 @@ Most command phase actions spend the acting province's turn.
|
||||
|
||||
<b>Vigor cost:</b> None. This restores up to 10 vigor for each ruling-faction hero in the province.
|
||||
|
||||
<b>Available when:</b> the acting province can pay the feast cost: 20 gold per ruling-faction hero in the province, multiplied by the province [price index](/provinces/#price-index).
|
||||
<b>Gold cost:</b> 20 gold for each ruling-faction hero in the province, multiplied by the province [price index](/provinces/#price-index) and rounded up. For example, a province with 3 ruling-faction heroes and a 1.25 price index pays 75 gold.
|
||||
|
||||
<b>Available when:</b> the acting province can pay the full feast cost.
|
||||
|
||||
<b>Effect:</b> Hold a feast in the acting province. Every ruling-faction hero in the province gains up to 10 loyalty, capped at 100, and up to 10 vigor, capped by constitution. Faction leaders do not gain loyalty, but can still gain vigor. The province spends gold, the price index may change, and feast-spending quest counters can advance.
|
||||
|
||||
@@ -323,9 +325,11 @@ You must use Travel before these commands appear. Once you are traveling, you ca
|
||||
|
||||
<b>Vigor cost:</b> None.
|
||||
|
||||
<b>Available when:</b> you are traveling, the province has gold, and there are free heroes who are not yet ready to join your faction but might be won over.
|
||||
<b>Gold cost:</b> 100 gold per selected hero, multiplied by the province [price index](/provinces/#price-index) and rounded to the nearest whole gold for each hero. The total cost is that per-hero cost multiplied by the number of selected heroes. For example, divining 3 heroes at a 1.25 price index costs 375 gold.
|
||||
|
||||
<b>Effect:</b> Spend gold to divine one or more [free heroes](/factions/#free-heroes). For each divined hero, you receive details about what could impress them enough to become recruitable. Fulfill that ask, and the hero can later appear under Recruit Heroes. Cost scales by the province price index, the province spends gold, and the price index may change.
|
||||
<b>Available when:</b> you are traveling, the province can pay the cost for at least one hero, and there are free heroes who are not yet ready to join your faction but might be won over.
|
||||
|
||||
<b>Effect:</b> Spend gold to divine one or more [free heroes](/factions/#free-heroes). For each divined hero, you receive details about what could impress them enough to become recruitable. Fulfill that ask, and the hero can later appear under Recruit Heroes. The province spends gold, and the price index may change.
|
||||
|
||||
<b>Selection:</b> choose the heroes to divine.
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Who You Command
|
||||
description: Faction leaders, vassals, province turns, and direct control.
|
||||
---
|
||||
|
||||
Eagle commands are issued through provinces, but who chooses a province's action depends on its ruling hero. The key distinction is between **faction leaders**, whom the player commands directly, and **vassals**, who govern automatically.
|
||||
|
||||
## Leadership roles
|
||||
|
||||
| Role | Directly commanded? | Main purpose |
|
||||
| --- | --- | --- |
|
||||
| Faction head | Yes | Defines the faction and provides its original leadership |
|
||||
| Other faction leader | Yes | Gives the player another directly controlled leader and a successor |
|
||||
| Vassal | No | Rules a province according to standing orders and local needs |
|
||||
| Unaffiliated hero | No | May be recruited, divined, apprehended, or otherwise interacted with |
|
||||
|
||||
The faction head is also a faction leader. **Swear Kinship** can promote a loyal faction hero into an additional faction leader. A faction leader who rules a province lets the player choose that province's normal command directly.
|
||||
|
||||
## Province turns
|
||||
|
||||
Most strategic actions belong to the province currently acting. During **Player Commands**, provinces led by faction leaders present their available commands to the player. A normal command such as Train, Improve, March, or Rest spends that province's turn.
|
||||
|
||||
The hero selected for a command is its actor, but selecting a different hero does not create another province turn. Several heroes may be eligible to perform the same province action; the player chooses which hero pays vigor and receives the associated experience.
|
||||
|
||||
Some commands explicitly do not take the turn. For example, **Issue Orders** changes standing instructions without consuming the acting province's normal action. The command reference labels these exceptions.
|
||||
|
||||
## Vassal-led provinces
|
||||
|
||||
A vassal-led province acts during **Vassal Commands**, before the main Player Commands phase. The vassal chooses an action using:
|
||||
|
||||
- the province's standing order: Entrust, Develop, Mobilize, or Expand;
|
||||
- the faction's focus province;
|
||||
- urgent local needs such as defense, riots, fatigue, low support, or beasts; and
|
||||
- the commands actually available in the province.
|
||||
|
||||
Standing orders guide a vassal; they do not guarantee one exact command. A vassal may address a crisis before following a long-term development or expansion preference.
|
||||
|
||||
## Multiple leaders and provinces
|
||||
|
||||
Gaining more faction leaders does not give one province several normal actions. It expands direct control:
|
||||
|
||||
- A leader can directly rule a province instead of leaving it to a vassal.
|
||||
- Leaders in different provinces can make those provinces player-controlled.
|
||||
- Leaders provide succession if the faction head dies.
|
||||
|
||||
Heroes who are present but do not rule the province can still be selected for eligible commands. Province leadership determines who chooses the action, not which local hero must perform it.
|
||||
|
||||
## A simple example
|
||||
|
||||
Suppose your faction controls three provinces:
|
||||
|
||||
1. Your faction head rules the capital. You choose its action during Player Commands.
|
||||
2. A second faction leader rules the frontier. You choose that province's action too.
|
||||
3. A vassal rules a farming province with Develop orders. The vassal chooses its own action during Vassal Commands.
|
||||
|
||||
The result is two direct player decisions and one automatic vassal decision, assuming no riot, diplomacy offer, battle decision, or other special phase adds another required choice.
|
||||
|
||||
## Special decisions
|
||||
|
||||
Attack decisions, defense decisions, diplomacy responses, prisoner handling, riots, and battle aftermath are conditional decisions rather than extra normal province turns. They appear only when the current game state requires them and can pause the shared phase until every required faction responds.
|
||||
|
||||
See [Eagle Round Flow](/concepts/eagle-round-flow/) for phase timing and [Eagle Commands](/commands/) for which actions consume a turn.
|
||||
@@ -30,6 +30,56 @@ Most rounds follow this broad rhythm:
|
||||
|
||||
Some phases are player-facing. Others are bookkeeping phases that advance automatically unless a special decision is needed.
|
||||
|
||||
## One Round Is One Month
|
||||
|
||||
The strategic date advances once per complete Eagle round, not once per command. A
|
||||
command submitted during Player Commands belongs to the current month's round. The
|
||||
server then continues through the remaining phases and starts the next round, whose
|
||||
New Round processing advances the date by one month.
|
||||
|
||||
In a one-player game, the automatic phases may finish so quickly that it looks as
|
||||
though the command itself advanced the month. If a decision or battle is pending,
|
||||
the game remains in the same round and month until that work is complete.
|
||||
|
||||
## Phase Diagram
|
||||
|
||||
This diagram groups the detailed phases into their player-visible states:
|
||||
|
||||
```text
|
||||
New Round and upkeep
|
||||
|
|
||||
v
|
||||
Province movement and events
|
||||
|
|
||||
+----> Riot or recruitment decision? ----> wait for commands
|
||||
|
|
||||
v
|
||||
Vassal Commands -> Player Commands --------------> wait for every required faction
|
||||
|
|
||||
v
|
||||
Conquest setup -> Attack Decision -> Defense Decision
|
||||
|
|
||||
v
|
||||
Battle Request and food use
|
||||
|
|
||||
v
|
||||
Battle Resolution ----------> wait for all Shardok battles
|
||||
|
|
||||
v
|
||||
Battle Aftermath -----------> wait for pending choices
|
||||
|
|
||||
v
|
||||
Diplomacy Resolution -------> wait for pending replies
|
||||
|
|
||||
v
|
||||
Recon Resolution
|
||||
|
|
||||
+-----------------> next month's New Round
|
||||
```
|
||||
|
||||
"Wait" means the phase does not advance yet. The server may be waiting for your
|
||||
choice, another faction's choice, or an external Shardok battle result.
|
||||
|
||||
## Automatic Upkeep
|
||||
|
||||
These phases usually advance without direct player input:
|
||||
@@ -130,6 +180,8 @@ Diplomatic offers are resolved after players choose how to answer them. Offers c
|
||||
|
||||
## Simultaneous Information
|
||||
|
||||
For a focused explanation of public, hidden, and potentially stale observations, see [Information and Recon](/concepts/information-and-recon/).
|
||||
|
||||
The important habit is to think in phases, not in a fully visible turn order.
|
||||
|
||||
If two factions both issue orders during Player Commands, those orders belong to the same command phase. When the phase ends, each faction receives the filtered results it is allowed to observe. Your own actions are visible to you. Allied and nearby events may be visible depending on game state. Enemy actions may be hidden until scouts, battles, diplomacy, or later consequences reveal them.
|
||||
|
||||
@@ -19,6 +19,8 @@ Eagle is the strategic layer. Shardok is the tactical layer. The two connect whe
|
||||
|
||||
## Strategic Movement
|
||||
|
||||
For a compact round-by-round view of the entire process, see [March and Battle Timeline](/concepts/march-timeline/).
|
||||
|
||||
In Eagle, armies move through the March command. A moving army has an origin province, destination province, arrival round, heroes, battalions, and supplies.
|
||||
|
||||
Because Eagle is simultaneous, multiple factions can send armies during the same strategic phase. Those armies arrive later during movement resolution, after every relevant player has had the chance to submit orders for the phase.
|
||||
|
||||
@@ -7,10 +7,15 @@ These pages explain how Eagle0's major systems fit together. Use them when you w
|
||||
|
||||
## Eagle
|
||||
|
||||
- [Victory and defeat](victory-and-defeat/) explains the strategic objective, leadership succession, faction destruction, and how the game ends.
|
||||
- [Who you command](command-authority/) explains faction heads, faction leaders, vassals, province turns, and direct control.
|
||||
- [Eagle round flow](eagle-round-flow/) explains how a strategic round advances, why actions are simultaneous, and when players see results.
|
||||
- [Information and recon](information-and-recon/) explains public, hidden, scouted, and potentially stale knowledge.
|
||||
- [From Eagle to Shardok](eagle-to-shardok/) explains how strategic movement turns into tactical battles and how battle results return to Eagle.
|
||||
- [March and battle timeline](march-timeline/) follows one army from order submission through arrival, battle, and aftermath.
|
||||
|
||||
## Shardok
|
||||
|
||||
- [Shardok combat fundamentals](shardok-combat/) explains turns, rounds, AP, movement, attacks, terrain, morale, and vigor.
|
||||
- [Shardok battles](shardok-battles/) explains tactical setup, turns, units, morale, vigor, terrain, and battle aftermath.
|
||||
- [Shardok victory conditions](shardok-victory-conditions/) explains how tactical battles end.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Information and Recon
|
||||
description: Public, private, stale, and scouted information in simultaneous Eagle rounds.
|
||||
---
|
||||
|
||||
Eagle does not give every faction a current view of the whole world. Results are filtered by province control, faction knowledge, scouting, diplomacy, alliances, and battle participation.
|
||||
|
||||
## Three kinds of uncertainty
|
||||
|
||||
- **Not present:** the game state confirms that something is absent.
|
||||
- **Not visible:** it may exist, but your faction has not earned that information.
|
||||
- **Known as of a date:** your faction observed it previously, but simultaneous actions may have changed it.
|
||||
|
||||
A blank enemy field can mean that the value is not visible; it does not necessarily mean the value is zero. An old observation describes the recorded date rather than guaranteeing the current state.
|
||||
|
||||
## Province information
|
||||
|
||||
Public province information can include identity, ruler, known events, known incoming armies, and an **as of** date. Full local information adds resources, caps, support, food consumption, battalions, heroes, orders, shipments, and events.
|
||||
|
||||
Controlling a province normally gives your faction its detailed state. Losing control does not mean every remembered fact remains current; the date tells you when the displayed knowledge was established.
|
||||
|
||||
## Simultaneous orders
|
||||
|
||||
Enemy factions choose during the same phase. Between an observation and a later arrival, a province may receive troops, change ruler, form a truce, or launch its own army. Observing a target does not reserve or freeze its state.
|
||||
|
||||
## Strategic Recon
|
||||
|
||||
Recon requires an eligible Ranger and targets a province not ruled by the acting faction. The command creates pending reconnaissance rather than instantly granting omniscience. Results are applied during **Recon Resolution**, after the relevant strategic activity.
|
||||
|
||||
Recon improves what your faction knows about the target at that point in the round. Later actions can make those details stale. The observation date records when that knowledge was established.
|
||||
|
||||
## Other ways information is earned
|
||||
|
||||
- Ruling a province exposes its local state.
|
||||
- Battle participants learn information revealed by the tactical encounter and its result.
|
||||
- Diplomacy reveals offers and relationship changes to the involved factions.
|
||||
- Allied or nearby events may be visible when the game state permits it.
|
||||
- Moving next to, scouting, or revealing hidden Shardok units affects tactical knowledge separately from strategic Recon.
|
||||
|
||||
Alliance does not make every hidden fact in the world public. Each faction can still have knowledge that the other does not.
|
||||
|
||||
## Example
|
||||
|
||||
In round 8, Recon reports an enemy frontier province with one battalion. You March toward it during round 9. The enemy simultaneously sends reinforcements. Your report was truthful as of round 8, but it did not promise that the round-9 defenders would be unchanged.
|
||||
|
||||
See [Eagle Round Flow](/concepts/eagle-round-flow/#simultaneous-information), [Recon](/commands/#recon), and [Provinces](/provinces/) for related rules.
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: March and Battle Timeline
|
||||
description: Where an army is from order submission through arrival and battle aftermath.
|
||||
---
|
||||
|
||||
March is a delayed strategic order. Selected heroes, battalions, gold, and food leave the origin immediately, but they do not enter the destination until later processing.
|
||||
|
||||
## The timeline
|
||||
|
||||
| When | State | What happens |
|
||||
| --- | --- | --- |
|
||||
| Player Commands, round N | March submitted | Forces and supplies leave the origin and form a moving army scheduled for round N+1. Each marching hero spends vigor. |
|
||||
| Province Move Resolution, round N+1 | Arrival checked | An army already friendly to the destination enters it. A hostile arrival waits outside rather than entering. |
|
||||
| After Player Commands, round N+1 | Hostile Army Setup | Hostile arrivals become attacking groups, one per faction, preserving their approach direction and carried supplies. |
|
||||
| Uncontested Conquest | No tactical defense needed | An unruled destination can be claimed without Shardok. Competing groups are compared by size, power, and a deterministic tie-breaker. |
|
||||
| Attack and Defense Decisions | A ruled province is contested | Attackers decide whether to press the attack; defenders may choose an army and fallback destination. Truces can turn armies back. |
|
||||
| Battle Request and Resolution | Shardok battle | Eagle sends armies, map, limited battle food, and victory conditions to Shardok, then waits for a result. |
|
||||
| Battle Aftermath | Consequences applied | Eagle resolves ownership, survivors, prisoners, supplies, devastation, prestige, and any allied-claimant decisions. |
|
||||
|
||||
## Friendly arrival
|
||||
|
||||
If your faction already rules the destination during Province Move Resolution, the army's heroes, battalions, and supplies enter the province. They are then present before later phases such as player commands and food consumption.
|
||||
|
||||
## Neutral or hostile arrival
|
||||
|
||||
A marching army does not occupy a neutral or enemy province during movement resolution. It remains a moving/hostile force until conquest or battle processing decides the outcome. This distinction matters for hero location, province leadership, and food.
|
||||
|
||||
An unruled province may be conquered without a tactical battle. A ruled and defended province can require attack and defense decisions followed by Shardok.
|
||||
|
||||
## Food and gold
|
||||
|
||||
Carried supplies travel with the army. Moving armies consume their own food while incoming. If food is insufficient, their battalions can suffer starvation losses.
|
||||
|
||||
When a normal province battle is created, attackers bring at most one month of food consumption from their carried food. Defenders receive at most one month from province food, removed when the battle is created. Remaining attacking supplies are applied to the battle province during resolution; whoever ultimately controls the province may therefore inherit them.
|
||||
|
||||
## One attacker wins
|
||||
|
||||
One victorious attacking faction conquers the province. Its surviving, non-fled heroes and battalions become the new ruling force, support becomes 0, orders reset to Entrust, and eligible defenders can become prisoners.
|
||||
|
||||
## Allied attackers win together
|
||||
|
||||
Eagle cannot automatically choose which allied winner keeps the province. Claimants enter Battle Aftermath. One may keep it; the others withdraw to adjacent provinces with permitted shares of the supplies they brought. Those withdrawals become incoming armies for the next round.
|
||||
|
||||
## Before confirming March
|
||||
|
||||
- Confirm the destination and arrival delay.
|
||||
- Leave enough heroes behind to retain the origin if necessary.
|
||||
- Carry enough food for transit and the intended battle.
|
||||
- Remember that the destination's ruler or diplomacy can change before arrival.
|
||||
- Expect an additional attack, defense, or aftermath decision when the army arrives.
|
||||
|
||||
See [March](/commands/#march) for selection rules and [From Eagle to Shardok](/concepts/eagle-to-shardok/) for detailed battle consequences.
|
||||
@@ -14,6 +14,8 @@ Source map:
|
||||
|
||||
Shardok is the tactical battle layer. Eagle decides that a battle should happen, sends armies into Shardok, and later receives the result back.
|
||||
|
||||
For the decision-making basics behind the command list, see [Shardok Combat Fundamentals](/concepts/shardok-combat/).
|
||||
|
||||
## Setup
|
||||
|
||||
At the start of a battle, each side places its units onto valid setup hexes. Some battles include reserve or reinforcement units that enter later. Rangers on stealth-capable units may be able to deploy hidden.
|
||||
@@ -22,6 +24,8 @@ The map comes from the province where the battle occurs. A province's Shardok ma
|
||||
|
||||
## Units
|
||||
|
||||
See [Battalions](/battalions/) for troop, type, training, armament, morale, and lifecycle fundamentals.
|
||||
|
||||
A Shardok unit usually represents a hero attached to a battalion. The hero brings stats, vigor, profession abilities, and special command access. The battalion brings troop count, type, morale, training, armament, movement costs, damage profile, and battlefield limits.
|
||||
|
||||
Some commands require a profession. Some also require the attached battalion type to support that role. For example, many spell commands require a unit that can cast, while Ranger stealth depends on a stealth-capable battalion.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: Shardok Combat Fundamentals
|
||||
description: Turns, rounds, Action Points, movement, attacks, terrain, morale, and vigor.
|
||||
---
|
||||
|
||||
Shardok is an objective-based hex battle, not a direct comparison of troop totals. Position, terrain, heroes, hidden information, and command timing determine which force can satisfy its victory conditions.
|
||||
|
||||
## Turns, units, and rounds
|
||||
|
||||
Players act according to the tactical state. A unit spends **Action Points (AP)** to move or use commands. Ending the player turn sets that player's remaining unit AP to 0. After every required side has had its opportunity, the tactical round closes and end-of-round victory conditions are checked.
|
||||
|
||||
## Movement and position
|
||||
|
||||
Movement cost depends on terrain and battalion type. Water, mountains, bridges, ice, fire, castles, and cities can restrict paths or change their value. Enemy proximity can affect hiding and movement choices. Use the highlighted valid targets rather than assuming every adjacent hex is traversable.
|
||||
|
||||
## Attacks and special actions
|
||||
|
||||
Melee and ranged attacks require valid targets and battlefield geometry. Line of fire can be blocked. Damage and resistance depend on the units, heroes, damage profiles, and terrain involved.
|
||||
|
||||
Many major actions require a minimum AP and then consume all remaining AP. The command reference states the exact rule. Unless an entry says otherwise, a morale-using unit needs at least 10 morale, and a unit with a hero needs at least 10 hero vigor.
|
||||
|
||||
## Heroes and battalions
|
||||
|
||||
The battalion provides troops, type, morale, training, armament, movement, and damage behavior. The attached hero provides stats, vigor, profession abilities, and special commands. Profession commands can also require a compatible battalion: casting and Ranger stealth are important examples.
|
||||
|
||||
## Terrain and objectives
|
||||
|
||||
Castles and cities may be defensive positions or critical objectives. Critical-tile victory normally requires a hero-bearing unit on every required tile at the end of the tactical round. Occupying an objective during your turn is therefore not always immediate victory; opponents may receive a chance to respond.
|
||||
|
||||
## Hidden units
|
||||
|
||||
Hidden units are not fully visible. Ranger scouting, adjacency, movement, and terrain can reveal them. Hidden units do not keep a player alive for Last Player Standing, although a capable hidden loser may flee when battle ends.
|
||||
|
||||
See [Shardok Commands](/shardok-commands/) for exact action requirements and [Shardok Victory Conditions](/concepts/shardok-victory-conditions/) for battle-ending checks.
|
||||
@@ -14,6 +14,28 @@ Shardok battles end when one of the battle's victory conditions is satisfied. Ea
|
||||
|
||||
In a normal Eagle province assault, attackers usually get **Hold Critical Tiles**, **Last Player Standing**, and **Last Alliance Standing**. The defender usually gets **Last Player Standing** and **Win After Max Rounds**.
|
||||
|
||||
## When conditions are checked
|
||||
|
||||
A **turn** is one player's opportunity to act. A **round** includes the required turns before end-of-round processing. This distinction determines whether the opposing side gets a response.
|
||||
|
||||
| Condition | Typical holder | Check timing |
|
||||
| --- | --- | --- |
|
||||
| Last Player Standing | Attacker and defender | As soon as only one eligible player survives |
|
||||
| Last Alliance Standing | Allied attackers | As soon as the only eligible survivors are mutually allied holders |
|
||||
| Hold Critical Tiles | Attackers | End of the tactical round |
|
||||
| Win After Max Rounds | Defender | End-of-round clock check |
|
||||
| Most Troops Standing | Special battle types | At its configured ending check |
|
||||
|
||||
The battle display's assigned conditions and maximum-round value are authoritative for that battle; the normal-assault assignments above are defaults, not promises for every scenario.
|
||||
|
||||
### Critical-tile response example
|
||||
|
||||
An attacker occupies the final critical tile during their turn. The battle does not end at that movement step. The defender receives the remaining opportunity provided by the tactical round and can retake or contest an objective. If eligible allied attackers still occupy every critical tile when the round closes, Hold Critical Tiles succeeds.
|
||||
|
||||
### Defender-clock example
|
||||
|
||||
The defender does not need to eliminate every attacker when Win After Max Rounds is assigned. The attacker must satisfy an attacking victory condition before the displayed round limit expires. Surviving without taking the objectives can therefore be a loss for the attacker.
|
||||
|
||||
## Last Player Standing
|
||||
|
||||
A player wins if they are the only surviving player and they have this victory condition.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Victory and Defeat
|
||||
description: How factions win, survive leadership losses, and leave the game.
|
||||
---
|
||||
|
||||
Eagle0 is a struggle to become the last surviving faction. The game ends when only one faction remains; that faction wins. If no factions remain, the game ends without a winner.
|
||||
|
||||
## When a faction survives
|
||||
|
||||
A faction remains in the game while it has both:
|
||||
|
||||
- at least one faction leader; and
|
||||
- at least one ruled province or moving army.
|
||||
|
||||
Losing a province is therefore serious, but it is not necessarily immediate defeat. An army already marching can keep a landless faction alive long enough to conquer somewhere else.
|
||||
|
||||
## Losing your faction head
|
||||
|
||||
The death of the faction head does not automatically destroy the faction. Another surviving faction leader succeeds them.
|
||||
|
||||
If no faction leader is available but the faction still has a province or moving army, the strongest surviving vassal can rise to become both faction leader and faction head. The faction is destroyed only if no vassal can succeed.
|
||||
|
||||
Creating additional leaders with **Swear Kinship** protects the faction against a sudden leadership loss. A candidate must belong to the faction, have at least 95 loyalty, and be with the faction head when the command is used.
|
||||
|
||||
## How a faction is destroyed
|
||||
|
||||
A faction is destroyed when either condition is true:
|
||||
|
||||
1. It rules no provinces and has no moving armies.
|
||||
2. It has no faction leaders after the succession check.
|
||||
|
||||
When a faction is destroyed:
|
||||
|
||||
- its provinces become unruled;
|
||||
- its incoming armies are removed and their supplies remain at their destinations;
|
||||
- its heroes lose their faction assignment; and
|
||||
- diplomacy and outstanding offers involving it are removed.
|
||||
|
||||
Those heroes are not necessarily killed. They may remain in the world in unaffiliated states and interact with surviving factions later.
|
||||
|
||||
## Strategic implications
|
||||
|
||||
- Keep a succession option by gaining another leader or maintaining a capable vassal.
|
||||
- Preserve at least one province or viable moving army.
|
||||
- Do not assume an enemy is eliminated merely because its last visible province falls; it may still have an army in motion.
|
||||
- Remember that conquest can release, capture, or displace heroes without destroying their former faction immediately.
|
||||
|
||||
## Quick answers
|
||||
|
||||
**Do I win a battle by killing the enemy faction head?**
|
||||
Not automatically. Leadership succession may keep the faction alive.
|
||||
|
||||
**Am I defeated as soon as I lose my last province?**
|
||||
Not if your faction still has a moving army and a leader.
|
||||
|
||||
**Can a faction with provinces survive after all its leaders die?**
|
||||
Only if a surviving vassal can rise during the succession check.
|
||||
|
||||
**How does the whole game end?**
|
||||
The final surviving faction wins. If every faction is destroyed, there is no winner.
|
||||
|
||||
For tactical battle victory rules, see [Shardok Victory Conditions](/concepts/shardok-victory-conditions/). For leadership roles, see [Heroes](/heroes/#faction-leaders) and [Factions](/factions/#leaders).
|
||||
@@ -88,6 +88,8 @@ A faction can have a focus province. The focus province helps direct [province o
|
||||
|
||||
## Faction Destruction
|
||||
|
||||
For a player-oriented overview of winning, losing, and leadership succession, see [Victory and Defeat](/concepts/victory-and-defeat/).
|
||||
|
||||
A faction is destroyed if either of these is true:
|
||||
|
||||
- It has no ruled provinces and no moving armies.
|
||||
|
||||
@@ -34,6 +34,8 @@ Vigor is a hero's current energy. Many commands require an acting hero and spend
|
||||
|
||||
Loyalty measures a hero's attachment to their faction. Feasts, events, captures, faction bias, and story consequences can move loyalty over time.
|
||||
|
||||
Loyalty is not faction bias. Loyalty describes an affiliated hero's current attachment; bias helps determine whether an unaffiliated or imprisoned hero will join a particular faction.
|
||||
|
||||
## Professions
|
||||
|
||||
Professions give heroes special command access, bonuses, and tactical identity. Some effects are Eagle strategic effects, while others apply only when the hero is attached to a Shardok battle unit.
|
||||
@@ -179,15 +181,31 @@ Heroes with no profession have no profession-specific command unlocks. They can
|
||||
Only heroes with **NoProfession** can gain a profession. At the end of a round, heroes with enough XP can gain stats. If a no-profession hero gains one or more stats, the game checks for profession gain:
|
||||
|
||||
- The hero gets one profession-gain roll for each stat that increased.
|
||||
- Eligible professions are based on all post-gain stats at or above the configured prime-stat threshold.
|
||||
- A stat must be at least **85 after the stat gains are applied** to qualify a profession.
|
||||
- Eligible professions are based on all post-gain stats at or above 85, not only the stats that increased that round.
|
||||
- Strength qualifies **Champion**.
|
||||
- Agility qualifies **Engineer** and **Ranger**.
|
||||
- Wisdom qualifies **Mage**.
|
||||
- Charisma qualifies **Necromancer** and **Paladin**.
|
||||
- Constitution qualifies **Warden**.
|
||||
|
||||
### Progression example
|
||||
|
||||
A NoProfession hero earns Agility-related XP from strategic actions. During end-of-round progression, enough XP may increase Agility. If the hero's Agility is at least 85 after that increase, Engineer and Ranger are eligible. The hero receives profession-gain rolls for stats that increased; the player does not directly choose between eligible professions.
|
||||
|
||||
If several stats increase together, each increase can create a roll and all post-gain qualifying stats participate in eligibility. A hero who already has a profession does not roll for a replacement.
|
||||
|
||||
### Planning progression
|
||||
|
||||
- Commands award XP in stats associated with the action.
|
||||
- Stat increases and profession checks happen during end-of-round processing, not when XP is awarded.
|
||||
- Reaching 85 in a qualifying stat makes its profession possible but does not guarantee a particular random result.
|
||||
- Choosing which eligible hero performs a command influences who receives its XP.
|
||||
|
||||
## Faction Leaders
|
||||
|
||||
For a side-by-side explanation of direct control, province turns, and vassal automation, see [Who You Command](/concepts/command-authority/).
|
||||
|
||||
Every faction starts with a **faction head**. The faction head is the hero who defines what the faction represents: its political identity, its public story, and the starting point for its leadership.
|
||||
|
||||
Factions can gain additional faction leaders through **Swear Kinship** with another hero. The candidate must already belong to the faction, must have at least 95 loyalty, and must be in a province with the faction head. When the command succeeds, that hero becomes one of the faction's leaders.
|
||||
|
||||
@@ -7,6 +7,8 @@ Eagle0pedia is the in-game reference companion for Eagle0. The tutorial answers
|
||||
|
||||
Eagle0 is a strategy game about factions, heroes, provinces, armies, diplomacy, and tactical battles. The broad strategic layer is **Eagle**: provinces produce food and gold, heroes lead factions or rule as vassals, armies march across the map, and diplomacy shapes the war. When armies clash, battles are resolved in **Shardok**, the tactical hex-based combat system.
|
||||
|
||||
Your strategic goal is to become the last surviving faction. Start with [Victory and Defeat](concepts/victory-and-defeat/) to learn how factions win, survive leadership losses, and leave the game.
|
||||
|
||||
Use these pages when you want a quick reference for commands, hero professions, faction leadership, province stats, or tactical battle options.
|
||||
|
||||
## Sections
|
||||
@@ -19,5 +21,6 @@ Use these pages when you want a quick reference for commands, hero professions,
|
||||
- [Eagle commands](commands/) explains the strategic commands available from province command panels.
|
||||
- [Shardok commands](shardok-commands/) explains tactical battle commands and profession-gated battlefield actions.
|
||||
- [Heroes](heroes/) covers stats, vigor, loyalty, professions, faction leaders, and battle capabilities.
|
||||
- [Battalions](battalions/) covers troop count, type, training, armament, morale, heroes, and battlefield outcomes.
|
||||
- [Factions](factions/) covers faction heads, leaders, prestige, diplomacy, and focus provinces.
|
||||
- [Provinces](provinces/) covers province stats, supplies, orders, armies, maps, castles, and unaffiliated heroes.
|
||||
|
||||
@@ -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.
|
||||
-->
|
||||
@@ -47,6 +47,18 @@ Source map for Shardok command requirements/costs:
|
||||
|
||||
Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Action Point cost**.
|
||||
|
||||
## Activations and follow-ups
|
||||
|
||||
A unit can keep acting while it has Action Points (AP) and at least one available command. Read each command's AP line as follows:
|
||||
|
||||
- **Consumes all remaining AP** or **sets the unit to 0 AP** means **ends activation**. The unit cannot act again this turn.
|
||||
- A command with only an exact AP cost spends that amount. If the unit still has AP afterward, its activation can continue with whatever commands are then available.
|
||||
- A command may also end the unit's useful activation because no legal commands remain, even if the display still shows AP.
|
||||
|
||||
Movement is the most important case. Each destination shows its movement cost and may show **follow-up available** icons. Those follow-ups are actions predicted to be legal *after reaching that destination*, such as Charge, Scout, Start Fire, Repair, Reduce, or Reinforce.
|
||||
|
||||
The follow-up list is a planning aid, not a complete list of everything the unit might do next. It does not include another Move, and battlefield information can change when the move resolves. A destination with no follow-up icons does not itself mean “ends activation”: compare the movement cost with the unit's remaining AP and check the commands offered after arrival.
|
||||
|
||||
## Tactical Commands
|
||||
|
||||
### Melee
|
||||
@@ -79,6 +91,8 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
<b>Action Point cost:</b> the cost to enter the target's terrain. Knights and Dragoons spend only that terrain cost and may be able to move again afterward; other units spend all remaining AP.
|
||||
|
||||
<b>Activation:</b> ends activation for units that spend all remaining AP. Knights and Dragoons can continue when they retain AP and another command is available.
|
||||
|
||||
<b>Available when:</b> a valid path reaches a target that can be attacked at the end of the move.
|
||||
|
||||
<b>Effect:</b> Move into an attack as one action. Charge attacks have a chance to stun the target unit.
|
||||
@@ -91,6 +105,8 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
<b>Action Point cost:</b> the terrain cost for each hex in the path, plus 1 extra AP for steps that start in enemy zone of control.
|
||||
|
||||
<b>Activation:</b> does not inherently end activation. The unit can continue if it has AP and a legal command after arriving. Follow-up icons on a destination preview eligible non-move actions there.
|
||||
|
||||
<b>Available when:</b> one or more reachable destination hexes are valid for the acting unit.
|
||||
|
||||
<b>Effect:</b> Move the unit along the chosen path to a reachable hex, paying terrain costs as it goes. Hidden units may reveal themselves if they move into exposed terrain or near an enemy Ranger.
|
||||
@@ -111,13 +127,13 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -213,21 +229,21 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
<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
|
||||
|
||||
@@ -235,33 +251,35 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
<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.
|
||||
|
||||
@@ -273,7 +291,7 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
<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.
|
||||
|
||||
@@ -283,11 +301,11 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
<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
|
||||
|
||||
@@ -295,71 +313,83 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
<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
|
||||
|
||||
@@ -391,21 +421,21 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
<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.
|
||||
|
||||
@@ -415,8 +445,8 @@ Unless noted otherwise: **10 morale**, **10 hero vigor**, and the listed **Actio
|
||||
|
||||
<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.
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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}");
|
||||
|
||||
+1
-6
@@ -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); }
|
||||
|
||||
|
||||
+43
-13
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -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++) {
|
||||
|
||||
-2
@@ -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; }
|
||||
|
||||
+2
@@ -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();
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
-4
@@ -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; }
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
-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));
|
||||
|
||||
|
||||
+963
-156
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -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() {
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+16
-16
@@ -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
|
||||
|
||||
+12
-12
@@ -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
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+168
@@ -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
|
||||
|
||||
+7
-7
@@ -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:
|
||||
|
||||
+32
-32
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+5
-5
@@ -789,7 +789,7 @@ MonoBehaviour:
|
||||
m_MinWidth: 0
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 80
|
||||
m_PreferredHeight: 30
|
||||
m_PreferredHeight: 48
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -3986,10 +3986,10 @@ 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: 30
|
||||
m_PreferredHeight: 48
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -4749,7 +4749,7 @@ MonoBehaviour:
|
||||
m_MinWidth: 0
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 80
|
||||
m_PreferredHeight: 30
|
||||
m_PreferredHeight: 48
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+2
-2
@@ -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:
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+9
-12
@@ -53,6 +53,8 @@ namespace eagle {
|
||||
public TMP_Text goldLabel;
|
||||
public TMP_Text foodLabel;
|
||||
|
||||
private ProvinceId _safePassageDestinationProvinceId;
|
||||
|
||||
private AttackDecisionAvailableCommand AttackDecisionCommand =>
|
||||
_availableCommand.AttackDecisionCommand;
|
||||
|
||||
@@ -67,7 +69,12 @@ namespace eagle {
|
||||
if (withdrawToggle.isOn)
|
||||
return new AttackDecisionType { Withdraw = new WithdrawDecision() };
|
||||
if (safePassageToggle.isOn)
|
||||
return new AttackDecisionType { SafePassage = new SafePassageDecision() };
|
||||
return new AttackDecisionType {
|
||||
SafePassage =
|
||||
new SafePassageDecision {
|
||||
ToProvinceId = _safePassageDestinationProvinceId
|
||||
}
|
||||
};
|
||||
|
||||
throw new Exception("No decision selected");
|
||||
}
|
||||
@@ -86,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) => {
|
||||
@@ -141,6 +138,7 @@ namespace eagle {
|
||||
break;
|
||||
case AttackDecisionType.SealedValueOneofCase.SafePassage:
|
||||
enableSafePassage = true;
|
||||
_safePassageDestinationProvinceId = ad.SafePassage.ToProvinceId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -235,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;
|
||||
|
||||
|
||||
-8
@@ -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;
|
||||
|
||||
-14
@@ -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])
|
||||
|
||||
-13
@@ -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;
|
||||
|
||||
|
||||
-31
@@ -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);
|
||||
|
||||
-1
@@ -58,7 +58,6 @@ namespace eagle {
|
||||
}
|
||||
|
||||
protected override void SetUpUI() {
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(nextHeroButton);
|
||||
if (_selectedHeroIndex >= HeroCount) { _selectedHeroIndex = 0; }
|
||||
DisplayHero();
|
||||
nextHeroButton.gameObject.SetActive(HeroCount > 1);
|
||||
|
||||
-1
@@ -54,7 +54,6 @@ namespace eagle {
|
||||
}
|
||||
|
||||
protected override void SetUpUI() {
|
||||
TouchTargetUtils.EnsureMinimumTouchTargets(payToggle, nextFactionButton);
|
||||
_demands = new List<TributeAndFaction>(ResolveTributeAvailableCommand.Demands);
|
||||
_demandIndex = 0;
|
||||
DisplayDemand();
|
||||
|
||||
-25
@@ -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);
|
||||
}
|
||||
|
||||
+51
-53
@@ -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:
|
||||
@@ -9745,6 +9787,7 @@ GameObject:
|
||||
- component: {fileID: 296148958613372072}
|
||||
- component: {fileID: 1445788806017443158}
|
||||
- component: {fileID: 3391666280041911029}
|
||||
- component: {fileID: 900000000000000006}
|
||||
m_Layer: 0
|
||||
m_Name: Standard Left Column
|
||||
m_TagString: Untagged
|
||||
@@ -9764,6 +9807,7 @@ RectTransform:
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 900000000000000002}
|
||||
- {fileID: 8450970734016342464}
|
||||
- {fileID: 5448146937309441081}
|
||||
- {fileID: 6624731758075026335}
|
||||
@@ -10392,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:
|
||||
@@ -11412,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
|
||||
@@ -11533,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
|
||||
@@ -13734,3 +13799,180 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 11e6c1f6549ca4d3b8f3b8d21085e5b2, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!1 &900000000000000001
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 900000000000000002}
|
||||
- component: {fileID: 900000000000000005}
|
||||
- component: {fileID: 900000000000000003}
|
||||
- component: {fileID: 900000000000000004}
|
||||
m_Layer: 0
|
||||
m_Name: Province Under Attack Alert
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
--- !u!224 &900000000000000002
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 900000000000000001}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 8194435045387419553}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 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!114 &900000000000000003
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 900000000000000001}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.55, g: 0.05, b: 0.04, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: UNDER ATTACK!
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: ec2736df0379a45bfa7349b652fd07d6, type: 2}
|
||||
m_sharedMaterial: {fileID: 1026247389042360723, guid: ec2736df0379a45bfa7349b652fd07d6,
|
||||
type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4278190335
|
||||
m_fontColor: {r: 0.55, g: 0.05, b: 0.04, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 24
|
||||
m_fontSizeBase: 24
|
||||
m_fontWeight: 700
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 1
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!114 &900000000000000004
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 900000000000000001}
|
||||
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: -1
|
||||
m_MinHeight: 34
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: 34
|
||||
m_FlexibleWidth: 1
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!222 &900000000000000005
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 900000000000000001}
|
||||
m_CullTransparentMesh: 0
|
||||
--- !u!114 &900000000000000006
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7089574085975417824}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9e3a7cbe4e1f4e59a2a57ea40c9f5e29, type: 3}
|
||||
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,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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+162
-53
@@ -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) {
|
||||
|
||||
+1
-4
@@ -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); }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user