mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 23:15:41 +00:00
Compare commits
100
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de22df3848 | ||
|
|
e281f3b012 | ||
|
|
9695db2536 | ||
|
|
aaba34e7de | ||
|
|
9e37236fe3 | ||
|
|
88d3b37a4c | ||
|
|
794a1fb503 | ||
|
|
97c243fa95 | ||
|
|
e0e0f88358 | ||
|
|
8e541e6d11 | ||
|
|
7afbde69f1 | ||
|
|
e02afd112e | ||
|
|
20b82af479 | ||
|
|
282b5fefa9 | ||
|
|
9549cf32e4 | ||
|
|
a1df9e87ee | ||
|
|
0bd6319c29 | ||
|
|
608a5fc0a5 | ||
|
|
201f637843 | ||
|
|
fa43fd6f48 | ||
|
|
37f9acf847 | ||
|
|
2ce4c7f0fb | ||
|
|
9cd57390e7 | ||
|
|
a1b26841dc | ||
|
|
61a2d2d988 | ||
|
|
cd5aed4cde | ||
|
|
f1c825b37e | ||
|
|
6d68d5dc8b | ||
|
|
748d5afee2 | ||
|
|
c1bbb11edc | ||
|
|
1e9953bc56 | ||
|
|
647ad25bb4 | ||
|
|
7221ee48d6 | ||
|
|
6bd0934144 | ||
|
|
b656ba4518 | ||
|
|
f4902727a5 | ||
|
|
7aee0ee789 | ||
|
|
961a7c81b2 | ||
|
|
d675758a9e | ||
|
|
2864bae977 | ||
|
|
b41b937662 | ||
|
|
201fa30acd | ||
|
|
df1923f076 | ||
|
|
721382a9e9 | ||
|
|
f006618060 | ||
|
|
93760dbfb4 | ||
|
|
59a0632af2 | ||
|
|
3c17e89f02 | ||
|
|
0dc5435b9a | ||
|
|
b55df7cf3e | ||
|
|
19a76d3015 | ||
|
|
a50bfe36b0 | ||
|
|
73ba9a3acc | ||
|
|
2332626198 | ||
|
|
cc463700d9 | ||
|
|
0788f9a155 | ||
|
|
44f9eba77f | ||
|
|
22f68fc3df | ||
|
|
b08c1755ac | ||
|
|
1b0ede178c | ||
|
|
258f739d02 | ||
|
|
f125c8f5f6 | ||
|
|
22b0bc00cc | ||
|
|
45942688a9 | ||
|
|
a7b7b0e444 | ||
|
|
76be41d841 | ||
|
|
8ee4e1475c | ||
|
|
c583e216d3 | ||
|
|
f3dea0306d | ||
|
|
acf57f3c96 | ||
|
|
9cb7cadcdf | ||
|
|
1bbdd18f9f | ||
|
|
aad241ed77 | ||
|
|
4ff93ac8fc | ||
|
|
e3b88215aa | ||
|
|
c89a4ef51b | ||
|
|
7d07bd97ad | ||
|
|
5a80fcf765 | ||
|
|
8a2bc8d984 | ||
|
|
50c632a8ea | ||
|
|
ac11292778 | ||
|
|
de01fc1451 | ||
|
|
fde8ccf675 | ||
|
|
1033a4b730 | ||
|
|
f3959c8c79 | ||
|
|
f7b76bb30d | ||
|
|
eaae3089de | ||
|
|
eb2e954c2c | ||
|
|
95b4201819 | ||
|
|
5ba1948c8e | ||
|
|
2f6ce2d0b7 | ||
|
|
df916f9c29 | ||
|
|
ddbc960945 | ||
|
|
e020a0f66c | ||
|
|
d60a9f79ec | ||
|
|
6b55be9457 | ||
|
|
716dd97f75 | ||
|
|
413f70a3b3 | ||
|
|
f2e08cdb5d | ||
|
|
056127c614 |
@@ -29,13 +29,17 @@ common --javacopt="-Xlint:-options"
|
||||
# Use host_linkopt for macOS-specific flags to avoid passing them to Linux cross-compilation
|
||||
common:macos --host_linkopt=-Wl,-no_warn_duplicate_libraries
|
||||
|
||||
# Workaround for grpc cf_event_engine build error on macOS with Bazel 7.x
|
||||
# See: https://github.com/grpc/grpc/issues/37619
|
||||
common:macos --features=-module_maps
|
||||
|
||||
# Fix Xcode version caching issue - avoids need for `bazel clean --expunge` after Xcode updates
|
||||
common:macos --repo_env=DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
|
||||
|
||||
common --java_language_version=17
|
||||
common --java_runtime_version=remotejdk_17
|
||||
common --tool_java_language_version=17
|
||||
common --tool_java_runtime_version=remotejdk_17
|
||||
common --java_language_version=25
|
||||
common --java_runtime_version=remotejdk_25
|
||||
common --tool_java_language_version=25
|
||||
common --tool_java_runtime_version=remotejdk_25
|
||||
|
||||
# Workspace status for build stamping (git commit, timestamp)
|
||||
common --workspace_status_command=tools/workspace_status.sh
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
7.6.1
|
||||
8.5.1
|
||||
|
||||
@@ -11,6 +11,7 @@ on:
|
||||
- 'src/main/protobuf/net/eagle0/eagle/internal/auth_internal.proto'
|
||||
- 'src/main/resources/net/eagle0/attributions.json'
|
||||
- 'ci/BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/auth_build.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -77,8 +78,14 @@ jobs:
|
||||
# Build the push target to get crane in runfiles
|
||||
bazel build //ci:auth_server_push
|
||||
|
||||
# Use crane directly for push
|
||||
CRANE="bazel-bin/ci/push_auth_server_push.sh.runfiles/rules_oci~~oci~oci_crane_darwin_arm64/crane"
|
||||
# Find crane binary in runfiles (path varies by Bazel version)
|
||||
RUNFILES="bazel-bin/ci/push_auth_server_push.sh.runfiles"
|
||||
CRANE=$(find "$RUNFILES" -path "*darwin*" -name crane 2>/dev/null | head -1)
|
||||
if [ -z "$CRANE" ] || [ ! -x "$CRANE" ]; then
|
||||
echo "ERROR: crane not found in runfiles"
|
||||
find "$RUNFILES" -name crane 2>/dev/null || echo "No crane found at all"
|
||||
exit 1
|
||||
fi
|
||||
echo "Using crane: $CRANE"
|
||||
|
||||
# Push with SHA tag
|
||||
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/bazel_test.yml'
|
||||
- '!src/main/csharp/**'
|
||||
- '!src/test/csharp/**'
|
||||
@@ -17,6 +18,7 @@ on:
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/bazel_test.yml'
|
||||
- '!src/main/csharp/**'
|
||||
- '!src/test/csharp/**'
|
||||
@@ -25,9 +27,8 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
lint:
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -35,6 +36,14 @@ jobs:
|
||||
lfs: false
|
||||
- name: Check BUILD.bazel dependencies
|
||||
run: ./scripts/check_build_deps.sh --strict
|
||||
|
||||
test:
|
||||
runs-on: [self-hosted, bazel]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: false
|
||||
- name: Run tests
|
||||
run: bazel test --build_event_json_file=test.json //src/test/... //src/main/go/...
|
||||
- name: Collect failed test logs
|
||||
|
||||
@@ -19,6 +19,7 @@ on:
|
||||
- 'src/main/resources/**'
|
||||
- 'ci/BUILD.bazel'
|
||||
- 'MODULE.bazel'
|
||||
- '.bazelrc'
|
||||
- 'docker-compose.prod.yml'
|
||||
- 'nginx/**'
|
||||
- '.github/workflows/docker_build.yml'
|
||||
@@ -111,16 +112,12 @@ jobs:
|
||||
|
||||
# Get crane from push target runfiles
|
||||
bazel build //ci:eagle_server_push
|
||||
CRANE="bazel-bin/ci/push_eagle_server_push.sh.runfiles/rules_oci~~oci~oci_crane_darwin_arm64/crane"
|
||||
RUNFILES="bazel-bin/ci/push_eagle_server_push.sh.runfiles"
|
||||
CRANE=$(find "$RUNFILES" -path "*darwin*" -name crane 2>/dev/null | head -1)
|
||||
|
||||
if [ ! -e "$CRANE" ]; then
|
||||
# Fallback: find any Darwin crane
|
||||
RUNFILES="bazel-bin/ci/push_eagle_server_push.sh.runfiles"
|
||||
CRANE=$(find "$RUNFILES" -path "*darwin*" -name crane 2>/dev/null | head -1)
|
||||
fi
|
||||
|
||||
if [ -z "$CRANE" ] || [ ! -e "$CRANE" ]; then
|
||||
echo "ERROR: crane not found"
|
||||
if [ -z "$CRANE" ] || [ ! -x "$CRANE" ]; then
|
||||
echo "ERROR: crane not found in runfiles"
|
||||
find "$RUNFILES" -name crane 2>/dev/null || echo "No crane found at all"
|
||||
exit 1
|
||||
fi
|
||||
echo "Using crane: $CRANE"
|
||||
@@ -167,6 +164,9 @@ jobs:
|
||||
EAGLE_ENABLE_S3: ${{ secrets.EAGLE_ENABLE_S3 }}
|
||||
DO_SPACES_ACCESS_KEY: ${{ secrets.DO_SPACES_ACCESS_KEY }}
|
||||
DO_SPACES_SECRET_KEY: ${{ secrets.DO_SPACES_SECRET_KEY }}
|
||||
# Admin server uses different credentials (for eagle0-assets bucket)
|
||||
ADMIN_S3_ACCESS_KEY: ${{ secrets.ACCESS_KEY_ID }}
|
||||
ADMIN_S3_SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
JWT_PRIVATE_KEY: ${{ secrets.JWT_PRIVATE_KEY }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
DISCORD_CLIENT_SECRET: ${{ secrets.DISCORD_CLIENT_SECRET }}
|
||||
@@ -288,6 +288,8 @@ jobs:
|
||||
export EAGLE_ENABLE_S3="${EAGLE_ENABLE_S3:-false}"
|
||||
export DO_SPACES_ACCESS_KEY="${DO_SPACES_ACCESS_KEY}"
|
||||
export DO_SPACES_SECRET_KEY="${DO_SPACES_SECRET_KEY}"
|
||||
export ADMIN_S3_ACCESS_KEY="${ADMIN_S3_ACCESS_KEY}"
|
||||
export ADMIN_S3_SECRET_KEY="${ADMIN_S3_SECRET_KEY}"
|
||||
export JWT_PRIVATE_KEY="${JWT_PRIVATE_KEY}"
|
||||
export DISCORD_CLIENT_ID="${DISCORD_CLIENT_ID}"
|
||||
export DISCORD_CLIENT_SECRET="${DISCORD_CLIENT_SECRET}"
|
||||
@@ -401,7 +403,9 @@ jobs:
|
||||
fi
|
||||
echo "Admin image verification passed"
|
||||
|
||||
# Cleanup
|
||||
# Cleanup - remove old images to prevent disk full
|
||||
docker container prune -f
|
||||
docker image prune -f
|
||||
# Remove images older than 24h (keeps recent images for rollback)
|
||||
docker image prune -a -f --filter "until=24h" || true
|
||||
DEPLOY_SCRIPT
|
||||
|
||||
@@ -10,6 +10,7 @@ on:
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/eagle_build.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -19,6 +20,7 @@ on:
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/eagle_build.yml'
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -184,11 +184,23 @@ jobs:
|
||||
- name: Upload to TestFlight
|
||||
if: ${{ github.event.inputs.skip_upload != 'true' }}
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
|
||||
# App Store Connect API Key (replaces deprecated altool with Apple ID)
|
||||
# Create at: https://appstoreconnect.apple.com/access/api
|
||||
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||
APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
|
||||
APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
# Write API key to file (xcodebuild needs a file path)
|
||||
API_KEY_PATH=$(mktemp)
|
||||
echo "$APP_STORE_CONNECT_API_KEY" > "$API_KEY_PATH"
|
||||
export APP_STORE_CONNECT_API_KEY_PATH="$API_KEY_PATH"
|
||||
|
||||
chmod +x ./ci/github_actions/upload_testflight.sh
|
||||
./ci/github_actions/upload_testflight.sh "$EAGLE0_BUILD_DIR/archive/eagle0.ipa"
|
||||
./ci/github_actions/upload_testflight.sh "$EAGLE0_BUILD_DIR/archive/eagle0.xcarchive" "$APPLE_TEAM_ID" "$PROFILE_UUID"
|
||||
|
||||
# Cleanup
|
||||
rm -f "$API_KEY_PATH"
|
||||
|
||||
- name: Upload IPA artifact
|
||||
# Only keep artifact if we skipped TestFlight upload (for debugging)
|
||||
|
||||
@@ -10,6 +10,7 @@ on:
|
||||
- 'src/main/resources/net/eagle0/shardok/**'
|
||||
- 'ci/BUILD.bazel'
|
||||
- 'MODULE.bazel'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/shardok_arm64_build.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
|
||||
@@ -11,6 +11,7 @@ on:
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/shardok_build.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -21,6 +22,7 @@ on:
|
||||
- 'WORKSPACE'
|
||||
- 'MODULE.bazel'
|
||||
- 'BUILD.bazel'
|
||||
- '.bazelrc'
|
||||
- '.github/workflows/shardok_build.yml'
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -110,6 +110,10 @@ bazel run gazelle # Update Go build files
|
||||
1. **If you modified any BUILD.bazel file:** Run `bazel run gazelle` and stage any changes it makes
|
||||
2. **If you modified C++ or C# files:** Run `clang-format -i` on the modified files
|
||||
3. **If you modified Scala files:** scalafmt will run automatically via pre-commit hook
|
||||
4. **ALWAYS run the full test suite for your language changes before pushing:**
|
||||
- For Scala changes: `bazel test //src/test/scala/...`
|
||||
- For C++ changes: `bazel test //src/test/cpp/...`
|
||||
- This catches exhaustive pattern match errors and other compile-time failures that single-target builds miss
|
||||
|
||||
The pre-commit hook runs gazelle but only checks if it succeeds - it does NOT verify the BUILD files are in canonical format. The `gazelle_test` will fail if deps are not alphabetically sorted. **Always run gazelle manually after BUILD file changes.**
|
||||
|
||||
|
||||
+113
-28
@@ -1,9 +1,9 @@
|
||||
module(name = "net_eagle0")
|
||||
|
||||
# Version constants
|
||||
SCALA_VERSION = "3.7.2"
|
||||
SCALA_VERSION = "3.7.4"
|
||||
|
||||
NETTY_VERSION = "4.1.110.Final"
|
||||
NETTY_VERSION = "4.1.127.Final"
|
||||
|
||||
SCALAPB_VERSION = "1.0.0-alpha.1"
|
||||
|
||||
@@ -14,13 +14,14 @@ AWS_SDK_VERSION = "2.41.18"
|
||||
#
|
||||
|
||||
bazel_dep(name = "bazel_skylib", version = "1.9.0")
|
||||
bazel_dep(name = "platforms", version = "1.0.0")
|
||||
bazel_dep(name = "rules_pkg", version = "1.2.0")
|
||||
|
||||
#
|
||||
# Language Support - Scala
|
||||
#
|
||||
|
||||
bazel_dep(name = "rules_scala", version = "7.1.1")
|
||||
bazel_dep(name = "rules_scala", version = "7.2.1")
|
||||
|
||||
scala_config = use_extension(
|
||||
"@rules_scala//scala/extensions:config.bzl",
|
||||
@@ -128,10 +129,22 @@ use_repo(
|
||||
#
|
||||
# Platform Support - Apple/iOS
|
||||
#
|
||||
# Note: rules_apple is NOT included in the main workspace due to rules_swift
|
||||
# version conflicts with grpc. SparklePlugin (which needs rules_apple) is built
|
||||
# in a separate workspace at sparkle_workspace/ to isolate the conflict.
|
||||
#
|
||||
|
||||
bazel_dep(name = "apple_support", version = "1.21.1", repo_name = "build_bazel_apple_support")
|
||||
bazel_dep(name = "rules_apple", version = "4.3.3", repo_name = "build_bazel_rules_apple")
|
||||
bazel_dep(name = "rules_swift", version = "2.4.0", repo_name = "build_bazel_rules_swift")
|
||||
bazel_dep(name = "apple_support", version = "1.23.1", repo_name = "build_bazel_apple_support")
|
||||
|
||||
# rules_swift is a transitive dep of grpc and flatbuffers with conflicting versions:
|
||||
# - grpc 1.76.0.bcr.1 requires rules_swift 3.x (compatibility level 3)
|
||||
# - flatbuffers requires rules_swift 2.x (compatibility level 2)
|
||||
# We don't use Swift directly, but we need to force a single version.
|
||||
# Force 3.x since grpc is more complex and harder to downgrade.
|
||||
single_version_override(
|
||||
module_name = "rules_swift",
|
||||
version = "3.1.2",
|
||||
)
|
||||
|
||||
# Register Apple CC toolchain for Objective-C compilation
|
||||
apple_cc_configure = use_extension(
|
||||
@@ -144,10 +157,12 @@ use_repo(apple_cc_configure, "local_config_apple_cc")
|
||||
# Protocol Buffers & RPC
|
||||
#
|
||||
|
||||
bazel_dep(name = "protobuf", version = "29.2", repo_name = "com_google_protobuf")
|
||||
bazel_dep(name = "grpc", version = "1.71.0")
|
||||
bazel_dep(name = "grpc-java", version = "1.71.0")
|
||||
bazel_dep(name = "flatbuffers", version = "25.9.23")
|
||||
bazel_dep(name = "protobuf", version = "33.5", repo_name = "com_google_protobuf")
|
||||
bazel_dep(name = "rules_proto", version = "7.1.0")
|
||||
bazel_dep(name = "rules_cc", version = "0.2.14")
|
||||
bazel_dep(name = "grpc", version = "1.74.0")
|
||||
bazel_dep(name = "grpc-java", version = "1.78.0")
|
||||
bazel_dep(name = "flatbuffers", version = "25.12.19")
|
||||
|
||||
#
|
||||
# Testing
|
||||
@@ -160,16 +175,24 @@ bazel_dep(name = "googletest", version = "1.17.0")
|
||||
#
|
||||
|
||||
bazel_dep(name = "rules_oci", version = "2.2.7")
|
||||
bazel_dep(name = "aspect_bazel_lib", version = "2.22.4")
|
||||
bazel_dep(name = "aspect_bazel_lib", version = "2.22.5")
|
||||
|
||||
oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
|
||||
|
||||
# Base image for Eagle (Java 17 JDK - includes jcmd for JFR dumps)
|
||||
# Base image for Eagle (Java 25 JRE - smaller runtime, no dev tools needed)
|
||||
oci.pull(
|
||||
name = "eclipse_temurin_17",
|
||||
name = "eclipse_temurin_25_jre",
|
||||
image = "docker.io/library/eclipse-temurin",
|
||||
platforms = ["linux/amd64"],
|
||||
tag = "17-jdk",
|
||||
tag = "25-jre",
|
||||
)
|
||||
|
||||
# Base image for JFR sidecar (Java 25 JDK - includes jcmd for JFR dumps)
|
||||
oci.pull(
|
||||
name = "eclipse_temurin_25_jdk",
|
||||
image = "docker.io/library/eclipse-temurin",
|
||||
platforms = ["linux/amd64"],
|
||||
tag = "25-jdk",
|
||||
)
|
||||
|
||||
# Base image for Shardok (Ubuntu 24.04 for C++ runtime)
|
||||
@@ -190,13 +213,14 @@ oci.pull(
|
||||
platforms = ["linux/amd64"],
|
||||
tag = "3.21",
|
||||
)
|
||||
use_repo(oci, "alpine_linux", "alpine_linux_linux_amd64", "eclipse_temurin_17", "eclipse_temurin_17_linux_amd64", "ubuntu_24_04", "ubuntu_24_04_linux_amd64", "ubuntu_24_04_linux_arm64_v8")
|
||||
use_repo(oci, "alpine_linux", "alpine_linux_linux_amd64", "eclipse_temurin_25_jdk", "eclipse_temurin_25_jdk_linux_amd64", "eclipse_temurin_25_jre", "eclipse_temurin_25_jre_linux_amd64", "ubuntu_24_04", "ubuntu_24_04_linux_amd64", "ubuntu_24_04_linux_arm64_v8")
|
||||
|
||||
#
|
||||
# Java/Scala Dependencies
|
||||
#
|
||||
|
||||
bazel_dep(name = "rules_jvm_external", version = "6.9")
|
||||
bazel_dep(name = "rules_java", version = "9.3.0")
|
||||
bazel_dep(name = "rules_jvm_external", version = "6.10")
|
||||
|
||||
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
||||
maven.install(
|
||||
@@ -267,6 +291,27 @@ maven.install(
|
||||
"https://repo1.maven.org/maven2",
|
||||
],
|
||||
)
|
||||
|
||||
# Force specific versions for dependencies with conflicts between grpc-java and protobuf
|
||||
maven.artifact(
|
||||
artifact = "gson",
|
||||
force_version = True,
|
||||
group = "com.google.code.gson",
|
||||
version = "2.11.0",
|
||||
)
|
||||
maven.artifact(
|
||||
artifact = "error_prone_annotations",
|
||||
force_version = True,
|
||||
group = "com.google.errorprone",
|
||||
version = "2.30.0",
|
||||
)
|
||||
maven.artifact(
|
||||
artifact = "guava",
|
||||
force_version = True,
|
||||
group = "com.google.guava",
|
||||
version = "33.3.1-android",
|
||||
)
|
||||
|
||||
use_repo(maven, "maven", "unpinned_maven")
|
||||
|
||||
#
|
||||
@@ -283,7 +328,14 @@ GTL_SHA = "1969c45dd76eac0dd87e9e2b65cffe358617f4fe1bcd203f72f427742537913a"
|
||||
|
||||
http_archive(
|
||||
name = "gtl",
|
||||
build_file = "@//external:BUILD.gtl",
|
||||
build_file_content = """
|
||||
cc_library(
|
||||
name = "gtl",
|
||||
hdrs = glob(["include/gtl/*.hpp"]),
|
||||
includes = ["include"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
sha256 = GTL_SHA,
|
||||
strip_prefix = "gtl-%s" % GTL_VERSION,
|
||||
url = "https://github.com/greg7mdp/gtl/archive/refs/tags/v%s.zip" % GTL_VERSION,
|
||||
@@ -303,16 +355,8 @@ http_archive(
|
||||
],
|
||||
)
|
||||
|
||||
# Sparkle framework for macOS auto-updates
|
||||
SPARKLE_VERSION = "2.6.4"
|
||||
|
||||
http_archive(
|
||||
name = "sparkle",
|
||||
build_file = "@//external:BUILD.sparkle",
|
||||
sha256 = "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b",
|
||||
strip_prefix = "",
|
||||
url = "https://github.com/sparkle-project/Sparkle/releases/download/%s/Sparkle-%s.tar.xz" % (SPARKLE_VERSION, SPARKLE_VERSION),
|
||||
)
|
||||
# Note: Sparkle framework is defined in sparkle_workspace/MODULE.bazel
|
||||
# (not in main workspace due to rules_swift version conflicts)
|
||||
|
||||
# Busybox static binary for Docker health checks (provides nc, wget, etc.)
|
||||
# Primary: DigitalOcean Spaces (public, reliable)
|
||||
@@ -346,7 +390,48 @@ LLVM_MINGW_VERSION = "20250305"
|
||||
|
||||
http_archive(
|
||||
name = "llvm_mingw",
|
||||
build_file = "@//external:BUILD.llvm_mingw",
|
||||
build_file_content = """
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "all_files",
|
||||
srcs = glob(["**/*"]),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "compiler_files",
|
||||
srcs = glob([
|
||||
"bin/x86_64-w64-mingw32-*",
|
||||
"bin/clang*",
|
||||
"bin/llvm-*",
|
||||
"bin/lld*",
|
||||
]),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "windows_x86_64_sysroot",
|
||||
srcs = glob([
|
||||
"x86_64-w64-mingw32/**/*",
|
||||
"generic-w64-mingw32/include/**/*",
|
||||
]),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "linker_files",
|
||||
srcs = glob([
|
||||
"bin/x86_64-w64-mingw32-*",
|
||||
"bin/lld*",
|
||||
"bin/ld.lld*",
|
||||
"lib/**/*",
|
||||
"x86_64-w64-mingw32/lib/**/*",
|
||||
]),
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"bin/x86_64-w64-mingw32-clang",
|
||||
"bin/x86_64-w64-mingw32-clang++",
|
||||
])
|
||||
""",
|
||||
sha256 = "32c24fc62fc8b9f8a900bf2c730b78b36767688f816f9d21e97a168289ff44e0",
|
||||
strip_prefix = "llvm-mingw-%s-ucrt-macos-14.4.1-universal" % LLVM_MINGW_VERSION,
|
||||
urls = ["https://github.com/fathonix/llvm-mingw-arm64ec-macos/releases/download/%s/llvm-mingw-%s-ucrt-macos-14.4.1-universal.tar.xz" % (LLVM_MINGW_VERSION, LLVM_MINGW_VERSION)],
|
||||
|
||||
Generated
+1074
-4432
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -73,7 +73,7 @@ pkg_tar(
|
||||
|
||||
oci_image(
|
||||
name = "eagle_server_image",
|
||||
base = "@eclipse_temurin_17_linux_amd64",
|
||||
base = "@eclipse_temurin_25_jre_linux_amd64",
|
||||
entrypoint = [
|
||||
"java",
|
||||
"-Xmx2g",
|
||||
@@ -288,7 +288,7 @@ pkg_tar(
|
||||
oci_image(
|
||||
name = "jfr_sidecar_image",
|
||||
# Use JDK base image - we need jcmd to dump JFR recordings
|
||||
base = "@eclipse_temurin_17_linux_amd64",
|
||||
base = "@eclipse_temurin_25_jdk_linux_amd64",
|
||||
entrypoint = ["/app/jfr_server_linux_amd64"],
|
||||
exposed_ports = ["8081/tcp"],
|
||||
tars = [
|
||||
|
||||
@@ -1,48 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Upload IPA to TestFlight using Apple ID credentials (same as Mac notarization)
|
||||
# Upload xcarchive to TestFlight using App Store Connect API Key
|
||||
# This replaces the deprecated altool which was removed in Xcode 14+
|
||||
#
|
||||
# Uses xcodebuild -exportArchive with destination=upload, which is Apple's
|
||||
# recommended approach for CI/CD pipelines.
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
IPA_PATH=${1:?Usage: upload_testflight.sh <ipa_path>}
|
||||
# Ensure xcodebuild uses Xcode.app, not Command Line Tools
|
||||
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
|
||||
|
||||
if [ ! -f "$IPA_PATH" ]; then
|
||||
echo "Error: IPA file not found: $IPA_PATH"
|
||||
ARCHIVE_PATH=${1:?Usage: upload_testflight.sh <xcarchive_path> <team_id> <profile_uuid>}
|
||||
TEAM_ID=${2:?Missing team ID}
|
||||
PROFILE_UUID=${3:?Missing provisioning profile UUID}
|
||||
|
||||
if [ ! -d "$ARCHIVE_PATH" ]; then
|
||||
echo "Error: xcarchive not found: $ARCHIVE_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Uploading to TestFlight: $IPA_PATH"
|
||||
echo "Uploading to TestFlight: $ARCHIVE_PATH"
|
||||
|
||||
# Uses same credentials as Mac notarization:
|
||||
# - APPLE_ID: Your Apple ID email
|
||||
# - APP_SPECIFIC_PASSWORD: App-specific password from appleid.apple.com
|
||||
# Requires App Store Connect API Key:
|
||||
# - APP_STORE_CONNECT_API_KEY_ID: Key ID from App Store Connect
|
||||
# - APP_STORE_CONNECT_API_ISSUER_ID: Issuer ID from App Store Connect
|
||||
# - APP_STORE_CONNECT_API_KEY_PATH: Path to .p8 private key file
|
||||
#
|
||||
# To create an API key:
|
||||
# 1. Go to https://appstoreconnect.apple.com/access/api
|
||||
# 2. Click the + button to create a new key
|
||||
# 3. Give it a name and Admin or App Manager access
|
||||
# 4. Download the .p8 file (you can only download it once!)
|
||||
# 5. Note the Key ID and Issuer ID shown on the page
|
||||
|
||||
if [ -z "${APPLE_ID:-}" ]; then
|
||||
echo "Error: APPLE_ID environment variable not set"
|
||||
if [ -z "${APP_STORE_CONNECT_API_KEY_ID:-}" ]; then
|
||||
echo "Error: APP_STORE_CONNECT_API_KEY_ID environment variable not set"
|
||||
echo "Create an API key at https://appstoreconnect.apple.com/access/api"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${APP_SPECIFIC_PASSWORD:-}" ]; then
|
||||
echo "Error: APP_SPECIFIC_PASSWORD environment variable not set"
|
||||
if [ -z "${APP_STORE_CONNECT_API_ISSUER_ID:-}" ]; then
|
||||
echo "Error: APP_STORE_CONNECT_API_ISSUER_ID environment variable not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Uploading with xcrun altool..."
|
||||
|
||||
# Capture output to check for errors (altool may return 0 even on failure)
|
||||
OUTPUT=$(xcrun altool --upload-app \
|
||||
--type ios \
|
||||
--file "$IPA_PATH" \
|
||||
--username "$APPLE_ID" \
|
||||
--password "$APP_SPECIFIC_PASSWORD" 2>&1) || true
|
||||
|
||||
echo "$OUTPUT"
|
||||
|
||||
# Check for error indicators in output
|
||||
if echo "$OUTPUT" | grep -q "ERROR:"; then
|
||||
echo "ERROR: Upload failed. See error messages above."
|
||||
if [ -z "${APP_STORE_CONNECT_API_KEY_PATH:-}" ]; then
|
||||
echo "Error: APP_STORE_CONNECT_API_KEY_PATH environment variable not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$APP_STORE_CONNECT_API_KEY_PATH" ]; then
|
||||
echo "Error: API key file not found: $APP_STORE_CONNECT_API_KEY_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create export options plist with upload destination
|
||||
EXPORT_OPTIONS_PLIST=$(mktemp)
|
||||
trap "rm -f $EXPORT_OPTIONS_PLIST" EXIT
|
||||
|
||||
cat > "$EXPORT_OPTIONS_PLIST" << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>destination</key>
|
||||
<string>upload</string>
|
||||
<key>method</key>
|
||||
<string>app-store-connect</string>
|
||||
<key>teamID</key>
|
||||
<string>$TEAM_ID</string>
|
||||
<key>uploadSymbols</key>
|
||||
<true/>
|
||||
<key>signingStyle</key>
|
||||
<string>manual</string>
|
||||
<key>signingCertificate</key>
|
||||
<string>Apple Distribution: Daniel Crosby (UWJ88DX8WQ)</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>net.eagle0.eagle</key>
|
||||
<string>$PROFILE_UUID</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
echo "Export options:"
|
||||
cat "$EXPORT_OPTIONS_PLIST"
|
||||
|
||||
echo "Uploading to App Store Connect..."
|
||||
|
||||
# Use xcodebuild to upload with App Store Connect API authentication
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath "$ARCHIVE_PATH" \
|
||||
-exportOptionsPlist "$EXPORT_OPTIONS_PLIST" \
|
||||
-authenticationKeyPath "$APP_STORE_CONNECT_API_KEY_PATH" \
|
||||
-authenticationKeyID "$APP_STORE_CONNECT_API_KEY_ID" \
|
||||
-authenticationKeyIssuerID "$APP_STORE_CONNECT_API_ISSUER_ID"
|
||||
|
||||
echo "Upload complete! Check App Store Connect for processing status."
|
||||
echo "The build should appear in TestFlight within 15-30 minutes after processing."
|
||||
|
||||
@@ -211,10 +211,10 @@ services:
|
||||
environment:
|
||||
# Secret for CI to authenticate client update notifications
|
||||
NOTIFY_SECRET: "${NOTIFY_SECRET:-}"
|
||||
# S3/Spaces credentials for What's New storage
|
||||
# S3/Spaces credentials for What's New storage (uses eagle0-assets bucket)
|
||||
DO_SPACES_ENDPOINT: "${DO_SPACES_ENDPOINT:-https://sfo3.digitaloceanspaces.com}"
|
||||
DO_SPACES_ACCESS_KEY: "${DO_SPACES_ACCESS_KEY:-}"
|
||||
DO_SPACES_SECRET_KEY: "${DO_SPACES_SECRET_KEY:-}"
|
||||
DO_SPACES_ACCESS_KEY: "${ADMIN_S3_ACCESS_KEY:-}"
|
||||
DO_SPACES_SECRET_KEY: "${ADMIN_S3_SECRET_KEY:-}"
|
||||
# No external port - accessed via nginx at admin.eagle0.net
|
||||
depends_on:
|
||||
- auth
|
||||
|
||||
@@ -12,7 +12,14 @@ When adding new quest types, use a three-PR strategy to ensure clients never see
|
||||
|
||||
3. **PR 3 - Quest Generation (Server)**: Add the actual quest creation logic to `QuestCreationUtils.scala`.
|
||||
|
||||
This order ensures that when the server starts generating the new quest type, all clients already know how to display it.
|
||||
**PR Dependencies:**
|
||||
```
|
||||
PR 1 (types)
|
||||
├── PR 2 (client)
|
||||
└── PR 3 (generation)
|
||||
```
|
||||
|
||||
PR 2 and PR 3 both depend on PR 1, but are independent of each other. They can be developed in parallel after PR 1 is merged, and merged in either order. The key constraint is that PR 3 should not be deployed before PR 2, so clients understand the quest type before the server starts generating it.
|
||||
|
||||
## Files to Modify
|
||||
|
||||
|
||||
+5
-2
@@ -182,11 +182,14 @@ All 26 tracks have CC licenses with proper attribution:
|
||||
- `failure_horn.mp3` - licensing issue, no replacement found in purchased assets
|
||||
- ~~`runaway.mp3`~~ - **REPLACED** (2026-01-27) with `MedievalArmyRunningLoop.mp3` from Freesound (CC BY 4.0)
|
||||
|
||||
**Presumed from Unity Asset Store purchases (31 files):**
|
||||
**Verified from [Medieval Combat Sounds](https://assetstore.unity.com/packages/audio/sound-fx/medieval-combat-sounds-100465) (Unity Asset Store):**
|
||||
- `duel_challenged.wav` - **REPLACED** (2026-02-03) with `Weapon Draw Metal 1.wav` from Medieval Combat Sounds
|
||||
|
||||
**Presumed from Unity Asset Store purchases (30 files):**
|
||||
Owner believes these are from: Fantasy Interface Sounds, Medieval Combat Sounds, Magic Spells Sound Effects LITE, and/or Medieval Battle Sound Pack.
|
||||
- `archery.mp3`, `battle_shout.mp3`, `boo.mp3`, `braved_water.mp3`
|
||||
- `build_bridge.mp3`, `build_bridge_failure.mp3`, `charge.mp3`
|
||||
- `dismiss_unit.mp3`, `duel_challenged.mp3`, `failure_horn.mp3`, `fear.mp3`, `fear_failed.mp3`
|
||||
- `dismiss_unit.mp3`, `failure_horn.mp3`, `fear.mp3`, `fear_failed.mp3`
|
||||
- `fire_extinguish.mp3`, `fire_spread.mp3`, `fire_start.mp3`, `fire_start_failure.mp3`
|
||||
- `freeze.mp3`, `holy_wave.mp3`, `holy_wave_damage.mp3`, `jail_door.mp3`, `lightning.mp3`
|
||||
- `melee.mp3`, `meteor.mp3`, `mind_control.mp3`, `move.mp3`, `move 1.mp3`
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
# rules_apple Workspace Separation
|
||||
|
||||
This document explains why `rules_apple` is built in a separate Bazel workspace (`sparkle_workspace/`) and what conditions need to be met before it can be reintegrated into the main workspace.
|
||||
|
||||
## Background
|
||||
|
||||
The main workspace cannot include `rules_apple` due to transitive dependency conflicts with `rules_swift`. Multiple dependencies require different major versions of `rules_swift`:
|
||||
|
||||
| Dependency | rules_swift Version | Compatibility Level |
|
||||
|------------|---------------------|---------------------|
|
||||
| grpc | 3.x | 3 |
|
||||
| flatbuffers | 2.x | 2 |
|
||||
| rules_apple | 2.x | 2 |
|
||||
|
||||
Bzlmod's `single_version_override` can force a single version, but compatibility levels 2 and 3 are incompatible. Forcing `rules_swift` 3.x (required for grpc) breaks `rules_apple` and `flatbuffers`.
|
||||
|
||||
## Current Solution
|
||||
|
||||
The `SparklePlugin` (the only component requiring `rules_apple`) is built in an isolated workspace:
|
||||
|
||||
```
|
||||
sparkle_workspace/
|
||||
├── MODULE.bazel # Minimal deps: apple_support, rules_apple, sparkle
|
||||
├── BUILD.bazel # Builds SparklePlugin.bundle
|
||||
├── SparklePlugin.m # Objective-C source
|
||||
├── Info.plist # Bundle metadata
|
||||
└── external/
|
||||
└── BUILD.sparkle # Build file for Sparkle framework
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Build script**: `scripts/build_sparkle_plugin.sh` builds the plugin from the separate workspace
|
||||
2. **Output**: The built `SparklePlugin.bundle` is placed in `Assets/Plugins/macOS/`
|
||||
3. **CI integration**: `mac_build.yml` calls `inject_sparkle.sh` to embed Sparkle into the Mac app
|
||||
|
||||
The main workspace uses `single_version_override` for `rules_swift` 3.x to satisfy grpc, and includes a comment noting that `rules_apple` is intentionally excluded.
|
||||
|
||||
## What SparklePlugin Does
|
||||
|
||||
SparklePlugin is a native macOS library that:
|
||||
- Initializes the [Sparkle](https://sparkle-project.org/) auto-update framework
|
||||
- Exposes C functions for Unity to call via P/Invoke:
|
||||
- `SparklePlugin_Initialize`
|
||||
- `SparklePlugin_CheckForUpdates`
|
||||
- `SparklePlugin_CheckForUpdatesInBackground`
|
||||
- `SparklePlugin_IsCheckingForUpdates`
|
||||
- `SparklePlugin_GetAutomaticallyChecksForUpdates`
|
||||
- `SparklePlugin_SetAutomaticallyChecksForUpdates`
|
||||
- `SparklePlugin_IsRunningFromReadOnlyVolume`
|
||||
- `SparklePlugin_ShowDMGWarning`
|
||||
|
||||
## Conditions for Reintegration
|
||||
|
||||
To move `rules_apple` back into the main workspace, **all** of the following must be true:
|
||||
|
||||
### 1. rules_swift Version Alignment
|
||||
|
||||
Check if grpc, flatbuffers, and rules_apple all support the same `rules_swift` major version:
|
||||
|
||||
```bash
|
||||
# Check what rules_swift version each dependency requires
|
||||
bazel mod graph 2>&1 | grep -A2 "rules_swift"
|
||||
```
|
||||
|
||||
Or check BCR directly:
|
||||
- https://registry.bazel.build/modules/grpc
|
||||
- https://registry.bazel.build/modules/flatbuffers
|
||||
- https://registry.bazel.build/modules/rules_apple
|
||||
|
||||
**Test**: After updating versions, verify you can add this to MODULE.bazel without errors:
|
||||
```python
|
||||
bazel_dep(name = "rules_apple", version = "X.Y.Z")
|
||||
```
|
||||
|
||||
### 2. Build Test
|
||||
|
||||
If the dependency can be added, test that both the main build and SparklePlugin work:
|
||||
|
||||
```bash
|
||||
# Main workspace builds
|
||||
bazel build //src/main/scala/net/eagle0/eagle:eagle_server
|
||||
bazel build //src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
bazel build //ci:eagle_server_image
|
||||
|
||||
# SparklePlugin builds (would move to main workspace)
|
||||
bazel build //sparkle:SparklePlugin # After moving files
|
||||
```
|
||||
|
||||
### 3. Files to Move
|
||||
|
||||
When reintegrating, move these from `sparkle_workspace/` to the main workspace:
|
||||
|
||||
| Source | Destination |
|
||||
|--------|-------------|
|
||||
| `sparkle_workspace/SparklePlugin.m` | `src/main/objc/net/eagle0/sparkle/SparklePlugin.m` |
|
||||
| `sparkle_workspace/Info.plist` | `src/main/objc/net/eagle0/sparkle/Info.plist` |
|
||||
| `sparkle_workspace/BUILD.bazel` | `src/main/objc/net/eagle0/sparkle/BUILD.bazel` |
|
||||
| `sparkle_workspace/external/BUILD.sparkle` | (inline into MODULE.bazel http_archive) |
|
||||
|
||||
### 4. MODULE.bazel Changes
|
||||
|
||||
Add to the main MODULE.bazel:
|
||||
|
||||
```python
|
||||
bazel_dep(name = "rules_apple", version = "X.Y.Z", repo_name = "build_bazel_rules_apple")
|
||||
|
||||
# Sparkle framework
|
||||
http_archive(
|
||||
name = "sparkle",
|
||||
build_file_content = """...""", # Contents from external/BUILD.sparkle
|
||||
sha256 = "...",
|
||||
url = "https://github.com/sparkle-project/Sparkle/releases/download/...",
|
||||
)
|
||||
```
|
||||
|
||||
Remove the `single_version_override` for `rules_swift` if no longer needed.
|
||||
|
||||
### 5. Update Build Scripts
|
||||
|
||||
- Update `scripts/build_sparkle_plugin.sh` to build from main workspace
|
||||
- Update `scripts/build_plugins.sh` if it references the separate workspace
|
||||
- Verify `mac_build.yml` still works
|
||||
|
||||
### 6. Cleanup
|
||||
|
||||
After successful integration:
|
||||
```bash
|
||||
rm -rf sparkle_workspace/
|
||||
```
|
||||
|
||||
Update comments in MODULE.bazel that reference the separate workspace.
|
||||
|
||||
## Version History
|
||||
|
||||
| Date | Event |
|
||||
|------|-------|
|
||||
| 2026-02-04 | Separated sparkle_workspace (PR #5882) to enable Bazel 8 upgrade |
|
||||
| 2026-02-05 | Upgraded to Bazel 8.5.1 (PR #5883) |
|
||||
|
||||
## Monitoring
|
||||
|
||||
Periodically check if the upstream dependencies have aligned:
|
||||
|
||||
1. **grpc releases**: https://github.com/grpc/grpc/releases
|
||||
2. **flatbuffers releases**: https://github.com/google/flatbuffers/releases
|
||||
3. **rules_apple releases**: https://github.com/bazelbuild/rules_apple/releases
|
||||
4. **BCR updates**: https://registry.bazel.build/
|
||||
|
||||
When a new version of any of these is released, check if the `rules_swift` requirements have converged.
|
||||
@@ -0,0 +1,230 @@
|
||||
# Tutorial Battle System
|
||||
|
||||
This document describes the tutorial battle system for first-time players. The system provides a scripted introductory battle that teaches combat basics while telling a story.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
When a new player starts their first game in tutorial mode, they experience:
|
||||
|
||||
1. **Narrative Intro** - Story screens introducing the scenario
|
||||
2. **Immediate Battle** - Skip strategic map, start directly in combat
|
||||
3. **Scripted Flee** - Enemy flees when player is "on the ropes"
|
||||
4. **Reinforcements** - Allied heroes arrive mid-battle
|
||||
5. **Heroes Join** - Reinforcement heroes join player's faction after battle
|
||||
|
||||
---
|
||||
|
||||
## Battle Configuration
|
||||
|
||||
### Defender (Player - John Ranil)
|
||||
- **Heroes**: John Ranil + 2 random sworn brothers (3 total)
|
||||
- **Units**:
|
||||
- 2x Light Infantry (300 troops each, 60 training/armament)
|
||||
- 1x Longbowmen (200 troops, 60 training/armament)
|
||||
- **Province**: 14 (Onmaa) with 40 support, 500 gold, 3000 food
|
||||
- **Restriction**: Cannot flee (must defend)
|
||||
|
||||
### Attacker (Ikhaan Tarn)
|
||||
- **Heroes**: Ikhaan Tarn + 2 sworn brothers (3 total)
|
||||
- **Units**:
|
||||
- 1x Heavy Cavalry (400 troops, 80 training/armament)
|
||||
- 1x Heavy Infantry (500 troops, 80 training/armament)
|
||||
- 1x Longbowmen (300 troops, 80 training/armament)
|
||||
- **Origin Province**: 32
|
||||
|
||||
### Flee Trigger
|
||||
Attacker flees when ANY of these conditions are met:
|
||||
- Player loses 1 unit
|
||||
- 5 rounds have passed
|
||||
|
||||
### Reinforcements
|
||||
When Tarn flees, these heroes arrive as player reinforcements:
|
||||
- Elena Fyar
|
||||
- Hedrick
|
||||
- The Boulder
|
||||
|
||||
---
|
||||
|
||||
## Implementation Architecture
|
||||
|
||||
### Phase 1: Narrative Intro System
|
||||
|
||||
**Proto Changes:**
|
||||
- `game_state_view.proto`: Added `TutorialNarrativeScreen` message and `pending_narrative_screens` field
|
||||
- `game_parameters.proto`: Added `TutorialNarrativeScreen` and `tutorial_narrative_screens` field
|
||||
|
||||
**Unity Client:**
|
||||
- `NarrativeScreenController.cs`: Modal screen with title, body text, optional image
|
||||
- `EagleGameController.cs`: Checks for pending narrative screens on game start
|
||||
|
||||
**Narrative Content** (defined in `tutorial_game_parameters.json`):
|
||||
```
|
||||
Screen 1: "The Engineer of Onmaa" - Player backstory
|
||||
Screen 2: "The Traitor Arrives" - Tarn's attack
|
||||
Screen 3: "Defend Your Home" - Call to action
|
||||
```
|
||||
|
||||
### Phase 2: Auto-Start Battle
|
||||
|
||||
**Configuration:**
|
||||
- `tutorial_game_parameters.json`: Contains `tutorialBattle` config with attacker, target, flee conditions
|
||||
- `game_parameters.proto`: `TutorialBattleConfig` message
|
||||
|
||||
**Game Creation:**
|
||||
- `NewGameCreation.scala`: `setupTutorialBattle()` method creates:
|
||||
- `HostileArmyGroup` with `Attacking` status (bypasses decision phase)
|
||||
- `Army` for defender
|
||||
- Both added to target province
|
||||
|
||||
**Battle Creation:**
|
||||
- When round advances to `BattleRequest` phase, `RequestBattlesAction` automatically creates the `ShardokBattle`
|
||||
|
||||
### Phase 3: Scripted Flee Mechanism
|
||||
|
||||
**C++ Component:**
|
||||
- `TutorialBattleController.hpp/cpp`: Controller for scripted battle logic
|
||||
|
||||
**Key Methods:**
|
||||
```cpp
|
||||
// Check if attacker should flee
|
||||
bool ShouldTriggerScriptedFlee(const GameStateW& state);
|
||||
|
||||
// Check if player (defender) can flee
|
||||
bool PlayerCanFlee(PlayerId playerId);
|
||||
|
||||
// Execute the scripted flee (100% success rate)
|
||||
std::vector<ActionResult> ExecuteScriptedFlee(...);
|
||||
```
|
||||
|
||||
**Integration Points (TODO):**
|
||||
- `ShardokEngine.cpp`: Check `ShouldTriggerScriptedFlee()` during turn processing
|
||||
- `FleeCommandFactory.cpp`: Disable flee commands for defender in tutorial mode
|
||||
|
||||
### Phase 4: Mid-Battle Reinforcements
|
||||
|
||||
**Action Types** (in `action_type.proto`):
|
||||
- `TUTORIAL_ENEMY_FLED = 78`
|
||||
- `TUTORIAL_REINFORCEMENTS_ARRIVED = 79`
|
||||
|
||||
**Implementation (TODO):**
|
||||
- When scripted flee triggers, create reinforcement units in defender's reserves
|
||||
- Units have `UnitStatus_RESERVE_UNIT` status
|
||||
- Reinforcements enter battle on subsequent turns
|
||||
|
||||
### Phase 5: Tutorial Popups
|
||||
|
||||
**Content Definitions** (in `TutorialContentDefinitions.cs`):
|
||||
- `tutorial_battle_started`: "Defend your province!"
|
||||
- `tutorial_enemy_fled`: "The enemy retreats!"
|
||||
- `tutorial_reinforcements_arrived`: "Allies have arrived!"
|
||||
- `tutorial_battle_victory`: Victory celebration
|
||||
|
||||
**Trigger Integration:**
|
||||
- `TutorialTriggerRegistry.cs`: Handles tutorial action types from `ActionResultView`
|
||||
- `ShardokGameController.cs`: Detects tutorial events and triggers popups
|
||||
|
||||
### Phase 6: Post-Battle Hero Joining
|
||||
|
||||
**Implementation (TODO):**
|
||||
- `ResolveBattleAction.scala`: Check if battle was tutorial and defender won
|
||||
- Create `TutorialHeroJoined` action results adding reinforcement heroes to player faction
|
||||
- Set appropriate loyalty, vigor, location for joined heroes
|
||||
|
||||
---
|
||||
|
||||
## File Summary
|
||||
|
||||
### New Files
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `TutorialBattleController.hpp/cpp` | C++ scripted flee and reinforcement logic |
|
||||
| `NarrativeScreenController.cs` | Unity narrative screen display |
|
||||
|
||||
### Modified Files
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `tutorial_game_parameters.json` | Battle configuration and narrative content |
|
||||
| `game_state_view.proto` | Narrative screens field |
|
||||
| `game_parameters.proto` | Tutorial battle config |
|
||||
| `player_info.proto` | Tutorial battle flags for Shardok |
|
||||
| `action_type.proto` | Tutorial action types |
|
||||
| `NewGameCreation.scala` | Auto-start battle setup |
|
||||
| `EagleGameController.cs` | Narrative display integration |
|
||||
| `TutorialContentDefinitions.cs` | Battle tutorial content |
|
||||
| `TutorialTriggerRegistry.cs` | Tutorial event triggers |
|
||||
|
||||
---
|
||||
|
||||
## Remaining Implementation
|
||||
|
||||
### ShardokEngine Integration
|
||||
Integrate `TutorialBattleController` into the Shardok battle loop:
|
||||
1. Initialize controller from battle config when battle starts
|
||||
2. Check `ShouldTriggerScriptedFlee()` at end of each round
|
||||
3. If true, call `ExecuteScriptedFlee()` and add results to action queue
|
||||
|
||||
### Disable Player Flee
|
||||
In `FleeCommandFactory.cpp`:
|
||||
- Check if tutorial mode is enabled
|
||||
- If defender, don't offer flee commands
|
||||
|
||||
### Reinforcement Units
|
||||
In `TutorialBattleController::ExecuteScriptedFlee()`:
|
||||
- Create `Unit` objects for Elena Fyar, Hedrick, The Boulder
|
||||
- Add to defender's reserve units
|
||||
- Generate `TUTORIAL_REINFORCEMENTS_ARRIVED` action
|
||||
|
||||
### Post-Battle Hero Joining
|
||||
In Eagle's battle resolution:
|
||||
- Detect tutorial battle completion
|
||||
- Create heroes in player's faction with proper stats
|
||||
- Generate notification action results
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] New user sees narrative screens before battle
|
||||
- [ ] Battle starts immediately after narratives (no strategic map)
|
||||
- [ ] Player has correct units (3 heroes, 3 battalions)
|
||||
- [ ] Attacker has correct units (3 heroes, 3 battalions)
|
||||
- [ ] Player cannot flee
|
||||
- [ ] Tarn flees after player loses 1 unit
|
||||
- [ ] Tarn flees after 5 rounds (if no units lost)
|
||||
- [ ] Reinforcements appear when Tarn flees
|
||||
- [ ] Tutorial popups appear at correct moments
|
||||
- [ ] Reinforcement heroes join faction after battle victory
|
||||
|
||||
---
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### tutorial_game_parameters.json
|
||||
```json
|
||||
{
|
||||
"tutorialBattle": {
|
||||
"attackerFactionHead": "Ikhaan Tarn",
|
||||
"targetProvinceId": 14,
|
||||
"fleeAfterDefenderUnitsLost": 1,
|
||||
"fleeAfterRounds": 5,
|
||||
"reinforcements": ["Elena Fyar", "Hedrick", "The Boulder"]
|
||||
},
|
||||
"tutorialNarrativeScreens": [
|
||||
{ "title": "...", "bodyText": "...", "imagePath": "" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### TutorialBattleConfig Proto (Shardok)
|
||||
```protobuf
|
||||
message TutorialBattleConfig {
|
||||
bool enabled = 1;
|
||||
int32 flee_after_defender_units_lost = 2;
|
||||
int32 flee_after_rounds = 3;
|
||||
int32 attacker_player_id = 4;
|
||||
bool defender_can_flee = 5;
|
||||
repeated string reinforcement_hero_names = 6;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,113 @@
|
||||
# AI Quest Completion Behavior
|
||||
|
||||
This document describes which quests the AI attempts to complete proactively to recruit unaffiliated heroes.
|
||||
|
||||
## Overview
|
||||
|
||||
The AI attempts to complete quests via `FulfillQuestsCommandSelector`, which is invoked by `MidGameAIClient.chosenFulfillEasyQuestsCommand`. The AI only considers quests from unaffiliated heroes in provinces ruled by a faction leader.
|
||||
|
||||
## Quests the AI Actively Completes
|
||||
|
||||
### Diplomacy Quests
|
||||
|
||||
| Quest | Handler | Conditions |
|
||||
|-------|---------|------------|
|
||||
| `AllianceQuest` | `AllianceQuestCommandChooser` | Target faction must meet trust conditions for alliance and not already be in an alliance |
|
||||
| `TruceWithFactionQuest` | `TruceWithFactionQuestCommandChooser` | Target faction must meet trust conditions for truce |
|
||||
| `TruceCountQuest` | `TruceCountQuestCommandChooser` | Picks a random faction that meets trust conditions and isn't already in a truce/alliance |
|
||||
|
||||
### Resource Giving Quests
|
||||
|
||||
| Quest | Handler | Notes |
|
||||
|-------|---------|-------|
|
||||
| `AlmsToProvinceQuest` | `AlmsToProvinceQuestCommandChooser` | Gives food to the specified province |
|
||||
| `AlmsAcrossRealmQuest` | `AlmsAcrossRealmQuestCommandChooser` | Gives food from the province with the largest surplus |
|
||||
| `GiveToHeroesInProvinceQuest` | `GiveToHeroesInProvinceQuestCommandChooser` | Gives gold to the hero with the lowest loyalty in the specified province |
|
||||
| `GiveToHeroesAcrossRealmQuest` | `GiveToHeroesAcrossRealmQuestCommandChooser` | Gives gold from the province with the most gold available |
|
||||
|
||||
### Province Development Quests
|
||||
|
||||
| Quest | Handler | Notes |
|
||||
|-------|---------|-------|
|
||||
| `ImproveAgricultureQuest` | `ImproveQuestCommandChooser` | Issues an Improve command with Agriculture type |
|
||||
| `ImproveEconomyQuest` | `ImproveQuestCommandChooser` | Issues an Improve command with Economy type |
|
||||
| `ImproveInfrastructureQuest` | `ImproveQuestCommandChooser` | Issues an Improve command with Infrastructure type |
|
||||
|
||||
### Other Quests
|
||||
|
||||
| Quest | Handler | Conditions |
|
||||
|-------|---------|------------|
|
||||
| `DismissSpecificVassalQuest` | `DismissSpecificVassalCommandChooser` | Only if province has more than 2 heroes AND the unaffiliated hero's power >= target hero's power * `RequiredPowerMultiplierForDismiss` |
|
||||
|
||||
## Quests the AI Does Not Attempt to Complete
|
||||
|
||||
The following quests have no handler in `FulfillQuestsCommandSelector` and must be completed naturally through gameplay:
|
||||
|
||||
### Combat/Military Quests
|
||||
- `DefeatFactionQuest` - Defeat a specific faction
|
||||
- `GrandArmyQuest` - Accumulate a large number of troops
|
||||
- `UpgradeBattalionQuest` - Upgrade a battalion to minimum armament/training
|
||||
- `WinBattleOutnumberedQuest` - Win a battle while outnumbered
|
||||
- `WinBattlesQuest` - Win a number of battles
|
||||
- `RescueImprisonedLeaderQuest` - Rescue an imprisoned leader from another faction
|
||||
|
||||
### Expansion Quests
|
||||
- `ExpandToProvincesQuest` - Expand to control a certain number of provinces
|
||||
- `SpecificExpansionQuest` - Conquer a specific province
|
||||
- `BorderSecurityQuest` - Have troops in a border province
|
||||
|
||||
### Prisoner Quests
|
||||
- `ExecutePrisonerQuest` - Execute a specific prisoner
|
||||
- `ExilePrisonerQuest` - Exile a specific prisoner
|
||||
- `ReleasePrisonerQuest` - Release a specific prisoner
|
||||
- `ReturnPrisonerQuest` - Return a prisoner to their faction
|
||||
- `ReleaseAllPrisonersQuest` - Release all prisoners
|
||||
|
||||
### Province Order Quests
|
||||
- `DevelopProvincesQuest` - Maintain provinces in Develop order for months
|
||||
- `MobilizeProvincesQuest` - Maintain provinces in Mobilize order for months
|
||||
- `RestProvinceQuest` - Use the Rest command in a specific province
|
||||
|
||||
### Reconnaissance Quests
|
||||
- `ReconProvincesQuest` - Reconnoiter a number of provinces
|
||||
- `ReconSpecificProvincesQuest` - Reconnoiter specific provinces
|
||||
|
||||
### Economic Quests
|
||||
- `TotalDevelopmentQuest` - Achieve total development level in a province
|
||||
- `WealthQuest` - Accumulate gold and food
|
||||
- `SpendOnFeastsQuest` - Spend gold on feasts
|
||||
- `SendSuppliesQuest` - Send food to a specific province
|
||||
- `RepairDevastationQuest` - Repair devastation
|
||||
|
||||
### Special Event Quests
|
||||
- `SuppressRiotByForceQuest` - Suppress a riot by force
|
||||
- `FightBeastsAloneQuest` - Fight beasts alone
|
||||
- `StartBlizzardQuest` - Start a blizzard in a province
|
||||
- `StartEpidemicQuest` - Start an epidemic in a province
|
||||
- `ApprehendOutlawQuest` - Apprehend an outlaw hero
|
||||
|
||||
### Miscellaneous
|
||||
- `BattalionDiversityQuest` - Have diverse battalion types
|
||||
- `SwearBrotherhoodWithHeroQuest` - Swear brotherhood with a specific hero
|
||||
- `BetrayAllyQuest` - Betray an allied faction
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The quest completion logic is located in:
|
||||
- `FulfillQuestsCommandSelector.scala` - Main entry point, iterates through choosers
|
||||
- `quest_command_selectors/` - Individual quest handlers
|
||||
|
||||
Each chooser extends either:
|
||||
- `QuestCommandChooser` - For quests requiring randomness
|
||||
- `DeterministicQuestCommandChooser` - For quests with deterministic command selection
|
||||
|
||||
The AI prioritizes quests in the order they appear in `FulfillQuestsCommandSelector.choosers`:
|
||||
1. Alliance
|
||||
2. TruceWithFaction
|
||||
3. Improve (Agriculture/Economy/Infrastructure)
|
||||
4. AlmsToProvince
|
||||
5. GiveToHeroesInProvince
|
||||
6. AlmsAcrossRealm
|
||||
7. GiveToHeroesAcrossRealm
|
||||
8. TruceCount
|
||||
9. DismissSpecificVassal
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
# Export BUILD files for use with http_archive build_file attribute
|
||||
exports_files([
|
||||
"BUILD.gtl",
|
||||
"BUILD.llvm_mingw",
|
||||
])
|
||||
|
||||
+343
-75
@@ -1,21 +1,226 @@
|
||||
{
|
||||
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
|
||||
"__INPUT_ARTIFACTS_HASH": 434250008,
|
||||
"__RESOLVED_ARTIFACTS_HASH": -824975294,
|
||||
"__INPUT_ARTIFACTS_HASH": {
|
||||
"com.amazonaws:aws-lambda-java-core": -2008008480,
|
||||
"com.amazonaws:aws-lambda-java-events": 904769795,
|
||||
"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.auto.value:auto-value": 1146221104,
|
||||
"com.google.auto.value:auto-value-annotations": -1101276935,
|
||||
"com.google.code.findbugs:jsr305": 495355163,
|
||||
"com.google.code.gson:gson": 1675696798,
|
||||
"com.google.errorprone:error_prone_annotations": 692295270,
|
||||
"com.google.guava:failureaccess": 1582410452,
|
||||
"com.google.guava:guava": 174556251,
|
||||
"com.google.j2objc:j2objc-annotations": 2003271689,
|
||||
"com.google.protobuf:protobuf-java": -1795397632,
|
||||
"com.google.protobuf:protobuf-java-util": -1033086717,
|
||||
"com.google.re2j:re2j": 1525409503,
|
||||
"com.google.s2a.proto.v2:s2a-proto": 898932763,
|
||||
"com.google.truth:truth": 1696781452,
|
||||
"com.nimbusds:nimbus-jose-jwt": 200797458,
|
||||
"com.squareup.okhttp3:okhttp": 916942239,
|
||||
"com.squareup.okhttp3:okhttp-sse": 1714840279,
|
||||
"com.squareup.okhttp:okhttp": -1467174407,
|
||||
"com.squareup.okio:okio": 795722926,
|
||||
"com.thesamet.scalapb:compilerplugin_3": -756207945,
|
||||
"com.thesamet.scalapb:lenses_3": 1060875741,
|
||||
"com.thesamet.scalapb:protoc-bridge_3": -1260006911,
|
||||
"com.thesamet.scalapb:scalapb-json4s_3": -772830803,
|
||||
"com.thesamet.scalapb:scalapb-runtime-grpc_3": 908736225,
|
||||
"com.thesamet.scalapb:scalapb-runtime_3": 1652882204,
|
||||
"io.netty:netty-buffer": -36891658,
|
||||
"io.netty:netty-codec": -1968637428,
|
||||
"io.netty:netty-codec-http": 2069139895,
|
||||
"io.netty:netty-codec-http2": 980426895,
|
||||
"io.netty:netty-codec-socks": -1802651606,
|
||||
"io.netty:netty-common": -644663253,
|
||||
"io.netty:netty-handler": 1555108312,
|
||||
"io.netty:netty-handler-proxy": -964658153,
|
||||
"io.netty:netty-resolver": -1317606544,
|
||||
"io.netty:netty-tcnative-boringssl-static": 2033070843,
|
||||
"io.netty:netty-tcnative-classes": 2023773715,
|
||||
"io.netty:netty-transport": 1563151065,
|
||||
"io.netty:netty-transport-native-epoll": -1215800119,
|
||||
"io.netty:netty-transport-native-unix-common": -818773192,
|
||||
"io.opencensus:opencensus-api": 2042364617,
|
||||
"io.opencensus:opencensus-contrib-grpc-metrics": 317094357,
|
||||
"io.perfmark:perfmark-api": 1019157794,
|
||||
"io.sentry:sentry": -947045362,
|
||||
"javax.xml.bind:jaxb-api": -1735500680,
|
||||
"junit:junit": -744267592,
|
||||
"org.checkerframework:checker-qual": -819031361,
|
||||
"org.codehaus.mojo:animal-sniffer-annotations": -1634689353,
|
||||
"org.json4s:json4s-ast_3": 687176866,
|
||||
"org.json4s:json4s-core_3": 977579605,
|
||||
"org.json4s:json4s-native_3": -123383795,
|
||||
"org.reactivestreams:reactive-streams": 509083534,
|
||||
"org.scalamock:scalamock_3": -610483078,
|
||||
"org.slf4j:slf4j-api": 1609745898,
|
||||
"org.slf4j:slf4j-simple": 1319506696,
|
||||
"repositories": -1949687017,
|
||||
"software.amazon.awssdk:aws-core": 1764800397,
|
||||
"software.amazon.awssdk:http-client-spi": 1697477859,
|
||||
"software.amazon.awssdk:regions": -663595789,
|
||||
"software.amazon.awssdk:s3": -544649668,
|
||||
"software.amazon.awssdk:s3-transfer-manager": -1414083014,
|
||||
"software.amazon.awssdk:sdk-core": -1921023350,
|
||||
"software.amazon.awssdk:utils": 1323800129
|
||||
},
|
||||
"__RESOLVED_ARTIFACTS_HASH": {
|
||||
"com.amazonaws:aws-lambda-java-core": 930138565,
|
||||
"com.amazonaws:aws-lambda-java-events": 582075020,
|
||||
"com.fasterxml.jackson.core:jackson-annotations": 1276887193,
|
||||
"com.fasterxml.jackson.core:jackson-core": 439118590,
|
||||
"com.fasterxml.jackson.core:jackson-databind": 1897319963,
|
||||
"com.github.stephenc.jcip:jcip-annotations": 2084352561,
|
||||
"com.google.android:annotations": 2096496032,
|
||||
"com.google.api.grpc:proto-google-common-protos": 2069379049,
|
||||
"com.google.api:api-common": -597713164,
|
||||
"com.google.auth:google-auth-library-credentials": -366937256,
|
||||
"com.google.auth:google-auth-library-oauth2-http": -472658017,
|
||||
"com.google.auto.value:auto-value": 1110951021,
|
||||
"com.google.auto.value:auto-value-annotations": 641018093,
|
||||
"com.google.code.findbugs:jsr305": 870839855,
|
||||
"com.google.code.gson:gson": 614185888,
|
||||
"com.google.errorprone:error_prone_annotations": -1097264484,
|
||||
"com.google.guava:failureaccess": -718864417,
|
||||
"com.google.guava:guava": 631608943,
|
||||
"com.google.guava:listenablefuture": 1079558157,
|
||||
"com.google.http-client:google-http-client": 418651909,
|
||||
"com.google.http-client:google-http-client-gson": -48153131,
|
||||
"com.google.j2objc:j2objc-annotations": -404209759,
|
||||
"com.google.protobuf:protobuf-java": 1668417350,
|
||||
"com.google.protobuf:protobuf-java-util": 1986864820,
|
||||
"com.google.re2j:re2j": -1688126233,
|
||||
"com.google.s2a.proto.v2:s2a-proto": 1518288545,
|
||||
"com.google.truth:truth": -1338076935,
|
||||
"com.nimbusds:nimbus-jose-jwt": -1437437976,
|
||||
"com.squareup.okhttp3:okhttp": 1262276211,
|
||||
"com.squareup.okhttp3:okhttp-sse": 505395183,
|
||||
"com.squareup.okhttp:okhttp": -1958498367,
|
||||
"com.squareup.okio:okio": -993505667,
|
||||
"com.squareup.okio:okio-jvm": 2073967287,
|
||||
"com.thesamet.scalapb:compilerplugin_3": -1711749067,
|
||||
"com.thesamet.scalapb:lenses_3": 81185934,
|
||||
"com.thesamet.scalapb:protoc-bridge_2.13": -1602581734,
|
||||
"com.thesamet.scalapb:protoc-bridge_3": -350123056,
|
||||
"com.thesamet.scalapb:protoc-gen_2.13": -1004483995,
|
||||
"com.thesamet.scalapb:scalapb-json4s_3": -1395179337,
|
||||
"com.thesamet.scalapb:scalapb-runtime-grpc_3": 1440887312,
|
||||
"com.thesamet.scalapb:scalapb-runtime_3": -298320448,
|
||||
"commons-codec:commons-codec": 1208138832,
|
||||
"commons-logging:commons-logging": 1248790901,
|
||||
"dev.dirs:directories": 513669062,
|
||||
"io.grpc:grpc-api": 1519481799,
|
||||
"io.grpc:grpc-context": 1273832094,
|
||||
"io.grpc:grpc-protobuf": -1491986464,
|
||||
"io.grpc:grpc-protobuf-lite": 1996390382,
|
||||
"io.grpc:grpc-stub": 206724158,
|
||||
"io.netty:netty-buffer": 1883825690,
|
||||
"io.netty:netty-codec": 1759679007,
|
||||
"io.netty:netty-codec-http": -965628835,
|
||||
"io.netty:netty-codec-http2": -631439859,
|
||||
"io.netty:netty-codec-socks": 1385966058,
|
||||
"io.netty:netty-common": -1574327641,
|
||||
"io.netty:netty-handler": 1812678974,
|
||||
"io.netty:netty-handler-proxy": 452260541,
|
||||
"io.netty:netty-resolver": -656912885,
|
||||
"io.netty:netty-tcnative-boringssl-static": -1618728145,
|
||||
"io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64": -46670590,
|
||||
"io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64": -323938727,
|
||||
"io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64": 1027623299,
|
||||
"io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64": -322798677,
|
||||
"io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64": 1803328375,
|
||||
"io.netty:netty-tcnative-classes": -498572591,
|
||||
"io.netty:netty-transport": 764254626,
|
||||
"io.netty:netty-transport-classes-epoll": 1006952729,
|
||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64": -737288378,
|
||||
"io.netty:netty-transport-native-unix-common": -1442019057,
|
||||
"io.opencensus:opencensus-api": 516564413,
|
||||
"io.opencensus:opencensus-contrib-grpc-metrics": -412818674,
|
||||
"io.opencensus:opencensus-contrib-http-util": 1093902672,
|
||||
"io.perfmark:perfmark-api": -1795841558,
|
||||
"io.sentry:sentry": 1818358364,
|
||||
"javax.activation:javax.activation-api": 1384047725,
|
||||
"javax.annotation:javax.annotation-api": -1009230154,
|
||||
"javax.xml.bind:jaxb-api": 238614541,
|
||||
"joda-time:joda-time": -926621536,
|
||||
"junit:junit": -1256429642,
|
||||
"org.apache.httpcomponents:httpclient": 546847276,
|
||||
"org.apache.httpcomponents:httpcore": 67453319,
|
||||
"org.checkerframework:checker-qual": 1860567376,
|
||||
"org.codehaus.mojo:animal-sniffer-annotations": -1201443841,
|
||||
"org.hamcrest:hamcrest-core": 649657847,
|
||||
"org.jetbrains.kotlin:kotlin-stdlib": -879725260,
|
||||
"org.jetbrains.kotlin:kotlin-stdlib-common": 393704220,
|
||||
"org.jetbrains.kotlin:kotlin-stdlib-jdk7": 1242909803,
|
||||
"org.jetbrains.kotlin:kotlin-stdlib-jdk8": -597817274,
|
||||
"org.jetbrains:annotations": 554168982,
|
||||
"org.json4s:json4s-ast_3": -1000402007,
|
||||
"org.json4s:json4s-core_3": -1662557228,
|
||||
"org.json4s:json4s-jackson-core_3": -1868355082,
|
||||
"org.json4s:json4s-native-core_3": 227547325,
|
||||
"org.json4s:json4s-native_3": 1646180259,
|
||||
"org.jspecify:jspecify": 117231129,
|
||||
"org.ow2.asm:asm": 611381606,
|
||||
"org.reactivestreams:reactive-streams": 1281941191,
|
||||
"org.scala-lang.modules:scala-collection-compat_3": 1599811889,
|
||||
"org.scala-lang:scala-library": -1079389784,
|
||||
"org.scala-lang:scala3-library_3": -212752345,
|
||||
"org.scalamock:scalamock_3": -1778657085,
|
||||
"org.slf4j:slf4j-api": -771685699,
|
||||
"org.slf4j:slf4j-simple": -776509812,
|
||||
"software.amazon.awssdk:annotations": -324257201,
|
||||
"software.amazon.awssdk:apache-client": 2066024214,
|
||||
"software.amazon.awssdk:arns": -1240703529,
|
||||
"software.amazon.awssdk:auth": 1612047768,
|
||||
"software.amazon.awssdk:aws-core": -62367447,
|
||||
"software.amazon.awssdk:aws-query-protocol": 570551548,
|
||||
"software.amazon.awssdk:aws-xml-protocol": -1850136736,
|
||||
"software.amazon.awssdk:checksums": -238416593,
|
||||
"software.amazon.awssdk:checksums-spi": 807336878,
|
||||
"software.amazon.awssdk:crt-core": 280528066,
|
||||
"software.amazon.awssdk:endpoints-spi": -241689351,
|
||||
"software.amazon.awssdk:http-auth": 236194098,
|
||||
"software.amazon.awssdk:http-auth-aws": -385949349,
|
||||
"software.amazon.awssdk:http-auth-aws-eventstream": -408169305,
|
||||
"software.amazon.awssdk:http-auth-spi": 211732368,
|
||||
"software.amazon.awssdk:http-client-spi": -1646055035,
|
||||
"software.amazon.awssdk:identity-spi": 90960913,
|
||||
"software.amazon.awssdk:json-utils": 740192539,
|
||||
"software.amazon.awssdk:metrics-spi": 692846272,
|
||||
"software.amazon.awssdk:netty-nio-client": 94159754,
|
||||
"software.amazon.awssdk:profiles": 1441805445,
|
||||
"software.amazon.awssdk:protocol-core": 204998161,
|
||||
"software.amazon.awssdk:regions": 407633714,
|
||||
"software.amazon.awssdk:retries": 1892797724,
|
||||
"software.amazon.awssdk:retries-spi": -2126209698,
|
||||
"software.amazon.awssdk:s3": -829433125,
|
||||
"software.amazon.awssdk:s3-transfer-manager": 761695256,
|
||||
"software.amazon.awssdk:sdk-core": 78965981,
|
||||
"software.amazon.awssdk:third-party-jackson-core": 24776375,
|
||||
"software.amazon.awssdk:utils": -1896886996,
|
||||
"software.amazon.awssdk:utils-lite": -446744113,
|
||||
"software.amazon.eventstream:eventstream": 666815114
|
||||
},
|
||||
"conflict_resolution": {
|
||||
"com.google.guava:failureaccess:1.0.1": "com.google.guava:failureaccess:1.0.2",
|
||||
"com.google.j2objc:j2objc-annotations:2.8": "com.google.j2objc:j2objc-annotations:3.0.0",
|
||||
"com.google.protobuf:protobuf-java:4.27.2": "com.google.protobuf:protobuf-java:4.28.2",
|
||||
"com.squareup.okio:okio:2.10.0": "com.squareup.okio:okio:3.6.0",
|
||||
"io.netty:netty-buffer:4.1.110.Final": "io.netty:netty-buffer:4.1.130.Final",
|
||||
"io.netty:netty-codec-http2:4.1.110.Final": "io.netty:netty-codec-http2:4.1.130.Final",
|
||||
"io.netty:netty-codec-http:4.1.110.Final": "io.netty:netty-codec-http:4.1.130.Final",
|
||||
"io.netty:netty-codec:4.1.110.Final": "io.netty:netty-codec:4.1.130.Final",
|
||||
"io.netty:netty-common:4.1.110.Final": "io.netty:netty-common:4.1.130.Final",
|
||||
"io.netty:netty-handler:4.1.110.Final": "io.netty:netty-handler:4.1.130.Final",
|
||||
"io.netty:netty-resolver:4.1.110.Final": "io.netty:netty-resolver:4.1.130.Final",
|
||||
"io.netty:netty-transport-native-unix-common:4.1.110.Final": "io.netty:netty-transport-native-unix-common:4.1.130.Final",
|
||||
"io.netty:netty-transport:4.1.110.Final": "io.netty:netty-transport:4.1.130.Final",
|
||||
"io.opencensus:opencensus-api:0.31.0": "io.opencensus:opencensus-api:0.31.1",
|
||||
"org.checkerframework:checker-qual:3.12.0": "org.checkerframework:checker-qual:3.43.0"
|
||||
"io.netty:netty-buffer:4.1.127.Final": "io.netty:netty-buffer:4.1.130.Final",
|
||||
"io.netty:netty-codec-http2:4.1.127.Final": "io.netty:netty-codec-http2:4.1.130.Final",
|
||||
"io.netty:netty-codec-http:4.1.127.Final": "io.netty:netty-codec-http:4.1.130.Final",
|
||||
"io.netty:netty-codec:4.1.127.Final": "io.netty:netty-codec:4.1.130.Final",
|
||||
"io.netty:netty-common:4.1.127.Final": "io.netty:netty-common:4.1.130.Final",
|
||||
"io.netty:netty-handler:4.1.127.Final": "io.netty:netty-handler:4.1.130.Final",
|
||||
"io.netty:netty-resolver:4.1.127.Final": "io.netty:netty-resolver:4.1.130.Final",
|
||||
"io.netty:netty-transport-native-unix-common:4.1.127.Final": "io.netty:netty-transport-native-unix-common:4.1.130.Final",
|
||||
"io.netty:netty-transport:4.1.127.Final": "io.netty:netty-transport:4.1.130.Final",
|
||||
"io.opencensus:opencensus-api:0.31.0": "io.opencensus:opencensus-api:0.31.1"
|
||||
},
|
||||
"artifacts": {
|
||||
"com.amazonaws:aws-lambda-java-core": {
|
||||
@@ -62,21 +267,27 @@
|
||||
},
|
||||
"com.google.api.grpc:proto-google-common-protos": {
|
||||
"shasums": {
|
||||
"jar": "0b27938f3d28ccd6884945d7e4f75f4e26a677bbf3cd39bbcb694f130f782aa9"
|
||||
"jar": "352ffc769f4d17e2d6227b6893ec368f4f07fbb069dd7aaa644b16949369f8b7"
|
||||
},
|
||||
"version": "2.51.0"
|
||||
"version": "2.63.1"
|
||||
},
|
||||
"com.google.api:api-common": {
|
||||
"shasums": {
|
||||
"jar": "f7073fb389c2aa329981ee65241db7947e8744c988e6c4d975f18bf2748b935a"
|
||||
},
|
||||
"version": "2.53.0"
|
||||
},
|
||||
"com.google.auth:google-auth-library-credentials": {
|
||||
"shasums": {
|
||||
"jar": "5dbf1207d14e093f67995f457cb69c3cf49bed1364150b23465e09acada65d96"
|
||||
"jar": "dd7e39b8f5c366b09f8c2d400b7b63c925499df813b0f0d78044abd72098e159"
|
||||
},
|
||||
"version": "1.24.1"
|
||||
"version": "1.40.0"
|
||||
},
|
||||
"com.google.auth:google-auth-library-oauth2-http": {
|
||||
"shasums": {
|
||||
"jar": "88a75cd4448ea2f3b46e48a89497a6cf0985a5fa4e21274af4940e07f59f6eaf"
|
||||
"jar": "67fe2c9df9780d8725c1dff61d2ffbbcb93fdcf15e91c44a5e610a4a69afd151"
|
||||
},
|
||||
"version": "1.24.1"
|
||||
"version": "1.40.0"
|
||||
},
|
||||
"com.google.auto.value:auto-value": {
|
||||
"shasums": {
|
||||
@@ -128,15 +339,15 @@
|
||||
},
|
||||
"com.google.http-client:google-http-client": {
|
||||
"shasums": {
|
||||
"jar": "390618d7b51704240b8fd28e1230fa35d220f93f4b4ba80f63e38db00dacb09e"
|
||||
"jar": "97a0666340956dbc96893bc8cf843a5b57367543f218feddad3ff433fbfdaa68"
|
||||
},
|
||||
"version": "1.44.2"
|
||||
"version": "2.0.2"
|
||||
},
|
||||
"com.google.http-client:google-http-client-gson": {
|
||||
"shasums": {
|
||||
"jar": "1119b66685195310375b717de2215d6c5d14fa8ed9f57e07b4fecd461e7b9db7"
|
||||
"jar": "37c8fd5c7869bc4abf3b3219c7e79cb566afa745559772e969c21595199600cd"
|
||||
},
|
||||
"version": "1.44.2"
|
||||
"version": "2.0.2"
|
||||
},
|
||||
"com.google.j2objc:j2objc-annotations": {
|
||||
"shasums": {
|
||||
@@ -150,17 +361,29 @@
|
||||
},
|
||||
"version": "4.28.2"
|
||||
},
|
||||
"com.google.protobuf:protobuf-java-util": {
|
||||
"shasums": {
|
||||
"jar": "a2665294d3e4675482bde593df8283f8c965f0207785e8e9b223f790644f5b08"
|
||||
},
|
||||
"version": "4.27.2"
|
||||
},
|
||||
"com.google.re2j:re2j": {
|
||||
"shasums": {
|
||||
"jar": "7b52c72156dd7f98b3237a5b35c1d34fba381b21048c89208913ad80a45dfbd7"
|
||||
},
|
||||
"version": "1.8"
|
||||
},
|
||||
"com.google.s2a.proto.v2:s2a-proto": {
|
||||
"shasums": {
|
||||
"jar": "3205a05a82003067fbbd28ec0381d4c75258c25eb772d37be62e8b12d0402936"
|
||||
},
|
||||
"version": "0.1.3"
|
||||
},
|
||||
"com.google.truth:truth": {
|
||||
"shasums": {
|
||||
"jar": "14c297bc64ca8bc15b6baf67f160627e4562ec91624797e312e907b431113508"
|
||||
"jar": "a6409e90fdd6b31f239b0c02065c43b53e1b09c72f2e08c3a86d04fe5910cf4b"
|
||||
},
|
||||
"version": "1.4.2"
|
||||
"version": "1.4.5"
|
||||
},
|
||||
"com.nimbusds:nimbus-jose-jwt": {
|
||||
"shasums": {
|
||||
@@ -266,33 +489,33 @@
|
||||
},
|
||||
"io.grpc:grpc-api": {
|
||||
"shasums": {
|
||||
"jar": "b5120a11da5ce5ddfab019bbb69f5868529c9b5def1ba5b283251cc95fb3ba91"
|
||||
"jar": "45faf2ac1bf2791e8fdabce53684a86b62c99b84cba26fb13a5ba3f4abf80d6c"
|
||||
},
|
||||
"version": "1.68.0"
|
||||
"version": "1.70.0"
|
||||
},
|
||||
"io.grpc:grpc-context": {
|
||||
"shasums": {
|
||||
"jar": "4ab6efb9cbadc88f8dc723ada3a61785da367697373d4432aef5222312aa70f6"
|
||||
"jar": "eb2824831c0ac03e741efda86b141aa863a481ebc4aaf5a5c1f13a481dbb40ff"
|
||||
},
|
||||
"version": "1.60.1"
|
||||
"version": "1.70.0"
|
||||
},
|
||||
"io.grpc:grpc-protobuf": {
|
||||
"shasums": {
|
||||
"jar": "79704cf169ee27151fce4375a4d91fe828d276d921eef5a7f497d020b0a5d345"
|
||||
"jar": "9b98039ed826604c46d6ac8f8a182d413d348ec6abe26467736b05aa92e7e1d3"
|
||||
},
|
||||
"version": "1.68.0"
|
||||
"version": "1.70.0"
|
||||
},
|
||||
"io.grpc:grpc-protobuf-lite": {
|
||||
"shasums": {
|
||||
"jar": "60e92e148b4f86c06885afa79a86beb74bffcdcba47f8b65dc7010ba6701fa80"
|
||||
"jar": "e7cc2ca8981672851cbebf83a24bfb93c1b2b058e75c1a817a757b914f33403d"
|
||||
},
|
||||
"version": "1.68.0"
|
||||
"version": "1.70.0"
|
||||
},
|
||||
"io.grpc:grpc-stub": {
|
||||
"shasums": {
|
||||
"jar": "7c4090509cd6ea0432305f9397da21127b691905bdcf163a306bedb1c6f4ead7"
|
||||
"jar": "5adaa1ec1f744b67ae14a8dbc39c9589c010fad0fd557b0a02966202e4d23a18"
|
||||
},
|
||||
"version": "1.68.0"
|
||||
"version": "1.70.0"
|
||||
},
|
||||
"io.netty:netty-buffer": {
|
||||
"shasums": {
|
||||
@@ -320,9 +543,9 @@
|
||||
},
|
||||
"io.netty:netty-codec-socks": {
|
||||
"shasums": {
|
||||
"jar": "976052a3c9bb280bc6d99f3a29e6404677cf958c3de05b205093d38c006b880c"
|
||||
"jar": "d3d251f9239951a845f22e39191f95471fb2eb7951b9878ea4555ccac99529fb"
|
||||
},
|
||||
"version": "4.1.110.Final"
|
||||
"version": "4.1.127.Final"
|
||||
},
|
||||
"io.netty:netty-common": {
|
||||
"shasums": {
|
||||
@@ -338,9 +561,9 @@
|
||||
},
|
||||
"io.netty:netty-handler-proxy": {
|
||||
"shasums": {
|
||||
"jar": "ad54ab4fe9c47ef3e723d71251126db53e8db543871adb9eafc94446539eff52"
|
||||
"jar": "2c0c8046e5d737e08f40a7c2907526648860d0434e125bd51de3c2cf390453fb"
|
||||
},
|
||||
"version": "4.1.110.Final"
|
||||
"version": "4.1.127.Final"
|
||||
},
|
||||
"io.netty:netty-resolver": {
|
||||
"shasums": {
|
||||
@@ -350,20 +573,20 @@
|
||||
},
|
||||
"io.netty:netty-tcnative-boringssl-static": {
|
||||
"shasums": {
|
||||
"jar": "3f7b4c3a51737965cd5b53777782c125784420458d96513cfac7412e4d1fa0c3",
|
||||
"linux-aarch_64": "523c43f67ad9040d70f9494fc28eebf711d8c54e2aa30e3fd1a199c38740f53b",
|
||||
"linux-x86_64": "3d773aac73fe40f5d04de37ce14a1f7abd27caf0b3bd8275884f5d2968b3e254",
|
||||
"osx-aarch_64": "0454c53e65da6e253b2104d1ae26ecc79df4faf999e8924b659846b5bf41e996",
|
||||
"osx-x86_64": "9c6a23335f296689fb3538bc49e4e280ff163675212c6fe01c9cf2a9273ee19a",
|
||||
"windows-x86_64": "b3e3e0559df29a5624bcf529cb8e2bd9375c6d68164dda338e841677586a14c4"
|
||||
"jar": "ec3b14ceff74c5d7e24a378e64074744cc5e7035c49cf6ca0a4e23dff9713d1f",
|
||||
"linux-aarch_64": "11dfee82cfcb5c4f596271fe1d56a70fe77b4034e54162d0bd211623b540857f",
|
||||
"linux-x86_64": "39ac6b1eb4ffc18d5fe9412cc6253a8954bafc8e3ca10109bc370490f73a3673",
|
||||
"osx-aarch_64": "eef3fc7cdc5e2dc8c5cb50bae11443052bf1ee2f6e8b64378565746d1529f05e",
|
||||
"osx-x86_64": "e3beaa3ebdb136569528c59aa826d771efadc0ac9db484c6c0e457fa5efd0f07",
|
||||
"windows-x86_64": "5458b4b502763d49cd2e16c434312687178afac9c164cabf75c4a8bcecf7ac50"
|
||||
},
|
||||
"version": "2.0.70.Final"
|
||||
"version": "2.0.74.Final"
|
||||
},
|
||||
"io.netty:netty-tcnative-classes": {
|
||||
"shasums": {
|
||||
"jar": "a79c1579313d4ad48a3ecc1d01a25da06d22d6449c3bcc369c2318749bcf55bc"
|
||||
"jar": "194874cf723794dd409fd1e728cd91ffbcff0584d73b4d8a1ad0d69d04acf9b3"
|
||||
},
|
||||
"version": "2.0.70.Final"
|
||||
"version": "2.0.74.Final"
|
||||
},
|
||||
"io.netty:netty-transport": {
|
||||
"shasums": {
|
||||
@@ -379,9 +602,9 @@
|
||||
},
|
||||
"io.netty:netty-transport-native-epoll": {
|
||||
"shasums": {
|
||||
"linux-x86_64": "dcd60c6b3076af307ab877201a136e1f1066c9be809aaed827391a23909f9135"
|
||||
"linux-x86_64": "3d03f27eea1cd23357a56a96e1eeedfeb3d74fa0ba4d5c36a862bd035056275b"
|
||||
},
|
||||
"version": "4.1.110.Final"
|
||||
"version": "4.1.127.Final"
|
||||
},
|
||||
"io.netty:netty-transport-native-unix-common": {
|
||||
"shasums": {
|
||||
@@ -425,6 +648,12 @@
|
||||
},
|
||||
"version": "1.2.0"
|
||||
},
|
||||
"javax.annotation:javax.annotation-api": {
|
||||
"shasums": {
|
||||
"jar": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b"
|
||||
},
|
||||
"version": "1.3.2"
|
||||
},
|
||||
"javax.xml.bind:jaxb-api": {
|
||||
"shasums": {
|
||||
"jar": "88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06"
|
||||
@@ -455,23 +684,17 @@
|
||||
},
|
||||
"version": "4.4.16"
|
||||
},
|
||||
"org.apache.tomcat:annotations-api": {
|
||||
"shasums": {
|
||||
"jar": "253829d3c12b7381d1044fc22c6436cff025fe0d459e4a329413e560a7d0dd13"
|
||||
},
|
||||
"version": "6.0.53"
|
||||
},
|
||||
"org.checkerframework:checker-qual": {
|
||||
"shasums": {
|
||||
"jar": "3fbc2e98f05854c3df16df9abaa955b91b15b3ecac33623208ed6424640ef0f6"
|
||||
"jar": "508c83c62c344f6f7ee28f47b88a8797d6116d043bfd1ca0576c828dd1df2880"
|
||||
},
|
||||
"version": "3.43.0"
|
||||
"version": "3.49.5"
|
||||
},
|
||||
"org.codehaus.mojo:animal-sniffer-annotations": {
|
||||
"shasums": {
|
||||
"jar": "c720e6e5bcbe6b2f48ded75a47bccdb763eede79d14330102e0d352e3d89ed92"
|
||||
"jar": "342f4d815eae69bb980620d0a622862709be37d38f47577675b42c739a962da9"
|
||||
},
|
||||
"version": "1.24"
|
||||
"version": "1.26"
|
||||
},
|
||||
"org.hamcrest:hamcrest-core": {
|
||||
"shasums": {
|
||||
@@ -539,11 +762,17 @@
|
||||
},
|
||||
"version": "4.1.0-M8"
|
||||
},
|
||||
"org.jspecify:jspecify": {
|
||||
"shasums": {
|
||||
"jar": "1fad6e6be7557781e4d33729d49ae1cdc8fdda6fe477bb0cc68ce351eafdfbab"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"org.ow2.asm:asm": {
|
||||
"shasums": {
|
||||
"jar": "3c6fac2424db3d4a853b669f4e3d1d9c3c552235e19a319673f887083c2303a1"
|
||||
"jar": "876eab6a83daecad5ca67eb9fcabb063c97b5aeb8cf1fca7a989ecde17522051"
|
||||
},
|
||||
"version": "9.6"
|
||||
"version": "9.8"
|
||||
},
|
||||
"org.reactivestreams:reactive-streams": {
|
||||
"shasums": {
|
||||
@@ -791,10 +1020,19 @@
|
||||
"com.google.api.grpc:proto-google-common-protos": [
|
||||
"com.google.protobuf:protobuf-java"
|
||||
],
|
||||
"com.google.api:api-common": [
|
||||
"com.google.auto.value:auto-value-annotations",
|
||||
"com.google.code.findbugs:jsr305",
|
||||
"com.google.errorprone:error_prone_annotations",
|
||||
"com.google.j2objc:j2objc-annotations",
|
||||
"javax.annotation:javax.annotation-api"
|
||||
],
|
||||
"com.google.auth:google-auth-library-oauth2-http": [
|
||||
"com.google.api:api-common",
|
||||
"com.google.auth:google-auth-library-credentials",
|
||||
"com.google.auto.value:auto-value-annotations",
|
||||
"com.google.code.findbugs:jsr305",
|
||||
"com.google.code.gson:gson",
|
||||
"com.google.errorprone:error_prone_annotations",
|
||||
"com.google.guava:guava",
|
||||
"com.google.http-client:google-http-client",
|
||||
@@ -826,12 +1064,24 @@
|
||||
"com.google.code.gson:gson",
|
||||
"com.google.http-client:google-http-client"
|
||||
],
|
||||
"com.google.protobuf:protobuf-java-util": [
|
||||
"com.google.code.findbugs:jsr305",
|
||||
"com.google.code.gson:gson",
|
||||
"com.google.errorprone:error_prone_annotations",
|
||||
"com.google.guava:guava",
|
||||
"com.google.j2objc:j2objc-annotations",
|
||||
"com.google.protobuf:protobuf-java"
|
||||
],
|
||||
"com.google.s2a.proto.v2:s2a-proto": [
|
||||
"io.grpc:grpc-protobuf",
|
||||
"io.grpc:grpc-stub"
|
||||
],
|
||||
"com.google.truth:truth": [
|
||||
"com.google.auto.value:auto-value-annotations",
|
||||
"com.google.errorprone:error_prone_annotations",
|
||||
"com.google.guava:guava",
|
||||
"junit:junit",
|
||||
"org.checkerframework:checker-qual",
|
||||
"org.jspecify:jspecify",
|
||||
"org.ow2.asm:asm"
|
||||
],
|
||||
"com.nimbusds:nimbus-jose-jwt": [
|
||||
@@ -919,7 +1169,8 @@
|
||||
"io.grpc:grpc-stub": [
|
||||
"com.google.errorprone:error_prone_annotations",
|
||||
"com.google.guava:guava",
|
||||
"io.grpc:grpc-api"
|
||||
"io.grpc:grpc-api",
|
||||
"org.codehaus.mojo:animal-sniffer-annotations"
|
||||
],
|
||||
"io.netty:netty-buffer": [
|
||||
"io.netty:netty-common"
|
||||
@@ -1409,11 +1660,17 @@
|
||||
"com.google.shopping.type",
|
||||
"com.google.type"
|
||||
],
|
||||
"com.google.api:api-common": [
|
||||
"com.google.api.core",
|
||||
"com.google.api.pathtemplate",
|
||||
"com.google.api.resourcenames"
|
||||
],
|
||||
"com.google.auth:google-auth-library-credentials": [
|
||||
"com.google.auth"
|
||||
],
|
||||
"com.google.auth:google-auth-library-oauth2-http": [
|
||||
"com.google.auth.http",
|
||||
"com.google.auth.mtls",
|
||||
"com.google.auth.oauth2"
|
||||
],
|
||||
"com.google.auto.value:auto-value": [
|
||||
@@ -1531,9 +1788,15 @@
|
||||
"com.google.protobuf",
|
||||
"com.google.protobuf.compiler"
|
||||
],
|
||||
"com.google.protobuf:protobuf-java-util": [
|
||||
"com.google.protobuf.util"
|
||||
],
|
||||
"com.google.re2j:re2j": [
|
||||
"com.google.re2j"
|
||||
],
|
||||
"com.google.s2a.proto.v2:s2a-proto": [
|
||||
"com.google.s2a.proto.v2"
|
||||
],
|
||||
"com.google.truth:truth": [
|
||||
"com.google.common.truth"
|
||||
],
|
||||
@@ -1841,6 +2104,11 @@
|
||||
"javax.activation:javax.activation-api": [
|
||||
"javax.activation"
|
||||
],
|
||||
"javax.annotation:javax.annotation-api": [
|
||||
"javax.annotation",
|
||||
"javax.annotation.security",
|
||||
"javax.annotation.sql"
|
||||
],
|
||||
"javax.xml.bind:jaxb-api": [
|
||||
"javax.xml.bind",
|
||||
"javax.xml.bind.annotation",
|
||||
@@ -1937,13 +2205,6 @@
|
||||
"org.apache.http.ssl",
|
||||
"org.apache.http.util"
|
||||
],
|
||||
"org.apache.tomcat:annotations-api": [
|
||||
"javax.annotation",
|
||||
"javax.annotation.security",
|
||||
"javax.ejb",
|
||||
"javax.persistence",
|
||||
"javax.xml.ws"
|
||||
],
|
||||
"org.checkerframework:checker-qual": [
|
||||
"org.checkerframework.checker.builder.qual",
|
||||
"org.checkerframework.checker.calledmethods.qual",
|
||||
@@ -1958,12 +2219,14 @@
|
||||
"org.checkerframework.checker.interning.qual",
|
||||
"org.checkerframework.checker.lock.qual",
|
||||
"org.checkerframework.checker.mustcall.qual",
|
||||
"org.checkerframework.checker.nonempty.qual",
|
||||
"org.checkerframework.checker.nullness.qual",
|
||||
"org.checkerframework.checker.optional.qual",
|
||||
"org.checkerframework.checker.propkey.qual",
|
||||
"org.checkerframework.checker.regex.qual",
|
||||
"org.checkerframework.checker.signature.qual",
|
||||
"org.checkerframework.checker.signedness.qual",
|
||||
"org.checkerframework.checker.sqlquotes.qual",
|
||||
"org.checkerframework.checker.tainting.qual",
|
||||
"org.checkerframework.checker.units.qual",
|
||||
"org.checkerframework.common.aliasing.qual",
|
||||
@@ -2051,6 +2314,9 @@
|
||||
"org.json4s:json4s-native_3": [
|
||||
"org.json4s.native"
|
||||
],
|
||||
"org.jspecify:jspecify": [
|
||||
"org.jspecify.annotations"
|
||||
],
|
||||
"org.ow2.asm:asm": [
|
||||
"org.objectweb.asm",
|
||||
"org.objectweb.asm.signature"
|
||||
@@ -2089,7 +2355,6 @@
|
||||
"scala.reflect",
|
||||
"scala.reflect.macros.internal",
|
||||
"scala.runtime",
|
||||
"scala.runtime.java8",
|
||||
"scala.sys",
|
||||
"scala.sys.process",
|
||||
"scala.util",
|
||||
@@ -2102,7 +2367,6 @@
|
||||
"scala.annotation",
|
||||
"scala.annotation.internal",
|
||||
"scala.annotation.unchecked",
|
||||
"scala.compiletime",
|
||||
"scala.compiletime.ops",
|
||||
"scala.compiletime.testing",
|
||||
"scala.deriving",
|
||||
@@ -2455,6 +2719,7 @@
|
||||
"com.github.stephenc.jcip:jcip-annotations",
|
||||
"com.google.android:annotations",
|
||||
"com.google.api.grpc:proto-google-common-protos",
|
||||
"com.google.api:api-common",
|
||||
"com.google.auth:google-auth-library-credentials",
|
||||
"com.google.auth:google-auth-library-oauth2-http",
|
||||
"com.google.auto.value:auto-value",
|
||||
@@ -2469,7 +2734,9 @@
|
||||
"com.google.http-client:google-http-client-gson",
|
||||
"com.google.j2objc:j2objc-annotations",
|
||||
"com.google.protobuf:protobuf-java",
|
||||
"com.google.protobuf:protobuf-java-util",
|
||||
"com.google.re2j:re2j",
|
||||
"com.google.s2a.proto.v2:s2a-proto",
|
||||
"com.google.truth:truth",
|
||||
"com.nimbusds:nimbus-jose-jwt",
|
||||
"com.squareup.okhttp3:okhttp",
|
||||
@@ -2519,12 +2786,12 @@
|
||||
"io.perfmark:perfmark-api",
|
||||
"io.sentry:sentry",
|
||||
"javax.activation:javax.activation-api",
|
||||
"javax.annotation:javax.annotation-api",
|
||||
"javax.xml.bind:jaxb-api",
|
||||
"joda-time:joda-time",
|
||||
"junit:junit",
|
||||
"org.apache.httpcomponents:httpclient",
|
||||
"org.apache.httpcomponents:httpcore",
|
||||
"org.apache.tomcat:annotations-api",
|
||||
"org.checkerframework:checker-qual",
|
||||
"org.codehaus.mojo:animal-sniffer-annotations",
|
||||
"org.hamcrest:hamcrest-core",
|
||||
@@ -2538,6 +2805,7 @@
|
||||
"org.json4s:json4s-jackson-core_3",
|
||||
"org.json4s:json4s-native-core_3",
|
||||
"org.json4s:json4s-native_3",
|
||||
"org.jspecify:jspecify",
|
||||
"org.ow2.asm:asm",
|
||||
"org.reactivestreams:reactive-streams",
|
||||
"org.scala-lang.modules:scala-collection-compat_3",
|
||||
@@ -2638,5 +2906,5 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": "2"
|
||||
"version": "3"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#
|
||||
# Build the SparklePlugin native library for Unity using Bazel
|
||||
#
|
||||
# The SparklePlugin is built in a separate Bazel workspace (sparkle_workspace/)
|
||||
# to avoid rules_swift version conflicts between grpc and rules_apple.
|
||||
#
|
||||
# Usage: build_sparkle_plugin.sh [output_dir]
|
||||
|
||||
set -euo pipefail
|
||||
@@ -11,12 +14,14 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
OUTPUT_DIR="${1:-$PROJECT_ROOT/src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Plugins/macOS}"
|
||||
|
||||
echo "=== Building SparklePlugin with Bazel ==="
|
||||
echo "=== Building SparklePlugin with Bazel (from sparkle_workspace) ==="
|
||||
|
||||
bazel build --config=mactools //src/main/objc/net/eagle0/sparkle:SparklePlugin
|
||||
# Build in the separate sparkle_workspace to avoid dependency conflicts
|
||||
cd "$PROJECT_ROOT/sparkle_workspace"
|
||||
bazel build //:SparklePlugin
|
||||
|
||||
# Get the zip path from bazel
|
||||
ZIP_PATH=$(bazel cquery --config=mactools --output=files //src/main/objc/net/eagle0/sparkle:SparklePlugin 2>/dev/null)
|
||||
ZIP_PATH=$(bazel cquery --output=files //:SparklePlugin 2>/dev/null)
|
||||
|
||||
echo "=== Extracting SparklePlugin.bundle ==="
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
@@ -22,11 +22,31 @@ NC='\033[0m' # No Color
|
||||
MODE="${1:-check}"
|
||||
EXIT_CODE=0
|
||||
|
||||
# Cache for expensive bazel query results
|
||||
LIBRARY_DEPS_CACHE=""
|
||||
MAIN_DEPS_CACHE=""
|
||||
|
||||
# Get deps of library/ (cached)
|
||||
get_library_deps() {
|
||||
if [ -z "$LIBRARY_DEPS_CACHE" ]; then
|
||||
LIBRARY_DEPS_CACHE=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...)' 2>/dev/null || true)
|
||||
fi
|
||||
echo "$LIBRARY_DEPS_CACHE"
|
||||
}
|
||||
|
||||
# Get deps of src/main (cached)
|
||||
get_main_deps() {
|
||||
if [ -z "$MAIN_DEPS_CACHE" ]; then
|
||||
MAIN_DEPS_CACHE=$(bazel query 'deps(//src/main/...)' 2>/dev/null || true)
|
||||
fi
|
||||
echo "$MAIN_DEPS_CACHE"
|
||||
}
|
||||
|
||||
# Rule 1: src/main should not depend on src/test
|
||||
check_main_depends_on_test() {
|
||||
echo -e "${YELLOW}Checking: src/main should not depend on src/test...${NC}"
|
||||
|
||||
violations=$(bazel query 'deps(//src/main/...) intersect //src/test/...' 2>/dev/null || true)
|
||||
violations=$(get_main_deps | grep "^//src/test/" || true)
|
||||
|
||||
if [ -n "$violations" ]; then
|
||||
echo -e "${RED}VIOLATION: src/main depends on src/test:${NC}"
|
||||
@@ -43,7 +63,7 @@ check_main_depends_on_test() {
|
||||
check_library_depends_on_scala_proto() {
|
||||
echo -e "${YELLOW}Checking: library/ should not depend on Scala proto types...${NC}"
|
||||
|
||||
violations=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...) intersect //src/main/protobuf/...' 2>/dev/null | grep "_scala_proto" || true)
|
||||
violations=$(get_library_deps | grep "^//src/main/protobuf/.*_scala_proto$" || true)
|
||||
if [ -z "$violations" ]; then
|
||||
count=0
|
||||
else
|
||||
@@ -65,7 +85,7 @@ check_library_depends_on_scala_proto() {
|
||||
check_library_depends_on_proto_converters() {
|
||||
echo -e "${YELLOW}Checking: library/ should not depend on proto_converters...${NC}"
|
||||
|
||||
violations=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...) intersect //src/main/scala/net/eagle0/eagle/model/proto_converters/...' 2>/dev/null | grep "^//" || true)
|
||||
violations=$(get_library_deps | grep "^//src/main/scala/net/eagle0/eagle/model/proto_converters/" || true)
|
||||
|
||||
if [ -n "$violations" ]; then
|
||||
count=$(echo "$violations" | wc -l | tr -d ' ')
|
||||
@@ -85,7 +105,8 @@ check_library_depends_on_proto_converters() {
|
||||
count_proto_deps() {
|
||||
echo -e "${YELLOW}=== Proto dependency counts ===${NC}"
|
||||
|
||||
scala_proto_results=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...) intersect //src/main/protobuf/...' 2>/dev/null | grep "_scala_proto" || true)
|
||||
library_deps=$(get_library_deps)
|
||||
scala_proto_results=$(echo "$library_deps" | grep "^//src/main/protobuf/.*_scala_proto$" || true)
|
||||
if [ -z "$scala_proto_results" ]; then
|
||||
scala_proto_count=0
|
||||
else
|
||||
@@ -94,7 +115,7 @@ count_proto_deps() {
|
||||
echo "library/ Scala proto deps: $scala_proto_count"
|
||||
|
||||
# C++/Go proto deps are expected (map generation tools)
|
||||
all_proto_count=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...) intersect //src/main/protobuf/...' 2>/dev/null | wc -l | tr -d ' ')
|
||||
all_proto_count=$(echo "$library_deps" | grep -c "^//src/main/protobuf/" || echo "0")
|
||||
echo "library/ all proto deps (includes C++/Go build tools): $all_proto_count"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
7.7.1
|
||||
@@ -0,0 +1,2 @@
|
||||
# Bazel symlinks
|
||||
bazel-*
|
||||
@@ -0,0 +1,28 @@
|
||||
module(name = "sparkle_workspace")
|
||||
|
||||
# Minimal dependencies for building SparklePlugin
|
||||
# This workspace is isolated from the main workspace to avoid
|
||||
# rules_swift version conflicts between grpc and rules_apple
|
||||
|
||||
bazel_dep(name = "apple_support", version = "1.21.1", repo_name = "build_bazel_apple_support")
|
||||
bazel_dep(name = "rules_apple", version = "4.3.3", repo_name = "build_bazel_rules_apple")
|
||||
|
||||
# Register Apple CC toolchain for Objective-C compilation
|
||||
apple_cc_configure = use_extension(
|
||||
"@build_bazel_apple_support//crosstool:setup.bzl",
|
||||
"apple_cc_configure_extension",
|
||||
)
|
||||
use_repo(apple_cc_configure, "local_config_apple_cc")
|
||||
|
||||
# Sparkle framework for macOS auto-updates
|
||||
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
SPARKLE_VERSION = "2.6.4"
|
||||
|
||||
http_archive(
|
||||
name = "sparkle",
|
||||
build_file = "//:external/BUILD.sparkle",
|
||||
sha256 = "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b",
|
||||
strip_prefix = "",
|
||||
url = "https://github.com/sparkle-project/Sparkle/releases/download/%s/Sparkle-%s.tar.xz" % (SPARKLE_VERSION, SPARKLE_VERSION),
|
||||
)
|
||||
Generated
+451
@@ -0,0 +1,451 @@
|
||||
{
|
||||
"lockFileVersion": 13,
|
||||
"registryFileHashes": {
|
||||
"https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953",
|
||||
"https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
|
||||
"https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
|
||||
"https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
|
||||
"https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
|
||||
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
|
||||
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d",
|
||||
"https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
|
||||
"https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74",
|
||||
"https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
|
||||
"https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
|
||||
"https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc3/source.json": "c16a6488fb279ef578da7098e605082d72ed85fc8d843eaae81e7d27d0f4625d",
|
||||
"https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
|
||||
"https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858",
|
||||
"https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e",
|
||||
"https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022",
|
||||
"https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206",
|
||||
"https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4",
|
||||
"https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8",
|
||||
"https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e",
|
||||
"https://bcr.bazel.build/modules/rules_apple/4.3.3/MODULE.bazel": "c5c2c4adeeac5f3f2f9b7f16abfa8be7ffefa596171d0d92bed4cae9ade0a498",
|
||||
"https://bcr.bazel.build/modules/rules_apple/4.3.3/source.json": "3cb1d69c8243ffcc42ecbf84ae8b9cccd7b1e2f091b0aee5a3e9c9a45267f312",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.11/MODULE.bazel": "9f249c5624a4788067b96b8b896be10c7e8b4375dc46f6d8e1e51100113e0992",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.2/source.json": "53fcb09b5816c83ca60d9d7493faf3bfaf410dfc2f15deb52d6ddd146b8d43f0",
|
||||
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
|
||||
"https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
|
||||
"https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86",
|
||||
"https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.5.1/source.json": "db1a77d81b059e0f84985db67a22f3f579a529a86b7997605be3d214a0abe38e",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197",
|
||||
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3",
|
||||
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5",
|
||||
"https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
|
||||
"https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
|
||||
"https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c",
|
||||
"https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a",
|
||||
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
|
||||
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
|
||||
"https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73",
|
||||
"https://bcr.bazel.build/modules/rules_proto/6.0.2/source.json": "17a2e195f56cb28d6bbf763e49973d13890487c6945311ed141e196fb660426d",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.3.0/source.json": "25932f917cd279c7baefa6cb1d3fa8750a7a29de522024449b19af6eab51f4a0",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3",
|
||||
"https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca",
|
||||
"https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd",
|
||||
"https://bcr.bazel.build/modules/rules_swift/2.4.0/source.json": "a6577f57f9febbdc015a01f2a8f3487422032f134d6c61d18ed8e8ca3b9acc7c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
|
||||
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91",
|
||||
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
|
||||
},
|
||||
"selectedYankedVersions": {},
|
||||
"moduleExtensions": {
|
||||
"@@rules_java~//java:rules_java_deps.bzl%compatibility_proxy": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "C4xqrMy1wN4iuTN6Z2eCm94S5XingHhD6uwrIXvCxVI=",
|
||||
"usagesDigest": "pwHZ+26iLgQdwvdZeA5wnAjKnNI3y6XO2VbhOTeo5h8=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"compatibility_proxy": {
|
||||
"bzlFile": "@@rules_java~//java:rules_java_deps.bzl",
|
||||
"ruleClassName": "_compatibility_proxy_repo_rule",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_java~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_kotlin~//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "eecmTsmdIQveoA97hPtH3/Ej/kugbdCI24bhXIXaly8=",
|
||||
"usagesDigest": "aJF6fLy82rR95Ff5CZPAqxNoFgOMLMN5ImfBS0nhnkg=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"com_github_jetbrains_kotlin_git": {
|
||||
"bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl",
|
||||
"ruleClassName": "kotlin_compiler_git_repository",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip"
|
||||
],
|
||||
"sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88"
|
||||
}
|
||||
},
|
||||
"com_github_jetbrains_kotlin": {
|
||||
"bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl",
|
||||
"ruleClassName": "kotlin_capabilities_repository",
|
||||
"attributes": {
|
||||
"git_repository_name": "com_github_jetbrains_kotlin_git",
|
||||
"compiler_version": "1.9.23"
|
||||
}
|
||||
},
|
||||
"com_github_google_ksp": {
|
||||
"bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:ksp.bzl",
|
||||
"ruleClassName": "ksp_compiler_plugin_repository",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip"
|
||||
],
|
||||
"sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d",
|
||||
"strip_version": "1.9.23-1.0.20"
|
||||
}
|
||||
},
|
||||
"com_github_pinterest_ktlint": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_file",
|
||||
"attributes": {
|
||||
"sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985",
|
||||
"urls": [
|
||||
"https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint"
|
||||
],
|
||||
"executable": true
|
||||
}
|
||||
},
|
||||
"rules_android": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806",
|
||||
"strip_prefix": "rules_android-0.1.1",
|
||||
"urls": [
|
||||
"https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_kotlin~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_python~//python/uv:uv.bzl%uv": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "Xpqjnjzy6zZ90Es9Wa888ZLHhn7IsNGbph/e6qoxzw8=",
|
||||
"usagesDigest": "qI5PVlIum/YAnGJg5oXGHzDkMFWt2aNSUZY4G8PBbic=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"uv": {
|
||||
"bzlFile": "@@rules_python~//python/uv/private:uv_toolchains_repo.bzl",
|
||||
"ruleClassName": "uv_toolchains_repo",
|
||||
"attributes": {
|
||||
"toolchain_type": "'@@rules_python~//python/uv:uv_toolchain_type'",
|
||||
"toolchain_names": [
|
||||
"none"
|
||||
],
|
||||
"toolchain_implementations": {
|
||||
"none": "'@@rules_python~//python:none'"
|
||||
},
|
||||
"toolchain_compatible_with": {
|
||||
"none": [
|
||||
"@platforms//:incompatible"
|
||||
]
|
||||
},
|
||||
"toolchain_target_settings": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_python~",
|
||||
"platforms",
|
||||
"platforms"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_swift~//swift:extensions.bzl%non_module_deps": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "l+Zu+SMObRQy3DG2LEw0eGVPkYRnyVj+M1QyR5AAFmM=",
|
||||
"usagesDigest": "l2vIL7SL4tJqHIVLWd78Y/ym+r0II64lmvoX8o+0Bb0=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"com_github_apple_swift_protobuf": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-protobuf/archive/1.20.2.tar.gz"
|
||||
],
|
||||
"sha256": "3fb50bd4d293337f202d917b6ada22f9548a0a0aed9d9a4d791e6fbd8a246ebb",
|
||||
"strip_prefix": "swift-protobuf-1.20.2/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_protobuf/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_grpc_grpc_swift": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/grpc/grpc-swift/archive/1.16.0.tar.gz"
|
||||
],
|
||||
"sha256": "58b60431d0064969f9679411264b82e40a217ae6bd34e17096d92cc4e47556a5",
|
||||
"strip_prefix": "grpc-swift-1.16.0/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_grpc_grpc_swift/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_apple_swift_docc_symbolkit": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-docc-symbolkit/archive/refs/tags/swift-5.10-RELEASE.tar.gz"
|
||||
],
|
||||
"sha256": "de1d4b6940468ddb53b89df7aa1a81323b9712775b0e33e8254fa0f6f7469a97",
|
||||
"strip_prefix": "swift-docc-symbolkit-swift-5.10-RELEASE",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_docc_symbolkit/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_apple_swift_nio": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-nio/archive/2.42.0.tar.gz"
|
||||
],
|
||||
"sha256": "e3304bc3fb53aea74a3e54bd005ede11f6dc357117d9b1db642d03aea87194a0",
|
||||
"strip_prefix": "swift-nio-2.42.0/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_apple_swift_nio_http2": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-nio-http2/archive/1.26.0.tar.gz"
|
||||
],
|
||||
"sha256": "f0edfc9d6a7be1d587e5b403f2d04264bdfae59aac1d74f7d974a9022c6d2b25",
|
||||
"strip_prefix": "swift-nio-http2-1.26.0/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_http2/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_apple_swift_nio_transport_services": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-nio-transport-services/archive/1.15.0.tar.gz"
|
||||
],
|
||||
"sha256": "f3498dafa633751a52b9b7f741f7ac30c42bcbeb3b9edca6d447e0da8e693262",
|
||||
"strip_prefix": "swift-nio-transport-services-1.15.0/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_transport_services/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_apple_swift_nio_extras": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-nio-extras/archive/1.4.0.tar.gz"
|
||||
],
|
||||
"sha256": "4684b52951d9d9937bb3e8ccd6b5daedd777021ef2519ea2f18c4c922843b52b",
|
||||
"strip_prefix": "swift-nio-extras-1.4.0/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_extras/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_apple_swift_log": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-log/archive/1.4.4.tar.gz"
|
||||
],
|
||||
"sha256": "48fe66426c784c0c20031f15dc17faf9f4c9037c192bfac2f643f65cb2321ba0",
|
||||
"strip_prefix": "swift-log-1.4.4/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_log/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_apple_swift_nio_ssl": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-nio-ssl/archive/2.23.0.tar.gz"
|
||||
],
|
||||
"sha256": "4787c63f61dd04d99e498adc3d1a628193387e41efddf8de19b8db04544d016d",
|
||||
"strip_prefix": "swift-nio-ssl-2.23.0/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_ssl/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_apple_swift_collections": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-collections/archive/1.0.4.tar.gz"
|
||||
],
|
||||
"sha256": "d9e4c8a91c60fb9c92a04caccbb10ded42f4cb47b26a212bc6b39cc390a4b096",
|
||||
"strip_prefix": "swift-collections-1.0.4/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_collections/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"com_github_apple_swift_atomics": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/apple/swift-atomics/archive/1.1.0.tar.gz"
|
||||
],
|
||||
"sha256": "1bee7f469f7e8dc49f11cfa4da07182fbc79eab000ec2c17bfdce468c5d276fb",
|
||||
"strip_prefix": "swift-atomics-1.1.0/",
|
||||
"build_file": "@@rules_swift~//third_party:com_github_apple_swift_atomics/BUILD.overlay"
|
||||
}
|
||||
},
|
||||
"build_bazel_rules_swift_index_import": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file": "@@rules_swift~//third_party:build_bazel_rules_swift_index_import/BUILD.overlay",
|
||||
"canonical_id": "index-import-5.8",
|
||||
"urls": [
|
||||
"https://github.com/MobileNativeFoundation/index-import/releases/download/5.8.0.1/index-import.tar.gz"
|
||||
],
|
||||
"sha256": "28c1ffa39d99e74ed70623899b207b41f79214c498c603915aef55972a851a15"
|
||||
}
|
||||
},
|
||||
"build_bazel_rules_swift_local_config": {
|
||||
"bzlFile": "@@rules_swift~//swift/internal:swift_autoconfiguration.bzl",
|
||||
"ruleClassName": "swift_autoconfiguration",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_swift~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# This file marks sparkle_workspace as a separate Bazel workspace.
|
||||
# It prevents Bazel from looking up to the parent directory's workspace.
|
||||
# The actual dependencies are defined in MODULE.bazel (bzlmod).
|
||||
@@ -1,4 +1,3 @@
|
||||
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application")
|
||||
load("//tools:copts.bzl", "COPTS")
|
||||
|
||||
cc_library(
|
||||
@@ -82,11 +81,3 @@ cc_binary(
|
||||
":fixed_action_point_distances_timer",
|
||||
],
|
||||
)
|
||||
|
||||
macos_command_line_application(
|
||||
name = "fixed_action_point_distances_timer_macos_app",
|
||||
minimum_os_version = "10.15",
|
||||
deps = [
|
||||
":fixed_action_point_distances_timer",
|
||||
],
|
||||
)
|
||||
|
||||
+29
-8
@@ -806,7 +806,9 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-create and launch a 7-faction game for first-time users.
|
||||
/// Auto-create and launch a tutorial game for first-time users.
|
||||
/// Tutorial mode: 3 factions (player, King, weak rival), player spawns at province 14 with
|
||||
/// quest.
|
||||
/// </summary>
|
||||
private void AutoCreateFirstGame() {
|
||||
if (fetchedNewGameLeaders == null || fetchedNewGameLeaders.Count == 0) {
|
||||
@@ -818,9 +820,9 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
var randomIndex = new System.Random().Next(fetchedNewGameLeaders.Count);
|
||||
var leaderTextId = fetchedNewGameLeaders[randomIndex].NameTextId;
|
||||
|
||||
// Create a 7-faction game (1 human + 6 AI) - same as lobby default
|
||||
Debug.Log($"[ConnectionHandler] Auto-creating 7-faction game with leader {leaderTextId}");
|
||||
CreateGame(leaderTextId, 1, 7);
|
||||
// Create a tutorial game (1 human, 3 total factions - player, King, and weak rival)
|
||||
Debug.Log($"[ConnectionHandler] Auto-creating tutorial game with leader {leaderTextId}");
|
||||
CreateGame(leaderTextId, 1, 3, isTutorialMode: true);
|
||||
}
|
||||
|
||||
private void StartListeningForLobbyUpdates() {
|
||||
@@ -946,10 +948,25 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
|
||||
private void ConfirmDropGame(long gameId) { _internalDropGame(gameId); }
|
||||
|
||||
/// <summary>
|
||||
/// 3-parameter overload for CreateCallback delegate compatibility.
|
||||
/// </summary>
|
||||
private void CreateGame(string leaderNameTextId, int humanPlayerCount, int totalPlayerCount) {
|
||||
CreateGame(leaderNameTextId, humanPlayerCount, totalPlayerCount, isTutorialMode: false);
|
||||
}
|
||||
|
||||
private void CreateGame(
|
||||
string leaderNameTextId,
|
||||
int humanPlayerCount,
|
||||
int totalPlayerCount,
|
||||
bool isTutorialMode) {
|
||||
// Track multiplayer status for tutorial system
|
||||
TutorialManager.IsMultiplayerGame = humanPlayerCount > 1;
|
||||
var game = _internalCreateGame(leaderNameTextId, humanPlayerCount, totalPlayerCount);
|
||||
var game = _internalCreateGame(
|
||||
leaderNameTextId,
|
||||
humanPlayerCount,
|
||||
totalPlayerCount,
|
||||
isTutorialMode);
|
||||
}
|
||||
|
||||
public void QuitButtonClicked() { Application.Quit(); }
|
||||
@@ -988,8 +1005,11 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
} catch (Exception e) { Console.WriteLine(e.ToString()); }
|
||||
}
|
||||
|
||||
private async Task<bool>
|
||||
_internalCreateGame(string desiredLeaderTextId, int humanPlayerCount, int totalPlayerCount) {
|
||||
private async Task<bool> _internalCreateGame(
|
||||
string desiredLeaderTextId,
|
||||
int humanPlayerCount,
|
||||
int totalPlayerCount,
|
||||
bool isTutorialMode = false) {
|
||||
try {
|
||||
return await _persistentClientConnection.SendUpdateStreamRequestAsync(
|
||||
new UpdateStreamRequest {
|
||||
@@ -997,7 +1017,8 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
new CreateGameRequest {
|
||||
DesiredLeaderTextId = desiredLeaderTextId,
|
||||
TotalPlayerCount = totalPlayerCount,
|
||||
HumanPlayerCount = humanPlayerCount
|
||||
HumanPlayerCount = humanPlayerCount,
|
||||
IsTutorialMode = isTutorialMode
|
||||
}
|
||||
});
|
||||
} catch (WebException e) {
|
||||
|
||||
+26
-30
@@ -45,14 +45,9 @@ namespace eagle {
|
||||
public Toggle demandTributeToggle;
|
||||
public Toggle withdrawToggle;
|
||||
public Toggle safePassageToggle;
|
||||
public ToggleGroup decisionToggleGroup;
|
||||
|
||||
public GameObject advanceImage;
|
||||
public GameObject demandTributeImage;
|
||||
public GameObject withdrawImage;
|
||||
public GameObject safePassageImage;
|
||||
|
||||
public Image goldIcon;
|
||||
public Image foodIcon;
|
||||
public GameObject tributeContainer;
|
||||
public Slider goldSlider;
|
||||
public Slider foodSlider;
|
||||
public TMP_Text goldLabel;
|
||||
@@ -140,21 +135,10 @@ namespace eagle {
|
||||
}
|
||||
}
|
||||
|
||||
advanceToggle.enabled = enableAttack;
|
||||
advanceToggle.gameObject.SetActive(enableAttack);
|
||||
advanceImage.SetActive(enableAttack);
|
||||
|
||||
withdrawToggle.enabled = enableWithdraw;
|
||||
withdrawToggle.gameObject.SetActive(enableWithdraw);
|
||||
withdrawImage.SetActive(enableWithdraw);
|
||||
|
||||
demandTributeToggle.enabled = enableDemandTribute;
|
||||
demandTributeToggle.gameObject.SetActive(enableDemandTribute);
|
||||
demandTributeImage.SetActive(enableDemandTribute);
|
||||
|
||||
safePassageToggle.enabled = enableSafePassage;
|
||||
safePassageToggle.gameObject.SetActive(enableSafePassage);
|
||||
safePassageImage.SetActive(enableSafePassage);
|
||||
ConfigureToggle(advanceToggle, enableAttack);
|
||||
ConfigureToggle(withdrawToggle, enableWithdraw);
|
||||
ConfigureToggle(demandTributeToggle, enableDemandTribute);
|
||||
ConfigureToggle(safePassageToggle, enableSafePassage);
|
||||
|
||||
goldSlider.minValue = 0;
|
||||
goldSlider.maxValue = maxGoldTribute;
|
||||
@@ -202,14 +186,7 @@ namespace eagle {
|
||||
foodLabel.text = ((int)newValue).ToString();
|
||||
}
|
||||
|
||||
private void EnableSliders(bool newEnabled) {
|
||||
goldIcon.gameObject.SetActive(newEnabled);
|
||||
foodIcon.gameObject.SetActive(newEnabled);
|
||||
goldSlider.gameObject.SetActive(newEnabled);
|
||||
foodSlider.gameObject.SetActive(newEnabled);
|
||||
goldLabel.gameObject.SetActive(newEnabled);
|
||||
foodLabel.gameObject.SetActive(newEnabled);
|
||||
}
|
||||
private void EnableSliders(bool newEnabled) { tributeContainer.SetActive(newEnabled); }
|
||||
|
||||
public void ToggleChanged(bool value) {
|
||||
bool enableSliders =
|
||||
@@ -218,5 +195,24 @@ namespace eagle {
|
||||
|
||||
EnableSliders(enableSliders);
|
||||
}
|
||||
|
||||
private const float DisabledAlpha = 0.35f;
|
||||
|
||||
private void ConfigureToggle(Toggle toggle, bool available) {
|
||||
if (toggle == null) return;
|
||||
|
||||
toggle.gameObject.SetActive(true);
|
||||
toggle.interactable = available;
|
||||
|
||||
if (!available) { toggle.isOn = false; }
|
||||
|
||||
toggle.group = available ? decisionToggleGroup : null;
|
||||
|
||||
var canvasGroup = toggle.GetComponent<CanvasGroup>();
|
||||
if (canvasGroup == null) {
|
||||
canvasGroup = toggle.gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
canvasGroup.alpha = available ? 1f : DisabledAlpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-3
@@ -23,6 +23,7 @@ namespace eagle {
|
||||
|
||||
public Toggle AdvanceToggle;
|
||||
public Toggle WithdrawToggle;
|
||||
public ToggleGroup decisionToggleGroup;
|
||||
|
||||
private FreeForAllDecisionAvailableCommand FreeForAllDecisionCommand =>
|
||||
_availableCommand.FreeForAllDecisionCommand;
|
||||
@@ -63,10 +64,32 @@ namespace eagle {
|
||||
row.Origin = _model.Provinces[army.OriginProvinceId].Name;
|
||||
});
|
||||
|
||||
AdvanceToggle.isOn = true;
|
||||
AdvanceToggle.enabled = true;
|
||||
WithdrawToggle.enabled = FreeForAllDecisionCommand.AvailableDecisions.Any(
|
||||
bool withdrawAvailable = FreeForAllDecisionCommand.AvailableDecisions.Any(
|
||||
x => x.SealedValueCase == AttackDecisionType.SealedValueOneofCase.Withdraw);
|
||||
|
||||
ConfigureToggle(AdvanceToggle, true);
|
||||
ConfigureToggle(WithdrawToggle, withdrawAvailable);
|
||||
|
||||
AdvanceToggle.isOn = true;
|
||||
}
|
||||
|
||||
private const float DisabledAlpha = 0.35f;
|
||||
|
||||
private void ConfigureToggle(Toggle toggle, bool available) {
|
||||
if (toggle == null) return;
|
||||
|
||||
toggle.gameObject.SetActive(true);
|
||||
toggle.interactable = available;
|
||||
|
||||
if (!available) { toggle.isOn = false; }
|
||||
|
||||
toggle.group = available ? decisionToggleGroup : null;
|
||||
|
||||
var canvasGroup = toggle.GetComponent<CanvasGroup>();
|
||||
if (canvasGroup == null) {
|
||||
canvasGroup = toggle.gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
canvasGroup.alpha = available ? 1f : DisabledAlpha;
|
||||
}
|
||||
|
||||
// Use this for initialization
|
||||
|
||||
+41
-43
@@ -15,12 +15,7 @@ namespace eagle {
|
||||
public Toggle ExileToggle;
|
||||
public Toggle ExecuteToggle;
|
||||
public Toggle ReturnToggle;
|
||||
|
||||
public RawImage RecruitIcon;
|
||||
public RawImage ImprisonIcon;
|
||||
public RawImage ExileIcon;
|
||||
public RawImage ExecuteIcon;
|
||||
public RawImage ReturnIcon;
|
||||
public ToggleGroup decisionToggleGroup;
|
||||
|
||||
public HeroDetailsController heroDetails;
|
||||
|
||||
@@ -95,51 +90,54 @@ namespace eagle {
|
||||
|
||||
messageUpdater.TextId = SelectedHeroWithOptions.MessageId;
|
||||
|
||||
// The order here is important: the last available toggle in this list
|
||||
// will be on by default
|
||||
ConditionallyEnable(
|
||||
ExecuteToggle,
|
||||
ExecuteIcon,
|
||||
CapturedHeroOption.ExecuteCapturedHeroOption);
|
||||
ConditionallyEnable(ExileToggle, ExileIcon, CapturedHeroOption.ExileCapturedHeroOption);
|
||||
ConditionallyEnable(
|
||||
ReturnToggle,
|
||||
ReturnIcon,
|
||||
CapturedHeroOption.ReturnCapturedHeroOption);
|
||||
ConditionallyEnable(
|
||||
ImprisonToggle,
|
||||
ImprisonIcon,
|
||||
CapturedHeroOption.ImprisonCapturedHeroOption);
|
||||
ConditionallyEnable(
|
||||
var options = SelectedHeroWithOptions.Options;
|
||||
ConfigureToggle(
|
||||
RecruitToggle,
|
||||
RecruitIcon,
|
||||
CapturedHeroOption.RecruitCapturedHeroOption);
|
||||
options.Contains(CapturedHeroOption.RecruitCapturedHeroOption));
|
||||
ConfigureToggle(
|
||||
ImprisonToggle,
|
||||
options.Contains(CapturedHeroOption.ImprisonCapturedHeroOption));
|
||||
ConfigureToggle(
|
||||
ExileToggle,
|
||||
options.Contains(CapturedHeroOption.ExileCapturedHeroOption));
|
||||
ConfigureToggle(
|
||||
ExecuteToggle,
|
||||
options.Contains(CapturedHeroOption.ExecuteCapturedHeroOption));
|
||||
ConfigureToggle(
|
||||
ReturnToggle,
|
||||
options.Contains(CapturedHeroOption.ReturnCapturedHeroOption));
|
||||
|
||||
// Select first available option (priority order: Recruit > Imprison > Exile > Execute >
|
||||
// Return)
|
||||
if (options.Contains(CapturedHeroOption.RecruitCapturedHeroOption))
|
||||
RecruitToggle.isOn = true;
|
||||
else if (options.Contains(CapturedHeroOption.ImprisonCapturedHeroOption))
|
||||
ImprisonToggle.isOn = true;
|
||||
else if (options.Contains(CapturedHeroOption.ExileCapturedHeroOption))
|
||||
ExileToggle.isOn = true;
|
||||
else if (options.Contains(CapturedHeroOption.ExecuteCapturedHeroOption))
|
||||
ExecuteToggle.isOn = true;
|
||||
else if (options.Contains(CapturedHeroOption.ReturnCapturedHeroOption))
|
||||
ReturnToggle.isOn = true;
|
||||
}
|
||||
|
||||
private bool ConditionallyEnable(Toggle toggle, CapturedHeroOption option) {
|
||||
bool enable = SelectedHeroWithOptions.Options.Contains(option);
|
||||
toggle.gameObject.SetActive(enable);
|
||||
private const float DisabledAlpha = 0.35f;
|
||||
|
||||
if (enable) {
|
||||
ExecuteToggle.isOn = false;
|
||||
ExileToggle.isOn = false;
|
||||
ImprisonToggle.isOn = false;
|
||||
RecruitToggle.isOn = false;
|
||||
ReturnToggle.isOn = false;
|
||||
private void ConfigureToggle(Toggle toggle, bool available) {
|
||||
if (toggle == null) return;
|
||||
|
||||
toggle.isOn = true;
|
||||
} else
|
||||
toggle.isOn = false;
|
||||
toggle.gameObject.SetActive(true);
|
||||
toggle.interactable = available;
|
||||
|
||||
return enable;
|
||||
}
|
||||
if (!available) { toggle.isOn = false; }
|
||||
|
||||
private void ConditionallyEnable(Toggle toggle, RawImage img, CapturedHeroOption option) {
|
||||
bool enable = ConditionallyEnable(toggle, option);
|
||||
toggle.group = available ? decisionToggleGroup : null;
|
||||
|
||||
Color color = img.color;
|
||||
color.a = enable ? 1.0f : 0.25f;
|
||||
img.color = color;
|
||||
var canvasGroup = toggle.GetComponent<CanvasGroup>();
|
||||
if (canvasGroup == null) {
|
||||
canvasGroup = toggle.gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
canvasGroup.alpha = available ? 1f : DisabledAlpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+77
-53
@@ -21,12 +21,7 @@ namespace eagle {
|
||||
public Toggle ExileToggle;
|
||||
public Toggle ExecuteToggle;
|
||||
public Toggle ReturnToggle;
|
||||
|
||||
public RawImage ReleaseIcon;
|
||||
public RawImage MoveIcon;
|
||||
public RawImage ExileIcon;
|
||||
public RawImage ExecuteIcon;
|
||||
public RawImage ReturnIcon;
|
||||
public ToggleGroup decisionToggleGroup;
|
||||
|
||||
public TMP_Dropdown moveToDropdown;
|
||||
|
||||
@@ -76,6 +71,33 @@ namespace eagle {
|
||||
public override string CommitWarningText =>
|
||||
$"This will create a treaty with {_model.FactionName(SelectedHero.FactionId.Value)}";
|
||||
|
||||
private List<PrisonerManagementOption> MoveOptions =>
|
||||
SelectedPrisonerToManage.AvailableOptions
|
||||
.Where(opt => opt.SealedValueCase ==
|
||||
PrisonerManagementOption.SealedValueOneofCase.Move)
|
||||
.ToList();
|
||||
|
||||
protected override ProvinceId? TargetedProvince {
|
||||
get {
|
||||
if (MoveToggle.isOn && moveToDropdown.value >= 0 &&
|
||||
moveToDropdown.value < MoveOptions.Count) {
|
||||
return MoveOptions[moveToDropdown.value].Move.ToProvinceId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set {
|
||||
if (!value.HasValue) return;
|
||||
|
||||
var moveOptions = MoveOptions;
|
||||
int index = moveOptions.FindIndex(opt => opt.Move.ToProvinceId == value.Value);
|
||||
if (index != -1) {
|
||||
MoveToggle.isOn = true;
|
||||
moveToDropdown.value = index;
|
||||
moveToDropdown.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PrisonerManagementOption SelectedOption {
|
||||
get {
|
||||
if (ExecuteToggle.isOn)
|
||||
@@ -143,28 +165,43 @@ namespace eagle {
|
||||
|
||||
MessageText.text = GetQuestMessagesForPrisoner(SelectedHero.Id);
|
||||
|
||||
// The order here is important: the last available toggle in this list
|
||||
// will be on by default
|
||||
ConditionallyEnable(
|
||||
ExecuteToggle,
|
||||
ExecuteIcon,
|
||||
PrisonerManagementOption.SealedValueOneofCase.Execute);
|
||||
ConditionallyEnable(
|
||||
ExileToggle,
|
||||
ExileIcon,
|
||||
PrisonerManagementOption.SealedValueOneofCase.Exile);
|
||||
ConditionallyEnable(
|
||||
ReturnToggle,
|
||||
ReturnIcon,
|
||||
PrisonerManagementOption.SealedValueOneofCase.Return);
|
||||
ConditionallyEnable(
|
||||
MoveToggle,
|
||||
MoveIcon,
|
||||
PrisonerManagementOption.SealedValueOneofCase.Move);
|
||||
ConditionallyEnable(
|
||||
ReleaseToggle,
|
||||
ReleaseIcon,
|
||||
PrisonerManagementOption.SealedValueOneofCase.Release);
|
||||
var options = SelectedPrisonerToManage.AvailableOptions;
|
||||
bool hasRelease = options.Any(
|
||||
opt => opt.SealedValueCase ==
|
||||
PrisonerManagementOption.SealedValueOneofCase.Release);
|
||||
bool hasMove = options.Any(
|
||||
opt => opt.SealedValueCase ==
|
||||
PrisonerManagementOption.SealedValueOneofCase.Move);
|
||||
bool hasExile = options.Any(
|
||||
opt => opt.SealedValueCase ==
|
||||
PrisonerManagementOption.SealedValueOneofCase.Exile);
|
||||
bool hasExecute = options.Any(
|
||||
opt => opt.SealedValueCase ==
|
||||
PrisonerManagementOption.SealedValueOneofCase.Execute);
|
||||
bool hasReturn = options.Any(
|
||||
opt => opt.SealedValueCase ==
|
||||
PrisonerManagementOption.SealedValueOneofCase.Return);
|
||||
|
||||
ConfigureToggle(ReleaseToggle, hasRelease);
|
||||
ConfigureToggle(MoveToggle, hasMove);
|
||||
ConfigureToggle(ExileToggle, hasExile);
|
||||
ConfigureToggle(ExecuteToggle, hasExecute);
|
||||
ConfigureToggle(ReturnToggle, hasReturn);
|
||||
|
||||
// Select first available option (priority order: Release > Move > Exile > Execute >
|
||||
// Return)
|
||||
if (hasRelease) ReleaseToggle.isOn = true;
|
||||
else if (hasMove)
|
||||
MoveToggle.isOn = true;
|
||||
else if (hasExile)
|
||||
ExileToggle.isOn = true;
|
||||
else if (hasExecute)
|
||||
ExecuteToggle.isOn = true;
|
||||
else if (hasReturn)
|
||||
ReturnToggle.isOn = true;
|
||||
|
||||
// Update dropdown visibility based on selected toggle
|
||||
moveToDropdown.gameObject.SetActive(MoveToggle.isOn);
|
||||
}
|
||||
|
||||
private string GetQuestMessagesForPrisoner(HeroId prisonerId) {
|
||||
@@ -225,36 +262,23 @@ namespace eagle {
|
||||
: "A free hero";
|
||||
}
|
||||
|
||||
private bool ConditionallyEnable(
|
||||
Toggle toggle,
|
||||
PrisonerManagementOption.SealedValueOneofCase option) {
|
||||
bool enable = SelectedPrisonerToManage.AvailableOptions.Any(
|
||||
opt => opt.SealedValueCase == option);
|
||||
toggle.gameObject.SetActive(enable);
|
||||
private const float DisabledAlpha = 0.35f;
|
||||
|
||||
if (enable) {
|
||||
ExecuteToggle.isOn = false;
|
||||
ExileToggle.isOn = false;
|
||||
MoveToggle.isOn = false;
|
||||
ReleaseToggle.isOn = false;
|
||||
ReturnToggle.isOn = false;
|
||||
private void ConfigureToggle(Toggle toggle, bool available) {
|
||||
if (toggle == null) return;
|
||||
|
||||
toggle.isOn = true;
|
||||
} else
|
||||
toggle.isOn = false;
|
||||
toggle.gameObject.SetActive(true);
|
||||
toggle.interactable = available;
|
||||
|
||||
return enable;
|
||||
}
|
||||
if (!available) { toggle.isOn = false; }
|
||||
|
||||
private void ConditionallyEnable(
|
||||
Toggle toggle,
|
||||
RawImage img,
|
||||
PrisonerManagementOption.SealedValueOneofCase option) {
|
||||
bool enable = ConditionallyEnable(toggle, option);
|
||||
toggle.group = available ? decisionToggleGroup : null;
|
||||
|
||||
Color color = img.color;
|
||||
color.a = enable ? 1.0f : 0.25f;
|
||||
img.color = color;
|
||||
var canvasGroup = toggle.GetComponent<CanvasGroup>();
|
||||
if (canvasGroup == null) {
|
||||
canvasGroup = toggle.gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
canvasGroup.alpha = available ? 1f : DisabledAlpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,24 @@ namespace eagle {
|
||||
case SealedValueOneofCase.FightBeastsAloneQuest: return "Fight Beasts Alone";
|
||||
case SealedValueOneofCase.DevelopProvincesQuest: return "Develop Provinces";
|
||||
case SealedValueOneofCase.MobilizeProvincesQuest: return "Mobilize Provinces";
|
||||
case SealedValueOneofCase.BattalionDiversityQuest: return "Battalion Diversity";
|
||||
case SealedValueOneofCase.ReleaseAllPrisonersQuest: return "Release All Prisoners";
|
||||
case SealedValueOneofCase.SwearBrotherhoodWithHeroQuest: return "Swear Brotherhood";
|
||||
case SealedValueOneofCase.BetrayAllyQuest: return "Betray Ally";
|
||||
case SealedValueOneofCase.BorderSecurityQuest: return "Border Security";
|
||||
case SealedValueOneofCase.WinBattleOutnumberedQuest:
|
||||
return "Win Battle Outnumbered";
|
||||
case SealedValueOneofCase.WinBattlesQuest: return "Win Battles";
|
||||
case SealedValueOneofCase.ApprehendOutlawQuest: return "Apprehend Outlaw";
|
||||
case SealedValueOneofCase.StartBlizzardQuest: return "Start Blizzard";
|
||||
case SealedValueOneofCase.StartEpidemicQuest: return "Start Epidemic";
|
||||
case SealedValueOneofCase.SpendOnFeastsQuest: return "Host Feasts";
|
||||
case SealedValueOneofCase.SendSuppliesQuest: return "Send Supplies";
|
||||
case SealedValueOneofCase.RestProvinceQuest: return "Rest Province";
|
||||
case SealedValueOneofCase.ReconProvincesQuest: return "Recon Provinces";
|
||||
case SealedValueOneofCase.RepairDevastationQuest: return "Repair Devastation";
|
||||
case SealedValueOneofCase.ReconSpecificProvincesQuest:
|
||||
return "Recon Specific Provinces";
|
||||
}
|
||||
|
||||
throw new ArgumentException($"unknown quest {quest}");
|
||||
|
||||
+37
-1
@@ -77,6 +77,10 @@ namespace eagle {
|
||||
public Button gameIdButton;
|
||||
public Button nextActiveProvinceButton;
|
||||
|
||||
[Header("Tutorial Narrative")]
|
||||
public NarrativeScreenController narrativeScreenController;
|
||||
private bool _narrativeScreensShown = false;
|
||||
|
||||
[Header("Commands")]
|
||||
public MouseHandler mouseHandlerPrefab;
|
||||
public GameObject commandPanel;
|
||||
@@ -524,6 +528,29 @@ namespace eagle {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows tutorial narrative screens before gameplay.
|
||||
/// After all screens are dismissed, proceeds to battle if one is waiting.
|
||||
/// </summary>
|
||||
private void ShowNarrativeScreens(IList<TutorialNarrativeScreen> screens) {
|
||||
if (narrativeScreenController == null) {
|
||||
Debug.LogWarning("EagleGameController: NarrativeScreenController not assigned");
|
||||
OnNarrativeComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
narrativeScreenController.Show(screens, OnNarrativeComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when narrative screens are complete.
|
||||
/// Proceeds to tutorial battle or normal game flow.
|
||||
/// </summary>
|
||||
private void OnNarrativeComplete() {
|
||||
// If there's a battle waiting, go directly to it
|
||||
if (Model != null && Model.RunningShardokGameModels.Count > 0) { GoToBattle(); }
|
||||
}
|
||||
|
||||
void SetHeaderString() {
|
||||
var date = Model.CurrentDate;
|
||||
if (date != null) {
|
||||
@@ -583,9 +610,18 @@ namespace eagle {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for tutorial narrative screens on first model load
|
||||
if (oldModel == null && !_narrativeScreensShown &&
|
||||
Model.PendingNarrativeScreens != null && Model.PendingNarrativeScreens.Count > 0) {
|
||||
_narrativeScreensShown = true;
|
||||
ShowNarrativeScreens(Model.PendingNarrativeScreens);
|
||||
return; // Wait for narrative to complete before proceeding
|
||||
}
|
||||
|
||||
// Start onboarding after first model update (UI panels are now populated)
|
||||
// Skip onboarding if we had narrative screens (tutorial battle mode)
|
||||
if (oldModel == null && TutorialManager.Instance != null &&
|
||||
!TutorialManager.Instance.State.OnboardingCompleted) {
|
||||
!TutorialManager.Instance.State.OnboardingCompleted && !_narrativeScreensShown) {
|
||||
TutorialManager.Instance.StartOnboarding();
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,11 @@ namespace eagle {
|
||||
public List<ProvinceId> ProvincesForFaction(FactionId factionId);
|
||||
|
||||
public List<ChronicleEntry> ChronicleEntries { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Narrative screens to display before gameplay (tutorial intro).
|
||||
/// </summary>
|
||||
public IList<TutorialNarrativeScreen> PendingNarrativeScreens { get; }
|
||||
}
|
||||
|
||||
public class GameModelUpdater : IClientConnectionSubscriber, IGameStateProvider {
|
||||
@@ -201,6 +206,9 @@ namespace eagle {
|
||||
|
||||
public List<ChronicleEntry> ChronicleEntries { get; set; }
|
||||
|
||||
public IList<TutorialNarrativeScreen> PendingNarrativeScreens =>
|
||||
GsView.PendingNarrativeScreens;
|
||||
|
||||
public List<AvailableCommand> AvailableCommandsForProvince(ProvinceId pid) {
|
||||
if (AvailableCommandsByProvince.TryGetValue(pid, out var cmds)) {
|
||||
return cmds.Commands.ToList();
|
||||
|
||||
+24
-1
@@ -9,6 +9,11 @@ namespace eagle {
|
||||
public class FreeHeroesTableController : MonoBehaviour {
|
||||
public GameObject panel;
|
||||
|
||||
[Header("Hero Backstory Popup")]
|
||||
public GameObject popupPanel;
|
||||
public HeroDetailsController popupPanelDetailsController;
|
||||
public GeneratedTextUpdater popupPanelBackstory;
|
||||
|
||||
private ProvinceView _currentProvince;
|
||||
public ProvinceView CurrentProvince {
|
||||
get => _currentProvince;
|
||||
@@ -73,6 +78,21 @@ namespace eagle {
|
||||
UpdateUnaffiliatedHeroSelections();
|
||||
}
|
||||
|
||||
public void LongHoverRowChanged(int? newIndex) {
|
||||
if (popupPanel == null) return;
|
||||
|
||||
if (newIndex is {} index) {
|
||||
var heroId = UnaffiliatedHeroes.ElementAt(index).HeroId;
|
||||
if (Model.Heroes.TryGetValue(heroId, out var hero)) {
|
||||
popupPanelDetailsController.SetHero(hero, Model);
|
||||
popupPanelBackstory.TextId = hero.BackstoryTextId;
|
||||
popupPanel.SetActive(true);
|
||||
}
|
||||
} else {
|
||||
popupPanel.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setUpUnaffiliatedHeroesTable() {
|
||||
unaffiliatedHeroesTable.RowCount = UnaffiliatedHeroCount;
|
||||
|
||||
@@ -88,7 +108,10 @@ namespace eagle {
|
||||
});
|
||||
}
|
||||
|
||||
void Start() { panel.SetActive(Model != null); }
|
||||
void Start() {
|
||||
panel.SetActive(Model != null);
|
||||
if (popupPanel != null) { popupPanel.SetActive(false); }
|
||||
}
|
||||
|
||||
private bool HasProvinceInfo =>
|
||||
(CurrentProvince != null && CurrentProvince.FullInfo != null);
|
||||
|
||||
+40
-10
@@ -1064,13 +1064,12 @@ namespace eagle {
|
||||
|
||||
private async void HandleStreamingCall() {
|
||||
LogFlow("HandleStreamingCall STARTED");
|
||||
// Capture thread token outside try block so it's accessible in catch blocks
|
||||
CancellationToken threadToken;
|
||||
lock (this) { threadToken = _currentThreadToken; }
|
||||
try {
|
||||
AsyncDuplexStreamingCall<UpdateStreamRequest, UpdateStreamResponse> sc;
|
||||
CancellationToken threadToken;
|
||||
lock (this) {
|
||||
sc = _streamingCall;
|
||||
threadToken = _currentThreadToken;
|
||||
}
|
||||
lock (this) { sc = _streamingCall; }
|
||||
|
||||
var waitStartTime = DateTime.UtcNow;
|
||||
while (sc != null && !_currentThreadToken.IsCancellationRequested &&
|
||||
@@ -1256,10 +1255,14 @@ namespace eagle {
|
||||
case StatusCode.Cancelled:
|
||||
LogConnectionEvent("disconnect", "Cancelled", StatusCode.Cancelled);
|
||||
_remoteEagleClientLogger.LogLine($"Got exception {e}");
|
||||
// Only check the main cancellation token, not the thread-specific one
|
||||
// The thread token gets cancelled during normal reconnection and
|
||||
// shouldn't prevent it
|
||||
// If main app is shutting down, don't reconnect
|
||||
if (_cancellationToken.IsCancellationRequested) { return; }
|
||||
// If thread token was cancelled, something else (Connect() or
|
||||
// CheckForIdleTimeout) already initiated reconnect
|
||||
if (threadToken.IsCancellationRequested) {
|
||||
LogFlow("Cancelled: skipping reconnect (thread token cancelled)");
|
||||
return;
|
||||
}
|
||||
|
||||
ScheduleReconnect("Cancelled");
|
||||
|
||||
@@ -1308,6 +1311,13 @@ namespace eagle {
|
||||
MarkAllShardokGamesForResync();
|
||||
CancelAllPendingSubscriptionAcks();
|
||||
|
||||
// If thread token was cancelled, something else (like CheckForIdleTimeout)
|
||||
// already disposed the call and initiated reconnect. Don't schedule another.
|
||||
if (threadToken.IsCancellationRequested) {
|
||||
LogFlow("ObjectDisposed: skipping reconnect (thread token cancelled)");
|
||||
return;
|
||||
}
|
||||
|
||||
ScheduleReconnect("ObjectDisposed");
|
||||
} catch (Exception e) {
|
||||
// Catch-all for any unexpected exceptions. Without this, exceptions in an
|
||||
@@ -1321,6 +1331,12 @@ namespace eagle {
|
||||
MarkAllShardokGamesForResync();
|
||||
CancelAllPendingSubscriptionAcks();
|
||||
|
||||
// If thread token was cancelled, something else already initiated reconnect
|
||||
if (threadToken.IsCancellationRequested) {
|
||||
LogFlow($"UnexpectedException: skipping reconnect (thread token cancelled)");
|
||||
return;
|
||||
}
|
||||
|
||||
ScheduleReconnect($"UnexpectedException-{e.GetType().Name}");
|
||||
}
|
||||
|
||||
@@ -1342,11 +1358,18 @@ namespace eagle {
|
||||
/// Called when connection drops to unblock any waiting subscription tasks.
|
||||
/// </summary>
|
||||
private void CancelAllPendingSubscriptionAcks() {
|
||||
List<TaskCompletionSource<SubscriptionAck>> tcsList;
|
||||
lock (_pendingSubscriptionAcks) {
|
||||
foreach (var tcs in _pendingSubscriptionAcks.Values) { tcs.TrySetCanceled(); }
|
||||
|
||||
// Snapshot values and clear dictionary BEFORE cancelling to avoid
|
||||
// "Collection was modified" exception. TrySetCanceled() can trigger
|
||||
// synchronous continuations that re-enter this lock (reentrant) and
|
||||
// call Remove() while we're still iterating.
|
||||
tcsList = _pendingSubscriptionAcks.Values.ToList();
|
||||
_pendingSubscriptionAcks.Clear();
|
||||
}
|
||||
|
||||
// Cancel outside lock to avoid reentrancy issues
|
||||
foreach (var tcs in tcsList) { tcs.TrySetCanceled(); }
|
||||
}
|
||||
|
||||
private void StartIdleCheckTimer() {
|
||||
@@ -1406,6 +1429,13 @@ namespace eagle {
|
||||
$"[IDLE_TIMEOUT] No messages received in {idleTime:F1}s (max={MaxIdleSeconds}s), forcing reconnect");
|
||||
LogConnectionEvent("idle_timeout", $"idle_time={idleTime:F1}s");
|
||||
|
||||
// Cancel thread token BEFORE disposing the call. This prevents
|
||||
// HandleStreamingCall from scheduling its own reconnect when it
|
||||
// catches the ObjectDisposedException from the disposed call.
|
||||
// Without this, we get a race: both CheckForIdleTimeout and
|
||||
// HandleStreamingCall try to reconnect, leading to state confusion.
|
||||
lock (this) { _threadCancellationTokenSource?.Cancel(); }
|
||||
|
||||
var toCancel = _streamingCall;
|
||||
if (toCancel != null) {
|
||||
toCancel.Dispose();
|
||||
|
||||
+60
-37
@@ -33,7 +33,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 1571934834221197146}
|
||||
m_Father: {fileID: 13469637781584011}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -107,20 +106,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_TextWrappingMode: 0
|
||||
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
|
||||
@@ -190,7 +192,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4705461973370478516}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -284,7 +285,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 3691368921260708877}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -379,7 +379,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 409974592681485098}
|
||||
m_Father: {fileID: 13469637781584011}
|
||||
m_RootOrder: 5
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -453,20 +452,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_TextWrappingMode: 0
|
||||
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
|
||||
@@ -537,7 +539,6 @@ RectTransform:
|
||||
- {fileID: 8905938057092819695}
|
||||
- {fileID: 4705461973370478516}
|
||||
m_Father: {fileID: 6123654153464171526}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -623,7 +624,6 @@ RectTransform:
|
||||
- {fileID: 6100877526870815819}
|
||||
- {fileID: 5909934266303401356}
|
||||
m_Father: {fileID: 3691368921260708877}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -687,7 +687,7 @@ GameObject:
|
||||
- component: {fileID: 1571934834221197146}
|
||||
- component: {fileID: 2314867006400566416}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -706,7 +706,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 3080737000364722689}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -754,7 +753,6 @@ RectTransform:
|
||||
- {fileID: 13469637781584011}
|
||||
- {fileID: 6123654154484763561}
|
||||
m_Father: {fileID: 6123654153464171526}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -818,7 +816,7 @@ GameObject:
|
||||
- component: {fileID: 7991069078033516274}
|
||||
- component: {fileID: 3709131301875944532}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -837,7 +835,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1392924472004276419}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -863,7 +860,7 @@ GameObject:
|
||||
- component: {fileID: 2443631178969516575}
|
||||
- component: {fileID: 8616701387306228446}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -882,7 +879,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4523044671415516144}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -908,7 +904,7 @@ GameObject:
|
||||
- component: {fileID: 7240827529169758877}
|
||||
- component: {fileID: 4768305797197202640}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -927,7 +923,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 107607736864462239}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -975,7 +970,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 7240827529169758877}
|
||||
m_Father: {fileID: 13469637781584011}
|
||||
m_RootOrder: 3
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -1049,20 +1043,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_TextWrappingMode: 0
|
||||
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
|
||||
@@ -1115,6 +1112,7 @@ GameObject:
|
||||
- component: {fileID: 7083373564964204245}
|
||||
- component: {fileID: 1611950019350954987}
|
||||
- component: {fileID: 7571393407091744620}
|
||||
- component: {fileID: 839825112791048530}
|
||||
m_Layer: 0
|
||||
m_Name: Unaffiliated Hero Row Prefab
|
||||
m_TagString: Untagged
|
||||
@@ -1137,7 +1135,6 @@ RectTransform:
|
||||
- {fileID: 3691368921260708877}
|
||||
- {fileID: 7084089121705905319}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
@@ -1224,7 +1221,6 @@ MonoBehaviour:
|
||||
conLabel: {fileID: 663501340024978645}
|
||||
archeryImage: {fileID: 6373503588455539415}
|
||||
fireImage: {fileID: 7060661334717899661}
|
||||
extinguishImage: {fileID: 0}
|
||||
--- !u!114 &1611950019350954987
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1283,6 +1279,30 @@ MonoBehaviour:
|
||||
m_ChildScaleWidth: 0
|
||||
m_ChildScaleHeight: 0
|
||||
m_ReverseArrangement: 0
|
||||
--- !u!114 &839825112791048530
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6123654153464171525}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 73953ac09da84b2d96cb90a2ab37ed79, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::TableRowHoverDetector
|
||||
onHoverBegin:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
onHoverEnd:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
onLongHoverBegin:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
onLongHoverEnd:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &6123654153502973338
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1316,7 +1336,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 6123654154611511663}
|
||||
m_Father: {fileID: 13469637781584011}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -1390,20 +1409,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
@@ -1452,7 +1474,7 @@ GameObject:
|
||||
- component: {fileID: 6123654154152031040}
|
||||
- component: {fileID: 6123654154152031042}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -1471,7 +1493,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 6123654154484763561}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -1518,7 +1539,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 6123654154152031040}
|
||||
m_Father: {fileID: 7084089121705905319}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -1593,20 +1613,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 25
|
||||
m_enableWordWrapping: 1
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
@@ -1635,7 +1658,7 @@ GameObject:
|
||||
- component: {fileID: 6123654154611511663}
|
||||
- component: {fileID: 6123654154611511657}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -1654,7 +1677,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 6123654153502973339}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -1702,7 +1724,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 7991069078033516274}
|
||||
m_Father: {fileID: 13469637781584011}
|
||||
m_RootOrder: 2
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -1776,20 +1797,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_TextWrappingMode: 0
|
||||
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
|
||||
@@ -1860,7 +1884,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 2443631178969516575}
|
||||
m_Father: {fileID: 13469637781584011}
|
||||
m_RootOrder: 4
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -1934,20 +1957,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_TextWrappingMode: 0
|
||||
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
|
||||
@@ -1996,7 +2022,7 @@ GameObject:
|
||||
- component: {fileID: 409974592681485098}
|
||||
- component: {fileID: 3950145718335139402}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -2015,7 +2041,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 6859780550973135875}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -2062,7 +2087,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4705461973370478516}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -2161,7 +2185,6 @@ RectTransform:
|
||||
- {fileID: 4523044671415516144}
|
||||
- {fileID: 6859780550973135875}
|
||||
m_Father: {fileID: 7084089121705905319}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
|
||||
+123
-6
@@ -147,6 +147,24 @@ namespace eagle {
|
||||
model);
|
||||
break;
|
||||
|
||||
case SealedValueOneofCase.SwearBrotherhoodWithHeroQuest:
|
||||
var brotherhoodDetails = quest.Details.SwearBrotherhoodWithHeroQuest;
|
||||
SetDynamicHeroQuestText(
|
||||
brotherhoodDetails.TargetHeroId,
|
||||
(heroName) => $"Swear brotherhood with {heroName}",
|
||||
"Swear brotherhood with a hero",
|
||||
model);
|
||||
break;
|
||||
|
||||
case SealedValueOneofCase.ApprehendOutlawQuest:
|
||||
var apprehendDetails = quest.Details.ApprehendOutlawQuest;
|
||||
SetDynamicHeroQuestText(
|
||||
apprehendDetails.OutlawHeroId,
|
||||
(heroName) => $"Apprehend the outlaw {heroName}",
|
||||
"Apprehend an outlaw",
|
||||
model);
|
||||
break;
|
||||
|
||||
default:
|
||||
// For all other quests, use the static method
|
||||
questLabel.text = ShortQuestString(quest, model);
|
||||
@@ -327,18 +345,117 @@ namespace eagle {
|
||||
var details = quest.Details.DevelopProvincesQuest;
|
||||
var targetMonths = quest.ComponentCount;
|
||||
var currentMonths = quest.ComponentsFulfilled;
|
||||
var progressText =
|
||||
currentMonths > 0 ? $" ({currentMonths}/{targetMonths} months)" : "";
|
||||
return $"Maintain {details.TargetProvinceCount} provinces with Develop orders for {targetMonths} months{progressText}";
|
||||
var currentProvinces =
|
||||
model.PlayerId.HasValue
|
||||
? model.ProvincesForFaction(model.PlayerId.Value)
|
||||
.Count(pid => model.Provinces[pid]
|
||||
.FullInfo?.ProvinceOrders ==
|
||||
ProvinceOrderType.Develop)
|
||||
: 0;
|
||||
var statusText = currentProvinces >= details.TargetProvinceCount
|
||||
? "currently on target"
|
||||
: "not currently meeting";
|
||||
return $"Maintain {details.TargetProvinceCount} provinces with Develop orders for {targetMonths} months ({currentMonths}/{targetMonths} months, {statusText})";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.MobilizeProvincesQuest: {
|
||||
var details = quest.Details.MobilizeProvincesQuest;
|
||||
var targetMonths = quest.ComponentCount;
|
||||
var currentMonths = quest.ComponentsFulfilled;
|
||||
var progressText =
|
||||
currentMonths > 0 ? $" ({currentMonths}/{targetMonths} months)" : "";
|
||||
return $"Maintain {details.TargetProvinceCount} provinces with Mobilize orders for {targetMonths} months{progressText}";
|
||||
var currentProvinces =
|
||||
model.PlayerId.HasValue
|
||||
? model.ProvincesForFaction(model.PlayerId.Value)
|
||||
.Count(pid => model.Provinces[pid]
|
||||
.FullInfo?.ProvinceOrders ==
|
||||
ProvinceOrderType.Mobilize)
|
||||
: 0;
|
||||
var statusText = currentProvinces >= details.TargetProvinceCount
|
||||
? "currently on target"
|
||||
: "not currently meeting";
|
||||
return $"Maintain {details.TargetProvinceCount} provinces with Mobilize orders for {targetMonths} months ({currentMonths}/{targetMonths} months, {statusText})";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.BattalionDiversityQuest:
|
||||
return "Have 3 different battalion types, each near full strength";
|
||||
|
||||
case SealedValueOneofCase.ReleaseAllPrisonersQuest:
|
||||
return "Release all prisoners (don't execute, move, or trade them)";
|
||||
|
||||
case SealedValueOneofCase.SwearBrotherhoodWithHeroQuest:
|
||||
return "Swear brotherhood with a hero";
|
||||
|
||||
case SealedValueOneofCase.BetrayAllyQuest: {
|
||||
var details = quest.Details.BetrayAllyQuest;
|
||||
return $"Break alliance with {model.FactionName(details.TargetFactionId)}";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.BorderSecurityQuest: {
|
||||
var details = quest.Details.BorderSecurityQuest;
|
||||
var provinceName = model.Provinces[details.TargetProvinceId].Name;
|
||||
return $"Control all provinces bordering {provinceName}";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.WinBattleOutnumberedQuest:
|
||||
return "Win a battle while outnumbered";
|
||||
|
||||
case SealedValueOneofCase.WinBattlesQuest: {
|
||||
var targetBattles = quest.ComponentCount;
|
||||
var wonBattles = quest.ComponentsFulfilled;
|
||||
return $"Win {targetBattles} battles ({wonBattles}/{targetBattles})";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.ApprehendOutlawQuest: {
|
||||
// This case is handled by SetQuestText with dynamic updates, this is fallback
|
||||
// only
|
||||
var details = quest.Details.ApprehendOutlawQuest;
|
||||
var heroName = GetHeroName(model, details.OutlawHeroId);
|
||||
return $"Apprehend the outlaw {heroName}";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.StartBlizzardQuest: {
|
||||
var details = quest.Details.StartBlizzardQuest;
|
||||
var provinceName = model.Provinces[details.ProvinceId].Name;
|
||||
return $"Start a blizzard in {provinceName}";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.StartEpidemicQuest: {
|
||||
var details = quest.Details.StartEpidemicQuest;
|
||||
var provinceName = model.Provinces[details.ProvinceId].Name;
|
||||
return $"Start an epidemic in {provinceName}";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.SpendOnFeastsQuest: {
|
||||
var details = quest.Details.SpendOnFeastsQuest;
|
||||
return $"Host feasts costing {details.TotalGold} gold ({quest.ComponentsFulfilled}/{quest.ComponentCount})";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.SendSuppliesQuest: {
|
||||
var details = quest.Details.SendSuppliesQuest;
|
||||
var provinceName = model.Provinces[details.TargetProvinceId].Name;
|
||||
return $"Send {details.TotalFood} food to {provinceName} ({quest.ComponentsFulfilled}/{quest.ComponentCount})";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.RestProvinceQuest: {
|
||||
var details = quest.Details.RestProvinceQuest;
|
||||
var provinceName = model.Provinces[details.TargetProvinceId].Name;
|
||||
return $"Keep {provinceName} at rest for {quest.ComponentCount} months ({quest.ComponentsFulfilled}/{quest.ComponentCount})";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.ReconProvincesQuest: {
|
||||
var details = quest.Details.ReconProvincesQuest;
|
||||
return $"Recon {details.TargetProvinceCount} provinces ({quest.ComponentsFulfilled}/{quest.ComponentCount})";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.ReconSpecificProvincesQuest: {
|
||||
var details = quest.Details.ReconSpecificProvincesQuest;
|
||||
var provinceNames =
|
||||
details.TargetProvinceIds.Select(pid => model.Provinces[pid].Name);
|
||||
return $"Recon {string.Join(", ", provinceNames)} ({quest.ComponentsFulfilled}/{quest.ComponentCount})";
|
||||
}
|
||||
|
||||
case SealedValueOneofCase.RepairDevastationQuest: {
|
||||
var details = quest.Details.RepairDevastationQuest;
|
||||
return $"Repair {details.TargetDevastationRepaired} devastation ({quest.ComponentsFulfilled}/{quest.ComponentCount})";
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -239,6 +239,8 @@ public class SettingsPanelController : MonoBehaviour {
|
||||
bugReportPanel.Configure(
|
||||
gameModelProvider: () => eagleGameController?.GameModel,
|
||||
gameIdProvider: () => eagleGameController?.ModelUpdater?.GameId);
|
||||
// Pass settings panel reference so it can be hidden during screenshot capture
|
||||
bugReportPanel.settingsPanel = panel;
|
||||
bugReportPanel.Show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using HexCell = HexGrid.CellGeometry;
|
||||
@@ -38,12 +39,19 @@ public class HexMesh : MonoBehaviour {
|
||||
|
||||
hexMesh.RecalculateNormals();
|
||||
|
||||
if (needsSetSharedMesh) {
|
||||
meshCollider.sharedMesh = hexMesh;
|
||||
if (needsSetSharedMesh && triangles.Count > 0) {
|
||||
// Defer mesh collider assignment to next frame to avoid PhysX cooking
|
||||
// errors during initial setup
|
||||
StartCoroutine(SetMeshColliderNextFrame());
|
||||
needsSetSharedMesh = false;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator SetMeshColliderNextFrame() {
|
||||
yield return null; // Wait one frame
|
||||
if (meshCollider != null && hexMesh != null) { meshCollider.sharedMesh = hexMesh; }
|
||||
}
|
||||
|
||||
private void Triangulate(HexCell cell) {
|
||||
Vector3 center = new Vector3(
|
||||
cell.AnchoredPosition.x,
|
||||
|
||||
+71
-7
@@ -74,6 +74,21 @@ namespace Shardok {
|
||||
public Button endTurnButton;
|
||||
public TMP_Text locationNameText;
|
||||
public TMP_Text roundInfoText;
|
||||
|
||||
[Header("Layout Variants")]
|
||||
public GameObject topRowContainer;
|
||||
public GameObject leftColumnContainer;
|
||||
public TMP_Text turnStatusLabelAlt;
|
||||
public TMP_Text locationNameTextAlt;
|
||||
public TMP_Text roundInfoTextAlt;
|
||||
public TMP_Text connectionStatusLabelAlt;
|
||||
|
||||
private double _aspectRatio;
|
||||
private const double WideScreenThreshold = 2.0;
|
||||
private bool IsWideScreen => _aspectRatio > WideScreenThreshold;
|
||||
private int _lastWidth;
|
||||
private int _lastHeight;
|
||||
|
||||
public TMP_Text gameOverText;
|
||||
public Canvas gameOverCanvas;
|
||||
public AudioSource audioClipSource;
|
||||
@@ -244,7 +259,10 @@ namespace Shardok {
|
||||
commandWarningPanel.SetActive(false);
|
||||
}
|
||||
|
||||
void Update() { HandleButton(); }
|
||||
void Update() {
|
||||
HandleButton();
|
||||
UpdateLayoutForAspectRatio();
|
||||
}
|
||||
|
||||
public void EndTurn() {
|
||||
endTurnButton.interactable = false;
|
||||
@@ -530,7 +548,7 @@ namespace Shardok {
|
||||
|
||||
if (Model.GameStatus != null &&
|
||||
(Model.GameStatus.State == GameStatus.Types.State.Victory)) {
|
||||
turnStatusLabel.text = "Game Over!";
|
||||
SetTurnStatusText("Game Over!");
|
||||
|
||||
gameOverText.text = Model.GameStatus.Description;
|
||||
gameOverCanvas.gameObject.SetActive(true);
|
||||
@@ -539,7 +557,7 @@ namespace Shardok {
|
||||
endTurnButton.interactable = true;
|
||||
} else if (Model.GameStatus != null && Model.MyTurn) {
|
||||
gameOverCanvas.gameObject.SetActive(false);
|
||||
turnStatusLabel.text = "Your Turn";
|
||||
SetTurnStatusText("Your Turn");
|
||||
|
||||
if (Model.InSetUp) {
|
||||
endTurnButton.GetComponentInChildren<TMP_Text>().text = "Commit";
|
||||
@@ -574,19 +592,20 @@ namespace Shardok {
|
||||
|
||||
SelectAppropriateDefaultCommand();
|
||||
} else {
|
||||
turnStatusLabel.text = $"{Model.CurrentPlayerName}'s Turn";
|
||||
SetTurnStatusText($"{Model.CurrentPlayerName}'s Turn");
|
||||
}
|
||||
|
||||
locationNameText.text = Model.LocationName;
|
||||
SetLocationNameText(Model.LocationName);
|
||||
|
||||
if (Model.History.Count > 0) {
|
||||
string monthString = new DateTime(777, Model.Month, 1)
|
||||
.ToString("MMMM", CultureInfo.InvariantCulture);
|
||||
roundInfoText.text = $"{monthString} {Model.CurrentRound}";
|
||||
string roundText = $"{monthString} {Model.CurrentRound}";
|
||||
Weather weather = Model.Weather;
|
||||
if (weather != null) {
|
||||
roundInfoText.text += ", " + ProtoExtensions.WeatherToString(weather);
|
||||
roundText += ", " + ProtoExtensions.WeatherToString(weather);
|
||||
}
|
||||
SetRoundInfoText(roundText);
|
||||
|
||||
// Update weather visual effects
|
||||
if (weatherEffectAnimator != null) { weatherEffectAnimator.SetWeather(weather); }
|
||||
@@ -2192,5 +2211,50 @@ namespace Shardok {
|
||||
int gridRow = Model.Map.RowCount - 1 - mapCoords.Row;
|
||||
return gridRow * Model.Map.ColumnCount + mapCoords.Column;
|
||||
}
|
||||
|
||||
#region Layout Variant Helpers
|
||||
|
||||
private void SetTurnStatusText(string text) {
|
||||
turnStatusLabel.text = text;
|
||||
if (turnStatusLabelAlt != null) turnStatusLabelAlt.text = text;
|
||||
}
|
||||
|
||||
private void SetLocationNameText(string text) {
|
||||
locationNameText.text = text;
|
||||
if (locationNameTextAlt != null) locationNameTextAlt.text = text;
|
||||
}
|
||||
|
||||
private void SetRoundInfoText(string text) {
|
||||
roundInfoText.text = text;
|
||||
if (roundInfoTextAlt != null) roundInfoTextAlt.text = text;
|
||||
}
|
||||
|
||||
private void SetConnectionStatusText(string text) {
|
||||
connectionStatusLabel.text = text;
|
||||
if (connectionStatusLabelAlt != null) connectionStatusLabelAlt.text = text;
|
||||
}
|
||||
|
||||
private void UpdateLayoutForAspectRatio() {
|
||||
var newWidth = Screen.width;
|
||||
var newHeight = Screen.height;
|
||||
|
||||
if (newWidth == _lastWidth && newHeight == _lastHeight) { return; }
|
||||
|
||||
_lastWidth = newWidth;
|
||||
_lastHeight = newHeight;
|
||||
_aspectRatio = (double)newWidth / newHeight;
|
||||
|
||||
if (IsWideScreen) {
|
||||
// Widescreen: use left column layout
|
||||
if (topRowContainer != null) topRowContainer.SetActive(false);
|
||||
if (leftColumnContainer != null) leftColumnContainer.SetActive(true);
|
||||
} else {
|
||||
// Normal/narrow: use top row layout
|
||||
if (topRowContainer != null) topRowContainer.SetActive(true);
|
||||
if (leftColumnContainer != null) leftColumnContainer.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+4
-3
@@ -1,19 +1,20 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e41d0485129e4d0da6d21b966709f4b
|
||||
guid: 744385d5812c6480f89b169c3604104f
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 6
|
||||
serializedVersion: 8
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
preloadAudioData: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
+36
-9
File diff suppressed because one or more lines are too long
+58
-1
@@ -34,6 +34,9 @@ namespace Eagle0.Tutorial {
|
||||
// Register Shardok (tactical battle) tutorials
|
||||
RegisterShardokTutorials(registry);
|
||||
|
||||
// Register tutorial battle tutorials (scripted first-time battle)
|
||||
RegisterTutorialBattleTutorials(registry);
|
||||
|
||||
// Register lobby tutorials (pre-game)
|
||||
RegisterLobbyTutorials(registry);
|
||||
}
|
||||
@@ -562,7 +565,8 @@ namespace Eagle0.Tutorial {
|
||||
"command_issue_orders",
|
||||
"Issue Orders",
|
||||
"Set standing orders for your provinces.\n\n" +
|
||||
"Orders determine how provinces behave each turn - prioritize military, economy, or defense. You can also designate a Focus Province for special attention.");
|
||||
"Orders determine how provinces behave each turn - prioritize military, economy, or defense.\n\n" +
|
||||
"You can also designate a <b>Focus Province</b> where your vassals will send excess supplies.");
|
||||
registry.RegisterTutorial(issueOrders, "command_panel_IssueOrdersCommand");
|
||||
|
||||
// Control Weather command
|
||||
@@ -1101,6 +1105,59 @@ namespace Eagle0.Tutorial {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
// ========== TUTORIAL BATTLE TUTORIALS ==========
|
||||
// These appear during the scripted tutorial battle for first-time players.
|
||||
|
||||
private static void RegisterTutorialBattleTutorials(TutorialTriggerRegistry registry) {
|
||||
// Tutorial battle started - special intro for the scripted battle
|
||||
var battleStarted = CreateSingleStepTutorial(
|
||||
"tutorial_battle_started",
|
||||
"Defend Your Home!",
|
||||
"The traitor Ikhaan Tarn attacks your province with his veterans. " +
|
||||
"Your small garrison is outnumbered, but you must hold the line.\n\n" +
|
||||
"Fight bravely! Position your units wisely and look for opportunities " +
|
||||
"to strike at the enemy's weak points.\n\n" +
|
||||
"<color=#FFCC00>Help is on the way...</color>",
|
||||
TutorialDisplayMode.Modal);
|
||||
registry.RegisterTutorial(battleStarted, "tutorial_battle_started");
|
||||
|
||||
// Tutorial enemy fled - when Tarn flees the battle
|
||||
var enemyFled = CreateSingleStepTutorial(
|
||||
"tutorial_enemy_fled",
|
||||
"The Enemy Retreats!",
|
||||
"Ikhaan Tarn has fled the battlefield! Your brave defense has " +
|
||||
"bought precious time.\n\n" +
|
||||
"Perhaps he realized that you are more formidable than he expected, " +
|
||||
"or perhaps something else has scared him off...",
|
||||
TutorialDisplayMode.Modal);
|
||||
registry.RegisterTutorial(enemyFled, "tutorial_enemy_fled");
|
||||
|
||||
// Tutorial reinforcements arrived - when heroes arrive to help
|
||||
var reinforcementsArrived = CreateSingleStepTutorial(
|
||||
"tutorial_reinforcements_arrived",
|
||||
"Allies Arrive!",
|
||||
"Reinforcements have arrived just in time! " +
|
||||
"<b>Elena Fyar</b>, <b>Hedrick</b>, and <b>The Boulder</b> have joined " +
|
||||
"your forces.\n\n" +
|
||||
"These heroes heard of your stand against the traitor and rode hard " +
|
||||
"to aid you. They will now fight under your banner.\n\n" +
|
||||
"<color=#00FF00>Check your reserves - new units are available!</color>",
|
||||
TutorialDisplayMode.Modal);
|
||||
registry.RegisterTutorial(reinforcementsArrived, "tutorial_reinforcements_arrived");
|
||||
|
||||
// Tutorial battle victory - when the player wins the tutorial battle
|
||||
var battleVictory = CreateSingleStepTutorial(
|
||||
"tutorial_battle_victory",
|
||||
"Victory!",
|
||||
"You have defended your home against the traitor's attack!\n\n" +
|
||||
"The heroes who came to your aid have sworn to serve you. " +
|
||||
"With their help, you can now begin building your power and " +
|
||||
"eventually bring Ikhaan Tarn to justice.\n\n" +
|
||||
"<b>Your journey as a ruler begins now.</b>",
|
||||
TutorialDisplayMode.Modal);
|
||||
registry.RegisterTutorial(battleVictory, "tutorial_battle_victory");
|
||||
}
|
||||
|
||||
// ========== PRE-GAME TUTORIALS ==========
|
||||
// These appear before the user enters a game (sign-in, lobby).
|
||||
|
||||
|
||||
+8
@@ -558,6 +558,14 @@ namespace Eagle0.Tutorial {
|
||||
case ActionType.CrossWaterFailed:
|
||||
OnGameEvent("terrain_water_encountered", result);
|
||||
break;
|
||||
|
||||
// Tutorial battle events
|
||||
case ActionType.TutorialEnemyFled:
|
||||
OnGameEvent("tutorial_enemy_fled", result);
|
||||
break;
|
||||
case ActionType.TutorialReinforcementsArrived:
|
||||
OnGameEvent("tutorial_reinforcements_arrived", result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Net.Eagle0.Eagle.Views;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Eagle0.Tutorial {
|
||||
/// <summary>
|
||||
/// Controller for displaying narrative screens before tutorial battles.
|
||||
/// Shows story screens with title, body text, and optional hero portraits.
|
||||
/// Chains through multiple screens before proceeding to gameplay.
|
||||
/// </summary>
|
||||
public class NarrativeScreenController : MonoBehaviour {
|
||||
[Header("Content")]
|
||||
[Tooltip("Title text")]
|
||||
public TextMeshProUGUI TitleText;
|
||||
|
||||
[Tooltip("Body text")]
|
||||
public TextMeshProUGUI BodyText;
|
||||
|
||||
[Tooltip("Optional hero portrait or scene image")]
|
||||
public Image SceneImage;
|
||||
|
||||
[Tooltip("Container for the scene image")]
|
||||
public GameObject SceneImageContainer;
|
||||
|
||||
[Header("Buttons")]
|
||||
[Tooltip("Continue button")]
|
||||
public Button ContinueButton;
|
||||
|
||||
[Tooltip("Text on continue button")]
|
||||
public TextMeshProUGUI ContinueButtonText;
|
||||
|
||||
[Tooltip("Skip button to skip all narrative")]
|
||||
public Button SkipButton;
|
||||
|
||||
[Header("Progress")]
|
||||
[Tooltip("Progress indicator (e.g., dots or page numbers)")]
|
||||
public TextMeshProUGUI ProgressText;
|
||||
|
||||
[Header("Background")]
|
||||
[Tooltip("Background panel container")]
|
||||
public GameObject PanelContainer;
|
||||
|
||||
[Tooltip("Fade overlay for transitions")]
|
||||
public CanvasGroup FadeOverlay;
|
||||
|
||||
[Header("Animation")]
|
||||
[Tooltip("Animator for screen transitions")]
|
||||
public Animator ScreenAnimator;
|
||||
|
||||
[Tooltip("Fade duration in seconds")]
|
||||
public float FadeDuration = 0.3f;
|
||||
|
||||
// Internal state
|
||||
private List<TutorialNarrativeScreen> _screens;
|
||||
private int _currentScreenIndex;
|
||||
private Action _onComplete;
|
||||
private bool _isTransitioning;
|
||||
|
||||
private void Awake() {
|
||||
if (ContinueButton != null) { ContinueButton.onClick.AddListener(OnContinueClicked); }
|
||||
if (SkipButton != null) { SkipButton.onClick.AddListener(OnSkipClicked); }
|
||||
|
||||
Hide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the narrative screen sequence.
|
||||
/// </summary>
|
||||
/// <param name="screens">List of narrative screens to display</param>
|
||||
/// <param name="onComplete">Callback when all screens are dismissed</param>
|
||||
public void Show(IEnumerable<TutorialNarrativeScreen> screens, Action onComplete) {
|
||||
_screens = new List<TutorialNarrativeScreen>(screens);
|
||||
_onComplete = onComplete;
|
||||
_currentScreenIndex = 0;
|
||||
|
||||
if (_screens.Count == 0) {
|
||||
_onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
gameObject.SetActive(true);
|
||||
if (PanelContainer != null) { PanelContainer.SetActive(true); }
|
||||
|
||||
DisplayCurrentScreen();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays the current screen in the sequence.
|
||||
/// </summary>
|
||||
private void DisplayCurrentScreen() {
|
||||
if (_currentScreenIndex >= _screens.Count) {
|
||||
CompleteNarrative();
|
||||
return;
|
||||
}
|
||||
|
||||
var screen = _screens[_currentScreenIndex];
|
||||
|
||||
// Set title
|
||||
if (TitleText != null) { TitleText.text = screen.Title ?? ""; }
|
||||
|
||||
// Set body text
|
||||
if (BodyText != null) {
|
||||
BodyText.text = screen.BodyText ?? "";
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(BodyText.GetComponent<RectTransform>());
|
||||
}
|
||||
|
||||
// Set scene image if provided
|
||||
if (SceneImageContainer != null) {
|
||||
bool hasImage = !string.IsNullOrEmpty(screen.ImagePath);
|
||||
SceneImageContainer.SetActive(hasImage);
|
||||
|
||||
if (hasImage && SceneImage != null) {
|
||||
// Load image from resources
|
||||
var sprite = Resources.Load<Sprite>(screen.ImagePath);
|
||||
if (sprite != null) {
|
||||
SceneImage.sprite = sprite;
|
||||
} else {
|
||||
Debug.LogWarning(
|
||||
$"NarrativeScreenController: Could not load image at '{screen.ImagePath}'");
|
||||
SceneImageContainer.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update button text based on whether this is the last screen
|
||||
if (ContinueButtonText != null) {
|
||||
bool isLastScreen = _currentScreenIndex >= _screens.Count - 1;
|
||||
ContinueButtonText.text = isLastScreen ? "Begin Battle" : "Continue";
|
||||
}
|
||||
|
||||
// Update progress indicator
|
||||
if (ProgressText != null) {
|
||||
if (_screens.Count > 1) {
|
||||
ProgressText.text = $"{_currentScreenIndex + 1} / {_screens.Count}";
|
||||
ProgressText.gameObject.SetActive(true);
|
||||
} else {
|
||||
ProgressText.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger show animation if available
|
||||
if (ScreenAnimator != null) { ScreenAnimator.SetTrigger("Show"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when Continue button is clicked.
|
||||
/// </summary>
|
||||
private void OnContinueClicked() {
|
||||
if (_isTransitioning) return;
|
||||
|
||||
_currentScreenIndex++;
|
||||
|
||||
if (_currentScreenIndex >= _screens.Count) {
|
||||
CompleteNarrative();
|
||||
} else {
|
||||
// Animate transition to next screen
|
||||
StartTransition(() => DisplayCurrentScreen());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when Skip button is clicked.
|
||||
/// </summary>
|
||||
private void OnSkipClicked() {
|
||||
if (_isTransitioning) return;
|
||||
CompleteNarrative();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a transition animation between screens.
|
||||
/// </summary>
|
||||
private void StartTransition(Action onTransitionComplete) {
|
||||
if (FadeOverlay == null || FadeDuration <= 0) {
|
||||
onTransitionComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
_isTransitioning = true;
|
||||
StartCoroutine(TransitionCoroutine(onTransitionComplete));
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator TransitionCoroutine(Action onComplete) {
|
||||
// Fade out
|
||||
float elapsed = 0f;
|
||||
while (elapsed < FadeDuration) {
|
||||
elapsed += Time.deltaTime;
|
||||
FadeOverlay.alpha = Mathf.Lerp(0f, 1f, elapsed / FadeDuration);
|
||||
yield return null;
|
||||
}
|
||||
FadeOverlay.alpha = 1f;
|
||||
|
||||
// Call the transition action
|
||||
onComplete?.Invoke();
|
||||
|
||||
// Fade in
|
||||
elapsed = 0f;
|
||||
while (elapsed < FadeDuration) {
|
||||
elapsed += Time.deltaTime;
|
||||
FadeOverlay.alpha = Mathf.Lerp(1f, 0f, elapsed / FadeDuration);
|
||||
yield return null;
|
||||
}
|
||||
FadeOverlay.alpha = 0f;
|
||||
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the narrative sequence and invokes the callback.
|
||||
/// </summary>
|
||||
private void CompleteNarrative() {
|
||||
var callback = _onComplete;
|
||||
Hide();
|
||||
callback?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the narrative screen.
|
||||
/// </summary>
|
||||
public void Hide() {
|
||||
if (ScreenAnimator != null) { ScreenAnimator.SetTrigger("Hide"); }
|
||||
if (PanelContainer != null) { PanelContainer.SetActive(false); }
|
||||
gameObject.SetActive(false);
|
||||
|
||||
_screens = null;
|
||||
_onComplete = null;
|
||||
_currentScreenIndex = 0;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the controller is currently showing screens.
|
||||
/// </summary>
|
||||
public bool IsShowing => gameObject.activeSelf && _screens != null && _screens.Count > 0;
|
||||
}
|
||||
}
|
||||
+118
-8
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using eagle;
|
||||
using Newtonsoft.Json;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
@@ -24,6 +26,13 @@ namespace common.GUIUtils {
|
||||
public Button cancelButton;
|
||||
public TextMeshProUGUI statusText;
|
||||
|
||||
[Header("Screenshot")]
|
||||
public Button screenshotButton;
|
||||
public TextMeshProUGUI screenshotStatusText;
|
||||
public GameObject screenshotPreviewContainer; // Container with LayoutElement
|
||||
public RawImage screenshotPreview;
|
||||
public GameObject settingsPanel;
|
||||
|
||||
// Optional reference to game model for state capture
|
||||
private Func<IGameModel> _gameModelProvider;
|
||||
private Func<long?> _gameIdProvider;
|
||||
@@ -32,6 +41,8 @@ namespace common.GUIUtils {
|
||||
private const string WebhookUrlKey = "bugReportWebhookUrl";
|
||||
|
||||
private bool _isSubmitting = false;
|
||||
private bool _hasPrePopulatedException = false;
|
||||
private byte[] _capturedScreenshot;
|
||||
|
||||
void Awake() {
|
||||
panel.SetActive(false);
|
||||
@@ -43,9 +54,11 @@ namespace common.GUIUtils {
|
||||
if (blockerButton == null) { blockerButton = modalBlocker.AddComponent<Button>(); }
|
||||
blockerButton.onClick.AddListener(Hide);
|
||||
}
|
||||
|
||||
submitButton.onClick.AddListener(OnSubmitClicked);
|
||||
cancelButton.onClick.AddListener(OnCancelClicked);
|
||||
|
||||
if (screenshotButton != null)
|
||||
screenshotButton.onClick.AddListener(OnScreenshotButtonClicked);
|
||||
}
|
||||
|
||||
void Update() {
|
||||
@@ -75,17 +88,47 @@ namespace common.GUIUtils {
|
||||
public static string GetWebhookUrl() { return PlayerPrefs.GetString(WebhookUrlKey, ""); }
|
||||
|
||||
public void Show() {
|
||||
descriptionInput.text = "";
|
||||
// Auto-include recent exceptions (unless already pre-populated from error popup)
|
||||
string prePopulated = null;
|
||||
if (!_hasPrePopulatedException &&
|
||||
ErrorHandler.Instance?.HasRecentExceptions(TimeSpan.FromMinutes(5)) == true) {
|
||||
var exceptionText = ErrorHandler.Instance.GetRecentExceptionsText();
|
||||
prePopulated = $"Recent errors:\n\n{exceptionText}\n\nDescription:\n";
|
||||
}
|
||||
_hasPrePopulatedException = false; // Reset for next time
|
||||
|
||||
ShowMainPanel(prePopulated);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the bug report panel with pre-populated exception info.
|
||||
/// Used when clicking "Report Bug" from the error popup.
|
||||
/// </summary>
|
||||
public void ShowWithExceptionInfo(string exceptionInfo) {
|
||||
_hasPrePopulatedException = true; // Skip auto-include since we have specific exception
|
||||
ShowMainPanel($"Exception occurred:\n\n{exceptionInfo}\n\nAdditional details:\n");
|
||||
}
|
||||
|
||||
private void ShowMainPanel(string prePopulatedText = null) {
|
||||
if (!string.IsNullOrEmpty(prePopulatedText)) {
|
||||
descriptionInput.text = prePopulatedText;
|
||||
} else {
|
||||
descriptionInput.text = "";
|
||||
}
|
||||
statusText.text = "";
|
||||
_isSubmitting = false;
|
||||
_capturedScreenshot = null;
|
||||
if (screenshotStatusText != null) screenshotStatusText.text = "";
|
||||
if (screenshotPreviewContainer != null) screenshotPreviewContainer.SetActive(false);
|
||||
UpdateButtonState();
|
||||
|
||||
panel.SetActive(true);
|
||||
modalBlocker.SetActive(true);
|
||||
|
||||
// Focus the input field
|
||||
// Focus the input field and move caret to end
|
||||
descriptionInput.Select();
|
||||
descriptionInput.ActivateInputField();
|
||||
descriptionInput.caretPosition = descriptionInput.text.Length;
|
||||
}
|
||||
|
||||
public void Hide() {
|
||||
@@ -95,6 +138,47 @@ namespace common.GUIUtils {
|
||||
|
||||
private void OnCancelClicked() { Hide(); }
|
||||
|
||||
public void OnScreenshotButtonClicked() { StartCoroutine(CaptureScreenshotCoroutine()); }
|
||||
|
||||
private IEnumerator CaptureScreenshotCoroutine() {
|
||||
if (screenshotStatusText != null) screenshotStatusText.text = "Capturing...";
|
||||
|
||||
// Hide ALL UI that might be overlaying the game
|
||||
panel.SetActive(false);
|
||||
modalBlocker.SetActive(false);
|
||||
if (settingsPanel != null) settingsPanel.SetActive(false);
|
||||
|
||||
// Wait for render to complete without UI
|
||||
yield return new WaitForEndOfFrame();
|
||||
|
||||
// Capture
|
||||
Texture2D screenshot =
|
||||
new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
|
||||
screenshot.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
|
||||
screenshot.Apply();
|
||||
|
||||
// Use PNG to avoid compression artifacts
|
||||
_capturedScreenshot = screenshot.EncodeToPNG();
|
||||
Destroy(screenshot);
|
||||
|
||||
// Restore UI
|
||||
if (settingsPanel != null) settingsPanel.SetActive(true);
|
||||
panel.SetActive(true);
|
||||
modalBlocker.SetActive(true);
|
||||
|
||||
if (screenshotStatusText != null)
|
||||
screenshotStatusText.text =
|
||||
$"Screenshot captured ({_capturedScreenshot.Length / 1024}KB)";
|
||||
|
||||
// Show preview thumbnail
|
||||
if (screenshotPreview != null) {
|
||||
var previewTex = new Texture2D(2, 2);
|
||||
previewTex.LoadImage(_capturedScreenshot);
|
||||
screenshotPreview.texture = previewTex;
|
||||
if (screenshotPreviewContainer != null) screenshotPreviewContainer.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSubmitClicked() {
|
||||
if (_isSubmitting) return;
|
||||
|
||||
@@ -212,6 +296,11 @@ namespace common.GUIUtils {
|
||||
webhookUrl.Contains("discord.com") || webhookUrl.Contains("discordapp.com");
|
||||
bool isSlack = webhookUrl.Contains("slack.com");
|
||||
|
||||
// Use multipart form for Discord with screenshot
|
||||
if (isDiscord && _capturedScreenshot != null) {
|
||||
return await SendDiscordWithScreenshot(webhookUrl, report, _capturedScreenshot);
|
||||
}
|
||||
|
||||
string json;
|
||||
if (isDiscord) {
|
||||
json = BuildDiscordPayload(report);
|
||||
@@ -241,6 +330,29 @@ namespace common.GUIUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool>
|
||||
SendDiscordWithScreenshot(string webhookUrl, BugReport report, byte[] screenshot) {
|
||||
var form = new List<IMultipartFormSection>();
|
||||
form.Add(new MultipartFormFileSection(
|
||||
"file",
|
||||
screenshot,
|
||||
"screenshot.png",
|
||||
"image/png"));
|
||||
form.Add(new MultipartFormDataSection("payload_json", BuildDiscordPayload(report)));
|
||||
|
||||
using var request = UnityWebRequest.Post(webhookUrl, form);
|
||||
var operation = request.SendWebRequest();
|
||||
|
||||
while (!operation.isDone) { await Task.Yield(); }
|
||||
|
||||
if (request.result != UnityWebRequest.Result.Success) {
|
||||
Debug.LogError($"Discord screenshot upload failed: {request.error}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string BuildDiscordPayload(BugReport report) {
|
||||
// Discord webhook format with embeds for nice formatting
|
||||
var truncatedLogs = TruncateForDiscord(report.RecentLogs, 1000);
|
||||
@@ -342,11 +454,9 @@ namespace common.GUIUtils {
|
||||
|
||||
private string EscapeJson(string text) {
|
||||
if (string.IsNullOrEmpty(text)) return "";
|
||||
return text.Replace("\\", "\\\\")
|
||||
.Replace("\"", "\\\"")
|
||||
.Replace("\n", "\\n")
|
||||
.Replace("\r", "\\r")
|
||||
.Replace("\t", "\\t");
|
||||
// JsonConvert.ToString returns "escaped string" with quotes, so strip them
|
||||
var serialized = JsonConvert.ToString(text);
|
||||
return serialized.Substring(1, serialized.Length - 2);
|
||||
}
|
||||
|
||||
private class BugReport {
|
||||
|
||||
+30
-1
@@ -8,11 +8,15 @@ using UnityEngine.UI;
|
||||
|
||||
namespace common.GUIUtils {
|
||||
public class ErrorHandler : MonoBehaviour {
|
||||
public static ErrorHandler Instance { get; private set; }
|
||||
|
||||
public GameObject panel;
|
||||
public TextMeshProUGUI errorTextField;
|
||||
public TextMeshProUGUI title;
|
||||
public Button dismissButton;
|
||||
public Button copyButton;
|
||||
public Button reportBugButton;
|
||||
public BugReportPanelController bugReportPanel;
|
||||
|
||||
private readonly int _maxErrors = 5;
|
||||
|
||||
@@ -83,7 +87,10 @@ namespace common.GUIUtils {
|
||||
}
|
||||
|
||||
// Register for log messages as early as possible
|
||||
void Awake() { Application.logMessageReceivedThreaded += HandleLog; }
|
||||
void Awake() {
|
||||
Instance = this;
|
||||
Application.logMessageReceivedThreaded += HandleLog;
|
||||
}
|
||||
|
||||
void Update() {
|
||||
// Handle errors that occurred before MainQueue was ready
|
||||
@@ -112,5 +119,27 @@ namespace common.GUIUtils {
|
||||
editor.SelectAll();
|
||||
editor.Copy();
|
||||
}
|
||||
|
||||
public void ReportBugClicked() {
|
||||
if (bugReportPanel != null) { bugReportPanel.ShowWithExceptionInfo(AllMessageText); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if there are any exceptions within the specified time window.
|
||||
/// </summary>
|
||||
public bool HasRecentExceptions(TimeSpan maxAge) {
|
||||
lock (_messages) { return _messages.Any(m => DateTime.UtcNow - m.Time < maxAge); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get formatted text of recent exceptions within the last 5 minutes.
|
||||
/// </summary>
|
||||
public string GetRecentExceptionsText() {
|
||||
lock (_messages) {
|
||||
var recent =
|
||||
_messages.Where(m => DateTime.UtcNow - m.Time < TimeSpan.FromMinutes(5));
|
||||
return string.Join("\n\n", recent.Select(m => m.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
-2
@@ -2,16 +2,56 @@
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
public class GeneralClickDetector : MonoBehaviour, IPointerClickHandler {
|
||||
public class GeneralClickDetector : MonoBehaviour,
|
||||
IPointerClickHandler,
|
||||
IPointerDownHandler,
|
||||
IPointerUpHandler {
|
||||
public UnityEvent<Vector2> onLeftClick;
|
||||
public UnityEvent<Vector2> onRightClick;
|
||||
|
||||
private const float LongPressSeconds = 0.5f;
|
||||
private const float MovementTolerancePixels = 10f;
|
||||
|
||||
private float? _pointerDownTime;
|
||||
private Vector2 _pointerDownPosition;
|
||||
private bool _longPressTriggered;
|
||||
|
||||
public void Start() {}
|
||||
|
||||
public void Update() {
|
||||
if (_pointerDownTime is {} downTime && !_longPressTriggered) {
|
||||
var elapsed = Time.time - downTime;
|
||||
var moved = Vector2.Distance(Input.mousePosition, _pointerDownPosition);
|
||||
|
||||
if (moved > MovementTolerancePixels) {
|
||||
// Finger moved too much, cancel long-press detection
|
||||
_pointerDownTime = null;
|
||||
} else if (elapsed > LongPressSeconds) {
|
||||
// Long-press detected - trigger right-click
|
||||
_longPressTriggered = true;
|
||||
onRightClick.Invoke(_pointerDownPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData) {
|
||||
// Only track long-press for touch input (pointerId >= 0)
|
||||
// Mouse has negative pointerIds (-1 left, -2 right, -3 middle)
|
||||
if (eventData.button == PointerEventData.InputButton.Left && eventData.pointerId >= 0) {
|
||||
_pointerDownTime = Time.time;
|
||||
_pointerDownPosition = eventData.position;
|
||||
_longPressTriggered = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerUp(PointerEventData eventData) { _pointerDownTime = null; }
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData) {
|
||||
var position = eventData.position;
|
||||
if (eventData.button == PointerEventData.InputButton.Left) {
|
||||
onLeftClick.Invoke(position);
|
||||
// Don't trigger left-click if we already triggered a long-press
|
||||
if (!_longPressTriggered) { onLeftClick.Invoke(position); }
|
||||
_longPressTriggered = false;
|
||||
} else if (eventData.button == PointerEventData.InputButton.Right) {
|
||||
onRightClick.Invoke(position);
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ PlayerSettings:
|
||||
AndroidFilterTouchesWhenObscured: 0
|
||||
AndroidEnableSustainedPerformanceMode: 0
|
||||
defaultScreenOrientation: 4
|
||||
targetDevice: 2
|
||||
targetDevice: 1
|
||||
useOnDemandResources: 0
|
||||
accelerometerFrequency: 60
|
||||
companyName: Shardok Games
|
||||
|
||||
@@ -3172,23 +3172,29 @@ type WhatsNewPageData struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
// fetchWhatsNewData fetches the what's-new JSON from S3
|
||||
// whatsNewPublicURL is the public URL for the what's-new JSON (read via HTTP, no credentials needed)
|
||||
const whatsNewPublicURL = "https://assets.eagle0.net/whats-new.json"
|
||||
|
||||
// fetchWhatsNewData fetches the what's-new JSON via public HTTP URL
|
||||
func fetchWhatsNewData() (*WhatsNewData, error) {
|
||||
bb, err := aws.NewBucketBasics()
|
||||
resp, err := http.Get(whatsNewPublicURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create S3 client: %w", err)
|
||||
return nil, fmt.Errorf("failed to fetch what's-new data: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
log.Printf("whats-new.json not found, returning empty data")
|
||||
return &WhatsNewData{Entries: []WhatsNewEntry{}}, nil
|
||||
}
|
||||
|
||||
data, err := bb.FetchBytes(whatsNewBucket, whatsNewKey)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to fetch what's-new data: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
// Check if it's a "not found" error - only then return empty data
|
||||
if strings.Contains(err.Error(), "NoSuchKey") {
|
||||
log.Printf("whats-new.json not found in S3, returning empty data")
|
||||
return &WhatsNewData{Entries: []WhatsNewEntry{}}, nil
|
||||
}
|
||||
// For any other error, return it so we can see what's wrong
|
||||
log.Printf("Failed to fetch whats-new.json: %v", err)
|
||||
return nil, fmt.Errorf("failed to fetch what's-new data: %w", err)
|
||||
return nil, fmt.Errorf("failed to read what's-new data: %w", err)
|
||||
}
|
||||
|
||||
var whatsNew WhatsNewData
|
||||
@@ -3196,7 +3202,7 @@ func fetchWhatsNewData() (*WhatsNewData, error) {
|
||||
return nil, fmt.Errorf("failed to parse what's-new JSON: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Fetched %d what's-new entries from S3", len(whatsNew.Entries))
|
||||
log.Printf("Fetched %d what's-new entries", len(whatsNew.Entries))
|
||||
return &whatsNew, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -53,18 +53,29 @@ func ReadAwsConfig() aws.Config {
|
||||
configMap, err := readConfig()
|
||||
var key string
|
||||
var secret string
|
||||
var endpoint string
|
||||
if err == nil {
|
||||
key = configMap["access_key"]
|
||||
secret = configMap["secret_key"]
|
||||
}
|
||||
if err != nil {
|
||||
if key == "" || secret == "" {
|
||||
// Try DO_SPACES_* env vars (used in docker-compose)
|
||||
key = os.Getenv("DO_SPACES_ACCESS_KEY")
|
||||
secret = os.Getenv("DO_SPACES_SECRET_KEY")
|
||||
endpoint = os.Getenv("DO_SPACES_ENDPOINT")
|
||||
}
|
||||
if key == "" || secret == "" {
|
||||
// Fall back to ACCESS_KEY_ID/SECRET_KEY (used by some CI workflows)
|
||||
key = os.Getenv("ACCESS_KEY_ID")
|
||||
secret = os.Getenv("SECRET_KEY")
|
||||
}
|
||||
if endpoint == "" {
|
||||
endpoint = "https://sfo3.digitaloceanspaces.com"
|
||||
}
|
||||
|
||||
sdkConfig := aws.Config{
|
||||
Credentials: credentials.NewStaticCredentialsProvider(key, secret, ""),
|
||||
BaseEndpoint: aws.String("https://sfo3.digitaloceanspaces.com"),
|
||||
BaseEndpoint: aws.String(endpoint),
|
||||
Region: "us-east-1",
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
load("@com_google_protobuf//bazel:cc_proto_library.bzl", "cc_proto_library")
|
||||
load("@grpc//bazel:cc_grpc_library.bzl", "cc_grpc_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library")
|
||||
|
||||
|
||||
@@ -349,6 +349,7 @@ message CreateGameRequest {
|
||||
string desired_leader_text_id = 1;
|
||||
int32 total_player_count = 2;
|
||||
int32 human_player_count = 3;
|
||||
bool is_tutorial_mode = 4;
|
||||
}
|
||||
|
||||
message CreateGameResponse {
|
||||
|
||||
@@ -45,6 +45,23 @@ message QuestDetails {
|
||||
FightBeastsAloneQuest fight_beasts_alone_quest = 28;
|
||||
DevelopProvincesQuest develop_provinces_quest = 29;
|
||||
MobilizeProvincesQuest mobilize_provinces_quest = 30;
|
||||
BattalionDiversityQuest battalion_diversity_quest = 31;
|
||||
ReleaseAllPrisonersQuest release_all_prisoners_quest = 32;
|
||||
SwearBrotherhoodWithHeroQuest swear_brotherhood_with_hero_quest = 33;
|
||||
BetrayAllyQuest betray_ally_quest = 34;
|
||||
BorderSecurityQuest border_security_quest = 35;
|
||||
WinBattleOutnumberedQuest win_battle_outnumbered_quest = 36;
|
||||
WinBattlesQuest win_battles_quest = 37;
|
||||
ApprehendOutlawQuest apprehend_outlaw_quest = 38;
|
||||
StartBlizzardQuest start_blizzard_quest = 39;
|
||||
StartEpidemicQuest start_epidemic_quest = 40;
|
||||
SpendOnFeastsQuest spend_on_feasts_quest = 41;
|
||||
SendSuppliesQuest send_supplies_quest = 42;
|
||||
RestProvinceQuest rest_province_quest = 43;
|
||||
ReconProvincesQuest recon_provinces_quest = 44;
|
||||
RepairDevastationQuest repair_devastation_quest = 45;
|
||||
ReconSpecificProvincesQuest recon_specific_provinces_quest = 46;
|
||||
StartDroughtQuest start_drought_quest = 47;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,4 +184,69 @@ message DevelopProvincesQuest {
|
||||
|
||||
message MobilizeProvincesQuest {
|
||||
int32 target_province_count = 1;
|
||||
}
|
||||
|
||||
message BattalionDiversityQuest {
|
||||
}
|
||||
|
||||
message ReleaseAllPrisonersQuest {
|
||||
}
|
||||
|
||||
message SwearBrotherhoodWithHeroQuest {
|
||||
int32 target_hero_id = 1;
|
||||
}
|
||||
|
||||
message BetrayAllyQuest {
|
||||
int32 target_faction_id = 1;
|
||||
}
|
||||
|
||||
message BorderSecurityQuest {
|
||||
int32 target_province_id = 1;
|
||||
}
|
||||
|
||||
message WinBattleOutnumberedQuest {
|
||||
}
|
||||
|
||||
message WinBattlesQuest {
|
||||
}
|
||||
|
||||
message ApprehendOutlawQuest {
|
||||
int32 outlaw_hero_id = 1;
|
||||
}
|
||||
|
||||
message StartBlizzardQuest {
|
||||
int32 province_id = 1;
|
||||
}
|
||||
|
||||
message StartEpidemicQuest {
|
||||
int32 province_id = 1;
|
||||
}
|
||||
|
||||
message StartDroughtQuest {
|
||||
int32 province_id = 1;
|
||||
}
|
||||
|
||||
message SpendOnFeastsQuest {
|
||||
int32 total_gold = 1;
|
||||
}
|
||||
|
||||
message SendSuppliesQuest {
|
||||
int32 target_province_id = 1;
|
||||
int32 total_food = 2;
|
||||
}
|
||||
|
||||
message RestProvinceQuest {
|
||||
int32 target_province_id = 1;
|
||||
}
|
||||
|
||||
message ReconProvincesQuest {
|
||||
int32 target_province_count = 1;
|
||||
}
|
||||
|
||||
message RepairDevastationQuest {
|
||||
int32 target_devastation_repaired = 1;
|
||||
}
|
||||
|
||||
message ReconSpecificProvincesQuest {
|
||||
repeated int32 target_province_ids = 1;
|
||||
}
|
||||
@@ -52,6 +52,22 @@ message SetFaction {
|
||||
int32 earliest_round_for_invitation = 5;
|
||||
}
|
||||
|
||||
// Configuration for tutorial battle that triggers at game start
|
||||
message TutorialBattleConfig {
|
||||
string attacker_faction_head = 1; // Faction head who attacks
|
||||
int32 target_province_id = 2; // Province to attack
|
||||
int32 flee_after_defender_units_lost = 3; // Attacker flees after defender loses N units
|
||||
int32 flee_after_rounds = 4; // Attacker flees after N rounds
|
||||
repeated string reinforcements = 5; // Hero names for reinforcements when attacker flees
|
||||
}
|
||||
|
||||
// Narrative screen shown before tutorial battle
|
||||
message TutorialNarrativeScreen {
|
||||
string title = 1;
|
||||
string body_text = 2;
|
||||
string image_path = 3;
|
||||
}
|
||||
|
||||
message GameParameters {
|
||||
string map_file_path = 2;
|
||||
|
||||
@@ -59,4 +75,8 @@ message GameParameters {
|
||||
ProvinceOverrides occupied_province_overrides = 4;
|
||||
|
||||
repeated SetFaction set_factions = 5;
|
||||
|
||||
// Tutorial-specific configuration
|
||||
TutorialBattleConfig tutorial_battle = 6;
|
||||
repeated TutorialNarrativeScreen tutorial_narrative_screens = 7;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,13 @@ option java_package = "net.eagle0.eagle.views";
|
||||
option java_outer_classname = "GameStateView";
|
||||
option objc_class_prefix = "E0G";
|
||||
|
||||
// Narrative screen for tutorial and story sequences
|
||||
message TutorialNarrativeScreen {
|
||||
string title = 1;
|
||||
string body_text = 2;
|
||||
string image_path = 3; // Optional hero portrait or scene image
|
||||
}
|
||||
|
||||
message GameStateView {
|
||||
int32 current_round_id = 1;
|
||||
.net.eagle0.eagle.common.RoundPhase current_phase = 2;
|
||||
@@ -43,6 +50,9 @@ message GameStateView {
|
||||
repeated .net.eagle0.eagle.common.BattalionType battalion_types = 12;
|
||||
|
||||
repeated .net.eagle0.eagle.common.ChronicleEntry chronicle_entries = 14;
|
||||
|
||||
// Tutorial narrative screens to display before gameplay
|
||||
repeated TutorialNarrativeScreen pending_narrative_screens = 15;
|
||||
}
|
||||
|
||||
message GameStateViewDiff {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
load("@com_google_protobuf//bazel:cc_proto_library.bzl", "cc_proto_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
|
||||
cc_proto_library(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
load("@com_google_protobuf//bazel:cc_proto_library.bzl", "cc_proto_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
load("@com_google_protobuf//bazel:cc_proto_library.bzl", "cc_proto_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
load("@com_google_protobuf//bazel:cc_proto_library.bzl", "cc_proto_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library")
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"mapFilePath": "/net/eagle0/eagle/province_map.tsv",
|
||||
"genericProvinceOverrides": {
|
||||
"economy": 20.0,
|
||||
"agriculture": 20.0,
|
||||
"infrastructure": 20.0,
|
||||
"specialBattalionTypes": []
|
||||
},
|
||||
"occupiedProvinceOverrides": {
|
||||
"randomRulingPlayerHeroCount": 0,
|
||||
"economy": 40.0,
|
||||
"agriculture": 40.0,
|
||||
"infrastructure": 45.0,
|
||||
"gold": 500,
|
||||
"food": 3000,
|
||||
"support": 40,
|
||||
"orders": "ENTRUST",
|
||||
"specialBattalionTypes": [],
|
||||
"battalions": [
|
||||
{
|
||||
"type": "LIGHT_INFANTRY",
|
||||
"size": 300,
|
||||
"training": 60,
|
||||
"armament": 60,
|
||||
"name": "Ranil's Spearmen"
|
||||
},
|
||||
{
|
||||
"type": "LIGHT_INFANTRY",
|
||||
"size": 300,
|
||||
"training": 60,
|
||||
"armament": 60,
|
||||
"name": "Ranil's Guard"
|
||||
},
|
||||
{
|
||||
"type": "LONGBOWMEN",
|
||||
"size": 200,
|
||||
"training": 60,
|
||||
"armament": 60,
|
||||
"name": "Ranil's Archers"
|
||||
}
|
||||
]
|
||||
},
|
||||
"setFactions": [
|
||||
{
|
||||
"factionHeadName": "Ikhaan Tarn",
|
||||
"swornBrotherNames": [],
|
||||
"occupiedProvinces": [
|
||||
{
|
||||
"provinceId": 32,
|
||||
"economy": 85.0,
|
||||
"agriculture": 91.0,
|
||||
"infrastructure": 66.0,
|
||||
"gold": 4400,
|
||||
"food": 5500,
|
||||
"support": 35,
|
||||
"orders": "ENTRUST",
|
||||
"rulingPlayerHeroNames": ["Ikhaan Tarn"],
|
||||
"randomRulingPlayerHeroCount": 2,
|
||||
"specialBattalionTypes": [],
|
||||
"battalions": [
|
||||
{
|
||||
"type": "HEAVY_CAVALRY",
|
||||
"size": 400,
|
||||
"training": 80,
|
||||
"armament": 80,
|
||||
"name": "Tarn's Knights"
|
||||
},
|
||||
{
|
||||
"type": "HEAVY_INFANTRY",
|
||||
"size": 500,
|
||||
"training": 80,
|
||||
"armament": 80,
|
||||
"name": "Tarn's Stormtroopers"
|
||||
},
|
||||
{
|
||||
"type": "LONGBOWMEN",
|
||||
"size": 300,
|
||||
"training": 80,
|
||||
"armament": 80,
|
||||
"name": "Tarn's Sharpshooters"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"startingTrusts": [],
|
||||
"earliestRoundForInvitation": 999
|
||||
}
|
||||
],
|
||||
"tutorialBattle": {
|
||||
"attackerFactionHead": "Ikhaan Tarn",
|
||||
"targetProvinceId": 14,
|
||||
"fleeAfterDefenderUnitsLost": 1,
|
||||
"fleeAfterRounds": 5,
|
||||
"reinforcements": ["Elena Fyar", "Hedrick", "The Boulder"]
|
||||
},
|
||||
"tutorialNarrativeScreens": [
|
||||
{
|
||||
"title": "The Engineer of Onmaa",
|
||||
"bodyText": "You are John Ranil, an engineer who once served the crown. When civil war erupted, you retreated to Onmaa, your ancestral home, hoping to wait out the conflict in peace.",
|
||||
"imagePath": ""
|
||||
},
|
||||
{
|
||||
"title": "The Traitor Arrives",
|
||||
"bodyText": "The traitor Ikhaan Tarn has arrived at Onmaa with his army. He once served King Bregos Fyar but betrayed him to join The Eagle, a mysterious mage from distant lands. Now Tarn seeks to expand his new master's domain.",
|
||||
"imagePath": ""
|
||||
},
|
||||
{
|
||||
"title": "Defend Your Home",
|
||||
"bodyText": "Against impossible odds, you must defend your home. Your small garrison is no match for Tarn's veterans, but you must hold the line until help arrives. Fight bravely, engineer.",
|
||||
"imagePath": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -58,4 +58,28 @@ object ApiKeys {
|
||||
lazy val anthropic: String = getKey(anthropicKeyName, anthropicEnvVar)
|
||||
|
||||
lazy val gemini: String = getKey(geminiKeyName, geminiEnvVar)
|
||||
|
||||
/** Check if OpenAI API key is available without throwing */
|
||||
def hasOpenAI: Boolean =
|
||||
Option(System.getenv(openAIEnvVar)).exists(_.nonEmpty) ||
|
||||
dictionary.get(openAIKeyName).exists(v => v.nonEmpty && !v.startsWith("your_"))
|
||||
|
||||
/** Check if Anthropic API key is available without throwing */
|
||||
def hasAnthropic: Boolean =
|
||||
Option(System.getenv(anthropicEnvVar)).exists(_.nonEmpty) ||
|
||||
dictionary.get(anthropicKeyName).exists(v => v.nonEmpty && !v.startsWith("your_"))
|
||||
|
||||
/** Check if Gemini API key is available without throwing */
|
||||
def hasGemini: Boolean =
|
||||
Option(System.getenv(geminiEnvVar)).exists(_.nonEmpty) ||
|
||||
dictionary.get(geminiKeyName).exists(v => v.nonEmpty && !v.startsWith("your_"))
|
||||
|
||||
/** Get list of providers with valid API keys configured */
|
||||
def availableProviders: Vector[String] =
|
||||
Vector("openai", "claude", "gemini").filter {
|
||||
case "openai" => hasOpenAI
|
||||
case "claude" => hasAnthropic
|
||||
case "gemini" => hasGemini
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
load("@rules_scala//scala:scala.bzl", "scala_binary", "scala_library")
|
||||
|
||||
filegroup(
|
||||
name = "api_keys_data",
|
||||
srcs = glob(["*_keys.txt"]),
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "api_keys",
|
||||
srcs = ["ApiKeys.scala"],
|
||||
data = [":api_keys_data"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "external_text_generation_error",
|
||||
srcs = ["ExternalTextGenerationError.scala"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
@@ -66,7 +68,11 @@ scala_library(
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
exports = [
|
||||
":external_text_generation_error",
|
||||
],
|
||||
deps = [
|
||||
":external_text_generation_error",
|
||||
":external_text_generation_service_impl",
|
||||
":rate_limits",
|
||||
":streaming_text_results",
|
||||
|
||||
@@ -17,8 +17,8 @@ object ClaudeServiceImpl {
|
||||
val model: String = "claude-sonnet-4-20250514"
|
||||
val maxTokens = 2048
|
||||
|
||||
private val apiKey = ApiKeys.anthropic
|
||||
private val baseURL = new URL("https://api.anthropic.com/v1/messages")
|
||||
private val apiKey = ApiKeys.anthropic
|
||||
private val baseURL: URL = URI.create("https://api.anthropic.com/v1/messages").toURL
|
||||
private def dictionaryFor(
|
||||
modelName: String
|
||||
): Map[String, Any] = Map(
|
||||
|
||||
+8
-14
@@ -18,16 +18,6 @@ import net.eagle0.common.sse.OkHttpSseListener
|
||||
import okhttp3.{MediaType, OkHttpClient, Request, RequestBody}
|
||||
import okhttp3.sse.EventSources
|
||||
|
||||
enum ExternalTextGenerationError extends Error:
|
||||
case RateLimit(code: Int, msg: String)
|
||||
case Http(code: Int, msg: String)
|
||||
case Timeout(msg: String)
|
||||
|
||||
def message: String = this match
|
||||
case RateLimit(_, msg) => msg
|
||||
case Http(_, msg) => msg
|
||||
case Timeout(msg) => msg
|
||||
|
||||
object ExternalTextGenerationCaller {
|
||||
private val initialBackoffSeconds: Double = 2.0
|
||||
|
||||
@@ -207,16 +197,20 @@ final class ExternalTextGenerationCaller(
|
||||
case Failure(exception) => Failure(exception)
|
||||
}.recoverWith {
|
||||
// Don't retry 4xx client errors - they won't succeed
|
||||
case e: ExternalTextGenerationError.Http =>
|
||||
case e: ExternalTextGenerationError.Http =>
|
||||
Future.failed(e)
|
||||
// Don't retry 5xx server errors at caller level - let LlmResolver handle failover
|
||||
case e: ExternalTextGenerationError.ServerError =>
|
||||
println(s"Server error ${e.code} - not retrying, will failover to another provider")
|
||||
Future.failed(e)
|
||||
// Retry transient errors (timeouts, network issues)
|
||||
case e: ExternalTextGenerationError.Timeout =>
|
||||
case e: ExternalTextGenerationError.Timeout =>
|
||||
println(s"Timeout error - retrying: ${e.message}")
|
||||
tryAgain()
|
||||
case e: IOException =>
|
||||
case e: IOException =>
|
||||
println(s"IOException - retrying: ${e.getMessage}")
|
||||
tryAgain()
|
||||
case e: Throwable =>
|
||||
case e: Throwable =>
|
||||
println(s"error ${e.getMessage} - retrying")
|
||||
tryAgain()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package net.eagle0.common.llm_integration
|
||||
|
||||
enum ExternalTextGenerationError extends Error:
|
||||
case RateLimit(code: Int, msg: String)
|
||||
case Http(code: Int, msg: String)
|
||||
case ServerError(code: Int, msg: String) // 5xx errors - should trigger provider failover
|
||||
case Timeout(msg: String)
|
||||
|
||||
def message: String = this match
|
||||
case RateLimit(_, msg) => msg
|
||||
case Http(_, msg) => msg
|
||||
case ServerError(_, msg) => msg
|
||||
case Timeout(msg) => msg
|
||||
+1
-1
@@ -16,7 +16,7 @@ object OpenAIChatCompletionsServiceImpl {
|
||||
val gpt5: String = "gpt-5.1"
|
||||
|
||||
private val apiKey = ApiKeys.openAI
|
||||
private val baseURL = new URL("https://api.openai.com/v1/chat/completions")
|
||||
private val baseURL: URL = URI.create("https://api.openai.com/v1/chat/completions").toURL
|
||||
private val temperature: Double = 1.0
|
||||
|
||||
// Non-reasoning models have -mini or -nano suffix; all others support reasoning_effort
|
||||
|
||||
@@ -26,7 +26,7 @@ object OpenAIResponsesServiceImpl {
|
||||
val gpt5: String = "gpt-5.1"
|
||||
|
||||
private val apiKey = ApiKeys.openAI
|
||||
private val baseURL = new URL("https://api.openai.com/v1/responses")
|
||||
private val baseURL: URL = URI.create("https://api.openai.com/v1/responses").toURL
|
||||
private val temperature: Double = 1.0
|
||||
|
||||
private def baseRequest(timeoutSeconds: Int): HttpRequest.Builder =
|
||||
|
||||
@@ -24,6 +24,7 @@ scala_library(
|
||||
srcs = ["OkHttpSseListener.scala"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/common/llm_integration:external_text_generation_error",
|
||||
"@maven//:com_squareup_okhttp3_okhttp",
|
||||
"@maven//:com_squareup_okhttp3_okhttp_sse",
|
||||
],
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.eagle0.common.sse
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.function.Consumer
|
||||
|
||||
import net.eagle0.common.llm_integration.ExternalTextGenerationError
|
||||
import okhttp3.sse.{EventSource, EventSourceListener}
|
||||
import okhttp3.Response
|
||||
|
||||
@@ -58,13 +59,28 @@ class OkHttpSseListener(messageDataConsumer: Consumer[String]) extends EventSour
|
||||
} else {
|
||||
println(s"SSE failure: $errorMessage")
|
||||
}
|
||||
// OkHttp can pass null throwable for HTTP error responses
|
||||
val exception =
|
||||
if t != null then t
|
||||
else
|
||||
new RuntimeException(
|
||||
s"SSE failed with response code ${Option(response).map(_.code().toString).getOrElse("unknown")}"
|
||||
)
|
||||
val _ = future.completeExceptionally(exception)
|
||||
|
||||
// Create appropriate exception based on response code
|
||||
val exception = if response != null && response.code() >= 500 then {
|
||||
// 5xx server errors - should trigger provider failover
|
||||
ExternalTextGenerationError.ServerError(
|
||||
response.code(),
|
||||
s"Server error ${response.code()}: $errorMessage"
|
||||
)
|
||||
} else if response != null && response.code() >= 400 then {
|
||||
// 4xx client errors
|
||||
ExternalTextGenerationError.Http(
|
||||
response.code(),
|
||||
s"HTTP error ${response.code()}: $errorMessage"
|
||||
)
|
||||
} else if t != null then {
|
||||
t
|
||||
} else {
|
||||
new RuntimeException(
|
||||
s"SSE failed with response code ${Option(response).map(_.code().toString).getOrElse("unknown")}"
|
||||
)
|
||||
}
|
||||
|
||||
val _ = future.completeExceptionally(exception)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-13
@@ -2,7 +2,6 @@ package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.settings.FeastGoldCostPerHero
|
||||
import net.eagle0.eagle.library.util.hero.HeroUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.FeastAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
@@ -10,17 +9,7 @@ import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
|
||||
object AvailableFeastCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
|
||||
private def hasHeroThatBenefits(
|
||||
gameState: GameState,
|
||||
province: ProvinceT
|
||||
): Boolean = {
|
||||
val factions = gameState.factions.values.toVector
|
||||
province.rulingFactionHeroIds
|
||||
.flatMap(hid => gameState.heroes.get(hid))
|
||||
.exists(h => h.vigor < h.constitution || HeroUtils.effectiveLoyalty(h, factions) < 100.0)
|
||||
}
|
||||
|
||||
def feastGoldCost(province: ProvinceT): Int =
|
||||
private def feastGoldCost(province: ProvinceT): Int =
|
||||
(province.rulingFactionHeroIds.length.toDouble * province.priceIndex * FeastGoldCostPerHero.intValue).ceil.toInt
|
||||
|
||||
override def availableCommand(
|
||||
@@ -32,7 +21,7 @@ object AvailableFeastCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
val goldCost = feastGoldCost(province)
|
||||
|
||||
Option.when(
|
||||
province.gold >= goldCost && hasHeroThatBenefits(gameState, province)
|
||||
province.gold >= goldCost
|
||||
) {
|
||||
FeastAvailable(
|
||||
goldCost = goldCost,
|
||||
|
||||
@@ -92,6 +92,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:min_support_for_taxes",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:portion_of_capacity_for_upgrade_battalion_quest",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/battalion_type_finder",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
@@ -1225,6 +1226,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:returning_heroes",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:province_view_filter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
# Transitive deps of action_result_concrete (needed because ActionResultC has fields of these types)
|
||||
@@ -1240,7 +1242,10 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:incoming_end_turn_action",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
+56
-1
@@ -6,9 +6,14 @@ import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentUtils
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship.RelationshipLevel.Ally
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.{
|
||||
ApprehendOutlawQuest,
|
||||
BetrayAllyQuest,
|
||||
BorderSecurityQuest,
|
||||
DefeatFactionQuest,
|
||||
DevelopProvincesQuest,
|
||||
DismissSpecificVassalQuest,
|
||||
@@ -18,11 +23,17 @@ import net.eagle0.eagle.model.state.quest.{
|
||||
Quest,
|
||||
ReleasePrisonerQuest,
|
||||
RescueImprisonedLeaderQuest,
|
||||
RestProvinceQuest,
|
||||
ReturnPrisonerQuest,
|
||||
SendSuppliesQuest,
|
||||
StartBlizzardQuest,
|
||||
StartDroughtQuest,
|
||||
StartEpidemicQuest,
|
||||
SwearBrotherhoodWithHeroQuest,
|
||||
TruceCountQuest,
|
||||
TruceWithFactionQuest
|
||||
}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.Prisoner
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.{Outlaw, Prisoner}
|
||||
|
||||
case class CheckForFailedQuestsAction(
|
||||
gameId: GameId,
|
||||
@@ -30,6 +41,7 @@ case class CheckForFailedQuestsAction(
|
||||
currentRoundId: RoundId,
|
||||
provinces: Vector[ProvinceT],
|
||||
factions: Vector[FactionT],
|
||||
heroes: Map[HeroId, HeroT],
|
||||
heroBackstoryTextIdLookup: HeroId => String
|
||||
) extends ProtolessSequentialResultsAction {
|
||||
override def results: Vector[ActionResultT] =
|
||||
@@ -139,6 +151,49 @@ case class CheckForFailedQuestsAction(
|
||||
// Fails if the faction has fewer provinces than the target count
|
||||
val factionProvinceCount = provinces.count(_.rulingFactionId == province.rulingFactionId)
|
||||
factionProvinceCount < q.targetProvinceCount
|
||||
case SwearBrotherhoodWithHeroQuest(targetHeroId) =>
|
||||
// Fails if the target hero is no longer in the faction
|
||||
!FactionUtils.heroIsInFaction(targetHeroId, province.rulingFactionId.get, heroes)
|
||||
case BetrayAllyQuest(targetFactionId) =>
|
||||
// Fails if the target faction no longer exists or is no longer an ally
|
||||
!factions.exists(_.id == targetFactionId) ||
|
||||
!factions
|
||||
.find(_.id == province.rulingFactionId.get)
|
||||
.exists(
|
||||
_.factionRelationships.exists(r => r.targetFactionId == targetFactionId && r.relationshipLevel == Ally)
|
||||
)
|
||||
case BorderSecurityQuest(targetProvinceId) =>
|
||||
// Fails if the target province is no longer controlled by the faction
|
||||
!provinces
|
||||
.find(_.id == targetProvinceId)
|
||||
.exists(_.rulingFactionId == province.rulingFactionId)
|
||||
case ApprehendOutlawQuest(outlawHeroId) =>
|
||||
// Fails if the hero is no longer an outlaw anywhere (e.g., apprehended by someone else)
|
||||
// Moving to a different province is fine - quest remains valid
|
||||
!provinces.exists(
|
||||
_.unaffiliatedHeroes.exists(uh => uh.heroId == outlawHeroId && uh.unaffiliatedHeroType == Outlaw)
|
||||
)
|
||||
case _: StartBlizzardQuest =>
|
||||
// Never fails - if a blizzard already exists, quest creation should not offer this
|
||||
false
|
||||
case _: StartDroughtQuest =>
|
||||
// Never fails - if a drought already exists, quest creation should not offer this
|
||||
false
|
||||
case _: StartEpidemicQuest =>
|
||||
// Never fails - if an epidemic already exists, quest creation should not offer this
|
||||
false
|
||||
case SendSuppliesQuest(_, _, targetProvinceId, _) =>
|
||||
// Fails if we no longer own the target province
|
||||
!provinces.exists(p =>
|
||||
p.id == targetProvinceId &&
|
||||
p.rulingFactionId == province.rulingFactionId
|
||||
)
|
||||
case RestProvinceQuest(_, _, targetProvinceId) =>
|
||||
// Fails if we no longer own the target province
|
||||
!provinces.exists(p =>
|
||||
p.id == targetProvinceId &&
|
||||
p.rulingFactionId == province.rulingFactionId
|
||||
)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
|
||||
+63
-1
@@ -4,6 +4,7 @@ import net.eagle0.eagle.{BattalionId, FactionId, GameId, HeroId, ProvinceId, Rou
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSequentialResultsAction
|
||||
import net.eagle0.eagle.library.settings.{MinSupportForTaxes, PortionOfCapacityForUpgradeBattalionQuest}
|
||||
import net.eagle0.eagle.library.util.battalion_type_finder.BattalionTypeFinder
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentUtils
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
@@ -17,6 +18,10 @@ import net.eagle0.eagle.model.state.province.ProvinceOrderType
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.{
|
||||
AllianceQuest,
|
||||
ApprehendOutlawQuest,
|
||||
BattalionDiversityQuest,
|
||||
BetrayAllyQuest,
|
||||
BorderSecurityQuest,
|
||||
ComponentQuest,
|
||||
DefeatFactionQuest,
|
||||
DevelopProvincesQuest,
|
||||
@@ -31,17 +36,24 @@ import net.eagle0.eagle.model.state.quest.{
|
||||
ImproveInfrastructureQuest,
|
||||
MobilizeProvincesQuest,
|
||||
Quest,
|
||||
ReleaseAllPrisonersQuest,
|
||||
ReleasePrisonerQuest,
|
||||
RescueImprisonedLeaderQuest,
|
||||
ReturnPrisonerQuest,
|
||||
SpecificExpansionQuest,
|
||||
StartBlizzardQuest,
|
||||
StartDroughtQuest,
|
||||
StartEpidemicQuest,
|
||||
SuppressRiotByForceQuest,
|
||||
SwearBrotherhoodWithHeroQuest,
|
||||
TotalDevelopmentQuest,
|
||||
TruceCountQuest,
|
||||
TruceWithFactionQuest,
|
||||
UpgradeBattalionQuest,
|
||||
WealthQuest
|
||||
WealthQuest,
|
||||
WinBattleOutnumberedQuest
|
||||
}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.{MovingPrisoner, Prisoner}
|
||||
import net.eagle0.eagle.model.state.BattalionType
|
||||
import net.eagle0.eagle.model.state.BattalionTypeId
|
||||
|
||||
@@ -126,6 +138,17 @@ case class CheckForFulfilledQuestsAction(
|
||||
battalionTypeId
|
||||
).capacity
|
||||
)
|
||||
case BattalionDiversityQuest =>
|
||||
// Fulfilled when province has 3+ different battalion types, each at near-full capacity (80%)
|
||||
val provinceBattalions = province.battalionIds.flatMap(battalionWithId)
|
||||
val typesAtNearFullCapacity = provinceBattalions
|
||||
.groupBy(_.typeId)
|
||||
.count {
|
||||
case (typeId, batts) =>
|
||||
val capacity = battalionType(typeId).capacity
|
||||
batts.exists(_.size >= 0.8 * capacity)
|
||||
}
|
||||
typesAtNearFullCapacity >= 3
|
||||
case GrandArmyQuest(totalTroopCount) =>
|
||||
province.battalionIds
|
||||
.flatMap(battalionWithId)
|
||||
@@ -170,6 +193,45 @@ case class CheckForFulfilledQuestsAction(
|
||||
false // action quests
|
||||
case FightBeastsAloneQuest =>
|
||||
false // action quest - fulfilled in SuppressBeastsCommand
|
||||
case ReleaseAllPrisonersQuest =>
|
||||
// Fulfilled when there are no prisoners or moving prisoners in faction's provinces
|
||||
val factionProvinces = provinces.filter(_.rulingFactionId == province.rulingFactionId)
|
||||
!factionProvinces.exists(
|
||||
_.unaffiliatedHeroes.exists(uh =>
|
||||
uh.unaffiliatedHeroType == Prisoner || uh.unaffiliatedHeroType == MovingPrisoner
|
||||
)
|
||||
)
|
||||
case _: SwearBrotherhoodWithHeroQuest =>
|
||||
false // action quest - fulfilled in SwearBrotherhoodCommand
|
||||
case _: BetrayAllyQuest =>
|
||||
false // action quest - fulfilled in BreakAllianceResolutionHelpers
|
||||
case BorderSecurityQuest(targetProvinceId) =>
|
||||
// Fulfilled when all neighbors of the target province are controlled by the faction or an ally
|
||||
provinceWithId(targetProvinceId).exists { targetProvince =>
|
||||
targetProvince.neighbors.forall { neighbor =>
|
||||
provinceWithId(neighbor.provinceId).exists { neighborProvince =>
|
||||
neighborProvince.rulingFactionId == province.rulingFactionId ||
|
||||
(for {
|
||||
questFactionId <- province.rulingFactionId
|
||||
neighborFactionId <- neighborProvince.rulingFactionId
|
||||
} yield FactionUtils.hasAlliance(questFactionId, neighborFactionId, factions))
|
||||
.getOrElse(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
case WinBattleOutnumberedQuest =>
|
||||
false // action quest - fulfilled in ResolveBattleAction
|
||||
case _: StartBlizzardQuest =>
|
||||
false // action quest - fulfilled in ControlWeatherCommand
|
||||
case _: StartDroughtQuest =>
|
||||
false // action quest - fulfilled in ControlWeatherCommand
|
||||
case _: StartEpidemicQuest =>
|
||||
false // action quest - fulfilled in ControlWeatherCommand
|
||||
// Note: WinBattlesQuest is handled by ComponentQuest case above
|
||||
// Note: SpendOnFeastsQuest, SendSuppliesQuest, RestProvinceQuest,
|
||||
// ReconProvincesQuest, ReconSpecificProvincesQuest, RepairDevastationQuest are ComponentQuests
|
||||
case _: ApprehendOutlawQuest =>
|
||||
false // action quest - fulfilled in ApprehendOutlawCommand
|
||||
}
|
||||
|
||||
private def incrementProvinceOrderQuestProgress(
|
||||
|
||||
+6
-4
@@ -27,6 +27,7 @@ import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.RoundPhase.ReconResolution
|
||||
import net.eagle0.eagle.GameId
|
||||
|
||||
case class EndDiplomacyResolutionPhaseAction(
|
||||
gameState: GameState,
|
||||
@@ -220,7 +221,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
(ba: BreakAlliance, fr: FunctionalRandom) => {
|
||||
val provinces = currentGameState.provinces.values.toVector
|
||||
val currentDate = currentGameState.currentDate.get
|
||||
resultsForBreakAlliance(ba, fr, provinces, factions, currentDate)
|
||||
resultsForBreakAlliance(ba, fr, provinces, factions, currentDate, currentGameState.gameId)
|
||||
},
|
||||
functionalRandom
|
||||
)(factions)
|
||||
@@ -414,7 +415,8 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
functionalRandom: FunctionalRandom,
|
||||
provinces: Vector[ProvinceT],
|
||||
factions: Vector[FactionT],
|
||||
currentDate: Date
|
||||
currentDate: Date,
|
||||
gameId: GameId
|
||||
): RandomState[Vector[ActionResultT]] =
|
||||
breakAllianceOffer.status match {
|
||||
case Accepted =>
|
||||
@@ -424,9 +426,9 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
currentDate = currentDate,
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
functionalRandom = functionalRandom
|
||||
functionalRandom = functionalRandom,
|
||||
gameId = gameId
|
||||
)
|
||||
.map(Vector(_))
|
||||
case Rejected =>
|
||||
// This one shouldn't be possible, but we'll resolve in the helper
|
||||
BreakAllianceResolutionHelpers
|
||||
|
||||
+2
-1
@@ -74,7 +74,8 @@ case class EndVassalCommandsPhaseAction(
|
||||
currentRoundId = gs.currentRoundId,
|
||||
provinces = gs.provinces.values.toVector,
|
||||
factions = gs.factions.values.toVector,
|
||||
hid => gs.heroes(hid).backstoryTextId
|
||||
heroes = gs.heroes,
|
||||
heroBackstoryTextIdLookup = hid => gs.heroes(hid).backstoryTextId
|
||||
).results
|
||||
)
|
||||
.withRandomActionResults((gs, fr) =>
|
||||
|
||||
+56
-2
@@ -1,9 +1,12 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessRandomSequentialResultsAction
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentUtils
|
||||
import net.eagle0.eagle.library.util.view_filters.ProvinceViewFilter
|
||||
import net.eagle0.eagle.library.util.ReturningHeroes
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
@@ -11,7 +14,9 @@ import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedFact
|
||||
import net.eagle0.eagle.model.action_result.types.ActionResultType.{EndReconResolutionPhase, ReconSucceeded}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.{IncomingEndTurnAction, IncomingRecon}
|
||||
import net.eagle0.eagle.model.state.province.{IncomingEndTurnAction, IncomingRecon, ProvinceT}
|
||||
import net.eagle0.eagle.model.state.quest.{ReconProvincesQuest, ReconSpecificProvincesQuest}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroT
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import net.eagle0.eagle.ProvinceId
|
||||
|
||||
@@ -64,6 +69,9 @@ case class PerformReconResolutionAction(
|
||||
// Extract heroId from the recon action details
|
||||
val heroId = incomingEndTurnAction.details.asInstanceOf[IncomingRecon].heroId
|
||||
|
||||
val factionProvinces = gameState.provinces.values.toVector
|
||||
.filter(_.rulingFactionId.contains(fromFactionId))
|
||||
|
||||
ReturningHeroes
|
||||
.heroesReturningToFaction(
|
||||
hids = Vector(heroId),
|
||||
@@ -73,7 +81,7 @@ case class PerformReconResolutionAction(
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.map { returningHeroes =>
|
||||
ActionResultC(
|
||||
val baseResult = ActionResultC(
|
||||
actionResultType = ReconSucceeded,
|
||||
changedHeroes = returningHeroes.changedHeroes,
|
||||
changedProvinces = Vector(
|
||||
@@ -103,6 +111,52 @@ case class PerformReconResolutionAction(
|
||||
)
|
||||
}.toVector
|
||||
)
|
||||
|
||||
// Increment recon quest counters
|
||||
val withReconProvincesQuestIncrement =
|
||||
reconProvincesQuestIncrementer(factionProvinces)(baseResult)
|
||||
reconSpecificProvincesQuestIncrementer(factionProvinces, provinceId)(withReconProvincesQuestIncrement)
|
||||
}
|
||||
}
|
||||
|
||||
private def reconProvincesQuestCheck(
|
||||
uh: UnaffiliatedHeroT,
|
||||
@unused province: ProvinceT
|
||||
): Boolean =
|
||||
uh.quest.exists {
|
||||
case q: ReconProvincesQuest => q.componentsFulfilled < q.componentCount
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private def reconProvincesQuestIncrementer(
|
||||
factionProvinces: Vector[ProvinceT]
|
||||
): ActionResultT => ActionResultT =
|
||||
QuestFulfillmentUtils.withCountersIncremented(
|
||||
provinces = factionProvinces,
|
||||
incrementAmount = 1,
|
||||
check = reconProvincesQuestCheck
|
||||
)
|
||||
|
||||
private def reconSpecificProvincesQuestCheck(
|
||||
reconnedProvinceId: ProvinceId
|
||||
)(
|
||||
uh: UnaffiliatedHeroT,
|
||||
@unused province: ProvinceT
|
||||
): Boolean =
|
||||
uh.quest.exists {
|
||||
case q: ReconSpecificProvincesQuest =>
|
||||
q.componentsFulfilled < q.componentCount &&
|
||||
q.targetProvinceIds.contains(reconnedProvinceId)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private def reconSpecificProvincesQuestIncrementer(
|
||||
factionProvinces: Vector[ProvinceT],
|
||||
reconnedProvinceId: ProvinceId
|
||||
): ActionResultT => ActionResultT =
|
||||
QuestFulfillmentUtils.withCountersIncremented(
|
||||
provinces = factionProvinces,
|
||||
incrementAmount = 1,
|
||||
check = reconSpecificProvincesQuestCheck(reconnedProvinceId)
|
||||
)
|
||||
}
|
||||
|
||||
+80
-4
@@ -21,7 +21,7 @@ import net.eagle0.eagle.model.state.battalion.{BattalionT, EventForBattalionBack
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.{EventForHeroBackstory, EventForHeroBackstoryT, HeroT}
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.{DefeatFactionQuest, Quest}
|
||||
import net.eagle0.eagle.model.state.quest.{DefeatFactionQuest, Quest, WinBattleOutnumberedQuest, WinBattlesQuest}
|
||||
import net.eagle0.eagle.model.state.shardok_battle.{BattleType, ShardokBattle, ShardokPlayer}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.{RecruitmentInfo, UnaffiliatedHeroType}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.concrete.UnaffiliatedHeroC
|
||||
@@ -148,15 +148,81 @@ case class ResolveBattleAction(
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private def winBattleOutnumberedQuestCheck(
|
||||
wasOutnumbered: Boolean
|
||||
): (Quest, ProvinceT) => Boolean =
|
||||
(q, _: ProvinceT) =>
|
||||
q match {
|
||||
case WinBattleOutnumberedQuest => wasOutnumbered
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private def winBattlesQuestIncrementResults(
|
||||
startingState: GameState,
|
||||
winningFids: Set[FactionId]
|
||||
): Vector[ActionResultT] = {
|
||||
val changedProvinces = winningFids.toVector
|
||||
.flatMap(fid => FactionUtils.provinces(fid, startingState.provinces.values.toVector))
|
||||
.flatMap { province =>
|
||||
QuestFulfillmentUtils.counterIncrementedCP(
|
||||
province = province,
|
||||
incrementAmount = 1,
|
||||
check = (uh, _) =>
|
||||
uh.quest.exists {
|
||||
case q: WinBattlesQuest => q.componentsFulfilled < q.componentCount
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Option
|
||||
.when(changedProvinces.nonEmpty) {
|
||||
ActionResultC(
|
||||
actionResultType = BattleEnded,
|
||||
changedProvinces = changedProvinces
|
||||
)
|
||||
}
|
||||
.toVector
|
||||
}
|
||||
|
||||
private def playerTroopCount(
|
||||
player: ShardokPlayer,
|
||||
battalions: Map[BattalionId, BattalionT]
|
||||
): Int =
|
||||
player.armyGroup.toVector
|
||||
.flatMap(_.armies)
|
||||
.flatMap(_.army.units)
|
||||
.flatMap(_.battalionId)
|
||||
.flatMap(bid => battalions.get(bid))
|
||||
.map(_.size)
|
||||
.sum
|
||||
|
||||
private def winnersFulfilledQuestActionResults(
|
||||
startingState: GameState,
|
||||
battle: ShardokBattle,
|
||||
battleResolution: BattleResolution
|
||||
): Vector[ActionResultT] =
|
||||
): Vector[ActionResultT] = {
|
||||
val battalionsMap = startingState.battalions
|
||||
|
||||
battleResolution.resolvedPlayers.toVector.partition(
|
||||
_.endGameCondition.isVictory
|
||||
) match {
|
||||
case (winners, losers) =>
|
||||
winners
|
||||
// Calculate total troop counts for winners and losers at the start of the battle
|
||||
val winningFids = winners.map(_.eagleFid).toSet
|
||||
val winningPlayers = battle.players.filter(p => winningFids.contains(p.eagleFid))
|
||||
val losingPlayers = battle.players.filterNot(p => winningFids.contains(p.eagleFid))
|
||||
|
||||
val winnerTroopCount = winningPlayers.map(p => playerTroopCount(p, battalionsMap)).sum
|
||||
val loserTroopCount = losingPlayers.map(p => playerTroopCount(p, battalionsMap)).sum
|
||||
|
||||
val wasOutnumbered = winnerTroopCount < loserTroopCount && winners.nonEmpty
|
||||
|
||||
// Increment WinBattlesQuest counters for all winners
|
||||
val winBattlesIncrementResults = winBattlesQuestIncrementResults(startingState, winningFids)
|
||||
|
||||
// Check for quest fulfillments
|
||||
val questFulfillmentResults = winners
|
||||
.flatMap(rsp => FactionUtils.provinces(rsp.eagleFid, startingState.provinces.values.toVector))
|
||||
.flatMap(p =>
|
||||
QuestFulfillmentUtils.fulfilledQuestResults(
|
||||
@@ -164,9 +230,18 @@ case class ResolveBattleAction(
|
||||
isFulfilled = defeatLoserQuestCompletionCheck(losers.map(_.eagleFid)),
|
||||
gameId = startingState.gameId,
|
||||
currentDate = startingState.currentDate.get
|
||||
)
|
||||
) ++
|
||||
QuestFulfillmentUtils.fulfilledQuestResults(
|
||||
province = p,
|
||||
isFulfilled = winBattleOutnumberedQuestCheck(wasOutnumbered),
|
||||
gameId = startingState.gameId,
|
||||
currentDate = startingState.currentDate.get
|
||||
)
|
||||
)
|
||||
|
||||
winBattlesIncrementResults ++ questFulfillmentResults
|
||||
}
|
||||
}
|
||||
|
||||
private def verifyHidsOutMatchHidsIn(
|
||||
battle: ShardokBattle,
|
||||
@@ -267,6 +342,7 @@ case class ResolveBattleAction(
|
||||
destroyedBattalionIds = destroyedBattalionIds(battleResolution)
|
||||
) +: winnersFulfilledQuestActionResults(
|
||||
startingState = startingState,
|
||||
battle = battle,
|
||||
battleResolution = battleResolution
|
||||
)
|
||||
}
|
||||
|
||||
+4
@@ -61,6 +61,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:returning_heroes",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
@@ -74,10 +75,13 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:client_text_visibility_extension_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero/concrete",
|
||||
],
|
||||
|
||||
+25
-4
@@ -1,6 +1,7 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action.diplomacy_helpers
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.{FactionId, GameId}
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
BreakAllianceCharismaXp,
|
||||
BreakAllianceWisdomXp,
|
||||
@@ -12,6 +13,7 @@ import net.eagle0.eagle.library.settings.{
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils.*
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentChecker
|
||||
import net.eagle0.eagle.library.util.ReturningHeroes
|
||||
import net.eagle0.eagle.library.EagleInternalException
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
@@ -29,10 +31,10 @@ import net.eagle0.eagle.model.state.faction.FactionRelationship.RelationshipLeve
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.hero.EventForHeroBackstory
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.{BetrayAllyQuest, Quest}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.{RecruitmentInfo, UnaffiliatedHeroT}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.concrete.UnaffiliatedHeroC
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.Prisoner
|
||||
import net.eagle0.eagle.FactionId
|
||||
|
||||
object BreakAllianceResolutionHelpers {
|
||||
private def returningHeroesForOffer(
|
||||
@@ -51,13 +53,19 @@ object BreakAllianceResolutionHelpers {
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
|
||||
private def betrayAllyQuestMatch(quest: Quest, betrayedFactionId: FactionId): Boolean = quest match {
|
||||
case BetrayAllyQuest(targetFactionId) => targetFactionId == betrayedFactionId
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def acceptedResult(
|
||||
breakAllianceOffer: BreakAlliance,
|
||||
currentDate: Date,
|
||||
provinces: Vector[ProvinceT],
|
||||
factions: Vector[FactionT],
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[ActionResultT] =
|
||||
functionalRandom: FunctionalRandom,
|
||||
gameId: GameId
|
||||
): RandomState[Vector[ActionResultT]] =
|
||||
returningHeroesForOffer(breakAllianceOffer, provinces, functionalRandom).map { returningHeroes =>
|
||||
val backstoryEvent = EventForHeroBackstory.BreakAllianceAmbassador(
|
||||
date = currentDate,
|
||||
@@ -66,7 +74,7 @@ object BreakAllianceResolutionHelpers {
|
||||
outcome = Accepted
|
||||
)
|
||||
|
||||
ActionResultC(
|
||||
val mainResult = ActionResultC(
|
||||
actionResultType = BreakAllianceAccepted,
|
||||
actingFactionId = Some(breakAllianceOffer.targetFactionId),
|
||||
changedHeroes = Vector(
|
||||
@@ -117,6 +125,19 @@ object BreakAllianceResolutionHelpers {
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// Check for BetrayAllyQuest fulfillment for the ORIGINATING faction (the one who broke the alliance)
|
||||
val questFulfillmentChecker = new QuestFulfillmentChecker(gameId, currentDate)
|
||||
val questFulfillmentResults = provinces
|
||||
.filter(_.rulingFactionId.contains(breakAllianceOffer.originatingFactionId))
|
||||
.flatMap { checkedProvince =>
|
||||
questFulfillmentChecker.fulfilledQuestResults(
|
||||
province = checkedProvince,
|
||||
isFulfilled = (quest, _) => betrayAllyQuestMatch(quest, breakAllianceOffer.targetFactionId)
|
||||
)
|
||||
}
|
||||
|
||||
mainResult +: questFulfillmentResults
|
||||
}
|
||||
|
||||
def rejectedResult(
|
||||
|
||||
+6
@@ -171,6 +171,9 @@ object RansomResolutionHelpers {
|
||||
) ++ questFulfillmentChecker.fulfilledQuestResults(
|
||||
province = questCheckProvince,
|
||||
isFulfilled = (quest, _ /*questHolderProvince*/ ) => matcher.returnPrisonerQuestMatch(quest)
|
||||
) ++ questFulfillmentChecker.failedQuestResults(
|
||||
province = questCheckProvince,
|
||||
isFailed = (quest, _ /*questHolderProvince*/ ) => matcher.releaseAllPrisonersQuestMatch(quest)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -194,6 +197,9 @@ object RansomResolutionHelpers {
|
||||
) ++ questFulfillmentChecker.failedQuestResults(
|
||||
province = questCheckProvince,
|
||||
isFailed = (quest, _ /*questHolderProvince*/ ) => matcher.returnPrisonerQuestMatch(quest)
|
||||
) ++ questFulfillmentChecker.failedQuestResults(
|
||||
province = questCheckProvince,
|
||||
isFailed = (quest, _ /*questHolderProvince*/ ) => matcher.releaseAllPrisonersQuestMatch(quest)
|
||||
)
|
||||
|
||||
ActionResultC(
|
||||
|
||||
+33
-8
@@ -1,8 +1,9 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.eagle.{FactionId, GameId, HeroId}
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSimpleAction
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSequentialResultsAction
|
||||
import net.eagle0.eagle.library.settings.{ApprehendOutlawVigorCost, FactionBiasFromImprisonment}
|
||||
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentChecker
|
||||
import net.eagle0.eagle.library.util.EagleRequire.clientRequire
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedHeroC, NotificationC, StatDelta}
|
||||
@@ -12,6 +13,7 @@ import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.action_result.NotificationDetails.OutlawApprehended
|
||||
import net.eagle0.eagle.model.action_result.NotificationT
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.{ApprehendOutlawQuest, Quest}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.{RecruitmentInfo, UnaffiliatedHeroType}
|
||||
|
||||
object ApprehendOutlawCommand {
|
||||
@@ -22,8 +24,10 @@ object ApprehendOutlawCommand {
|
||||
outlawHeroIds: Vector[HeroId],
|
||||
actingHeroId: HeroId,
|
||||
heroIdToApprehend: HeroId,
|
||||
actingProvince: ProvinceT
|
||||
): ProtolessSimpleAction = {
|
||||
actingProvince: ProvinceT,
|
||||
allProvinces: Vector[ProvinceT],
|
||||
questFulfillmentChecker: QuestFulfillmentChecker
|
||||
): ProtolessSequentialResultsAction = {
|
||||
clientRequire(
|
||||
availableHeroIds.contains(actingHeroId),
|
||||
s"Selected hid $actingHeroId was not among $availableHeroIds"
|
||||
@@ -40,7 +44,9 @@ object ApprehendOutlawCommand {
|
||||
gameId = gameId,
|
||||
province = actingProvince,
|
||||
actingHeroId = actingHeroId,
|
||||
targetedHeroId = heroIdToApprehend
|
||||
targetedHeroId = heroIdToApprehend,
|
||||
allProvinces = allProvinces,
|
||||
questFulfillmentChecker = questFulfillmentChecker
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,10 +55,17 @@ object ApprehendOutlawCommand {
|
||||
gameId: GameId,
|
||||
province: ProvinceT,
|
||||
actingHeroId: HeroId,
|
||||
targetedHeroId: HeroId
|
||||
) extends ProtolessSimpleAction {
|
||||
targetedHeroId: HeroId,
|
||||
allProvinces: Vector[ProvinceT],
|
||||
questFulfillmentChecker: QuestFulfillmentChecker
|
||||
) extends ProtolessSequentialResultsAction {
|
||||
|
||||
override def immediateExecute: ActionResultT = {
|
||||
private def apprehendOutlawQuestMatch(quest: Quest): Boolean = quest match {
|
||||
case ApprehendOutlawQuest(outlawHeroId) => outlawHeroId == targetedHeroId
|
||||
case _ => false
|
||||
}
|
||||
|
||||
override def results: Vector[ActionResultT] = {
|
||||
val requestId = s"outlaw_apprehended_${targetedHeroId}_${province.id}"
|
||||
|
||||
val llmRequest = LlmRequestT.OutlawApprehendedMessage(
|
||||
@@ -78,7 +91,7 @@ object ApprehendOutlawCommand {
|
||||
deferred = true
|
||||
)
|
||||
|
||||
ActionResultC(
|
||||
val mainResult = ActionResultC(
|
||||
actionResultType = ActionResultType.OutlawApprehended,
|
||||
actingFactionId = Some(factionId),
|
||||
provinceId = Some(province.id),
|
||||
@@ -112,6 +125,18 @@ object ApprehendOutlawCommand {
|
||||
newNotifications = Vector(notification),
|
||||
newGeneratedTextRequests = Vector(llmRequest)
|
||||
)
|
||||
|
||||
// Check for quest fulfillment - only in provinces ruled by the acting faction
|
||||
val questFulfillmentResults = allProvinces
|
||||
.filter(_.rulingFactionId.contains(factionId))
|
||||
.flatMap { checkedProvince =>
|
||||
questFulfillmentChecker.fulfilledQuestResults(
|
||||
province = checkedProvince,
|
||||
isFulfilled = (quest, _) => apprehendOutlawQuestMatch(quest)
|
||||
)
|
||||
}
|
||||
|
||||
mainResult +: questFulfillmentResults
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,10 +122,11 @@ scala_library(
|
||||
],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:apprehend_outlaw_vigor_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:faction_bias_from_imprisonment",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_component_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
@@ -140,6 +141,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
],
|
||||
)
|
||||
@@ -419,6 +421,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:loyalty_gain_from_feast",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:vigor_gain_from_feast",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:price_index_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
@@ -432,6 +435,8 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -678,6 +683,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:improvement_support_gain",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:improvement_xp_per_stat",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
@@ -695,6 +701,8 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1133,6 +1141,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:rest_vigor_gain",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
@@ -1147,6 +1156,8 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1285,10 +1296,11 @@ scala_library(
|
||||
],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:accept_brotherhood_charisma_xp",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:swear_brotherhood_charisma_xp",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
@@ -1301,6 +1313,8 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -115,7 +115,9 @@ class CommandFactory extends TCommandFactory {
|
||||
availableHeroIds = ac.availableHeroIds,
|
||||
outlawHeroIds = ac.outlaws.map(_.heroId),
|
||||
actingHeroId = sc.actingHeroId,
|
||||
heroIdToApprehend = sc.heroIdToApprehend
|
||||
heroIdToApprehend = sc.heroIdToApprehend,
|
||||
allProvinces = gameState.provinces.values.toVector,
|
||||
questFulfillmentChecker = questFulfillmentChecker(gameState)
|
||||
)
|
||||
case (ac: ArmTroopsAvailable, sc: ArmTroopsSelected) =>
|
||||
ArmTroopsCommand.make(
|
||||
@@ -210,7 +212,8 @@ class CommandFactory extends TCommandFactory {
|
||||
currentRoundId = gameState.currentRoundId,
|
||||
allProvinces = allProvinces(gameState),
|
||||
factions = allFactions(gameState),
|
||||
battalions = allBattalions(gameState)
|
||||
battalions = allBattalions(gameState),
|
||||
heroes = gameState.heroes.values.toVector
|
||||
)
|
||||
case (ac: ExileVassalAvailable, sc: ExileVassalSelected) =>
|
||||
ExileVassalCommand.make(
|
||||
@@ -224,12 +227,13 @@ class CommandFactory extends TCommandFactory {
|
||||
)
|
||||
case (ac: FeastAvailable, _: FeastSelected.type) =>
|
||||
val province = gameState.provinces(ac.actingProvinceId)
|
||||
val faction = gameState.factions(actingFactionId)
|
||||
FeastCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
province = province,
|
||||
rulingFactionHeroes = province.rulingFactionHeroIds
|
||||
.map(gameState.heroes),
|
||||
factionLeaderIds = gameState.factions(actingFactionId).leaderIds,
|
||||
factionLeaderIds = faction.leaderIds,
|
||||
goldCost = ac.goldCost
|
||||
)
|
||||
case (ac: FreeForAllDecisionAvailable, sc: FreeForAllDecisionSelected) =>
|
||||
@@ -321,7 +325,9 @@ class CommandFactory extends TCommandFactory {
|
||||
improvementType = improvementTypeMap(sc.improvementType),
|
||||
availableHeroIds = ac.availableHeroIds,
|
||||
availableImprovementTypes = ac.availableTypes.map(improvementTypeMap),
|
||||
lockImprovementType = sc.lockType
|
||||
lockImprovementType = sc.lockType,
|
||||
factionProvinces = gameState.provinces.values.toVector
|
||||
.filter(_.rulingFactionId.contains(actingFactionId))
|
||||
)
|
||||
case (ac: IssueOrdersAvailable, sc: IssueOrdersSelected) =>
|
||||
IssueOrdersCommand.make(
|
||||
@@ -643,7 +649,9 @@ class CommandFactory extends TCommandFactory {
|
||||
RestCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
provinceId = ac.actingProvinceId,
|
||||
factionHeroesInProvince = factionHeroesInProvince(gameState, ac.actingProvinceId)
|
||||
factionHeroesInProvince = factionHeroesInProvince(gameState, ac.actingProvinceId),
|
||||
factionProvinces = gameState.provinces.values.toVector
|
||||
.filter(_.rulingFactionId.contains(actingFactionId))
|
||||
)
|
||||
case (ac: ReturnAvailable, _: ReturnSelected.type) =>
|
||||
ReturnCommand.make(
|
||||
@@ -703,7 +711,9 @@ class CommandFactory extends TCommandFactory {
|
||||
factionHeadHeroId = gameState.factions(actingFactionId).factionHeadId,
|
||||
currentDate = gameState.currentDate.get,
|
||||
currentRoundId = gameState.currentRoundId,
|
||||
gameId = gameState.gameId
|
||||
gameId = gameState.gameId,
|
||||
allProvinces = allProvinces(gameState),
|
||||
questFulfillmentChecker = questFulfillmentChecker(gameState)
|
||||
)
|
||||
|
||||
case (ac: TradeAvailable, sc: TradeSelected) =>
|
||||
|
||||
@@ -18,12 +18,19 @@ import net.eagle0.eagle.model.action_result.NotificationDetails.Divined
|
||||
import net.eagle0.eagle.model.action_result.NotificationT.Llm
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.{
|
||||
AlmsToProvinceQuest,
|
||||
DefeatFactionQuest,
|
||||
GiveToHeroesInProvinceQuest,
|
||||
ImproveAgricultureQuest,
|
||||
ImproveEconomyQuest,
|
||||
ImproveInfrastructureQuest,
|
||||
SpecificExpansionQuest,
|
||||
StartBlizzardQuest,
|
||||
StartDroughtQuest,
|
||||
StartEpidemicQuest,
|
||||
TruceWithFactionQuest
|
||||
}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroT
|
||||
@@ -43,7 +50,8 @@ object DivineCommand {
|
||||
currentRoundId: RoundId,
|
||||
allProvinces: Vector[ProvinceT],
|
||||
factions: Vector[FactionT],
|
||||
battalions: Vector[BattalionT]
|
||||
battalions: Vector[BattalionT],
|
||||
heroes: Vector[HeroT]
|
||||
): ProtolessRandomSimpleAction = {
|
||||
commandRequire(
|
||||
selectedHeroIds.distinct.size == selectedHeroIds.size,
|
||||
@@ -75,7 +83,8 @@ object DivineCommand {
|
||||
currentRoundId = currentRoundId,
|
||||
allProvinces = allProvinces,
|
||||
factions = factions,
|
||||
battalions = battalions
|
||||
battalions = battalions,
|
||||
heroes = heroes
|
||||
)
|
||||
}
|
||||
|
||||
@@ -87,7 +96,8 @@ object DivineCommand {
|
||||
currentRoundId: RoundId,
|
||||
allProvinces: Vector[ProvinceT],
|
||||
factions: Vector[FactionT],
|
||||
battalions: Vector[BattalionT]
|
||||
battalions: Vector[BattalionT],
|
||||
heroes: Vector[HeroT]
|
||||
) extends ProtolessRandomSimpleAction {
|
||||
override def immediateExecute(
|
||||
functionalRandom: FunctionalRandom
|
||||
@@ -111,6 +121,7 @@ object DivineCommand {
|
||||
allProvinces = allProvinces,
|
||||
factions = factions,
|
||||
battalions = battalions,
|
||||
heroes = heroes,
|
||||
functionalRandom = fr
|
||||
)
|
||||
.map { newRI =>
|
||||
@@ -142,13 +153,19 @@ object DivineCommand {
|
||||
case (updatedUH, llmRequest) =>
|
||||
val updatedProvinceIds: Vector[ProvinceId] =
|
||||
updatedUH.quest.collect {
|
||||
case SpecificExpansionQuest(provinceId) =>
|
||||
case SpecificExpansionQuest(provinceId) =>
|
||||
Vector(provinceId)
|
||||
case DefeatFactionQuest(targetFactionId) =>
|
||||
case ImproveAgricultureQuest(provinceId, _) =>
|
||||
Vector(provinceId)
|
||||
case ImproveEconomyQuest(provinceId, _) =>
|
||||
Vector(provinceId)
|
||||
case ImproveInfrastructureQuest(provinceId, _) =>
|
||||
Vector(provinceId)
|
||||
case DefeatFactionQuest(targetFactionId) =>
|
||||
FactionUtils
|
||||
.provinces(targetFactionId, allProvinces)
|
||||
.map(_.id)
|
||||
case TruceWithFactionQuest(targetFactionId) =>
|
||||
case TruceWithFactionQuest(targetFactionId) =>
|
||||
FactionUtils
|
||||
.provinces(targetFactionId, allProvinces)
|
||||
.map(_.id)
|
||||
@@ -166,6 +183,12 @@ object DivineCommand {
|
||||
_ /* totalGold */
|
||||
) =>
|
||||
Vector(provinceId)
|
||||
case StartBlizzardQuest(provinceId) =>
|
||||
Vector(provinceId)
|
||||
case StartDroughtQuest(provinceId) =>
|
||||
Vector(provinceId)
|
||||
case StartEpidemicQuest(provinceId) =>
|
||||
Vector(provinceId)
|
||||
}
|
||||
.getOrElse(Vector())
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.eagle0.eagle.library.actions.impl.command
|
||||
import net.eagle0.eagle.{FactionId, HeroId}
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSimpleAction
|
||||
import net.eagle0.eagle.library.settings.{LoyaltyGainFromFeast, VigorGainFromFeast}
|
||||
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentUtils
|
||||
import net.eagle0.eagle.library.util.PriceIndexUtils
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedHeroC, StatDelta, StatNoChange}
|
||||
@@ -10,6 +11,7 @@ import net.eagle0.eagle.model.action_result.types.ActionResultType.Feast
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.SpendOnFeastsQuest
|
||||
|
||||
object FeastCommand {
|
||||
def make(
|
||||
@@ -56,18 +58,43 @@ object FeastCommand {
|
||||
)
|
||||
)
|
||||
|
||||
private def changedProvinces: Vector[ChangedProvinceC] =
|
||||
Vector(
|
||||
ChangedProvinceC(
|
||||
provinceId = province.id,
|
||||
goldDelta = Some(-goldCost),
|
||||
newPriceIndex = PriceIndexUtils.maybeNewPriceIndexFromGoldSpend(
|
||||
currentPriceIndex = province.priceIndex,
|
||||
goldSpent = goldCost
|
||||
)
|
||||
)
|
||||
private def changedProvinces: Vector[ChangedProvinceC] = {
|
||||
// Increment SpendOnFeastsQuest progress by goldCost for unaffiliated hero in this province
|
||||
val questProgressChange = QuestFulfillmentUtils.counterIncrementedCP(
|
||||
province = province,
|
||||
incrementAmount = goldCost,
|
||||
check = (uh, _) =>
|
||||
uh.quest.exists {
|
||||
case q: SpendOnFeastsQuest => q.componentsFulfilled < q.componentCount
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
||||
questProgressChange match {
|
||||
case Some(cp) =>
|
||||
Vector(
|
||||
cp.copy(
|
||||
goldDelta = Some(-goldCost),
|
||||
newPriceIndex = PriceIndexUtils.maybeNewPriceIndexFromGoldSpend(
|
||||
currentPriceIndex = province.priceIndex,
|
||||
goldSpent = goldCost
|
||||
)
|
||||
)
|
||||
)
|
||||
case None =>
|
||||
Vector(
|
||||
ChangedProvinceC(
|
||||
provinceId = province.id,
|
||||
goldDelta = Some(-goldCost),
|
||||
newPriceIndex = PriceIndexUtils.maybeNewPriceIndexFromGoldSpend(
|
||||
currentPriceIndex = province.priceIndex,
|
||||
goldSpent = goldCost
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override def immediateExecute: ActionResultT =
|
||||
ActionResultC(
|
||||
actionResultType = Feast,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
import net.eagle0.eagle.{FactionId, HeroId}
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSimpleAction
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
@@ -10,6 +12,7 @@ import net.eagle0.eagle.library.settings.{
|
||||
ImprovementSupportGain,
|
||||
ImprovementXpPerStat
|
||||
}
|
||||
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentUtils
|
||||
import net.eagle0.eagle.library.util.EagleRequire.commandRequire
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.{ChangedProvinceC, NewLockedImprovementType}
|
||||
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedHeroC, StatDelta}
|
||||
@@ -17,6 +20,8 @@ import net.eagle0.eagle.model.action_result.types.ActionResultType.Improve
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.state.hero.{HeroT, Profession}
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.RepairDevastationQuest
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroT
|
||||
import net.eagle0.eagle.model.state.ImprovementType
|
||||
|
||||
object ImproveCommand {
|
||||
@@ -27,7 +32,8 @@ object ImproveCommand {
|
||||
improvementType: ImprovementType,
|
||||
lockImprovementType: Boolean,
|
||||
availableHeroIds: Vector[HeroId],
|
||||
availableImprovementTypes: Vector[ImprovementType]
|
||||
availableImprovementTypes: Vector[ImprovementType],
|
||||
factionProvinces: Vector[ProvinceT]
|
||||
): ProtolessSimpleAction = {
|
||||
commandRequire(
|
||||
availableHeroIds.contains(actingHero.id),
|
||||
@@ -44,7 +50,8 @@ object ImproveCommand {
|
||||
actingHero = actingHero,
|
||||
province = actingProvince,
|
||||
improvementType = improvementType,
|
||||
lockType = lockImprovementType
|
||||
lockType = lockImprovementType,
|
||||
factionProvinces = factionProvinces
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,7 +60,8 @@ object ImproveCommand {
|
||||
province: ProvinceT,
|
||||
actingHero: HeroT,
|
||||
improvementType: ImprovementType,
|
||||
lockType: Boolean
|
||||
lockType: Boolean,
|
||||
factionProvinces: Vector[ProvinceT]
|
||||
) extends ProtolessSimpleAction {
|
||||
private val maxImprovement = 100.0
|
||||
|
||||
@@ -130,7 +138,7 @@ object ImproveCommand {
|
||||
agilityXpDelta = Some(ImprovementXpPerStat.intValue)
|
||||
)
|
||||
|
||||
ActionResultC(
|
||||
val baseResult = ActionResultC(
|
||||
actionResultType = Improve,
|
||||
actingFactionId = Some(factionId),
|
||||
provinceId = Some(provinceId),
|
||||
@@ -138,8 +146,33 @@ object ImproveCommand {
|
||||
changedHeroes = Vector(leaderAfter),
|
||||
changedProvinces = Vector(changedProvince)
|
||||
)
|
||||
|
||||
// Increment RepairDevastationQuest progress when repairing devastation
|
||||
improvementType match {
|
||||
case ImprovementType.Devastation =>
|
||||
repairDevastationQuestIncrementer(withProfessionBonus.toInt)(baseResult)
|
||||
case _ => baseResult
|
||||
}
|
||||
}
|
||||
|
||||
private def repairDevastationQuestCheck(
|
||||
uh: UnaffiliatedHeroT,
|
||||
@unused province: ProvinceT
|
||||
): Boolean =
|
||||
uh.quest.exists {
|
||||
case q: RepairDevastationQuest => q.componentsFulfilled < q.componentCount
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private def repairDevastationQuestIncrementer(
|
||||
devastationRepaired: Int
|
||||
): ActionResultT => ActionResultT =
|
||||
QuestFulfillmentUtils.withCountersIncremented(
|
||||
provinces = factionProvinces,
|
||||
incrementAmount = devastationRepaired,
|
||||
check = repairDevastationQuestCheck
|
||||
)
|
||||
|
||||
private def ImprovementFromZero(actor: HeroT): Double =
|
||||
ImprovementPerStat.doubleValue * (actor.strength + actor.agility) / 2.0
|
||||
|
||||
|
||||
+6
@@ -271,6 +271,9 @@ object ManagePrisonersCommand {
|
||||
) ++ questFulfillmentChecker.failedQuestResults(
|
||||
province = checkedProvince,
|
||||
isFailed = (quest, _ /*questHolderProvince*/ ) => returnPrisonerQuestMatch(quest)
|
||||
) ++ questFulfillmentChecker.failedQuestResults(
|
||||
province = checkedProvince,
|
||||
isFailed = (quest, _ /*questHolderProvince*/ ) => releaseAllPrisonersQuestMatch(quest)
|
||||
)
|
||||
}
|
||||
case Execute =>
|
||||
@@ -321,6 +324,9 @@ object ManagePrisonersCommand {
|
||||
) ++ questFulfillmentChecker.failedQuestResults(
|
||||
province = checkedProvince,
|
||||
isFailed = (quest, _ /*questHolderProvince*/ ) => returnPrisonerQuestMatch(quest)
|
||||
) ++ questFulfillmentChecker.failedQuestResults(
|
||||
province = checkedProvince,
|
||||
isFailed = (quest, _ /*questHolderProvince*/ ) => releaseAllPrisonersQuestMatch(quest)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user