mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 04:56:26 +00:00
Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d55bb8436 | ||
|
|
7a31d34aba | ||
|
|
9a4dda0eea | ||
|
|
353eb3907c | ||
|
|
ded06ba815 | ||
|
|
70392b109a | ||
|
|
821e3f1a4a | ||
|
|
ebe819a5a1 | ||
|
|
3e868d0305 | ||
|
|
e46867333c | ||
|
|
2d59d4ff51 | ||
|
|
d493182184 | ||
|
|
8ce4e4c4a9 | ||
|
|
9729110ea8 | ||
|
|
2224e78a36 | ||
|
|
007a57eea2 | ||
|
|
3febf2a6cd | ||
|
|
26204cc879 | ||
|
|
7032260a31 | ||
|
|
23ab715ffc | ||
|
|
73f42d5b51 | ||
|
|
3cb61f69f4 | ||
|
|
62fff3c0ea | ||
|
|
ca86926a25 | ||
|
|
1d334c91ae | ||
|
|
64e1e46e0a | ||
|
|
4722a40384 | ||
|
|
6653a14660 | ||
|
|
056571651e | ||
|
|
d8224e7886 | ||
|
|
9cc9a65e4a | ||
|
|
025563b607 | ||
|
|
030f6b2c2e | ||
|
|
1d5dd7b971 | ||
|
|
649e80c4ac | ||
|
|
a1b4e41553 | ||
|
|
1c24474a0b | ||
|
|
26eec6cabc | ||
|
|
d134f40438 | ||
|
|
a638dd26ca | ||
|
|
5e93b0eaa0 | ||
|
|
6940312d3f | ||
|
|
243f0d43e7 | ||
|
|
1ddd015449 | ||
|
|
da85d0983a |
@@ -112,6 +112,16 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Copy update-env script to server
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.DO_DROPLET_IP }}
|
||||
username: deploy
|
||||
key: ${{ secrets.DO_SSH_KEY }}
|
||||
source: "deploy/update-env.sh,deploy/env.template"
|
||||
target: /opt/eagle0/
|
||||
strip_components: 1
|
||||
|
||||
- name: Deploy auth service to production
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
@@ -124,36 +134,18 @@ jobs:
|
||||
set -x
|
||||
cd /opt/eagle0
|
||||
|
||||
# Update AUTH_IMAGE in .env file (preserve other vars)
|
||||
if [ -f .env ]; then
|
||||
# Remove old AUTH_IMAGE line and add new one
|
||||
grep -v '^AUTH_IMAGE=' .env > .env.tmp || true
|
||||
echo "AUTH_IMAGE=${AUTH_IMAGE}" >> .env.tmp
|
||||
# Also update OAuth credentials
|
||||
grep -v '^DISCORD_CLIENT_ID=' .env.tmp > .env.tmp2 || true
|
||||
echo "DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID:-}" >> .env.tmp2
|
||||
grep -v '^DISCORD_CLIENT_SECRET=' .env.tmp2 > .env.tmp3 || true
|
||||
echo "DISCORD_CLIENT_SECRET=${DISCORD_CLIENT_SECRET:-}" >> .env.tmp3
|
||||
grep -v '^GOOGLE_CLIENT_ID=' .env.tmp3 > .env.tmp4 || true
|
||||
echo "GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}" >> .env.tmp4
|
||||
grep -v '^GOOGLE_CLIENT_SECRET=' .env.tmp4 > .env.tmp5 || true
|
||||
echo "GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}" >> .env.tmp5
|
||||
# Update JWT private key (JWK format for auth service bootstrap)
|
||||
grep -v '^JWT_PRIVATE_KEY=' .env.tmp5 > .env.tmp6 || true
|
||||
echo "JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:-}" >> .env.tmp6
|
||||
# Update Fastmail JMAP credentials for email sending
|
||||
grep -v '^FASTMAIL_API_TOKEN=' .env.tmp6 > .env.tmp7 || true
|
||||
echo "FASTMAIL_API_TOKEN=${FASTMAIL_API_TOKEN:-}" >> .env.tmp7
|
||||
grep -v '^FASTMAIL_FROM_EMAIL=' .env.tmp7 > .env.tmp8 || true
|
||||
echo "FASTMAIL_FROM_EMAIL=${FASTMAIL_FROM_EMAIL:-}" >> .env.tmp8
|
||||
grep -v '^FASTMAIL_FROM_NAME=' .env.tmp8 > .env || true
|
||||
echo "FASTMAIL_FROM_NAME=${FASTMAIL_FROM_NAME:-}" >> .env
|
||||
rm -f .env.tmp .env.tmp2 .env.tmp3 .env.tmp4 .env.tmp5 .env.tmp6 .env.tmp7 .env.tmp8
|
||||
chmod 600 .env
|
||||
else
|
||||
echo "ERROR: .env file not found. Run main deploy first."
|
||||
exit 1
|
||||
fi
|
||||
# Update env vars using shared script (preserves vars set by other workflows)
|
||||
chmod +x update-env.sh
|
||||
./update-env.sh \
|
||||
"AUTH_IMAGE=${AUTH_IMAGE}" \
|
||||
"DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}" \
|
||||
"DISCORD_CLIENT_SECRET=${DISCORD_CLIENT_SECRET}" \
|
||||
"GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}" \
|
||||
"GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}" \
|
||||
"JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY}" \
|
||||
"FASTMAIL_API_TOKEN=${FASTMAIL_API_TOKEN}" \
|
||||
"FASTMAIL_FROM_EMAIL=${FASTMAIL_FROM_EMAIL}" \
|
||||
"FASTMAIL_FROM_NAME=${FASTMAIL_FROM_NAME}"
|
||||
|
||||
# Login to registry
|
||||
echo "${{ secrets.DO_REGISTRY_TOKEN }}" | docker login registry.digitalocean.com -u "${{ secrets.DO_REGISTRY_TOKEN }}" --password-stdin
|
||||
|
||||
+153
-431
@@ -26,203 +26,71 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-eagle:
|
||||
# Single consolidated build job - builds all images with one bazel invocation
|
||||
# This uses 1 runner slot instead of 4, and Bazel parallelizes internally
|
||||
build-all:
|
||||
runs-on: self-hosted
|
||||
outputs:
|
||||
image_tag: ${{ steps.push-eagle.outputs.image_tag }}
|
||||
eagle_image_tag: ${{ steps.push-images.outputs.eagle_image_tag }}
|
||||
shardok_image_tag: ${{ steps.push-images.outputs.shardok_image_tag }}
|
||||
admin_image_tag: ${{ steps.push-images.outputs.admin_image_tag }}
|
||||
jfr_sidecar_image_tag: ${{ steps.push-images.outputs.jfr_sidecar_image_tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: false
|
||||
|
||||
- name: Build Eagle Docker image
|
||||
id: build-eagle
|
||||
- name: Build all Docker images
|
||||
id: build-all
|
||||
run: |
|
||||
set -ex
|
||||
bazel build --platforms=//:linux_x86_64 //ci:eagle_server_image
|
||||
|
||||
# Also build the warmup tool for Linux
|
||||
bazel build //src/main/go/net/eagle0/warmup:warmup_linux_amd64
|
||||
# Build ALL images in a single bazel command - Bazel parallelizes internally
|
||||
# This is much more efficient than 4 separate GitHub Actions jobs
|
||||
echo "=== Building all Docker images ==="
|
||||
bazel build \
|
||||
--platforms=//:linux_x86_64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux//:all \
|
||||
//ci:eagle_server_image \
|
||||
//ci:shardok_server_image \
|
||||
//ci:admin_server_image \
|
||||
//ci:jfr_sidecar_image \
|
||||
//src/main/go/net/eagle0/warmup:warmup_linux_amd64
|
||||
|
||||
# Copy warmup binary to scripts/ so it gets deployed with other scripts
|
||||
# Copy warmup binary to scripts/ for deployment
|
||||
mkdir -p scripts/bin
|
||||
cp bazel-bin/src/main/go/net/eagle0/warmup/warmup_linux_amd64_/warmup_linux_amd64 scripts/bin/warmup
|
||||
|
||||
# Save the resolved path before any other bazel command changes bazel-bin symlink
|
||||
IMAGE_PATH=$(readlink -f bazel-bin/ci/eagle_server_image)
|
||||
echo "Image path: $IMAGE_PATH"
|
||||
echo "image_path=$IMAGE_PATH" >> $GITHUB_OUTPUT
|
||||
# Save all image paths before any other bazel command changes bazel-bin symlink
|
||||
EAGLE_PATH=$(readlink -f bazel-bin/ci/eagle_server_image)
|
||||
SHARDOK_PATH=$(readlink -f bazel-bin/ci/shardok_server_image)
|
||||
ADMIN_PATH=$(readlink -f bazel-bin/ci/admin_server_image)
|
||||
JFR_PATH=$(readlink -f bazel-bin/ci/jfr_sidecar_image)
|
||||
|
||||
- name: Login to DigitalOcean Container Registry
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DO_TOKEN: ${{ secrets.DO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
AUTH=$(echo -n "${DO_TOKEN}:${DO_TOKEN}" | base64)
|
||||
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
|
||||
# Also set for current directory in case Bazel uses different home
|
||||
mkdir -p .docker
|
||||
cp ~/.docker/config.json .docker/
|
||||
echo "eagle_path=$EAGLE_PATH" >> $GITHUB_OUTPUT
|
||||
echo "shardok_path=$SHARDOK_PATH" >> $GITHUB_OUTPUT
|
||||
echo "admin_path=$ADMIN_PATH" >> $GITHUB_OUTPUT
|
||||
echo "jfr_path=$JFR_PATH" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Push Eagle image to DO registry
|
||||
id: push-eagle
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DOCKER_CONFIG: ${{ github.workspace }}/.docker
|
||||
run: |
|
||||
set -ex
|
||||
echo "=== Image paths ==="
|
||||
echo "Eagle: $EAGLE_PATH"
|
||||
echo "Shardok: $SHARDOK_PATH"
|
||||
echo "Admin: $ADMIN_PATH"
|
||||
echo "JFR Sidecar: $JFR_PATH"
|
||||
|
||||
# Use cross-compiled image path from build step
|
||||
EAGLE_IMAGE="${{ steps.build-eagle.outputs.image_path }}"
|
||||
echo "Using Eagle image: $EAGLE_IMAGE"
|
||||
|
||||
if [ -z "$EAGLE_IMAGE" ] || [ ! -d "$EAGLE_IMAGE" ]; then
|
||||
echo "ERROR: Eagle image not found at: $EAGLE_IMAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Debug: show OCI layout contents
|
||||
echo "=== OCI Layout Contents ==="
|
||||
cat "$EAGLE_IMAGE/index.json"
|
||||
echo ""
|
||||
echo "=== Blobs ==="
|
||||
ls -la "$EAGLE_IMAGE/blobs/sha256/" | head -20
|
||||
|
||||
# Verify OCI layout consistency before pushing
|
||||
echo "=== Verifying OCI layout consistency ==="
|
||||
for digest in $(cat "$EAGLE_IMAGE/index.json" | grep -o '"sha256:[^"]*"' | tr -d '"'); do
|
||||
blob_path="$EAGLE_IMAGE/blobs/${digest/://}"
|
||||
if [ ! -f "$blob_path" ]; then
|
||||
echo "ERROR: Blob not found: $blob_path"
|
||||
exit 1
|
||||
fi
|
||||
actual_digest="sha256:$(shasum -a 256 "$blob_path" | cut -d' ' -f1)"
|
||||
if [ "$digest" != "$actual_digest" ]; then
|
||||
echo "ERROR: Digest mismatch for $blob_path"
|
||||
echo " Index says: $digest"
|
||||
echo " Actual: $actual_digest"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Verified: $digest"
|
||||
done
|
||||
|
||||
# Build the push target to get crane in runfiles
|
||||
bazel build //ci:eagle_server_push
|
||||
|
||||
# Use crane directly for push (avoids OCI->Docker digest mismatch)
|
||||
CRANE="bazel-bin/ci/push_eagle_server_push.sh.runfiles/rules_oci~~oci~oci_crane_darwin_arm64/crane"
|
||||
echo "Using crane: $CRANE"
|
||||
|
||||
# Push with SHA tag
|
||||
GIT_SHA=$(git rev-parse --short=8 HEAD)
|
||||
IMAGE_TAG="registry.digitalocean.com/eagle0/eagle-server:${GIT_SHA}"
|
||||
echo "Pushing eagle image: $IMAGE_TAG"
|
||||
$CRANE push "$EAGLE_IMAGE" "$IMAGE_TAG"
|
||||
|
||||
# Verify push by checking what's in the registry
|
||||
echo "=== Verifying push ==="
|
||||
$CRANE manifest "$IMAGE_TAG" | head -50
|
||||
PUSHED_DIGEST=$($CRANE digest "$IMAGE_TAG")
|
||||
echo "Registry reports digest: $PUSHED_DIGEST"
|
||||
|
||||
# Output the full image tag for deploy step
|
||||
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# Also update :latest for convenience (but deploy won't use it)
|
||||
echo "Copying to :latest tag"
|
||||
$CRANE copy "$IMAGE_TAG" "registry.digitalocean.com/eagle0/eagle-server:latest"
|
||||
|
||||
build-shardok:
|
||||
runs-on: self-hosted
|
||||
outputs:
|
||||
image_tag: ${{ steps.push-shardok.outputs.image_tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: false
|
||||
|
||||
- name: Build Shardok binary (cross-compile for Linux)
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
# Step 1: Build JUST the binary with cross-compilation
|
||||
# We need --extra_toolchains to force the Linux toolchain to be used
|
||||
# because toolchains_llvm registers with dev_dependency=True
|
||||
echo "=== Building shardok-server binary for linux-x86_64 ==="
|
||||
bazel build \
|
||||
--platforms=//:linux_x86_64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux//:all \
|
||||
//src/main/cpp/net/eagle0/shardok:shardok-server
|
||||
|
||||
# Step 2: Check the binary directly from bazel-bin
|
||||
# bazel-bin is a symlink that points to the correct output directory
|
||||
LINUX_BIN="bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server"
|
||||
echo "=== Checking binary at: $LINUX_BIN ==="
|
||||
|
||||
if [ ! -f "$LINUX_BIN" ]; then
|
||||
echo "ERROR: Binary not found at $LINUX_BIN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Debug: show what bazel-bin points to
|
||||
echo "bazel-bin symlink target: $(readlink bazel-bin || echo 'not a symlink')"
|
||||
|
||||
# Step 3: Verify it's ELF (Linux) not Mach-O (macOS)
|
||||
echo "=== Verifying binary format ==="
|
||||
MAGIC=$(head -c 4 "$LINUX_BIN" | xxd -p)
|
||||
echo "Binary magic bytes: $MAGIC"
|
||||
|
||||
if [ "$MAGIC" = "7f454c46" ]; then
|
||||
echo "SUCCESS: Binary is ELF format (Linux)"
|
||||
elif [ "$MAGIC" = "cfaeedfe" ] || [ "$MAGIC" = "cffaedfe" ]; then
|
||||
echo "ERROR: Binary is Mach-O format (macOS) - cross-compilation failed!"
|
||||
echo ""
|
||||
echo "Debug info:"
|
||||
echo "- bazel-bin points to: $(readlink bazel-bin)"
|
||||
file "$LINUX_BIN" || true
|
||||
exit 1
|
||||
else
|
||||
echo "WARNING: Unknown binary format: $MAGIC"
|
||||
file "$LINUX_BIN" || true
|
||||
fi
|
||||
|
||||
- name: Build Shardok Docker image
|
||||
id: build-shardok
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
# Build the OCI image with cross-compilation flags
|
||||
bazel build \
|
||||
--platforms=//:linux_x86_64 \
|
||||
--extra_toolchains=@llvm_toolchain_linux//:all \
|
||||
//ci:shardok_server_image
|
||||
|
||||
# The image is output to bazel-bin which is a symlink.
|
||||
# Resolve it now before any other bazel commands change where it points.
|
||||
IMAGE_PATH=$(readlink -f bazel-bin/ci/shardok_server_image)
|
||||
echo "Image path: $IMAGE_PATH"
|
||||
echo "image_path=$IMAGE_PATH" >> $GITHUB_OUTPUT
|
||||
|
||||
# Verify the binary inside the tar layer is ELF
|
||||
echo "=== Verifying binary in image tar ==="
|
||||
# Verify Shardok binary is ELF (Linux) format
|
||||
echo "=== Verifying Shardok binary format ==="
|
||||
BINARY_TAR="bazel-bin/ci/shardok_binary_layer.tar"
|
||||
if [ -f "$BINARY_TAR" ]; then
|
||||
echo "Checking binary in $BINARY_TAR"
|
||||
# Extract just the first 4 bytes of the binary from the tar
|
||||
MAGIC=$(tar -xOf "$BINARY_TAR" app/shardok-server 2>/dev/null | head -c 4 | xxd -p)
|
||||
echo "Binary magic in tar: $MAGIC"
|
||||
if [ "$MAGIC" = "7f454c46" ]; then
|
||||
echo "SUCCESS: Binary in tar is ELF format (Linux)"
|
||||
echo "SUCCESS: Binary is ELF format (Linux)"
|
||||
else
|
||||
echo "ERROR: Binary in tar is NOT ELF format!"
|
||||
echo "This means pkg_tar is packaging the wrong binary."
|
||||
echo "ERROR: Binary is NOT ELF format!"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "WARNING: Could not find $BINARY_TAR"
|
||||
fi
|
||||
|
||||
- name: Login to DigitalOcean Container Registry
|
||||
@@ -233,210 +101,79 @@ jobs:
|
||||
mkdir -p ~/.docker
|
||||
AUTH=$(echo -n "${DO_TOKEN}:${DO_TOKEN}" | base64)
|
||||
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
|
||||
# Also set for current directory in case Bazel uses different home
|
||||
mkdir -p .docker
|
||||
cp ~/.docker/config.json .docker/
|
||||
|
||||
- name: Push Shardok image to DO registry
|
||||
id: push-shardok
|
||||
- name: Push all images to DO registry
|
||||
id: push-images
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DOCKER_CONFIG: ${{ github.workspace }}/.docker
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
# Use cross-compiled image path from build step
|
||||
CROSS_IMAGE="${{ steps.build-shardok.outputs.image_path }}"
|
||||
echo "Using cross-compiled image: $CROSS_IMAGE"
|
||||
GIT_SHA=$(git rev-parse --short=8 HEAD)
|
||||
|
||||
if [ -z "$CROSS_IMAGE" ] || [ ! -d "$CROSS_IMAGE" ]; then
|
||||
echo "ERROR: Cross-compiled image not found at: $CROSS_IMAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get crane from Eagle push target (which doesn't need cross-compilation)
|
||||
# This gives us a macOS crane binary we can actually run.
|
||||
# We can't build shardok_server_push with platform flags because it would
|
||||
# download a Linux crane that can't run on macOS.
|
||||
# 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"
|
||||
|
||||
# Find the Darwin crane binary (may be a symlink)
|
||||
RUNFILES="bazel-bin/ci/push_eagle_server_push.sh.runfiles"
|
||||
CRANE=$(find "$RUNFILES" -path "*darwin*" -name crane 2>/dev/null | head -1)
|
||||
if [ -z "$CRANE" ]; then
|
||||
# Fallback to any crane
|
||||
CRANE=$(find "$RUNFILES" -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. Listing runfiles:"
|
||||
find "$RUNFILES" -name crane 2>/dev/null || true
|
||||
echo "ERROR: crane not found"
|
||||
exit 1
|
||||
fi
|
||||
echo "Using crane: $CRANE"
|
||||
|
||||
# Push the cross-compiled image with SHA tag
|
||||
GIT_SHA=$(git rev-parse --short=8 HEAD)
|
||||
IMAGE_TAG="registry.digitalocean.com/eagle0/shardok-server:${GIT_SHA}"
|
||||
echo "Pushing shardok image: $IMAGE_TAG"
|
||||
$CRANE push "$CROSS_IMAGE" "$IMAGE_TAG"
|
||||
# Push Eagle image
|
||||
EAGLE_IMAGE="${{ steps.build-all.outputs.eagle_path }}"
|
||||
EAGLE_TAG="registry.digitalocean.com/eagle0/eagle-server:${GIT_SHA}"
|
||||
echo "Pushing Eagle: $EAGLE_TAG"
|
||||
$CRANE push "$EAGLE_IMAGE" "$EAGLE_TAG"
|
||||
$CRANE copy "$EAGLE_TAG" "registry.digitalocean.com/eagle0/eagle-server:latest"
|
||||
echo "eagle_image_tag=$EAGLE_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# Output the full image tag for deploy step
|
||||
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||
# Push Shardok image
|
||||
SHARDOK_IMAGE="${{ steps.build-all.outputs.shardok_path }}"
|
||||
SHARDOK_TAG="registry.digitalocean.com/eagle0/shardok-server:${GIT_SHA}"
|
||||
echo "Pushing Shardok: $SHARDOK_TAG"
|
||||
$CRANE push "$SHARDOK_IMAGE" "$SHARDOK_TAG"
|
||||
$CRANE copy "$SHARDOK_TAG" "registry.digitalocean.com/eagle0/shardok-server:latest"
|
||||
echo "shardok_image_tag=$SHARDOK_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# Also update :latest for convenience (but deploy won't use it)
|
||||
echo "Copying to :latest tag"
|
||||
$CRANE copy "$IMAGE_TAG" "registry.digitalocean.com/eagle0/shardok-server:latest"
|
||||
# Push Admin image
|
||||
ADMIN_IMAGE="${{ steps.build-all.outputs.admin_path }}"
|
||||
ADMIN_TAG="registry.digitalocean.com/eagle0/admin-server:${GIT_SHA}"
|
||||
echo "Pushing Admin: $ADMIN_TAG"
|
||||
$CRANE push "$ADMIN_IMAGE" "$ADMIN_TAG"
|
||||
$CRANE copy "$ADMIN_TAG" "registry.digitalocean.com/eagle0/admin-server:latest"
|
||||
echo "admin_image_tag=$ADMIN_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
build-admin:
|
||||
runs-on: self-hosted
|
||||
outputs:
|
||||
image_tag: ${{ steps.push-admin.outputs.image_tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: false
|
||||
# Push JFR Sidecar image
|
||||
JFR_IMAGE="${{ steps.build-all.outputs.jfr_path }}"
|
||||
JFR_TAG="registry.digitalocean.com/eagle0/jfr-sidecar:${GIT_SHA}"
|
||||
echo "Pushing JFR Sidecar: $JFR_TAG"
|
||||
$CRANE push "$JFR_IMAGE" "$JFR_TAG"
|
||||
$CRANE copy "$JFR_TAG" "registry.digitalocean.com/eagle0/jfr-sidecar:latest"
|
||||
echo "jfr_sidecar_image_tag=$JFR_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build Admin Server Docker image
|
||||
id: build-admin
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
# Build admin server image (Go binary has explicit goos/goarch in BUILD.bazel)
|
||||
bazel build //ci:admin_server_image
|
||||
|
||||
# Save the resolved path before any other bazel command changes bazel-bin symlink
|
||||
IMAGE_PATH=$(readlink -f bazel-bin/ci/admin_server_image)
|
||||
echo "Image path: $IMAGE_PATH"
|
||||
echo "image_path=$IMAGE_PATH" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Login to DigitalOcean Container Registry
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DO_TOKEN: ${{ secrets.DO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
AUTH=$(echo -n "${DO_TOKEN}:${DO_TOKEN}" | base64)
|
||||
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
|
||||
mkdir -p .docker
|
||||
cp ~/.docker/config.json .docker/
|
||||
|
||||
- name: Push Admin image to DO registry
|
||||
id: push-admin
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DOCKER_CONFIG: ${{ github.workspace }}/.docker
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
ADMIN_IMAGE="${{ steps.build-admin.outputs.image_path }}"
|
||||
echo "Using Admin image: $ADMIN_IMAGE"
|
||||
|
||||
if [ -z "$ADMIN_IMAGE" ] || [ ! -d "$ADMIN_IMAGE" ]; then
|
||||
echo "ERROR: Admin image not found at: $ADMIN_IMAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the push target to get crane in runfiles
|
||||
bazel build //ci:admin_server_push
|
||||
|
||||
# Use crane directly for push
|
||||
CRANE="bazel-bin/ci/push_admin_server_push.sh.runfiles/rules_oci~~oci~oci_crane_darwin_arm64/crane"
|
||||
echo "Using crane: $CRANE"
|
||||
|
||||
# Push with SHA tag
|
||||
GIT_SHA=$(git rev-parse --short=8 HEAD)
|
||||
IMAGE_TAG="registry.digitalocean.com/eagle0/admin-server:${GIT_SHA}"
|
||||
echo "Pushing admin image: $IMAGE_TAG"
|
||||
$CRANE push "$ADMIN_IMAGE" "$IMAGE_TAG"
|
||||
|
||||
# Output the full image tag for deploy step
|
||||
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# Also update :latest for convenience
|
||||
echo "Copying to :latest tag"
|
||||
$CRANE copy "$IMAGE_TAG" "registry.digitalocean.com/eagle0/admin-server:latest"
|
||||
|
||||
build-jfr-sidecar:
|
||||
runs-on: self-hosted
|
||||
outputs:
|
||||
image_tag: ${{ steps.push-jfr-sidecar.outputs.image_tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: false
|
||||
|
||||
- name: Build JFR Sidecar Docker image
|
||||
id: build-jfr-sidecar
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
# Build JFR sidecar image (Go binary has explicit goos/goarch in BUILD.bazel)
|
||||
bazel build //ci:jfr_sidecar_image
|
||||
|
||||
# Save the resolved path before any other bazel command changes bazel-bin symlink
|
||||
IMAGE_PATH=$(readlink -f bazel-bin/ci/jfr_sidecar_image)
|
||||
echo "Image path: $IMAGE_PATH"
|
||||
echo "image_path=$IMAGE_PATH" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Login to DigitalOcean Container Registry
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DO_TOKEN: ${{ secrets.DO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
AUTH=$(echo -n "${DO_TOKEN}:${DO_TOKEN}" | base64)
|
||||
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
|
||||
mkdir -p .docker
|
||||
cp ~/.docker/config.json .docker/
|
||||
|
||||
- name: Push JFR Sidecar image to DO registry
|
||||
id: push-jfr-sidecar
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DOCKER_CONFIG: ${{ github.workspace }}/.docker
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
JFR_IMAGE="${{ steps.build-jfr-sidecar.outputs.image_path }}"
|
||||
echo "Using JFR Sidecar image: $JFR_IMAGE"
|
||||
|
||||
if [ -z "$JFR_IMAGE" ] || [ ! -d "$JFR_IMAGE" ]; then
|
||||
echo "ERROR: JFR Sidecar image not found at: $JFR_IMAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the push target to get crane in runfiles
|
||||
bazel build //ci:jfr_sidecar_push
|
||||
|
||||
# Use crane directly for push
|
||||
CRANE="bazel-bin/ci/push_jfr_sidecar_push.sh.runfiles/rules_oci~~oci~oci_crane_darwin_arm64/crane"
|
||||
echo "Using crane: $CRANE"
|
||||
|
||||
# Push with SHA tag
|
||||
GIT_SHA=$(git rev-parse --short=8 HEAD)
|
||||
IMAGE_TAG="registry.digitalocean.com/eagle0/jfr-sidecar:${GIT_SHA}"
|
||||
echo "Pushing JFR sidecar image: $IMAGE_TAG"
|
||||
$CRANE push "$JFR_IMAGE" "$IMAGE_TAG"
|
||||
|
||||
# Output the full image tag for deploy step
|
||||
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# Also update :latest for convenience
|
||||
echo "Copying to :latest tag"
|
||||
$CRANE copy "$IMAGE_TAG" "registry.digitalocean.com/eagle0/jfr-sidecar:latest"
|
||||
echo "=== All images pushed successfully ==="
|
||||
|
||||
deploy:
|
||||
runs-on: self-hosted
|
||||
needs: [build-eagle, build-shardok, build-admin, build-jfr-sidecar]
|
||||
needs: [build-all]
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
environment: production
|
||||
env:
|
||||
EAGLE_IMAGE: ${{ needs.build-eagle.outputs.image_tag }}
|
||||
SHARDOK_IMAGE: ${{ needs.build-shardok.outputs.image_tag }}
|
||||
ADMIN_IMAGE: ${{ needs.build-admin.outputs.image_tag }}
|
||||
JFR_SIDECAR_IMAGE: ${{ needs.build-jfr-sidecar.outputs.image_tag }}
|
||||
EAGLE_IMAGE: ${{ needs.build-all.outputs.eagle_image_tag }}
|
||||
SHARDOK_IMAGE: ${{ needs.build-all.outputs.shardok_image_tag }}
|
||||
ADMIN_IMAGE: ${{ needs.build-all.outputs.admin_image_tag }}
|
||||
JFR_SIDECAR_IMAGE: ${{ needs.build-all.outputs.jfr_sidecar_image_tag }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
GPT_MODEL_NAME: ${{ secrets.GPT_MODEL_NAME }}
|
||||
EAGLE_ENABLE_S3: ${{ secrets.EAGLE_ENABLE_S3 }}
|
||||
@@ -450,6 +187,9 @@ jobs:
|
||||
SHARDOK_ADDRESS: ${{ secrets.SHARDOK_ADDRESS }}
|
||||
SHARDOK_AUTH_TOKEN: ${{ secrets.SHARDOK_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
FASTMAIL_API_TOKEN: ${{ secrets.FASTMAIL_API_TOKEN }}
|
||||
FASTMAIL_FROM_EMAIL: ${{ secrets.FASTMAIL_FROM_EMAIL }}
|
||||
FASTMAIL_FROM_NAME: ${{ secrets.FASTMAIL_FROM_NAME }}
|
||||
DO_DROPLET_IP: ${{ secrets.DO_DROPLET_IP }}
|
||||
DO_REGISTRY_TOKEN: ${{ secrets.DO_REGISTRY_TOKEN }}
|
||||
steps:
|
||||
@@ -471,10 +211,19 @@ jobs:
|
||||
|
||||
- name: Copy config files to droplet
|
||||
run: |
|
||||
scp -i ~/.ssh/deploy_key \
|
||||
docker-compose.prod.yml nginx/nginx.conf \
|
||||
scripts/deploy-blue-green.sh scripts/warmup-eagle.sh scripts/bin/warmup \
|
||||
deploy@"$DO_DROPLET_IP":/opt/eagle0/
|
||||
# Create directory structure on remote
|
||||
ssh -i ~/.ssh/deploy_key deploy@"$DO_DROPLET_IP" bash -s << 'SETUP_DIRS'
|
||||
set -e
|
||||
mkdir -p /opt/eagle0/scripts/bin /opt/eagle0/nginx /opt/eagle0/deploy
|
||||
rm -f /opt/eagle0/scripts/bin/warmup
|
||||
SETUP_DIRS
|
||||
|
||||
# Copy files
|
||||
scp -i ~/.ssh/deploy_key docker-compose.prod.yml deploy@"$DO_DROPLET_IP":/opt/eagle0/
|
||||
scp -i ~/.ssh/deploy_key nginx/nginx.conf deploy@"$DO_DROPLET_IP":/opt/eagle0/nginx/
|
||||
scp -i ~/.ssh/deploy_key scripts/deploy-blue-green.sh scripts/warmup-eagle.sh deploy@"$DO_DROPLET_IP":/opt/eagle0/scripts/
|
||||
scp -i ~/.ssh/deploy_key scripts/bin/warmup deploy@"$DO_DROPLET_IP":/opt/eagle0/scripts/bin/
|
||||
scp -i ~/.ssh/deploy_key deploy/env.template deploy/update-env.sh deploy@"$DO_DROPLET_IP":/opt/eagle0/deploy/
|
||||
|
||||
- name: Deploy to production droplet
|
||||
run: |
|
||||
@@ -500,130 +249,103 @@ jobs:
|
||||
SHARDOK_ADDRESS="${SHARDOK_ADDRESS}"
|
||||
SHARDOK_AUTH_TOKEN="${SHARDOK_AUTH_TOKEN}"
|
||||
SENTRY_DSN="${SENTRY_DSN}"
|
||||
FASTMAIL_API_TOKEN="${FASTMAIL_API_TOKEN}"
|
||||
FASTMAIL_FROM_EMAIL="${FASTMAIL_FROM_EMAIL}"
|
||||
FASTMAIL_FROM_NAME="${FASTMAIL_FROM_NAME}"
|
||||
DO_REGISTRY_TOKEN="${DO_REGISTRY_TOKEN}"
|
||||
|
||||
# Check Docker has IPv6 support for connecting to Hetzner Shardok
|
||||
# (One-time setup: sudo tee /etc/docker/daemon.json <<< '{"ipv6": true, "ip6tables": true, "experimental": true, "fixed-cidr-v6": "fd00::/80"}' && sudo systemctl restart docker)
|
||||
# Check Docker has IPv6 support
|
||||
if ! cat /etc/docker/daemon.json 2>/dev/null | grep -q '"ip6tables"'; then
|
||||
echo "WARNING: Docker IPv6 not configured. Eagle may not reach Hetzner Shardok."
|
||||
echo "Run: sudo tee /etc/docker/daemon.json <<< '{\"ipv6\": true, \"ip6tables\": true, \"experimental\": true, \"fixed-cidr-v6\": \"fd00::/80\"}' && sudo systemctl restart docker"
|
||||
fi
|
||||
|
||||
# Write env vars to .env file for docker-compose
|
||||
# Preserve AUTH_IMAGE if it exists (managed by auth_build.yml)
|
||||
EXISTING_AUTH_IMAGE=""
|
||||
if [ -f .env ]; then
|
||||
EXISTING_AUTH_IMAGE=\$(grep '^AUTH_IMAGE=' .env | cut -d= -f2- || true)
|
||||
fi
|
||||
|
||||
rm -f .env 2>/dev/null || true
|
||||
cat > .env << EOF
|
||||
EAGLE_IMAGE=\${EAGLE_IMAGE}
|
||||
SHARDOK_IMAGE=\${SHARDOK_IMAGE}
|
||||
ADMIN_IMAGE=\${ADMIN_IMAGE}
|
||||
JFR_SIDECAR_IMAGE=\${JFR_SIDECAR_IMAGE}
|
||||
AUTH_IMAGE=\${EXISTING_AUTH_IMAGE:-registry.digitalocean.com/eagle0/auth-server:latest}
|
||||
OPENAI_API_KEY=\${OPENAI_API_KEY:-}
|
||||
GPT_MODEL_NAME=\${GPT_MODEL_NAME:-gpt-4o}
|
||||
EAGLE_ENABLE_S3=\${EAGLE_ENABLE_S3:-false}
|
||||
DO_SPACES_ACCESS_KEY=\${DO_SPACES_ACCESS_KEY:-}
|
||||
DO_SPACES_SECRET_KEY=\${DO_SPACES_SECRET_KEY:-}
|
||||
JWT_PRIVATE_KEY=\${JWT_PRIVATE_KEY:-}
|
||||
DISCORD_CLIENT_ID=\${DISCORD_CLIENT_ID:-}
|
||||
DISCORD_CLIENT_SECRET=\${DISCORD_CLIENT_SECRET:-}
|
||||
GOOGLE_CLIENT_ID=\${GOOGLE_CLIENT_ID:-}
|
||||
GOOGLE_CLIENT_SECRET=\${GOOGLE_CLIENT_SECRET:-}
|
||||
SHARDOK_ADDRESS=\${SHARDOK_ADDRESS:-shardok:40042}
|
||||
SHARDOK_AUTH_TOKEN=\${SHARDOK_AUTH_TOKEN:-}
|
||||
SENTRY_DSN=\${SENTRY_DSN:-}
|
||||
EOF
|
||||
chmod 600 .env
|
||||
# Update env vars
|
||||
chmod +x deploy/update-env.sh
|
||||
cd deploy && ./update-env.sh \
|
||||
"EAGLE_IMAGE=\${EAGLE_IMAGE}" \
|
||||
"SHARDOK_IMAGE=\${SHARDOK_IMAGE}" \
|
||||
"ADMIN_IMAGE=\${ADMIN_IMAGE}" \
|
||||
"JFR_SIDECAR_IMAGE=\${JFR_SIDECAR_IMAGE}" \
|
||||
"OPENAI_API_KEY=\${OPENAI_API_KEY}" \
|
||||
"GPT_MODEL_NAME=\${GPT_MODEL_NAME:-gpt-4o}" \
|
||||
"EAGLE_ENABLE_S3=\${EAGLE_ENABLE_S3:-false}" \
|
||||
"DO_SPACES_ACCESS_KEY=\${DO_SPACES_ACCESS_KEY}" \
|
||||
"DO_SPACES_SECRET_KEY=\${DO_SPACES_SECRET_KEY}" \
|
||||
"JWT_PRIVATE_KEY=\${JWT_PRIVATE_KEY}" \
|
||||
"DISCORD_CLIENT_ID=\${DISCORD_CLIENT_ID}" \
|
||||
"DISCORD_CLIENT_SECRET=\${DISCORD_CLIENT_SECRET}" \
|
||||
"GOOGLE_CLIENT_ID=\${GOOGLE_CLIENT_ID}" \
|
||||
"GOOGLE_CLIENT_SECRET=\${GOOGLE_CLIENT_SECRET}" \
|
||||
"SHARDOK_ADDRESS=\${SHARDOK_ADDRESS:-shardok:40042}" \
|
||||
"SHARDOK_AUTH_TOKEN=\${SHARDOK_AUTH_TOKEN}" \
|
||||
"SENTRY_DSN=\${SENTRY_DSN}" \
|
||||
"FASTMAIL_API_TOKEN=\${FASTMAIL_API_TOKEN}" \
|
||||
"FASTMAIL_FROM_EMAIL=\${FASTMAIL_FROM_EMAIL}" \
|
||||
"FASTMAIL_FROM_NAME=\${FASTMAIL_FROM_NAME}"
|
||||
cd ..
|
||||
|
||||
# Login to registry
|
||||
echo "\$DO_REGISTRY_TOKEN" | docker login registry.digitalocean.com -u "\$DO_REGISTRY_TOKEN" --password-stdin
|
||||
|
||||
# Use exact image tags passed from build jobs (no :latest fallback)
|
||||
# Note: AUTH_IMAGE is managed separately by auth_build.yml
|
||||
echo "Using images: \$EAGLE_IMAGE, \$SHARDOK_IMAGE, \$ADMIN_IMAGE, \$JFR_SIDECAR_IMAGE"
|
||||
|
||||
# Use crane to pull images (handles OCI format correctly) then load into Docker
|
||||
# This avoids digest mismatch from DO registry's OCI->Docker format conversion
|
||||
# Install crane for pulling OCI images
|
||||
echo "Installing crane..."
|
||||
rm -f crane
|
||||
curl -sL https://github.com/google/go-containerregistry/releases/download/v0.20.2/go-containerregistry_Linux_x86_64.tar.gz | tar xzf - crane
|
||||
chmod +x crane
|
||||
|
||||
# crane uses Docker config for auth
|
||||
echo "Pulling Eagle image with crane..."
|
||||
./crane pull "\${EAGLE_IMAGE}" eagle.tar || { echo "ERROR: Failed to pull eagle image"; exit 1; }
|
||||
echo "Loading Eagle image into Docker..."
|
||||
docker load -i eagle.tar
|
||||
rm eagle.tar
|
||||
# Pull and load all images
|
||||
echo "Pulling Eagle image..."
|
||||
./crane pull "\${EAGLE_IMAGE}" eagle.tar && docker load -i eagle.tar && rm eagle.tar
|
||||
|
||||
echo "Pulling Shardok image with crane..."
|
||||
./crane pull "\${SHARDOK_IMAGE}" shardok.tar || { echo "ERROR: Failed to pull shardok image"; exit 1; }
|
||||
echo "Loading Shardok image into Docker..."
|
||||
docker load -i shardok.tar
|
||||
rm shardok.tar
|
||||
echo "Pulling Shardok image..."
|
||||
./crane pull "\${SHARDOK_IMAGE}" shardok.tar && docker load -i shardok.tar && rm shardok.tar
|
||||
|
||||
echo "Pulling Admin image with crane..."
|
||||
./crane pull "\${ADMIN_IMAGE}" admin.tar || { echo "ERROR: Failed to pull admin image"; exit 1; }
|
||||
echo "Loading Admin image into Docker..."
|
||||
docker load -i admin.tar
|
||||
rm admin.tar
|
||||
echo "Pulling Admin image..."
|
||||
./crane pull "\${ADMIN_IMAGE}" admin.tar && docker load -i admin.tar && rm admin.tar
|
||||
|
||||
echo "Pulling JFR Sidecar image with crane..."
|
||||
./crane pull "\${JFR_SIDECAR_IMAGE}" jfr-sidecar.tar || { echo "ERROR: Failed to pull jfr-sidecar image"; exit 1; }
|
||||
echo "Loading JFR Sidecar image into Docker..."
|
||||
docker load -i jfr-sidecar.tar
|
||||
rm jfr-sidecar.tar
|
||||
echo "Pulling JFR Sidecar image..."
|
||||
./crane pull "\${JFR_SIDECAR_IMAGE}" jfr-sidecar.tar && docker load -i jfr-sidecar.tar && rm jfr-sidecar.tar
|
||||
|
||||
rm ./crane
|
||||
|
||||
# Also pull other compose images
|
||||
# Pull other compose images
|
||||
docker pull nginx:alpine || true
|
||||
docker pull certbot/certbot || true
|
||||
|
||||
echo "All images pulled successfully"
|
||||
|
||||
# Recreate non-Eagle services (not auth - managed by auth_build.yml)
|
||||
docker compose -f docker-compose.prod.yml up -d --no-deps --force-recreate shardok admin jfr-sidecar
|
||||
# Recreate non-Eagle services
|
||||
docker compose -f docker-compose.prod.yml up -d --no-deps --force-recreate shardok admin
|
||||
|
||||
# Deploy Eagle with blue-green (zero-downtime) if scripts are available
|
||||
# Deploy Eagle with blue-green
|
||||
if [ -x "/opt/eagle0/scripts/deploy-blue-green.sh" ]; then
|
||||
echo "Using blue-green deployment for Eagle..."
|
||||
chmod +x /opt/eagle0/scripts/*.sh
|
||||
# Make warmup binary executable if present
|
||||
if [ -f "/opt/eagle0/scripts/bin/warmup" ]; then
|
||||
chmod +x /opt/eagle0/scripts/bin/warmup
|
||||
fi
|
||||
[ -f "/opt/eagle0/scripts/bin/warmup" ] && chmod +x /opt/eagle0/scripts/bin/warmup
|
||||
GIT_SHA=\$(echo "\${EAGLE_IMAGE}" | sed 's/.*://')
|
||||
/opt/eagle0/scripts/deploy-blue-green.sh "\${GIT_SHA}" || {
|
||||
echo "Blue-green deployment failed, falling back to direct restart"
|
||||
echo "Blue-green failed, falling back to direct restart"
|
||||
docker compose -f docker-compose.prod.yml up -d --no-deps --force-recreate eagle-blue
|
||||
}
|
||||
else
|
||||
echo "Blue-green scripts not found, using direct restart..."
|
||||
docker compose -f docker-compose.prod.yml up -d --no-deps --force-recreate eagle-blue
|
||||
fi
|
||||
|
||||
# Ensure auth is running (but don't force-recreate it)
|
||||
# Start jfr-sidecar after eagle-blue
|
||||
docker compose -f docker-compose.prod.yml up -d --no-deps --force-recreate jfr-sidecar
|
||||
|
||||
# Ensure auth is running
|
||||
docker compose -f docker-compose.prod.yml up -d auth
|
||||
|
||||
# Restart nginx to pick up new container IPs
|
||||
# (nginx caches DNS at startup, so it needs restart after eagle/shardok)
|
||||
# Restart nginx
|
||||
docker compose -f docker-compose.prod.yml up -d --force-recreate nginx
|
||||
|
||||
# Cleanup orphaned containers
|
||||
docker compose -f docker-compose.prod.yml up -d --remove-orphans
|
||||
|
||||
# Wait for health checks
|
||||
# Verify
|
||||
sleep 10
|
||||
docker compose -f docker-compose.prod.yml ps
|
||||
|
||||
# Verify containers are using correct images
|
||||
echo "=== Verifying container image tags ==="
|
||||
docker compose -f docker-compose.prod.yml images
|
||||
|
||||
# Cleanup old images
|
||||
# Cleanup
|
||||
docker container prune -f
|
||||
docker image prune -f
|
||||
DEPLOY_SCRIPT
|
||||
|
||||
@@ -10,6 +10,7 @@ on:
|
||||
paths:
|
||||
- ".github/workflows/installer_build.yml"
|
||||
- "src/main/csharp/net/eagle0/clients/win/installer/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -29,6 +30,19 @@ jobs:
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Inject manifest public key
|
||||
env:
|
||||
MANIFEST_PUBLIC_KEY: ${{ secrets.MANIFEST_PUBLIC_KEY }}
|
||||
run: |
|
||||
if [ -n "$MANIFEST_PUBLIC_KEY" ]; then
|
||||
CONFIG_FILE="src/main/csharp/net/eagle0/clients/win/installer/EagleInstaller/configuration.txt"
|
||||
echo "manifest_public_key = $MANIFEST_PUBLIC_KEY" >> "$CONFIG_FILE"
|
||||
echo "Injected manifest public key into configuration.txt"
|
||||
cat "$CONFIG_FILE"
|
||||
else
|
||||
echo "MANIFEST_PUBLIC_KEY not set, skipping injection"
|
||||
fi
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore src/main/csharp/net/eagle0/clients/win/installer/EagleInstaller/EagleInstaller.csproj
|
||||
|
||||
@@ -68,15 +82,30 @@ jobs:
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
MANIFEST_SIGNING_KEY: ${{ secrets.MANIFEST_SIGNING_KEY }}
|
||||
run: |
|
||||
# Create installer manifest content
|
||||
# Create installer manifest content
|
||||
INSTALLER_SHA=$(sha256sum ./installer-output/EagleInstaller.exe | cut -d' ' -f1)
|
||||
echo "installer_version=$INSTALLER_SHA" > /tmp/installer_manifest.txt
|
||||
echo "installer_url=installer/EagleInstaller.exe" >> /tmp/installer_manifest.txt
|
||||
|
||||
|
||||
echo "=== Installer manifest content ==="
|
||||
cat /tmp/installer_manifest.txt
|
||||
echo "=================================="
|
||||
|
||||
# Update the unified manifest
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- installer /tmp/installer_manifest.txt
|
||||
|
||||
# Write signing key to temp file (if available)
|
||||
SIGNING_ARGS=""
|
||||
if [ -n "$MANIFEST_SIGNING_KEY" ]; then
|
||||
echo "$MANIFEST_SIGNING_KEY" > /tmp/manifest_signing_key
|
||||
chmod 600 /tmp/manifest_signing_key
|
||||
SIGNING_ARGS="/tmp/manifest_signing_key"
|
||||
echo "Manifest signing key available"
|
||||
else
|
||||
echo "Warning: MANIFEST_SIGNING_KEY not set, manifest will be unsigned"
|
||||
fi
|
||||
|
||||
# Update the unified manifest (with optional signing)
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- installer /tmp/installer_manifest.txt $SIGNING_ARGS
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/manifest_signing_key
|
||||
@@ -0,0 +1,154 @@
|
||||
name: Mac Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- ".github/workflows/mac_build.yml"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/**"
|
||||
- "src/main/proto/**"
|
||||
- "src/main/go/net/eagle0/build/mac_build_handler/**"
|
||||
- "scripts/build_protos.sh"
|
||||
- "scripts/build_mac_plugin.sh"
|
||||
- "scripts/inject_sparkle.sh"
|
||||
- "scripts/codesign_mac_app.sh"
|
||||
- "scripts/notarize_mac_app.sh"
|
||||
- "ci/github_actions/build_mac.sh"
|
||||
- "ci/github_actions/build_unity_mac.sh"
|
||||
- "ci/mac/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/mac_build.yml"
|
||||
- "src/main/csharp/net/eagle0/clients/unity/**"
|
||||
- "src/main/go/net/eagle0/build/mac_build_handler/**"
|
||||
- "scripts/build_mac_plugin.sh"
|
||||
- "scripts/inject_sparkle.sh"
|
||||
- "scripts/codesign_mac_app.sh"
|
||||
- "scripts/notarize_mac_app.sh"
|
||||
- "ci/github_actions/build_mac.sh"
|
||||
- "ci/github_actions/build_unity_mac.sh"
|
||||
- "ci/mac/**"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_signing:
|
||||
description: 'Skip code signing, notarization, and deploy (build only)'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
mac-unity:
|
||||
runs-on: self-hosted
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
clean: false
|
||||
fetch-depth: 0 # For version numbering from git history
|
||||
|
||||
- name: Pull LFS files
|
||||
run: git lfs pull
|
||||
|
||||
- name: Restore Library/
|
||||
run: ./ci/github_actions/restore_library.sh
|
||||
|
||||
- name: Build Mac Unity
|
||||
run: ./ci/github_actions/build_unity_mac.sh "/tmp/eagle0/eagle0MAC"
|
||||
|
||||
- name: Persist Library/
|
||||
run: ./ci/github_actions/persist_library.sh
|
||||
|
||||
- name: Inject Sparkle Framework
|
||||
if: success()
|
||||
env:
|
||||
SPARKLE_EDDSA_PUBLIC_KEY: ${{ secrets.SPARKLE_EDDSA_PUBLIC_KEY }}
|
||||
run: |
|
||||
chmod +x ./scripts/inject_sparkle.sh
|
||||
./scripts/inject_sparkle.sh "/tmp/eagle0/eagle0MAC/eagle0.app"
|
||||
|
||||
- name: Import Code Signing Certificate
|
||||
if: success() && ((github.event_name == 'workflow_dispatch' && github.event.inputs.skip_signing != 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
run: |
|
||||
# Generate random keychain password (only used within this workflow run)
|
||||
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
|
||||
echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV
|
||||
|
||||
# Decode certificate
|
||||
echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
|
||||
|
||||
# Delete any existing keychain from previous runs
|
||||
security delete-keychain build.keychain 2>/dev/null || true
|
||||
|
||||
# Create temporary keychain
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
|
||||
# Import certificate
|
||||
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
|
||||
|
||||
# Allow codesign to access keychain
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
|
||||
# Clean up
|
||||
rm certificate.p12
|
||||
|
||||
- name: Code Sign App
|
||||
if: success() && ((github.event_name == 'workflow_dispatch' && github.event.inputs.skip_signing != 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
|
||||
env:
|
||||
SIGNING_IDENTITY: ${{ secrets.SIGNING_IDENTITY }}
|
||||
run: |
|
||||
chmod +x ./scripts/codesign_mac_app.sh
|
||||
./scripts/codesign_mac_app.sh "/tmp/eagle0/eagle0MAC/eagle0.app" "ci/mac/eagle0.entitlements"
|
||||
|
||||
- name: Notarize App
|
||||
if: success() && ((github.event_name == 'workflow_dispatch' && github.event.inputs.skip_signing != 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
|
||||
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
chmod +x ./scripts/notarize_mac_app.sh
|
||||
./scripts/notarize_mac_app.sh "/tmp/eagle0/eagle0MAC/eagle0.app"
|
||||
|
||||
- name: Deploy Mac Build
|
||||
if: success() && ((github.event_name == 'workflow_dispatch' && github.event.inputs.skip_signing != 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
SPARKLE_EDDSA_PRIVATE_KEY: ${{ secrets.SPARKLE_EDDSA_PRIVATE_KEY }}
|
||||
run: |
|
||||
# Write private key to temp file for signing
|
||||
SPARKLE_PRIVATE_KEY_PATH="/tmp/sparkle_private_key"
|
||||
echo "$SPARKLE_EDDSA_PRIVATE_KEY" > "$SPARKLE_PRIVATE_KEY_PATH"
|
||||
chmod 600 "$SPARKLE_PRIVATE_KEY_PATH"
|
||||
|
||||
VERSION=$(git describe --tags --always)
|
||||
BUILD_NUMBER=$(git rev-list --count HEAD)
|
||||
|
||||
bazel run //src/main/go/net/eagle0/build/mac_build_handler:mac_build_handler -- \
|
||||
"/tmp/eagle0/eagle0MAC/eagle0.app" \
|
||||
"$VERSION" \
|
||||
"$BUILD_NUMBER" \
|
||||
"$SPARKLE_PRIVATE_KEY_PATH"
|
||||
|
||||
rm "$SPARKLE_PRIVATE_KEY_PATH"
|
||||
|
||||
- name: Cleanup Keychain
|
||||
if: always()
|
||||
run: |
|
||||
security delete-keychain build.keychain 2>/dev/null || true
|
||||
|
||||
- name: Archive Build Log
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: editor_mac.log
|
||||
path: /tmp/eagle0/editor_mac.log
|
||||
@@ -63,7 +63,24 @@ jobs:
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- unity3d /tmp/unity_manifest.txt
|
||||
MANIFEST_SIGNING_KEY: ${{ secrets.MANIFEST_SIGNING_KEY }}
|
||||
run: |
|
||||
# Write signing key to temp file (if available)
|
||||
SIGNING_ARGS=""
|
||||
if [ -n "$MANIFEST_SIGNING_KEY" ]; then
|
||||
echo "$MANIFEST_SIGNING_KEY" > /tmp/manifest_signing_key
|
||||
chmod 600 /tmp/manifest_signing_key
|
||||
SIGNING_ARGS="/tmp/manifest_signing_key"
|
||||
echo "Manifest signing key available"
|
||||
else
|
||||
echo "Warning: MANIFEST_SIGNING_KEY not set, manifest will be unsigned"
|
||||
fi
|
||||
|
||||
# Update the unified manifest (with optional signing)
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- unity3d /tmp/unity_manifest.txt $SIGNING_ARGS
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/manifest_signing_key
|
||||
- name: Archive build log
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -38,3 +38,4 @@ scripts/refresh_name_layers/refresh_name_layers.zip
|
||||
api_keys.txt
|
||||
|
||||
src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/Packages/com.unity.dedicated-server/
|
||||
node_modules/
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# CLAUDE.md
|
||||
|
||||
## CRITICAL GIT RULES (NEVER VIOLATE)
|
||||
|
||||
**NEVER push directly to main/master.** No exceptions. Not for "small changes." Not for docs. Not ever.
|
||||
|
||||
**NEVER merge PRs.** You create PRs. The user merges them. No exceptions.
|
||||
|
||||
**ALWAYS use this workflow:**
|
||||
1. Create a feature branch from origin/main
|
||||
2. Commit to that branch
|
||||
3. Create a PR with `gh pr create`
|
||||
4. Wait for user to merge (DO NOT run `gh pr merge`)
|
||||
|
||||
If you catch yourself about to run `git push origin main` or `git push origin <branch>:main`, STOP. You are about to violate a critical rule. Create a PR instead.
|
||||
|
||||
If you catch yourself about to run `gh pr merge`, STOP. Only the user merges PRs.
|
||||
|
||||
---
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
+19
-12
@@ -13,8 +13,8 @@ AWS_SDK_VERSION = "2.28.1"
|
||||
# Core Build Tools
|
||||
#
|
||||
|
||||
bazel_dep(name = "bazel_skylib", version = "1.8.1")
|
||||
bazel_dep(name = "rules_pkg", version = "1.1.0")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.9.0")
|
||||
bazel_dep(name = "rules_pkg", version = "1.2.0")
|
||||
|
||||
#
|
||||
# Language Support - Scala
|
||||
@@ -102,8 +102,8 @@ sysroot(
|
||||
# Language Support - Go
|
||||
#
|
||||
|
||||
bazel_dep(name = "rules_go", version = "0.56.1", repo_name = "io_bazel_rules_go")
|
||||
bazel_dep(name = "gazelle", version = "0.45.0", repo_name = "bazel_gazelle")
|
||||
bazel_dep(name = "rules_go", version = "0.59.0", repo_name = "io_bazel_rules_go")
|
||||
bazel_dep(name = "gazelle", version = "0.47.0", repo_name = "bazel_gazelle")
|
||||
|
||||
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
|
||||
go_sdk.download(version = "1.23.3")
|
||||
@@ -127,8 +127,8 @@ use_repo(
|
||||
#
|
||||
|
||||
bazel_dep(name = "apple_support", version = "1.21.1", repo_name = "build_bazel_apple_support")
|
||||
bazel_dep(name = "rules_apple", version = "3.16.1", repo_name = "build_bazel_rules_apple")
|
||||
bazel_dep(name = "rules_swift", version = "2.3.1", repo_name = "build_bazel_rules_swift")
|
||||
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")
|
||||
|
||||
#
|
||||
# Protocol Buffers & RPC
|
||||
@@ -149,8 +149,8 @@ bazel_dep(name = "googletest", version = "1.17.0")
|
||||
# Container Images (OCI)
|
||||
#
|
||||
|
||||
bazel_dep(name = "rules_oci", version = "2.2.6")
|
||||
bazel_dep(name = "aspect_bazel_lib", version = "2.16.0")
|
||||
bazel_dep(name = "rules_oci", version = "2.2.7")
|
||||
bazel_dep(name = "aspect_bazel_lib", version = "2.22.4")
|
||||
|
||||
oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
|
||||
|
||||
@@ -186,7 +186,7 @@ use_repo(oci, "alpine_linux", "alpine_linux_linux_amd64", "eclipse_temurin_17",
|
||||
# Java/Scala Dependencies
|
||||
#
|
||||
|
||||
bazel_dep(name = "rules_jvm_external", version = "6.3")
|
||||
bazel_dep(name = "rules_jvm_external", version = "6.9")
|
||||
|
||||
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
||||
maven.install(
|
||||
@@ -294,11 +294,15 @@ http_archive(
|
||||
)
|
||||
|
||||
# Busybox static binary for Docker health checks (provides nc, wget, etc.)
|
||||
# https://busybox.net/downloads/binaries/
|
||||
# Primary: DigitalOcean Spaces (public, reliable)
|
||||
# Fallback: busybox.net (can be unreliable/slow)
|
||||
http_file(
|
||||
name = "busybox_x86_64",
|
||||
sha256 = "6e123e7f3202a8c1e9b1f94d8941580a25135382b99e8d3e34fb858bba311348",
|
||||
urls = ["https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox"],
|
||||
urls = [
|
||||
"https://eagle0-sysroot.sfo3.digitaloceanspaces.com/busybox/busybox-1.35.0-x86_64-linux-musl",
|
||||
"https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox",
|
||||
],
|
||||
downloaded_file_path = "busybox",
|
||||
executable = True,
|
||||
)
|
||||
@@ -306,7 +310,10 @@ http_file(
|
||||
http_file(
|
||||
name = "busybox_aarch64",
|
||||
sha256 = "141adb1b625a6f44c4b114f76b4387b4ea4f7ab802b88eb40e0d2f6adcccb1c3",
|
||||
urls = ["https://busybox.net/downloads/binaries/1.35.0-aarch64-linux-musl/busybox"],
|
||||
urls = [
|
||||
# TODO: Upload aarch64 binary to GitHub release when needed
|
||||
"https://busybox.net/downloads/binaries/1.35.0-aarch64-linux-musl/busybox",
|
||||
],
|
||||
downloaded_file_path = "busybox",
|
||||
executable = True,
|
||||
)
|
||||
|
||||
Generated
+79
-22
@@ -27,9 +27,9 @@
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/1.40.3/MODULE.bazel": "668e6bcb4d957fc0e284316dba546b705c8d43c857f87119619ee83c4555b859",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/MODULE.bazel": "cb1ba9f9999ed0bc08600c221f532c1ddd8d217686b32ba7d45b0713b5131452",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.16.0/MODULE.bazel": "852f9ebbda017572a7c113a2434592dd3b2f55cd9a0faea3d4be5a09a59e4900",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/source.json": "ffab9254c65ba945f8369297ad97ca0dec213d3adc6e07877e23a48624a8b456",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.4/MODULE.bazel": "a05cbd9bc16712a58dc27ffe0dceaefd0da59a9bd87a227379b2a934b26a39ab",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.4/source.json": "9780bc57f521968ee82b7c3e85b7d0c71518fb7ce83ed7a9e5077ce20923207b",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.2/MODULE.bazel": "780d1a6522b28f5edb7ea09630748720721dfe27690d65a2d33aa7509de77e07",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838",
|
||||
@@ -57,12 +57,15 @@
|
||||
"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.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.38.0/MODULE.bazel": "f9b8a9c890ebd216b4049fd12a31d3c2602e3403c7af636b04fbbd7453edc9c9",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.38.0/source.json": "31ba776c122b54a2885e23651642e32f087a87bf025465f8040751894b571277",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
|
||||
"https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d",
|
||||
"https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751",
|
||||
"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",
|
||||
@@ -77,7 +80,8 @@
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0",
|
||||
"https://bcr.bazel.build/modules/bazel_worker_api/0.0.6/MODULE.bazel": "fd1f9432ca04c947e91b500df69ce7c5b6dbfe1bc45ab1820338205dae3383a6",
|
||||
"https://bcr.bazel.build/modules/bazel_worker_api/0.0.6/source.json": "5d68545f224904745a3cabd35aea6bc2b6cc5a78b7f49f3f69660eab2eeeb273",
|
||||
"https://bcr.bazel.build/modules/boringssl/0.0.0-20211025-d4f1ab9/MODULE.bazel": "6ee6353f8b1a701fe2178e1d925034294971350b6d3ac37e67e5a7d463267834",
|
||||
@@ -115,8 +119,8 @@
|
||||
"https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.37.0/MODULE.bazel": "d1327ba0907d0275ed5103bfbbb13518f6c04955b402213319d0d6c0ce9839d4",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.45.0/MODULE.bazel": "ecd19ebe9f8e024e1ccffb6d997cc893a974bcc581f1ae08f386bdd448b10687",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.45.0/source.json": "111d182facc5f5e80f0b823d5f077b74128f40c3fd2eccc89a06f34191bd3392",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.47.0/MODULE.bazel": "b61bb007c4efad134aa30ee7f4a8e2a39b22aa5685f005edaa022fbd1de43ebc",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.47.0/source.json": "aeb2e5df14b7fb298625d75d08b9c65bdb0b56014c5eb89da9e5dd0572280ae6",
|
||||
"https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
|
||||
"https://bcr.bazel.build/modules/google_benchmark/1.8.4/MODULE.bazel": "c6d54a11dcf64ee63545f42561eda3fd94c1b5f5ebe1357011de63ae33739d5e",
|
||||
"https://bcr.bazel.build/modules/google_benchmark/1.8.5/MODULE.bazel": "9ba9b31b984022828a950e3300410977eda2e35df35584c6b0b2d0c2e52766b7",
|
||||
@@ -176,6 +180,7 @@
|
||||
"https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/MODULE.bazel": "b3925269f63561b8b880ae7cf62ccf81f6ece55b62cd791eda9925147ae116ec",
|
||||
"https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/source.json": "da1cb1add160f5e5074b7272e9db6fd8f1b3336c15032cd0a653af9d2f484aed",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.5/source.json": "2326db2f6592578177751c3e1f74786b79382cd6008834c9d01ec865b9126a85",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
|
||||
@@ -229,9 +234,9 @@
|
||||
"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/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a",
|
||||
"https://bcr.bazel.build/modules/rules_apple/3.16.1/MODULE.bazel": "8294474defa70af2534a558ab905c083d69203344145e6f7d544d5098611ec7d",
|
||||
"https://bcr.bazel.build/modules/rules_apple/3.16.1/source.json": "9190fd9d34a5d048bfbba8a530a57f2c2bf3f61e5634a9ab0b6ab005458857f9",
|
||||
"https://bcr.bazel.build/modules/rules_apple/3.5.1/MODULE.bazel": "3d1bbf65ad3692003d36d8a29eff54d4e5c1c5f4bfb60f79e28646a924d9101c",
|
||||
"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_buf/0.1.1/MODULE.bazel": "6189aec18a4f7caff599ad41b851ab7645d4f1e114aa6431acf9b0666eb92162",
|
||||
"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",
|
||||
@@ -246,6 +251,8 @@
|
||||
"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.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.2.14/source.json": "55d0a4587c5592fad350f6e698530f4faf0e7dd15e69d43f8d87e220c78bea54",
|
||||
"https://bcr.bazel.build/modules/rules_foreign_cc/0.10.1/MODULE.bazel": "b9527010e5fef060af92b6724edb3691970a5b1f76f74b21d39f7d433641be60",
|
||||
@@ -263,8 +270,8 @@
|
||||
"https://bcr.bazel.build/modules/rules_go/0.48.0/MODULE.bazel": "d00ebcae0908ee3f5e6d53f68677a303d6d59a77beef879598700049c3980a03",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.53.0/MODULE.bazel": "a4ed760d3ac0dbc0d7b967631a9a3fd9100d28f7d9fcf214b4df87d4bfff5f9a",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.56.1/MODULE.bazel": "d5b835c548ac917345f1780cd2da52edc1130a908fe091c92096895303ae78a0",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.56.1/source.json": "0c902f7272e8d4e47e459af97be472bc19dadbbe6023a0719d1adce8483ac75a",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.59.0/MODULE.bazel": "b7e43e7414a3139a7547d1b4909b29085fbe5182b6c58cbe1ed4c6272815aeae",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.59.0/source.json": "1df17bb7865cfc029492c30163cee891d0dd8658ea0d5bfdf252c4b6db5c1ef6",
|
||||
"https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
|
||||
"https://bcr.bazel.build/modules/rules_java/5.1.0/MODULE.bazel": "324b6478b0343a3ce7a9add8586ad75d24076d6d43d2f622990b9c1cfd8a1b15",
|
||||
"https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86",
|
||||
@@ -292,7 +299,8 @@
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.0/MODULE.bazel": "37c93a5a78d32e895d52f86a8d0416176e915daabd029ccb5594db422e87c495",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4",
|
||||
"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_jvm_external/6.9/MODULE.bazel": "07c5db05527db7744a54fcffd653e1550d40e0540207a7f7e6d0a4de5bef8274",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.9/source.json": "b12970214f3cc144b26610caeb101fa622d910f1ab3d98f0bae1058edbd00bd4",
|
||||
"https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59",
|
||||
"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",
|
||||
@@ -306,12 +314,12 @@
|
||||
"https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d",
|
||||
"https://bcr.bazel.build/modules/rules_nodejs/6.3.3/MODULE.bazel": "b66eadebd10f1f1b25f52f95ab5213a57e82c37c3f656fcd9a57ad04d2264ce7",
|
||||
"https://bcr.bazel.build/modules/rules_nodejs/6.3.3/source.json": "45bd343155bdfed2543f0e39b80ff3f6840efc31975da4b5795797f4c94147ad",
|
||||
"https://bcr.bazel.build/modules/rules_oci/2.2.6/MODULE.bazel": "2ba6ddd679269e00aeffe9ca04faa2d0ca4129650982c9246d0d459fe2da47d9",
|
||||
"https://bcr.bazel.build/modules/rules_oci/2.2.6/source.json": "94e7decb8f95d9465b0bbea71c65064cd16083be1350c7468f131818641dc4a5",
|
||||
"https://bcr.bazel.build/modules/rules_oci/2.2.7/MODULE.bazel": "f6150e4b224d459f7f6523ef65967464ca4efdd266c7fbf2f5a2a51011957e0c",
|
||||
"https://bcr.bazel.build/modules/rules_oci/2.2.7/source.json": "b099f02af330f47f19dc67fc9300ef6e1937a8c86882690db0e7a2fcea8c7f6b",
|
||||
"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.1.0/MODULE.bazel": "9db8031e71b6ef32d1846106e10dd0ee2deac042bd9a2de22b4761b0c3036453",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.1.0/source.json": "fef768df13a92ce6067e1cd0cdc47560dace01354f1d921cfb1d632511f7d608",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.2.0/source.json": "9062e00845bf91a4247465d371baa837adf9b6ff44c542f73ba084f07667e1dc",
|
||||
"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.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483",
|
||||
@@ -334,7 +342,8 @@
|
||||
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.0.0/source.json": "b0162a65c6312e45e7912e39abd1a7f8856c2c7e41ecc9b6dc688a6f6400a917",
|
||||
"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_rust/0.45.1/MODULE.bazel": "a69d0db3a958fab2c6520961e1b2287afcc8b36690fd31bbc4f6f7391397150d",
|
||||
"https://bcr.bazel.build/modules/rules_scala/7.1.1/MODULE.bazel": "b1f80c52ae49b27d41b9291d8b328b69247de2b7596d35d09afe6147b82cf562",
|
||||
"https://bcr.bazel.build/modules/rules_scala/7.1.1/source.json": "5038cb231d4020c5965c920681cf961a7bf137b40315025e40f3a7b6a0ac1f0f",
|
||||
@@ -345,8 +354,8 @@
|
||||
"https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca",
|
||||
"https://bcr.bazel.build/modules/rules_swift/1.18.0/MODULE.bazel": "a6aba73625d0dc64c7b4a1e831549b6e375fbddb9d2dde9d80c9de6ec45b24c9",
|
||||
"https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046",
|
||||
"https://bcr.bazel.build/modules/rules_swift/2.3.1/MODULE.bazel": "0b42093600d9226bcbdb31fb86d25d4204293d716fdbb2e50a1852547032a660",
|
||||
"https://bcr.bazel.build/modules/rules_swift/2.3.1/source.json": "87d28609c37d2061db2f6fc3aae8ab7fbda9adf556cd88fbd0c7d520b8d81391",
|
||||
"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.0/MODULE.bazel": "f9f1f46ba8d9c3362648eea571c6f9100680efc44913618811b58cc9c02cd678",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
|
||||
@@ -360,6 +369,7 @@
|
||||
"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/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468",
|
||||
"https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351",
|
||||
"https://bcr.bazel.build/modules/tar.bzl/0.6.0/MODULE.bazel": "a3584b4edcfafcabd9b0ef9819808f05b372957bbdff41601429d5fd0aac2e7c",
|
||||
"https://bcr.bazel.build/modules/tar.bzl/0.6.0/source.json": "4a620381df075a16cb3a7ed57bd1d05f7480222394c64a20fa51bdb636fda658",
|
||||
"https://bcr.bazel.build/modules/toolchains_llvm/1.6.0/MODULE.bazel": "39603859cafb1c6830160fcd6370552e836790e6abb2bfb8d13bff53c0c10a64",
|
||||
@@ -386,7 +396,7 @@
|
||||
"@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "Z3yAd66IJL0GAZUTSeMOjoHiE1SZPPwiIs/XQui5BvE=",
|
||||
"usagesDigest": "TOb4CUri5UsTKxgIDTNzR0ddIc21eYLCRIm+jqQmjlg=",
|
||||
"usagesDigest": "tl3VVeQX3Hzh7FhM2gjnkCwEJpRMlY5S6a850WY/xvc=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
@@ -413,7 +423,7 @@
|
||||
},
|
||||
"@@aspect_rules_esbuild~//esbuild:extensions.bzl%esbuild": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "8jv3p0xDR/oitFeH8y0+Y5xlyrUbfsTRlc9TSwYkwl8=",
|
||||
"bzlTransitiveDigest": "RzTo7pj0Tdkwa3Ld5BAVhGFSvWroST8GAQLuwwC0jdQ=",
|
||||
"usagesDigest": "iDVoyPxUeADmfK8ssoyG3Ehq1bj6p7A43LpEiE266os=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
@@ -521,6 +531,11 @@
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"bazel_lib",
|
||||
"bazel_lib~"
|
||||
],
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"bazel_skylib",
|
||||
@@ -555,6 +570,11 @@
|
||||
"aspect_rules_js~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"bazel_lib~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1292,8 +1312,8 @@
|
||||
},
|
||||
"@@rules_oci~//oci:extensions.bzl%oci": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "FaY+7xb13bB3hmxqwAWaGp3Tf3Q4Nfdlr+F38CP5mcg=",
|
||||
"usagesDigest": "39yHQmifPoGf+JSYpnQSnJGugxbrFVge/+ENmUiqZ6M=",
|
||||
"bzlTransitiveDigest": "AOLP47LtVHSKSDiukosQymx543OwcgeoQP666wwuj3o=",
|
||||
"usagesDigest": "3Xsv1/UEV8MOARW4BZScPw3Gxtx19OQ5EXOqcL1p9bI=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
@@ -1580,6 +1600,43 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@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_scala~//scala/extensions:config.bzl%scala_config": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "TdBxhkZTM7VU6teIFS+KoonKU7wmb5BL7leCWWx7yX8=",
|
||||
@@ -5056,7 +5113,7 @@
|
||||
"@@rules_swift~//swift:extensions.bzl%non_module_deps": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "PAIMhc1bVKfcyoHeg0xO8LMS9KN5yzbsMGwa5O2ifJM=",
|
||||
"usagesDigest": "A3fzk5iHsrLdI3PokT1bHIdeJ2j9tc09H3/3Old6IfU=",
|
||||
"usagesDigest": "l2vIL7SL4tJqHIVLWd78Y/ym+r0II64lmvoX8o+0Bb0=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
. ./ci/unity_version.sh
|
||||
|
||||
WORKSPACE=$(pwd)
|
||||
UNITY_INSTALL_PATH="/Applications/Unity/Hub/Editor"
|
||||
BUILD_DIR=$1
|
||||
LOG_PATH=$2
|
||||
|
||||
echo "Building Mac in $BUILD_DIR"
|
||||
|
||||
echo "Cleaning up $BUILD_DIR"
|
||||
/bin/rm -rf "$BUILD_DIR"
|
||||
/bin/mkdir -p "$BUILD_DIR"
|
||||
|
||||
${UNITY_INSTALL_PATH}/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \
|
||||
-nographics \
|
||||
-batchmode \
|
||||
-quit \
|
||||
-buildOSXUniversalPlayer "$BUILD_DIR/eagle0.app" \
|
||||
-logFile "$LOG_PATH" \
|
||||
-projectPath "$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
COMMIT=$(/usr/bin/git rev-parse --short HEAD)
|
||||
|
||||
/bin/echo "build protos"
|
||||
./scripts/build_protos.sh
|
||||
|
||||
/bin/echo "build Mac plugin"
|
||||
./scripts/build_mac_plugin.sh
|
||||
|
||||
git log -3
|
||||
|
||||
/bin/echo "build Mac"
|
||||
LOG_PATH="/tmp/eagle0/editor_mac.log"
|
||||
BUILD_DIR=$1
|
||||
|
||||
./ci/github_actions/build_mac.sh "$BUILD_DIR" "$LOG_PATH"
|
||||
@@ -1,6 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
set -uxo pipefail
|
||||
|
||||
/bin/echo "persist Library/"
|
||||
/usr/bin/rsync -rtlDvq src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ /tmp/eagle0/Library/
|
||||
|
||||
# rsync may exit with code 23 ("partial transfer due to error") if Unity's
|
||||
# temporary files vanish during the copy. This is acceptable for a cache.
|
||||
/usr/bin/rsync -rtlDvq src/main/csharp/net/eagle0/clients/unity/eagle0/Library/ /tmp/eagle0/Library/
|
||||
rsync_exit=$?
|
||||
|
||||
if [ $rsync_exit -eq 0 ]; then
|
||||
exit 0
|
||||
elif [ $rsync_exit -eq 23 ]; then
|
||||
echo "Warning: rsync exited with 23 (some files vanished during copy). This is expected for Unity temp files."
|
||||
exit 0
|
||||
else
|
||||
echo "Error: rsync failed with exit code $rsync_exit"
|
||||
exit $rsync_exit
|
||||
fi
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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>
|
||||
<!-- Allow JIT compilation (required for Mono/IL2CPP) -->
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<!-- Allow unsigned executable memory (required for Unity) -->
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<!-- Disable library validation (required for plugins) -->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<!-- Allow outgoing network connections -->
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,40 @@
|
||||
# Environment template for production deployment
|
||||
# This file defines all env vars used by docker-compose.prod.yml
|
||||
# Workflows should update their specific vars without overwriting others
|
||||
|
||||
# Container images (managed by respective build workflows)
|
||||
EAGLE_IMAGE=registry.digitalocean.com/eagle0/eagle-server:latest
|
||||
SHARDOK_IMAGE=registry.digitalocean.com/eagle0/shardok-server:latest
|
||||
ADMIN_IMAGE=registry.digitalocean.com/eagle0/admin-server:latest
|
||||
JFR_SIDECAR_IMAGE=registry.digitalocean.com/eagle0/jfr-sidecar:latest
|
||||
AUTH_IMAGE=registry.digitalocean.com/eagle0/auth-server:latest
|
||||
|
||||
# OpenAI / LLM
|
||||
OPENAI_API_KEY=
|
||||
GPT_MODEL_NAME=gpt-4o
|
||||
|
||||
# DigitalOcean Spaces (S3-compatible storage)
|
||||
EAGLE_ENABLE_S3=false
|
||||
DO_SPACES_ACCESS_KEY=
|
||||
DO_SPACES_SECRET_KEY=
|
||||
|
||||
# JWT authentication
|
||||
JWT_PRIVATE_KEY=
|
||||
|
||||
# OAuth providers
|
||||
DISCORD_CLIENT_ID=
|
||||
DISCORD_CLIENT_SECRET=
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
|
||||
# Shardok connection
|
||||
SHARDOK_ADDRESS=shardok:40042
|
||||
SHARDOK_AUTH_TOKEN=
|
||||
|
||||
# Monitoring
|
||||
SENTRY_DSN=
|
||||
|
||||
# Email (Fastmail JMAP)
|
||||
FASTMAIL_API_TOKEN=
|
||||
FASTMAIL_FROM_EMAIL=
|
||||
FASTMAIL_FROM_NAME=
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# Update .env file without losing other variables
|
||||
# Usage: ./update-env.sh KEY1=value1 KEY2=value2 ...
|
||||
#
|
||||
# This script:
|
||||
# 1. Creates .env from template if it doesn't exist
|
||||
# 2. Updates only the specified KEY=value pairs
|
||||
# 3. Preserves all other existing values
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ENV_FILE="${ENV_FILE:-/opt/eagle0/.env}"
|
||||
TEMPLATE_FILE="${TEMPLATE_FILE:-$SCRIPT_DIR/env.template}"
|
||||
|
||||
# Create .env from template if it doesn't exist
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
if [ -f "$TEMPLATE_FILE" ]; then
|
||||
echo "Creating .env from template..."
|
||||
grep -v '^#' "$TEMPLATE_FILE" | grep -v '^$' > "$ENV_FILE"
|
||||
else
|
||||
echo "Creating empty .env..."
|
||||
touch "$ENV_FILE"
|
||||
fi
|
||||
chmod 600 "$ENV_FILE"
|
||||
fi
|
||||
|
||||
# Process each KEY=VALUE argument
|
||||
for arg in "$@"; do
|
||||
# Skip empty args
|
||||
[ -z "$arg" ] && continue
|
||||
|
||||
# Parse KEY=VALUE
|
||||
KEY="${arg%%=*}"
|
||||
VALUE="${arg#*=}"
|
||||
|
||||
# Skip if no key
|
||||
[ -z "$KEY" ] && continue
|
||||
|
||||
# Skip setting empty values (keeps existing value)
|
||||
if [ -z "$VALUE" ]; then
|
||||
echo "Skipping $KEY (empty value)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Remove existing line for this key and add new one
|
||||
if grep -q "^${KEY}=" "$ENV_FILE" 2>/dev/null; then
|
||||
# Key exists, update it
|
||||
sed -i "s|^${KEY}=.*|${KEY}=${VALUE}|" "$ENV_FILE"
|
||||
echo "Updated $KEY"
|
||||
else
|
||||
# Key doesn't exist, add it
|
||||
echo "${KEY}=${VALUE}" >> "$ENV_FILE"
|
||||
echo "Added $KEY"
|
||||
fi
|
||||
done
|
||||
|
||||
chmod 600 "$ENV_FILE"
|
||||
echo "Done updating $ENV_FILE"
|
||||
+16
-8
@@ -24,12 +24,13 @@ http {
|
||||
# This prevents stale IP caching when containers restart
|
||||
resolver 127.0.0.11 valid=10s ipv6=off;
|
||||
|
||||
# Upstream for Eagle gRPC server
|
||||
# Eagle backend address - configured via variable for blue-green deployments
|
||||
# Using a variable makes nginx resolve the hostname at request time, not config load time
|
||||
# This allows nginx to reload even when the backend is temporarily down
|
||||
# For blue-green deployments, this is switched between eagle-blue:40032 and eagle-green:40032
|
||||
# by scripts/deploy-blue-green.sh, then nginx is reloaded with 'nginx -s reload'
|
||||
upstream eagle_grpc {
|
||||
server eagle-blue:40032;
|
||||
keepalive 100;
|
||||
map $host $eagle_backend {
|
||||
default "eagle-blue:40032";
|
||||
}
|
||||
|
||||
# HTTP server for Let's Encrypt challenge and redirect
|
||||
@@ -73,8 +74,8 @@ http {
|
||||
# Rate limiting
|
||||
limit_req zone=grpc_limit burst=50 nodelay;
|
||||
|
||||
# gRPC proxy
|
||||
grpc_pass grpc://eagle_grpc;
|
||||
# gRPC proxy - uses variable for blue-green deployment support
|
||||
grpc_pass grpc://$eagle_backend;
|
||||
|
||||
# Timeouts for long-running streams
|
||||
grpc_read_timeout 1200s;
|
||||
@@ -90,8 +91,8 @@ http {
|
||||
# Rate limiting
|
||||
limit_req zone=grpc_limit burst=50 nodelay;
|
||||
|
||||
# gRPC proxy
|
||||
grpc_pass grpc://eagle_grpc;
|
||||
# gRPC proxy - uses variable for blue-green deployment support
|
||||
grpc_pass grpc://$eagle_backend;
|
||||
|
||||
# Timeouts
|
||||
grpc_read_timeout 30s;
|
||||
@@ -108,6 +109,13 @@ http {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Invitation landing page (proxied to Go auth service)
|
||||
location /invite/ {
|
||||
proxy_pass http://auth:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Code sign a macOS .app bundle for distribution
|
||||
# Usage: codesign_mac_app.sh <app_path> [entitlements_path]
|
||||
#
|
||||
# Environment variables:
|
||||
# SIGNING_IDENTITY - The signing identity (default: "Developer ID Application")
|
||||
# KEYCHAIN_PASSWORD - Password to unlock the build keychain (optional)
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
APP_PATH="$1"
|
||||
ENTITLEMENTS_PATH="${2:-}"
|
||||
SIGNING_IDENTITY="${SIGNING_IDENTITY:-Developer ID Application}"
|
||||
|
||||
if [ ! -d "$APP_PATH" ]; then
|
||||
echo "ERROR: App not found at $APP_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Unlock keychain if password provided
|
||||
if [ -n "${KEYCHAIN_PASSWORD:-}" ]; then
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain || true
|
||||
fi
|
||||
|
||||
echo "=== Signing nested components first ==="
|
||||
|
||||
# Sign all dylibs
|
||||
find "$APP_PATH" -name "*.dylib" -print0 | while IFS= read -r -d '' item; do
|
||||
echo "Signing dylib: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
# Sign all bundles (plugins)
|
||||
find "$APP_PATH" -name "*.bundle" -print0 | while IFS= read -r -d '' item; do
|
||||
echo "Signing bundle: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
# Sign XPC services (inside Sparkle framework)
|
||||
find "$APP_PATH" -name "*.xpc" -print0 | while IFS= read -r -d '' item; do
|
||||
echo "Signing XPC service: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
# Sign nested apps (like Sparkle's Updater.app)
|
||||
find "$APP_PATH" -path "*/Frameworks/*.app" -print0 | while IFS= read -r -d '' item; do
|
||||
echo "Signing nested app: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
# Sign standalone executables inside frameworks (like Autoupdate)
|
||||
find "$APP_PATH" -path "*/Frameworks/*/Versions/*/Autoupdate" -type f -print0 | while IFS= read -r -d '' item; do
|
||||
echo "Signing executable: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
# Sign all frameworks (after their contents are signed)
|
||||
find "$APP_PATH" -name "*.framework" -print0 | while IFS= read -r -d '' item; do
|
||||
echo "Signing framework: $item"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$item"
|
||||
done
|
||||
|
||||
echo "=== Signing main app bundle ==="
|
||||
|
||||
if [ -n "$ENTITLEMENTS_PATH" ] && [ -f "$ENTITLEMENTS_PATH" ]; then
|
||||
echo "Using entitlements: $ENTITLEMENTS_PATH"
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--entitlements "$ENTITLEMENTS_PATH" \
|
||||
--sign "$SIGNING_IDENTITY" "$APP_PATH"
|
||||
else
|
||||
codesign --force --verify --verbose --timestamp --options runtime \
|
||||
--sign "$SIGNING_IDENTITY" "$APP_PATH"
|
||||
fi
|
||||
|
||||
echo "=== Verifying signature ==="
|
||||
codesign --verify --verbose=4 "$APP_PATH"
|
||||
|
||||
echo "=== Checking Gatekeeper assessment ==="
|
||||
spctl --assess --type exec -v "$APP_PATH" || echo "Note: Gatekeeper may reject until notarized"
|
||||
|
||||
echo "Code signing complete: $APP_PATH"
|
||||
@@ -48,6 +48,44 @@ get_active_instance() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Pull image with retry using crane (handles OCI/Docker digest mismatch)
|
||||
pull_with_retry() {
|
||||
local image=$1
|
||||
local max_attempts=${2:-3}
|
||||
local attempt=1
|
||||
|
||||
# Use crane if available (handles OCI format correctly)
|
||||
if [ -x "${APP_DIR}/crane" ]; then
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
log_info "Pulling image with crane (attempt ${attempt}/${max_attempts})..."
|
||||
if "${APP_DIR}/crane" pull "${image}" /tmp/image.tar && docker load -i /tmp/image.tar; then
|
||||
rm -f /tmp/image.tar
|
||||
log_info "Image pulled and loaded successfully"
|
||||
return 0
|
||||
fi
|
||||
rm -f /tmp/image.tar
|
||||
log_warn "Pull failed, retrying in 5 seconds..."
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
else
|
||||
# Fallback to docker pull if crane not available
|
||||
log_warn "crane not found at ${APP_DIR}/crane, falling back to docker pull"
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
log_info "Pulling image (attempt ${attempt}/${max_attempts})..."
|
||||
if docker pull "${image}"; then
|
||||
log_info "Image pulled successfully"
|
||||
return 0
|
||||
fi
|
||||
log_warn "Pull failed, retrying in 5 seconds..."
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
fi
|
||||
log_error "Failed to pull image after ${max_attempts} attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Wait for a container to be healthy
|
||||
wait_for_healthy() {
|
||||
local container=$1
|
||||
@@ -93,9 +131,11 @@ main() {
|
||||
log_info "Active instance: eagle-${active}"
|
||||
log_info "Staging instance: eagle-${staging}"
|
||||
|
||||
# Pull the new image
|
||||
log_info "Pulling new image..."
|
||||
docker pull "${new_image}"
|
||||
# Pull the new image (with retry for intermittent registry issues)
|
||||
if ! pull_with_retry "${new_image}" 3; then
|
||||
log_error "Failed to pull new image, aborting deployment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start staging instance with new image
|
||||
log_info "Starting eagle-${staging} with new image..."
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// +build ignore
|
||||
|
||||
// Script to generate Ed25519 key pair for manifest signing.
|
||||
// Run with: go run scripts/generate_manifest_keys.go
|
||||
//
|
||||
// This will output:
|
||||
// - Private key (base64): Store as MANIFEST_SIGNING_KEY GitHub secret
|
||||
// - Public key (base64): Embed in EagleInstaller for verification
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Generate Ed25519 key pair
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate key pair: %v", err)
|
||||
}
|
||||
|
||||
// Encode keys as base64
|
||||
privateKeyB64 := base64.StdEncoding.EncodeToString(privateKey)
|
||||
publicKeyB64 := base64.StdEncoding.EncodeToString(publicKey)
|
||||
|
||||
fmt.Println("=== Ed25519 Key Pair for Manifest Signing ===")
|
||||
fmt.Println()
|
||||
fmt.Println("PRIVATE KEY (store as GitHub secret MANIFEST_SIGNING_KEY):")
|
||||
fmt.Println(privateKeyB64)
|
||||
fmt.Println()
|
||||
fmt.Println("PUBLIC KEY (embed in EagleInstaller.cs for verification):")
|
||||
fmt.Println(publicKeyB64)
|
||||
fmt.Println()
|
||||
fmt.Printf("Private key size: %d bytes\n", len(privateKey))
|
||||
fmt.Printf("Public key size: %d bytes\n", len(publicKey))
|
||||
}
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Inject Sparkle framework into a macOS .app bundle for auto-updates
|
||||
# Usage: inject_sparkle.sh <app_path>
|
||||
#
|
||||
# Environment variables (required):
|
||||
# SPARKLE_EDDSA_PUBLIC_KEY - EdDSA public key for verifying updates
|
||||
#
|
||||
# Optional environment variables:
|
||||
# SPARKLE_FEED_URL - Appcast URL (default: https://assets.eagle0.net/mac/appcast.xml)
|
||||
# SPARKLE_VERSION - Sparkle version to use (default: 2.6.4)
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
APP_PATH="$1"
|
||||
SPARKLE_VERSION="${SPARKLE_VERSION:-2.6.4}"
|
||||
SPARKLE_FEED_URL="${SPARKLE_FEED_URL:-https://assets.eagle0.net/mac/appcast.xml}"
|
||||
SPARKLE_CACHE_DIR="/tmp/sparkle-cache"
|
||||
|
||||
if [ ! -d "$APP_PATH" ]; then
|
||||
echo "ERROR: App not found at $APP_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${SPARKLE_EDDSA_PUBLIC_KEY:-}" ]; then
|
||||
echo "ERROR: SPARKLE_EDDSA_PUBLIC_KEY environment variable not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Download Sparkle if not cached
|
||||
SPARKLE_DIR="$SPARKLE_CACHE_DIR/Sparkle-$SPARKLE_VERSION"
|
||||
if [ ! -d "$SPARKLE_DIR/Sparkle.framework" ]; then
|
||||
echo "=== Downloading Sparkle $SPARKLE_VERSION ==="
|
||||
mkdir -p "$SPARKLE_CACHE_DIR"
|
||||
SPARKLE_URL="https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz"
|
||||
curl -L "$SPARKLE_URL" | tar -xJ -C "$SPARKLE_CACHE_DIR"
|
||||
mv "$SPARKLE_CACHE_DIR/Sparkle-$SPARKLE_VERSION" "$SPARKLE_DIR" 2>/dev/null || true
|
||||
# If the extracted directory doesn't match version pattern, it may just be "Sparkle"
|
||||
if [ ! -d "$SPARKLE_DIR" ]; then
|
||||
mkdir -p "$SPARKLE_DIR"
|
||||
mv "$SPARKLE_CACHE_DIR/Sparkle.framework" "$SPARKLE_DIR/" 2>/dev/null || true
|
||||
mv "$SPARKLE_CACHE_DIR/bin" "$SPARKLE_DIR/" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "=== Injecting Sparkle framework ==="
|
||||
FRAMEWORKS_DIR="$APP_PATH/Contents/Frameworks"
|
||||
mkdir -p "$FRAMEWORKS_DIR"
|
||||
|
||||
# Copy Sparkle framework
|
||||
cp -R "$SPARKLE_DIR/Sparkle.framework" "$FRAMEWORKS_DIR/"
|
||||
|
||||
# Also copy the XPC services if present
|
||||
if [ -d "$SPARKLE_DIR/Sparkle.framework/Versions/B/XPCServices" ]; then
|
||||
echo "Sparkle XPC services present"
|
||||
fi
|
||||
|
||||
echo "=== Updating Info.plist ==="
|
||||
PLIST_PATH="$APP_PATH/Contents/Info.plist"
|
||||
|
||||
# Add Sparkle configuration to Info.plist
|
||||
/usr/libexec/PlistBuddy -c "Delete :SUFeedURL" "$PLIST_PATH" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Add :SUFeedURL string '$SPARKLE_FEED_URL'" "$PLIST_PATH"
|
||||
|
||||
/usr/libexec/PlistBuddy -c "Delete :SUPublicEDKey" "$PLIST_PATH" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Add :SUPublicEDKey string '$SPARKLE_EDDSA_PUBLIC_KEY'" "$PLIST_PATH"
|
||||
|
||||
/usr/libexec/PlistBuddy -c "Delete :SUEnableAutomaticChecks" "$PLIST_PATH" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Add :SUEnableAutomaticChecks bool true" "$PLIST_PATH"
|
||||
|
||||
# Set bundle version from git for Sparkle version comparison
|
||||
VERSION=$(git describe --tags --always 2>/dev/null || echo "1.0.0")
|
||||
BUILD_NUMBER=$(git rev-list --count HEAD 2>/dev/null || echo "1")
|
||||
|
||||
echo "Setting version: $VERSION (build $BUILD_NUMBER)"
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" "$PLIST_PATH" 2>/dev/null || \
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string '$VERSION'" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$PLIST_PATH" 2>/dev/null || \
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleVersion string '$BUILD_NUMBER'" "$PLIST_PATH"
|
||||
|
||||
# Add URL scheme for invitation codes (eagle0://invite?code=XXXX)
|
||||
echo "=== Adding URL scheme for invitation codes ==="
|
||||
/usr/libexec/PlistBuddy -c "Delete :CFBundleURLTypes" "$PLIST_PATH" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes array" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:0 dict" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:0:CFBundleURLName string 'com.Shardok-Games.eagle0'" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:0:CFBundleURLSchemes array" "$PLIST_PATH"
|
||||
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string 'eagle0'" "$PLIST_PATH"
|
||||
|
||||
echo "=== Sparkle injection complete ==="
|
||||
echo "App: $APP_PATH"
|
||||
echo "Feed URL: $SPARKLE_FEED_URL"
|
||||
echo "Version: $VERSION (build $BUILD_NUMBER)"
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Notarize a macOS .app bundle with Apple
|
||||
# Usage: notarize_mac_app.sh <app_path>
|
||||
#
|
||||
# Environment variables (required):
|
||||
# APPLE_ID - Apple Developer account email
|
||||
# APP_SPECIFIC_PASSWORD - App-specific password for notarytool
|
||||
# TEAM_ID - Apple Developer Team ID
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
APP_PATH="$1"
|
||||
|
||||
if [ ! -d "$APP_PATH" ]; then
|
||||
echo "ERROR: App not found at $APP_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${APPLE_ID:-}" ] || [ -z "${APP_SPECIFIC_PASSWORD:-}" ] || [ -z "${TEAM_ID:-}" ]; then
|
||||
echo "ERROR: Required environment variables not set"
|
||||
echo " APPLE_ID: ${APPLE_ID:-<not set>}"
|
||||
echo " APP_SPECIFIC_PASSWORD: ${APP_SPECIFIC_PASSWORD:+<set>}"
|
||||
echo " TEAM_ID: ${TEAM_ID:-<not set>}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create ZIP for notarization submission
|
||||
ZIP_PATH="${APP_PATH%.app}.zip"
|
||||
echo "=== Creating ZIP for notarization: $ZIP_PATH ==="
|
||||
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
|
||||
|
||||
echo "=== Submitting to Apple for notarization ==="
|
||||
SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$TEAM_ID" \
|
||||
--wait 2>&1) || true
|
||||
|
||||
echo "$SUBMIT_OUTPUT"
|
||||
|
||||
# Extract submission ID and status (look for " status:" to avoid matching "Current status:")
|
||||
SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep "id:" | head -1 | awk '{print $2}')
|
||||
STATUS=$(echo "$SUBMIT_OUTPUT" | grep "^ status:" | awk '{print $2}')
|
||||
|
||||
echo "Submission ID: $SUBMISSION_ID"
|
||||
echo "Status: $STATUS"
|
||||
|
||||
# Clean up the zip
|
||||
rm "$ZIP_PATH"
|
||||
|
||||
if [ "$STATUS" != "Accepted" ]; then
|
||||
echo "=== Notarization failed! Fetching log for details ==="
|
||||
xcrun notarytool log "$SUBMISSION_ID" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$TEAM_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Stapling notarization ticket to app ==="
|
||||
# Retry stapling - Apple's CloudKit can have a brief delay after notarization completes
|
||||
MAX_STAPLE_ATTEMPTS=5
|
||||
STAPLE_ATTEMPT=1
|
||||
while [ $STAPLE_ATTEMPT -le $MAX_STAPLE_ATTEMPTS ]; do
|
||||
echo "Stapling attempt $STAPLE_ATTEMPT/$MAX_STAPLE_ATTEMPTS..."
|
||||
if xcrun stapler staple "$APP_PATH"; then
|
||||
echo "Stapling successful"
|
||||
break
|
||||
fi
|
||||
if [ $STAPLE_ATTEMPT -eq $MAX_STAPLE_ATTEMPTS ]; then
|
||||
echo "ERROR: Stapling failed after $MAX_STAPLE_ATTEMPTS attempts"
|
||||
exit 1
|
||||
fi
|
||||
echo "Stapling failed, waiting 10 seconds before retry..."
|
||||
sleep 10
|
||||
STAPLE_ATTEMPT=$((STAPLE_ATTEMPT + 1))
|
||||
done
|
||||
|
||||
echo "=== Verifying notarization ==="
|
||||
xcrun stapler validate "$APP_PATH"
|
||||
spctl --assess --type exec -v "$APP_PATH"
|
||||
|
||||
echo "Notarization complete: $APP_PATH"
|
||||
@@ -107,6 +107,23 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
|
||||
alCache,
|
||||
battalionTypeGetter,
|
||||
braveWaterCost));
|
||||
} else if (!defenderPositions.empty()) {
|
||||
// Defenders exist but none are on castles - they're scattering/fleeing.
|
||||
// Chase them down rather than holding empty castles, since eliminating
|
||||
// all defenders also wins the battle via LAST_PLAYER_STANDING.
|
||||
chosenStrategy = AttackUnitsStrategy(GenerateTargetPriorities(
|
||||
Occupants(
|
||||
*gameState->units(),
|
||||
gameState->hex_map()->row_count(),
|
||||
gameState->hex_map()->column_count()),
|
||||
gameState->hex_map(),
|
||||
defenderPositions,
|
||||
attackerPid,
|
||||
attackerUnits,
|
||||
apdCache,
|
||||
alCache,
|
||||
battalionTypeGetter,
|
||||
braveWaterCost));
|
||||
} else {
|
||||
chosenStrategy = HoldCastlesStrategy;
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Notification/NotificationManagerEditor.cs" />
|
||||
<Compile Include="Assets/Modern UI Pack/Scripts/Button/ButtonManagerBasicEditor.cs" />
|
||||
<Compile Include="Assets/ConnectionHandler/RunningGameItem.cs" />
|
||||
<Compile Include="Assets/Tutorial/UI/TutorialCanvasBuilder.cs" />
|
||||
<Compile Include="Assets/Shardok/Table Rows/ArmyRowController.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/ARNNotifications/PrisonerExchangeDetailsNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/Notification.cs" />
|
||||
@@ -385,6 +386,7 @@
|
||||
<Compile Include="Assets/Eagle/Table Rows/IncomingArmyTableRow.cs" />
|
||||
<Compile Include="Assets/ConnectionHandler/CreateGameItem.cs" />
|
||||
<Compile Include="Assets/Eagle/EagleGameController.cs" />
|
||||
<Compile Include="Assets/Tutorial/TutorialTestSetup.cs" />
|
||||
<Compile Include="Assets/Eagle/Notifications/FailedSwearBrotherhoodNotificationGenerator.cs" />
|
||||
<Compile Include="Assets/Shardok/FireEffectAnimator.cs" />
|
||||
<Compile Include="Assets/common/GUIUtils/EventBasedTable.cs" />
|
||||
|
||||
+6
-1
@@ -122,8 +122,13 @@ namespace Auth {
|
||||
string localAppData =
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
return Path.Combine(localAppData, "eagle0");
|
||||
#elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
|
||||
// Mac: ~/Library/Application Support/eagle0
|
||||
string appSupport =
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
return Path.Combine(appSupport, "eagle0");
|
||||
#else
|
||||
// Other platforms don't use the Windows installer
|
||||
// Other platforms not yet supported
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -2992,6 +2992,90 @@ MonoBehaviour:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_IsOn: 1
|
||||
--- !u!1 &16227905
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 16227909}
|
||||
- component: {fileID: 16227908}
|
||||
- component: {fileID: 16227907}
|
||||
- component: {fileID: 16227906}
|
||||
m_Layer: 0
|
||||
m_Name: TutorialManager
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &16227906
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 16227905}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 370b3269832442e196d1b432bdae2f1e, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Eagle0.Tutorial.TutorialTestSetup
|
||||
EnableTestTutorials: 1
|
||||
ResetProgressOnStart: 0
|
||||
--- !u!114 &16227907
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 16227905}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 52abae862492497cb7a3f8c72f837b37, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Eagle0.Tutorial.TutorialUIManager
|
||||
ModalPanel: {fileID: 0}
|
||||
OverlayController: {fileID: 0}
|
||||
HintIndicatorPrefab: {fileID: 0}
|
||||
TutorialCanvas: {fileID: 0}
|
||||
CanvasFont: {fileID: 11400000, guid: ec2736df0379a45bfa7349b652fd07d6, type: 2}
|
||||
UseFallbackUI: 1
|
||||
FallbackFont: {fileID: 12800000, guid: f0f6814ff8ef048bcbfd5002f77188ea, type: 3}
|
||||
AutoBuildCanvasUI: 1
|
||||
--- !u!114 &16227908
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 16227905}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c529affa73b04d129ec5826ed0ac75e7, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Eagle0.Tutorial.TutorialManager
|
||||
TutorialsEnabled: 1
|
||||
OnboardingSequence: {fileID: 0}
|
||||
UIManager: {fileID: 16227907}
|
||||
DebugLogging: 0
|
||||
--- !u!4 &16227909
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 16227905}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 2560, y: 1440, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &17277548
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -8532,10 +8616,10 @@ RectTransform:
|
||||
- {fileID: 1396438488}
|
||||
m_Father: {fileID: 2130578398}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 333.5, y: -11.255}
|
||||
m_SizeDelta: {x: 200, y: 20}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &75920627
|
||||
MonoBehaviour:
|
||||
@@ -10927,10 +11011,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2130578398}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 218.5, y: -11.255}
|
||||
m_SizeDelta: {x: 20, y: 20}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &103511974
|
||||
MonoBehaviour:
|
||||
@@ -12298,10 +12382,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 683322975}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 306.5, y: -40}
|
||||
m_SizeDelta: {x: 51, y: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &114306270
|
||||
MonoBehaviour:
|
||||
@@ -15614,6 +15698,10 @@ MonoBehaviour:
|
||||
displayNameField: {fileID: 1956057658}
|
||||
setDisplayNameButton: {fileID: 700483942}
|
||||
displayNameErrorText: {fileID: 77360322}
|
||||
invitationCodePanel: {fileID: 0}
|
||||
invitationCodeField: {fileID: 0}
|
||||
submitInvitationCodeButton: {fileID: 0}
|
||||
invitationCodeErrorText: {fileID: 0}
|
||||
connectionStatusText: {fileID: 77360322}
|
||||
connectionPanel: {fileID: 596196018}
|
||||
gameSelectionPanel: {fileID: 1102342355}
|
||||
@@ -20624,10 +20712,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 256, y: -32.504997}
|
||||
m_SizeDelta: {x: 492, y: 45.01}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &171635196
|
||||
MonoBehaviour:
|
||||
@@ -29397,7 +29485,7 @@ RectTransform:
|
||||
m_Father: {fileID: 208702977}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 10, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -31829,7 +31917,7 @@ RectTransform:
|
||||
m_Father: {fileID: 1396438488}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 20, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -33967,6 +34055,7 @@ MonoBehaviour:
|
||||
eagleGameController: {fileID: 1965467940}
|
||||
ShardokCanvas: {fileID: 2088078267}
|
||||
connectionHandler: {fileID: 135486720}
|
||||
resetTutorialsButton: {fileID: 0}
|
||||
--- !u!1 &305665272
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -36778,10 +36867,10 @@ RectTransform:
|
||||
- {fileID: 1666727525}
|
||||
m_Father: {fileID: 2039520752}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 321, y: -11.255}
|
||||
m_SizeDelta: {x: 200, y: 20}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &329919723
|
||||
MonoBehaviour:
|
||||
@@ -47131,8 +47220,8 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1666727525}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.1, y: 0}
|
||||
m_AnchorMax: {x: 0.1, y: 1}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 20, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -64015,10 +64104,10 @@ RectTransform:
|
||||
- {fileID: 1375685348}
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 256, y: -215.03}
|
||||
m_SizeDelta: {x: 492, y: 30}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &548637657
|
||||
MonoBehaviour:
|
||||
@@ -71487,10 +71576,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 10, y: -120.03}
|
||||
m_SizeDelta: {x: 0, y: 10}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &590819392
|
||||
MonoBehaviour:
|
||||
@@ -74595,6 +74684,146 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 624827069}
|
||||
m_CullTransparentMesh: 0
|
||||
--- !u!1 &625062879
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 625062880}
|
||||
- component: {fileID: 625062882}
|
||||
- component: {fileID: 625062881}
|
||||
m_Layer: 0
|
||||
m_Name: Open Resources
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &625062880
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 625062879}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 673182551}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &625062881
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 625062879}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: 'Reset Tutorials
|
||||
|
||||
'
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: ec2736df0379a45bfa7349b652fd07d6, type: 2}
|
||||
m_sharedMaterial: {fileID: 1026247389042360723, guid: ec2736df0379a45bfa7349b652fd07d6,
|
||||
type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4281479730
|
||||
m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 18
|
||||
m_fontSizeBase: 18
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!222 &625062882
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 625062879}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &625252446
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -82452,6 +82681,160 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 673008387}
|
||||
m_CullTransparentMesh: 0
|
||||
--- !u!1 &673182550
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 673182551}
|
||||
- component: {fileID: 673182555}
|
||||
- component: {fileID: 673182554}
|
||||
- component: {fileID: 673182553}
|
||||
- component: {fileID: 673182552}
|
||||
m_Layer: 0
|
||||
m_Name: Reset Tutorials Button
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &673182551
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 673182550}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 625062880}
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &673182552
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 673182550}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: 30
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: 30
|
||||
m_FlexibleWidth: 1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &673182553
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 673182550}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 673182554}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls:
|
||||
- m_Target: {fileID: 300036928}
|
||||
m_TargetAssemblyTypeName: SettingsPanelController, Assembly-CSharp
|
||||
m_MethodName: OnResetTutorialsClick
|
||||
m_Mode: 1
|
||||
m_Arguments:
|
||||
m_ObjectArgument: {fileID: 0}
|
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
|
||||
m_IntArgument: 0
|
||||
m_FloatArgument: 0
|
||||
m_StringArgument:
|
||||
m_BoolArgument: 0
|
||||
m_CallState: 2
|
||||
--- !u!114 &673182554
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 673182550}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!222 &673182555
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 673182550}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &673307124
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -83590,10 +83973,10 @@ RectTransform:
|
||||
- {fileID: 1119022577}
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 256, y: -354}
|
||||
m_SizeDelta: {x: 492, y: 40}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &683322976
|
||||
MonoBehaviour:
|
||||
@@ -93480,7 +93863,7 @@ GameObject:
|
||||
- component: {fileID: 749366808}
|
||||
- component: {fileID: 749366807}
|
||||
m_Layer: 0
|
||||
m_Name: Button
|
||||
m_Name: Open Resources Button
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -93501,10 +93884,10 @@ RectTransform:
|
||||
- {fileID: 392813076}
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 256, y: -250.03}
|
||||
m_SizeDelta: {x: 492, y: 30}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &749366807
|
||||
MonoBehaviour:
|
||||
@@ -95360,10 +95743,10 @@ RectTransform:
|
||||
- {fileID: 699949417}
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 256, y: -180.03}
|
||||
m_SizeDelta: {x: 492, y: 30}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &770361456
|
||||
MonoBehaviour:
|
||||
@@ -96170,10 +96553,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2039520752}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 208.5, y: -11.255}
|
||||
m_SizeDelta: {x: 15, y: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &779503534
|
||||
MonoBehaviour:
|
||||
@@ -99264,10 +99647,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2039520752}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 446, y: -11.255}
|
||||
m_SizeDelta: {x: 40, y: 15.01}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &806086202
|
||||
MonoBehaviour:
|
||||
@@ -99806,10 +100189,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 10, y: -313.27002}
|
||||
m_SizeDelta: {x: 0, y: 31.459991}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &808870206
|
||||
MonoBehaviour:
|
||||
@@ -105263,10 +105646,10 @@ RectTransform:
|
||||
- {fileID: 113544276}
|
||||
m_Father: {fileID: 1590704177}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 333.5, y: -11.255}
|
||||
m_SizeDelta: {x: 200, y: 20}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &858025936
|
||||
MonoBehaviour:
|
||||
@@ -121385,10 +121768,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1590704177}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 196, y: -11.255}
|
||||
m_SizeDelta: {x: 15, y: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &972401127
|
||||
MonoBehaviour:
|
||||
@@ -122506,7 +122889,7 @@ RectTransform:
|
||||
m_Father: {fileID: 113544276}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 20, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -123520,7 +123903,7 @@ RectTransform:
|
||||
m_Father: {fileID: 1425392451}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0.1, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 10, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -140643,10 +141026,10 @@ RectTransform:
|
||||
- {fileID: 717768291}
|
||||
m_Father: {fileID: 683322975}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 417, y: -20}
|
||||
m_SizeDelta: {x: 150, y: 40}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1119022578
|
||||
MonoBehaviour:
|
||||
@@ -147583,10 +147966,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 683322975}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 135.5, y: -40}
|
||||
m_SizeDelta: {x: 51, y: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1184808158
|
||||
MonoBehaviour:
|
||||
@@ -153109,10 +153492,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2130578398}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 108.5, y: -11.255}
|
||||
m_SizeDelta: {x: 150, y: 22.51}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1219993393
|
||||
MonoBehaviour:
|
||||
@@ -174192,10 +174575,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2130578398}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 196, y: -11.255}
|
||||
m_SizeDelta: {x: 15, y: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1390131026
|
||||
MonoBehaviour:
|
||||
@@ -178285,10 +178668,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2039520752}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 111, y: -11.255}
|
||||
m_SizeDelta: {x: 170, y: 22.51}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1415802613
|
||||
MonoBehaviour:
|
||||
@@ -178444,10 +178827,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2130578398}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 448.5, y: -11.255}
|
||||
m_SizeDelta: {x: 20, y: 20}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1415887397
|
||||
MonoBehaviour:
|
||||
@@ -181904,10 +182287,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1590704177}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 108.5, y: -11.255}
|
||||
m_SizeDelta: {x: 150, y: 22.51}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1448655991
|
||||
MonoBehaviour:
|
||||
@@ -196899,10 +197282,10 @@ RectTransform:
|
||||
- {fileID: 1122913432}
|
||||
m_Father: {fileID: 683322975}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 50, y: -20}
|
||||
m_SizeDelta: {x: 100, y: 40}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1557328375
|
||||
MonoBehaviour:
|
||||
@@ -198649,7 +199032,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 2147483647
|
||||
m_IsActive: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1566150938
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -198670,6 +199053,7 @@ RectTransform:
|
||||
- {fileID: 770361455}
|
||||
- {fileID: 548637656}
|
||||
- {fileID: 749366806}
|
||||
- {fileID: 673182551}
|
||||
- {fileID: 2039520752}
|
||||
- {fileID: 808870205}
|
||||
- {fileID: 683322975}
|
||||
@@ -200241,10 +200625,10 @@ RectTransform:
|
||||
- {fileID: 2123746368}
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 256, y: -98.774994}
|
||||
m_SizeDelta: {x: 492, y: 22.51}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1590704178
|
||||
MonoBehaviour:
|
||||
@@ -219035,10 +219419,10 @@ RectTransform:
|
||||
- {fileID: 543540660}
|
||||
m_Father: {fileID: 683322975}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 221, y: -20}
|
||||
m_SizeDelta: {x: 100, y: 40}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1775111780
|
||||
MonoBehaviour:
|
||||
@@ -221290,7 +221674,7 @@ RectTransform:
|
||||
m_Father: {fileID: 1110049512}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 10, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -247406,10 +247790,10 @@ RectTransform:
|
||||
- {fileID: 806086201}
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 256, y: -281.285}
|
||||
m_SizeDelta: {x: 492, y: 22.51}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &2039520753
|
||||
MonoBehaviour:
|
||||
@@ -252578,10 +252962,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1590704177}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 218.5, y: -11.255}
|
||||
m_SizeDelta: {x: 20, y: 20}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &2096030393
|
||||
MonoBehaviour:
|
||||
@@ -255937,7 +256321,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
m_IsActive: 0
|
||||
--- !u!224 &2109805906
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -255957,7 +256341,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 256, y: -145.03}
|
||||
m_AnchoredPosition: {x: 256, y: -142.85143}
|
||||
m_SizeDelta: {x: 492, y: 30}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &2109805907
|
||||
@@ -257369,10 +257753,10 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1590704177}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 448.5, y: -11.255}
|
||||
m_SizeDelta: {x: 20, y: 20}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &2123746369
|
||||
MonoBehaviour:
|
||||
@@ -258672,10 +259056,10 @@ RectTransform:
|
||||
- {fileID: 1415887396}
|
||||
m_Father: {fileID: 1566150938}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 256, y: -71.265}
|
||||
m_SizeDelta: {x: 492, y: 22.51}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &2130578399
|
||||
MonoBehaviour:
|
||||
@@ -268781,6 +269165,7 @@ SceneRoots:
|
||||
m_Roots:
|
||||
- {fileID: 279614944}
|
||||
- {fileID: 815907755}
|
||||
- {fileID: 16227909}
|
||||
- {fileID: 122332320}
|
||||
- {fileID: 682560790}
|
||||
- {fileID: 1342510189}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using common;
|
||||
using eagle;
|
||||
using Eagle0.Tutorial;
|
||||
using Net.Eagle0.Shardok.Api;
|
||||
using Shardok;
|
||||
using TMPro;
|
||||
@@ -49,6 +50,8 @@ public class SettingsPanelController : MonoBehaviour {
|
||||
public GameObject ShardokCanvas;
|
||||
public ConnectionHandler connectionHandler;
|
||||
|
||||
public Button resetTutorialsButton;
|
||||
|
||||
void Start() {
|
||||
_active = false;
|
||||
panel.SetActive(false);
|
||||
@@ -148,6 +151,11 @@ public class SettingsPanelController : MonoBehaviour {
|
||||
Process.Start(Path.Combine(Application.persistentDataPath, "eagle0", "Resources"));
|
||||
}
|
||||
|
||||
public void OnResetTutorialsClick() {
|
||||
TutorialManager.Instance?.ResetAllProgress();
|
||||
UnityEngine.Debug.Log("Tutorial progress reset");
|
||||
}
|
||||
|
||||
private void HandleLobby() {
|
||||
eagleGameController.StopAll();
|
||||
eagleGameController.gameObject.SetActive(false);
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# Tutorial System for Eagle0
|
||||
|
||||
## Overview
|
||||
|
||||
A modular tutorial system for the Eagle0 Unity client supporting:
|
||||
- **Onboarding**: Full guided playthrough of first several turns
|
||||
- **Contextual tutorials**: First-encounter and first-attempt triggers with subtle hints
|
||||
- **Mixed UI**: Modals for concepts, overlays for UI guidance, hint indicators
|
||||
- **Local persistence**: PlayerPrefs-based state tracking
|
||||
|
||||
---
|
||||
|
||||
## Current Status
|
||||
|
||||
### Completed
|
||||
- [x] **Phase 1: Foundation** - TutorialState, TutorialManager, TutorialStep/Sequence
|
||||
- [x] **Phase 2: UI** - Canvas-based modal with runtime construction
|
||||
- TutorialCanvasBuilder creates full Canvas UI at runtime (no prefab needed)
|
||||
- Fantasy RPG color scheme (dark purple panel, gold accents)
|
||||
- Title, description, icon, progress bar, buttons
|
||||
- IMGUI fallback still available if needed
|
||||
- [x] **Phase 3: Triggers** - Event-based trigger system with game controller hooks
|
||||
- [x] **Test setup** - TutorialTestSetup with welcome intro, battle, command tutorials
|
||||
- [x] **Settings integration** - Reset Tutorials button in Settings panel
|
||||
- [x] **Game entry trigger** - Welcome tutorial shows when entering a game (not lobby)
|
||||
- [x] **Font assignment** - Stoke-Regular-SDF assigned to TutorialUIManager.CanvasFont
|
||||
|
||||
### Future Work
|
||||
- [ ] **Lobby tutorial helper** - Separate tutorial for lobby/game selection
|
||||
- [ ] **Overlay system** - TutorialOverlayController for highlighting UI elements
|
||||
- [ ] **Hint indicators** - TutorialHintIndicator for subtle pulsing dots
|
||||
- [ ] **Real tutorial content** - Replace test tutorials with actual onboarding sequence
|
||||
- [ ] **More contextual triggers** - Diplomacy, hero recruitment, spells, terrain, etc.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
TutorialManager (Singleton, DontDestroyOnLoad)
|
||||
├── TutorialState (PlayerPrefs persistence)
|
||||
├── TutorialTriggerRegistry
|
||||
│ ├── OnboardingTriggers
|
||||
│ └── ContextualTriggers
|
||||
└── TutorialUIManager
|
||||
├── TutorialCanvasBuilder (runtime Canvas construction)
|
||||
├── TutorialModalPanel (wired up by builder)
|
||||
├── TutorialOverlayController (stub)
|
||||
├── TutorialHintIndicator (stub)
|
||||
└── IMGUI Fallback (working)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
Assets/Tutorial/
|
||||
├── TutorialManager.cs ✓ Singleton, coordinates everything
|
||||
├── TutorialState.cs ✓ PlayerPrefs persistence
|
||||
├── TutorialTestSetup.cs ✓ Test tutorials (welcome, battle, command)
|
||||
├── TUTORIAL_PLAN.md ✓ This document
|
||||
├── Content/
|
||||
│ ├── TutorialStep.cs ✓ Step data structure
|
||||
│ └── TutorialSequence.cs ✓ Sequence ScriptableObject
|
||||
├── Triggers/
|
||||
│ └── TutorialTriggerRegistry.cs ✓ Event routing
|
||||
└── UI/
|
||||
├── TutorialUIManager.cs ✓ UI coordination + auto-build Canvas
|
||||
├── TutorialCanvasBuilder.cs ✓ Runtime Canvas UI construction
|
||||
├── TutorialModalPanel.cs ✓ Canvas-based modal panel controller
|
||||
├── TutorialOverlayController.cs Stub (future)
|
||||
└── TutorialHintIndicator.cs Stub (future)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Unity Setup
|
||||
|
||||
### Required Inspector Assignments
|
||||
1. **TutorialUIManager.CanvasFont** → `Stoke-Regular-SDF` (TextMeshPro font)
|
||||
2. **TutorialUIManager.FallbackFont** → `Stoke-Regular` (for IMGUI backup)
|
||||
|
||||
### No Prefab Required
|
||||
The `TutorialCanvasBuilder` creates the entire Canvas UI hierarchy at runtime:
|
||||
- Canvas with ScreenSpaceOverlay, sorting order 1000
|
||||
- Modal blocker (dark semi-transparent background)
|
||||
- Panel with vertical layout (title, description, progress, buttons)
|
||||
- All buttons wired to TutorialModalPanel handlers
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
### EagleGameController.cs
|
||||
- `SetUpGame()`: Calls `TutorialManager.Instance.Initialize(this, null)` → triggers "game_started" event
|
||||
- `SwapModel()`: Calls `OnModelUpdated()`
|
||||
- `ProvinceWasSelected()`: Calls `OnProvinceSelected()`
|
||||
- `PostCommittedCommand()`: Calls `OnCommandIssued()`
|
||||
|
||||
### ShardokGameController.cs
|
||||
- `SetUpGame()`: Calls `TutorialManager.Instance.Initialize(null, this)`
|
||||
- `ModelUpdated()`: Calls `OnBattleAction()`
|
||||
- `OnTurnEnded()`: Calls `OnTurnEnded()`
|
||||
- Unit selection: Calls `OnUnitSelected()`
|
||||
|
||||
### SettingsPanelController.cs
|
||||
- Reset Tutorials button: Calls `TutorialManager.Instance.ResetAllProgress()`
|
||||
|
||||
---
|
||||
|
||||
## Test Tutorials (Current)
|
||||
|
||||
| ID | Trigger Event | Title | When |
|
||||
|----|---------------|-------|------|
|
||||
| `test_intro` | `game_started` | Welcome to Eagle0! | Entering a game |
|
||||
| `test_first_battle` | `battle_entered` | Battle Begins! | First tactical battle |
|
||||
| `test_first_command` | `command_issued` | Command Issued! | First strategic command |
|
||||
|
||||
---
|
||||
|
||||
## Onboarding Flow (Planned)
|
||||
|
||||
| Step | Type | Content | Completion |
|
||||
|------|------|---------|------------|
|
||||
| 1 | Modal | Welcome to Eagle0 | Button click |
|
||||
| 2 | Overlay | Map overview - "Select a province" | Province selected |
|
||||
| 3 | Overlay | Province info panel explanation | Button click |
|
||||
| 4 | Overlay | Command buttons - "Try March" | March issued |
|
||||
| 5 | Modal | Turn cycle explanation | Button click |
|
||||
| 6 | Hidden | Wait for battle available | Model update |
|
||||
| 7 | Modal | Battle introduction | Button click |
|
||||
| 8 | Overlay | "Battle!" button highlight | Battle entered |
|
||||
| 9 | Modal | Tactical combat overview | Button click |
|
||||
| 10 | Overlay | Select and move a unit | Move issued |
|
||||
| 11 | Overlay | Attack an enemy | Attack issued |
|
||||
| 12 | Overlay | End Turn button | Turn ended |
|
||||
| 13 | Modal | Onboarding complete! | Button click |
|
||||
|
||||
---
|
||||
|
||||
## Contextual Tutorials (Planned)
|
||||
|
||||
### Strategic (First Encounter)
|
||||
| ID | Trigger | Display |
|
||||
|----|---------|---------|
|
||||
| `diplomacy_offer` | First diplomacy command available | Modal |
|
||||
| `hero_recruitment` | First recruitment available | Modal |
|
||||
| `province_riot` | First riot in owned province | Modal |
|
||||
| `weather_control` | First weather command available | Overlay |
|
||||
| `prisoner_capture` | First prisoner captured | Modal |
|
||||
|
||||
### Tactical (First Encounter/Attempt)
|
||||
| ID | Trigger | Display |
|
||||
|----|---------|---------|
|
||||
| `spell_lightning` | Lightning spell available | Tooltip |
|
||||
| `spell_meteor` | Meteor spell available | Modal |
|
||||
| `spell_holywave` | Holy Wave available | Tooltip |
|
||||
| `spell_raisedead` | Raise Dead available | Modal |
|
||||
| `terrain_fire` | Fire hex encountered | Tooltip |
|
||||
| `terrain_water` | Water adjacent to unit | Tooltip |
|
||||
| `ability_charge` | Charge command available | Overlay |
|
||||
| `flanking` | Flanking opportunity | Hint |
|
||||
|
||||
### Lobby (Future)
|
||||
| ID | Trigger | Display |
|
||||
|----|---------|---------|
|
||||
| `lobby_welcome` | First time in lobby | Modal |
|
||||
| `lobby_join_game` | Viewing game list | Overlay |
|
||||
| `lobby_create_game` | Create game button | Tooltip |
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
1. Ensure TutorialManager GameObject exists with:
|
||||
- TutorialManager component
|
||||
- TutorialUIManager component (assign fonts!)
|
||||
- TutorialTestSetup component
|
||||
2. Enable `Debug Logging` on TutorialManager for console output
|
||||
3. Play game → enter a game → see "Welcome to Eagle0!" modal
|
||||
4. Use Settings → Reset Tutorials to test again
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69183de61f7d440a29b4a4270395a180
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -83,6 +83,9 @@ namespace Eagle0.Tutorial {
|
||||
public void Initialize(EagleGameController eagle, ShardokGameController shardok) {
|
||||
_triggerRegistry?.Initialize(eagle, shardok);
|
||||
Log($"TutorialManager initialized with controllers - Eagle: {eagle != null}, Shardok: {shardok != null}");
|
||||
|
||||
// Trigger game_started event when entering a game (not lobby)
|
||||
if (eagle != null) { _triggerRegistry?.OnGameEvent("game_started"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,7 +103,7 @@ namespace Eagle0.Tutorial {
|
||||
}
|
||||
|
||||
if (OnboardingSequence == null) {
|
||||
Debug.LogWarning("TutorialManager: No onboarding sequence assigned");
|
||||
Log("StartOnboarding: No onboarding sequence assigned, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Eagle0.Tutorial {
|
||||
/// <summary>
|
||||
/// Test component that sets up a simple tutorial for end-to-end testing.
|
||||
/// Attach to TutorialManager GameObject to register test tutorials on startup.
|
||||
/// Can be removed once actual tutorial content is created in the Editor.
|
||||
/// </summary>
|
||||
public class TutorialTestSetup : MonoBehaviour {
|
||||
[Header("Test Configuration")]
|
||||
[Tooltip("Enable test tutorials")]
|
||||
public bool EnableTestTutorials = true;
|
||||
|
||||
[Tooltip("Reset tutorial progress on startup (for testing)")]
|
||||
public bool ResetProgressOnStart = false;
|
||||
|
||||
private void Start() {
|
||||
if (!EnableTestTutorials) return;
|
||||
|
||||
var manager = TutorialManager.Instance;
|
||||
if (manager == null) {
|
||||
Debug.LogWarning("TutorialTestSetup: TutorialManager not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ResetProgressOnStart) { manager.ResetAllProgress(); }
|
||||
|
||||
RegisterTestTutorials(manager);
|
||||
Debug.Log("TutorialTestSetup: Test tutorials registered");
|
||||
}
|
||||
|
||||
private void RegisterTestTutorials(TutorialManager manager) {
|
||||
// Create welcome/intro tutorial that shows on game start
|
||||
var introTutorial = CreateIntroTutorial();
|
||||
manager.TriggerRegistry.RegisterTutorial(introTutorial, "game_started");
|
||||
|
||||
// Create a simple "first battle" tutorial
|
||||
var firstBattleTutorial = CreateFirstBattleTutorial();
|
||||
manager.TriggerRegistry.RegisterTutorial(firstBattleTutorial, "battle_entered");
|
||||
|
||||
// Create a simple "first command" tutorial
|
||||
var firstCommandTutorial = CreateFirstCommandTutorial();
|
||||
manager.TriggerRegistry.RegisterTutorial(firstCommandTutorial, "command_issued");
|
||||
|
||||
// Note: "game_started" event is triggered by EagleGameController.SetUpGame()
|
||||
// via TutorialManager.Initialize(), not here
|
||||
}
|
||||
|
||||
private TutorialSequence CreateIntroTutorial() {
|
||||
var sequence = ScriptableObject.CreateInstance<TutorialSequence>();
|
||||
sequence.SequenceId = "test_intro";
|
||||
sequence.DisplayName = "Welcome";
|
||||
|
||||
sequence.Steps.Add(new TutorialStep {
|
||||
StepId = "welcome",
|
||||
Title = "Welcome to Eagle0!",
|
||||
Description =
|
||||
"Eagle0 is a strategy game where you command armies, manage provinces, and engage in tactical combat.\n\nClick on provinces to view their details and issue commands. When battles occur, you'll fight them out on a hex-based tactical map.\n\nGood luck, Commander!",
|
||||
DisplayMode = TutorialDisplayMode.Modal,
|
||||
CompletionType = TutorialCompletionType.ButtonClick,
|
||||
AllowSkip = false
|
||||
});
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
private TutorialSequence CreateFirstBattleTutorial() {
|
||||
var sequence = ScriptableObject.CreateInstance<TutorialSequence>();
|
||||
sequence.SequenceId = "test_first_battle";
|
||||
sequence.DisplayName = "First Battle";
|
||||
|
||||
sequence.Steps.Add(new TutorialStep {
|
||||
StepId = "battle_intro",
|
||||
Title = "Battle Begins!",
|
||||
Description =
|
||||
"You've entered tactical combat. Command your units on the hex grid to defeat the enemy.\n\n(This is a test tutorial to verify the tutorial system is working.)",
|
||||
DisplayMode = TutorialDisplayMode.Modal,
|
||||
CompletionType = TutorialCompletionType.ButtonClick,
|
||||
AllowSkip = true
|
||||
});
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
private TutorialSequence CreateFirstCommandTutorial() {
|
||||
var sequence = ScriptableObject.CreateInstance<TutorialSequence>();
|
||||
sequence.SequenceId = "test_first_command";
|
||||
sequence.DisplayName = "First Command";
|
||||
|
||||
sequence.Steps.Add(new TutorialStep {
|
||||
StepId = "command_issued_intro",
|
||||
Title = "Command Issued!",
|
||||
Description =
|
||||
"You've issued your first command. Commands are processed at the end of each turn.\n\n(This is a test tutorial to verify the tutorial system is working.)",
|
||||
DisplayMode = TutorialDisplayMode.Modal,
|
||||
CompletionType = TutorialCompletionType.ButtonClick,
|
||||
AllowSkip = true
|
||||
});
|
||||
|
||||
return sequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 370b3269832442e196d1b432bdae2f1e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Eagle0.Tutorial {
|
||||
/// <summary>
|
||||
/// Builds the tutorial Canvas UI programmatically at runtime.
|
||||
/// Creates a complete TutorialModalPanel with all required UI elements.
|
||||
/// </summary>
|
||||
public static class TutorialCanvasBuilder {
|
||||
// Colors matching the game's fantasy RPG style
|
||||
private static readonly Color PanelBackgroundColor = new Color(0.15f, 0.12f, 0.2f, 0.95f);
|
||||
private static readonly Color BorderColor = new Color(0.6f, 0.5f, 0.3f, 1f);
|
||||
private static readonly Color TitleColor = new Color(1f, 0.9f, 0.7f, 1f);
|
||||
private static readonly Color TextColor = new Color(0.9f, 0.85f, 0.8f, 1f);
|
||||
private static readonly Color ButtonColor = new Color(0.4f, 0.3f, 0.2f, 1f);
|
||||
private static readonly Color ButtonHoverColor = new Color(0.5f, 0.4f, 0.25f, 1f);
|
||||
private static readonly Color ButtonTextColor = new Color(1f, 0.95f, 0.85f, 1f);
|
||||
private static readonly Color ModalBlockerColor = new Color(0f, 0f, 0f, 0.75f);
|
||||
private static readonly Color ProgressBarColor = new Color(0.6f, 0.5f, 0.3f, 1f);
|
||||
private static readonly Color ProgressBackgroundColor = new Color(0.2f, 0.15f, 0.1f, 1f);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a complete tutorial Canvas with modal panel.
|
||||
/// Returns the Canvas GameObject and sets up the TutorialModalPanel component.
|
||||
/// </summary>
|
||||
/// <param name="font">TextMeshPro font to use (assign Stoke-Regular-SDF in
|
||||
/// inspector)</param>
|
||||
public static Canvas BuildTutorialCanvas(TMP_FontAsset font) {
|
||||
// Create Canvas
|
||||
GameObject canvasObj = new GameObject("TutorialCanvas");
|
||||
Canvas canvas = canvasObj.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 1000;
|
||||
|
||||
CanvasScaler scaler = canvasObj.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920, 1080);
|
||||
scaler.matchWidthOrHeight = 0.5f;
|
||||
|
||||
canvasObj.AddComponent<GraphicRaycaster>();
|
||||
|
||||
// Create Modal Blocker
|
||||
GameObject blockerObj = CreateModalBlocker(canvasObj.transform);
|
||||
|
||||
// Create Panel Container
|
||||
GameObject panelContainer = CreatePanelContainer(canvasObj.transform, font);
|
||||
|
||||
// Add TutorialModalPanel component and wire it up
|
||||
TutorialModalPanel modalPanel = canvasObj.AddComponent<TutorialModalPanel>();
|
||||
WireUpModalPanel(modalPanel, blockerObj, panelContainer, font);
|
||||
|
||||
// Set up button listeners (Awake runs before fields are wired, so do it here)
|
||||
SetupButtonListeners(modalPanel);
|
||||
|
||||
// Start with everything hidden
|
||||
blockerObj.SetActive(false);
|
||||
panelContainer.SetActive(false);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
private static GameObject CreateModalBlocker(Transform parent) {
|
||||
GameObject blocker = new GameObject("ModalBlocker");
|
||||
blocker.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform rect = blocker.AddComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.offsetMin = Vector2.zero;
|
||||
rect.offsetMax = Vector2.zero;
|
||||
|
||||
Image image = blocker.AddComponent<Image>();
|
||||
image.color = ModalBlockerColor;
|
||||
image.raycastTarget = true;
|
||||
|
||||
return blocker;
|
||||
}
|
||||
|
||||
private static GameObject CreatePanelContainer(Transform parent, TMP_FontAsset font) {
|
||||
// Panel Container (centered)
|
||||
GameObject panel = new GameObject("PanelContainer");
|
||||
panel.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform panelRect = panel.AddComponent<RectTransform>();
|
||||
panelRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
panelRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
panelRect.sizeDelta = new Vector2(800, 550);
|
||||
panelRect.anchoredPosition = Vector2.zero;
|
||||
|
||||
// Panel Background
|
||||
Image panelBg = panel.AddComponent<Image>();
|
||||
panelBg.color = PanelBackgroundColor;
|
||||
panelBg.raycastTarget = true;
|
||||
|
||||
// Add outline/border effect
|
||||
Outline outline = panel.AddComponent<Outline>();
|
||||
outline.effectColor = BorderColor;
|
||||
outline.effectDistance = new Vector2(3, 3);
|
||||
|
||||
// Add vertical layout group
|
||||
VerticalLayoutGroup layout = panel.AddComponent<VerticalLayoutGroup>();
|
||||
layout.padding = new RectOffset(40, 40, 30, 30);
|
||||
layout.spacing = 20;
|
||||
layout.childAlignment = TextAnchor.UpperCenter;
|
||||
layout.childControlHeight = false;
|
||||
layout.childControlWidth = true;
|
||||
layout.childForceExpandHeight = false;
|
||||
layout.childForceExpandWidth = true;
|
||||
|
||||
// Title
|
||||
CreateTitle(panel.transform, font);
|
||||
|
||||
// Icon (placeholder - starts hidden)
|
||||
CreateIcon(panel.transform);
|
||||
|
||||
// Description
|
||||
CreateDescription(panel.transform, font);
|
||||
|
||||
// Flexible spacer to push buttons to bottom
|
||||
CreateFlexibleSpacer(panel.transform);
|
||||
|
||||
// Progress section
|
||||
CreateProgressSection(panel.transform, font);
|
||||
|
||||
// Button row
|
||||
CreateButtonRow(panel.transform, font);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private static void CreateTitle(Transform parent, TMP_FontAsset font) {
|
||||
GameObject titleObj = new GameObject("TitleText");
|
||||
titleObj.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform rect = titleObj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(720, 60);
|
||||
|
||||
TextMeshProUGUI text = titleObj.AddComponent<TextMeshProUGUI>();
|
||||
text.text = "Tutorial";
|
||||
text.fontSize = 42;
|
||||
text.fontStyle = FontStyles.Bold;
|
||||
text.color = TitleColor;
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
if (font != null) text.font = font;
|
||||
|
||||
LayoutElement layoutElement = titleObj.AddComponent<LayoutElement>();
|
||||
layoutElement.preferredHeight = 60;
|
||||
}
|
||||
|
||||
private static void CreateIcon(Transform parent) {
|
||||
GameObject iconObj = new GameObject("IconImage");
|
||||
iconObj.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform rect = iconObj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(100, 100);
|
||||
|
||||
Image image = iconObj.AddComponent<Image>();
|
||||
image.color = Color.white;
|
||||
image.preserveAspect = true;
|
||||
|
||||
LayoutElement layoutElement = iconObj.AddComponent<LayoutElement>();
|
||||
layoutElement.preferredHeight = 100;
|
||||
layoutElement.preferredWidth = 100;
|
||||
|
||||
// Start hidden
|
||||
iconObj.SetActive(false);
|
||||
}
|
||||
|
||||
private static void CreateDescription(Transform parent, TMP_FontAsset font) {
|
||||
GameObject descObj = new GameObject("DescriptionText");
|
||||
descObj.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform rect = descObj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(720, 200);
|
||||
|
||||
TextMeshProUGUI text = descObj.AddComponent<TextMeshProUGUI>();
|
||||
text.text = "";
|
||||
text.fontSize = 26;
|
||||
text.color = TextColor;
|
||||
text.alignment = TextAlignmentOptions.TopLeft;
|
||||
text.enableWordWrapping = true;
|
||||
text.overflowMode = TextOverflowModes.Overflow; // Don't truncate
|
||||
if (font != null) text.font = font;
|
||||
|
||||
LayoutElement layoutElement = descObj.AddComponent<LayoutElement>();
|
||||
layoutElement.preferredHeight = 200;
|
||||
layoutElement.minHeight = 100;
|
||||
}
|
||||
|
||||
private static void CreateSpacer(Transform parent, float height) {
|
||||
GameObject spacer = new GameObject("Spacer");
|
||||
spacer.transform.SetParent(parent, false);
|
||||
|
||||
spacer.AddComponent<RectTransform>();
|
||||
|
||||
LayoutElement layoutElement = spacer.AddComponent<LayoutElement>();
|
||||
layoutElement.preferredHeight = height;
|
||||
}
|
||||
|
||||
private static void CreateFlexibleSpacer(Transform parent) {
|
||||
GameObject spacer = new GameObject("FlexibleSpacer");
|
||||
spacer.transform.SetParent(parent, false);
|
||||
|
||||
spacer.AddComponent<RectTransform>();
|
||||
|
||||
LayoutElement layoutElement = spacer.AddComponent<LayoutElement>();
|
||||
layoutElement.flexibleHeight = 1;
|
||||
}
|
||||
|
||||
private static void CreateProgressSection(Transform parent, TMP_FontAsset font) {
|
||||
GameObject progressSection = new GameObject("ProgressSection");
|
||||
progressSection.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform rect = progressSection.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(720, 50);
|
||||
|
||||
VerticalLayoutGroup layout = progressSection.AddComponent<VerticalLayoutGroup>();
|
||||
layout.spacing = 8;
|
||||
layout.childAlignment = TextAnchor.MiddleCenter;
|
||||
layout.childControlHeight = false;
|
||||
layout.childControlWidth = true;
|
||||
|
||||
LayoutElement layoutElement = progressSection.AddComponent<LayoutElement>();
|
||||
layoutElement.preferredHeight = 50;
|
||||
|
||||
// Progress Text
|
||||
GameObject progressTextObj = new GameObject("ProgressText");
|
||||
progressTextObj.transform.SetParent(progressSection.transform, false);
|
||||
|
||||
RectTransform textRect = progressTextObj.AddComponent<RectTransform>();
|
||||
textRect.sizeDelta = new Vector2(720, 24);
|
||||
|
||||
TextMeshProUGUI progressText = progressTextObj.AddComponent<TextMeshProUGUI>();
|
||||
progressText.text = "Step 1 of 1";
|
||||
progressText.fontSize = 20;
|
||||
progressText.color = TextColor;
|
||||
progressText.alignment = TextAlignmentOptions.Center;
|
||||
if (font != null) progressText.font = font;
|
||||
|
||||
LayoutElement textLayout = progressTextObj.AddComponent<LayoutElement>();
|
||||
textLayout.preferredHeight = 24;
|
||||
|
||||
// Progress Slider
|
||||
CreateProgressSlider(progressSection.transform);
|
||||
}
|
||||
|
||||
private static void CreateProgressSlider(Transform parent) {
|
||||
GameObject sliderObj = new GameObject("ProgressSlider");
|
||||
sliderObj.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform sliderRect = sliderObj.AddComponent<RectTransform>();
|
||||
sliderRect.sizeDelta = new Vector2(400, 12);
|
||||
|
||||
Slider slider = sliderObj.AddComponent<Slider>();
|
||||
slider.minValue = 0;
|
||||
slider.maxValue = 1;
|
||||
slider.value = 0;
|
||||
slider.interactable = false;
|
||||
|
||||
LayoutElement sliderLayout = sliderObj.AddComponent<LayoutElement>();
|
||||
sliderLayout.preferredHeight = 12;
|
||||
sliderLayout.preferredWidth = 400;
|
||||
|
||||
// Background
|
||||
GameObject bgObj = new GameObject("Background");
|
||||
bgObj.transform.SetParent(sliderObj.transform, false);
|
||||
|
||||
RectTransform bgRect = bgObj.AddComponent<RectTransform>();
|
||||
bgRect.anchorMin = Vector2.zero;
|
||||
bgRect.anchorMax = Vector2.one;
|
||||
bgRect.offsetMin = Vector2.zero;
|
||||
bgRect.offsetMax = Vector2.zero;
|
||||
|
||||
Image bgImage = bgObj.AddComponent<Image>();
|
||||
bgImage.color = ProgressBackgroundColor;
|
||||
|
||||
// Fill Area
|
||||
GameObject fillArea = new GameObject("Fill Area");
|
||||
fillArea.transform.SetParent(sliderObj.transform, false);
|
||||
|
||||
RectTransform fillAreaRect = fillArea.AddComponent<RectTransform>();
|
||||
fillAreaRect.anchorMin = Vector2.zero;
|
||||
fillAreaRect.anchorMax = Vector2.one;
|
||||
fillAreaRect.offsetMin = Vector2.zero;
|
||||
fillAreaRect.offsetMax = Vector2.zero;
|
||||
|
||||
// Fill
|
||||
GameObject fill = new GameObject("Fill");
|
||||
fill.transform.SetParent(fillArea.transform, false);
|
||||
|
||||
RectTransform fillRect = fill.AddComponent<RectTransform>();
|
||||
fillRect.anchorMin = Vector2.zero;
|
||||
fillRect.anchorMax = new Vector2(0, 1);
|
||||
fillRect.offsetMin = Vector2.zero;
|
||||
fillRect.offsetMax = Vector2.zero;
|
||||
|
||||
Image fillImage = fill.AddComponent<Image>();
|
||||
fillImage.color = ProgressBarColor;
|
||||
|
||||
// Wire up slider
|
||||
slider.fillRect = fillRect;
|
||||
}
|
||||
|
||||
private static void SetupButtonListeners(TutorialModalPanel panel) {
|
||||
// Button listeners need to be set up after wiring since Awake runs before fields exist
|
||||
if (panel.ContinueButton != null) {
|
||||
panel.ContinueButton.onClick.RemoveAllListeners();
|
||||
panel.ContinueButton.onClick.AddListener(panel.OnContinueClicked);
|
||||
}
|
||||
if (panel.SkipButton != null) {
|
||||
panel.SkipButton.onClick.RemoveAllListeners();
|
||||
panel.SkipButton.onClick.AddListener(panel.OnSkipClicked);
|
||||
}
|
||||
if (panel.SkipAllButton != null) {
|
||||
panel.SkipAllButton.onClick.RemoveAllListeners();
|
||||
panel.SkipAllButton.onClick.AddListener(panel.OnSkipAllClicked);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateButtonRow(Transform parent, TMP_FontAsset font) {
|
||||
GameObject buttonRow = new GameObject("ButtonRow");
|
||||
buttonRow.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform rect = buttonRow.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(720, 60);
|
||||
|
||||
HorizontalLayoutGroup layout = buttonRow.AddComponent<HorizontalLayoutGroup>();
|
||||
layout.spacing = 20;
|
||||
layout.childAlignment = TextAnchor.MiddleCenter;
|
||||
layout.childControlHeight = false;
|
||||
layout.childControlWidth = false;
|
||||
layout.childForceExpandHeight = false;
|
||||
layout.childForceExpandWidth = false;
|
||||
|
||||
LayoutElement layoutElement = buttonRow.AddComponent<LayoutElement>();
|
||||
layoutElement.preferredHeight = 60;
|
||||
|
||||
// Skip Button (left)
|
||||
CreateButton(buttonRow.transform, "SkipButton", "Skip", 150, 50, font);
|
||||
|
||||
// Skip All Button
|
||||
CreateButton(buttonRow.transform, "SkipAllButton", "Skip All", 150, 50, font);
|
||||
|
||||
// Spacer
|
||||
GameObject spacer = new GameObject("ButtonSpacer");
|
||||
spacer.transform.SetParent(buttonRow.transform, false);
|
||||
spacer.AddComponent<RectTransform>();
|
||||
LayoutElement spacerLayout = spacer.AddComponent<LayoutElement>();
|
||||
spacerLayout.flexibleWidth = 1;
|
||||
|
||||
// Continue Button (right)
|
||||
CreateButton(buttonRow.transform, "ContinueButton", "Continue", 180, 50, font);
|
||||
}
|
||||
|
||||
private static GameObject CreateButton(
|
||||
Transform parent,
|
||||
string name,
|
||||
string text,
|
||||
float width,
|
||||
float height,
|
||||
TMP_FontAsset font) {
|
||||
GameObject buttonObj = new GameObject(name);
|
||||
buttonObj.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform rect = buttonObj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(width, height);
|
||||
|
||||
Image image = buttonObj.AddComponent<Image>();
|
||||
image.color = ButtonColor;
|
||||
|
||||
Button button = buttonObj.AddComponent<Button>();
|
||||
ColorBlock colors = button.colors;
|
||||
colors.normalColor = ButtonColor;
|
||||
colors.highlightedColor = ButtonHoverColor;
|
||||
colors.pressedColor = ButtonHoverColor;
|
||||
colors.selectedColor = ButtonHoverColor;
|
||||
button.colors = colors;
|
||||
|
||||
// Add outline
|
||||
Outline outline = buttonObj.AddComponent<Outline>();
|
||||
outline.effectColor = BorderColor;
|
||||
outline.effectDistance = new Vector2(2, 2);
|
||||
|
||||
LayoutElement layoutElement = buttonObj.AddComponent<LayoutElement>();
|
||||
layoutElement.preferredWidth = width;
|
||||
layoutElement.preferredHeight = height;
|
||||
|
||||
// Button text
|
||||
GameObject textObj = new GameObject("Text");
|
||||
textObj.transform.SetParent(buttonObj.transform, false);
|
||||
|
||||
RectTransform textRect = textObj.AddComponent<RectTransform>();
|
||||
textRect.anchorMin = Vector2.zero;
|
||||
textRect.anchorMax = Vector2.one;
|
||||
textRect.offsetMin = Vector2.zero;
|
||||
textRect.offsetMax = Vector2.zero;
|
||||
|
||||
TextMeshProUGUI buttonText = textObj.AddComponent<TextMeshProUGUI>();
|
||||
buttonText.text = text;
|
||||
buttonText.fontSize = 24;
|
||||
buttonText.color = ButtonTextColor;
|
||||
buttonText.alignment = TextAlignmentOptions.Center;
|
||||
if (font != null) buttonText.font = font;
|
||||
|
||||
return buttonObj;
|
||||
}
|
||||
|
||||
private static void WireUpModalPanel(
|
||||
TutorialModalPanel panel,
|
||||
GameObject blocker,
|
||||
GameObject panelContainer,
|
||||
TMP_FontAsset font) {
|
||||
panel.ModalBlocker = blocker;
|
||||
panel.PanelContainer = panelContainer;
|
||||
|
||||
// Find and wire up components
|
||||
Transform containerTransform = panelContainer.transform;
|
||||
|
||||
// Title
|
||||
Transform titleTransform = containerTransform.Find("TitleText");
|
||||
if (titleTransform != null) {
|
||||
panel.TitleText = titleTransform.GetComponent<TextMeshProUGUI>();
|
||||
}
|
||||
|
||||
// Icon
|
||||
Transform iconTransform = containerTransform.Find("IconImage");
|
||||
if (iconTransform != null) { panel.IconImage = iconTransform.GetComponent<Image>(); }
|
||||
|
||||
// Description
|
||||
Transform descTransform = containerTransform.Find("DescriptionText");
|
||||
if (descTransform != null) {
|
||||
panel.DescriptionText = descTransform.GetComponent<TextMeshProUGUI>();
|
||||
}
|
||||
|
||||
// Progress Section
|
||||
Transform progressSection = containerTransform.Find("ProgressSection");
|
||||
if (progressSection != null) {
|
||||
Transform progressTextTransform = progressSection.Find("ProgressText");
|
||||
if (progressTextTransform != null) {
|
||||
panel.ProgressText = progressTextTransform.GetComponent<TextMeshProUGUI>();
|
||||
}
|
||||
|
||||
Transform sliderTransform = progressSection.Find("ProgressSlider");
|
||||
if (sliderTransform != null) {
|
||||
panel.ProgressSlider = sliderTransform.GetComponent<Slider>();
|
||||
}
|
||||
}
|
||||
|
||||
// Button Row
|
||||
Transform buttonRow = containerTransform.Find("ButtonRow");
|
||||
if (buttonRow != null) {
|
||||
Transform skipTransform = buttonRow.Find("SkipButton");
|
||||
if (skipTransform != null) {
|
||||
panel.SkipButton = skipTransform.GetComponent<Button>();
|
||||
}
|
||||
|
||||
Transform skipAllTransform = buttonRow.Find("SkipAllButton");
|
||||
if (skipAllTransform != null) {
|
||||
panel.SkipAllButton = skipAllTransform.GetComponent<Button>();
|
||||
}
|
||||
|
||||
Transform continueTransform = buttonRow.Find("ContinueButton");
|
||||
if (continueTransform != null) {
|
||||
panel.ContinueButton = continueTransform.GetComponent<Button>();
|
||||
Transform continueTextTransform = continueTransform.Find("Text");
|
||||
if (continueTextTransform != null) {
|
||||
panel.ContinueButtonText =
|
||||
continueTextTransform.GetComponent<TextMeshProUGUI>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98467fdbaf584fbb928a373340b542d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
-3
@@ -135,19 +135,31 @@ namespace Eagle0.Tutorial {
|
||||
_onSkip = null;
|
||||
}
|
||||
|
||||
private void OnContinueClicked() {
|
||||
/// <summary>
|
||||
/// Called when Continue button is clicked.
|
||||
/// Public to allow external setup of button listeners.
|
||||
/// </summary>
|
||||
public void OnContinueClicked() {
|
||||
var callback = _onContinue;
|
||||
Hide();
|
||||
callback?.Invoke();
|
||||
}
|
||||
|
||||
private void OnSkipClicked() {
|
||||
/// <summary>
|
||||
/// Called when Skip button is clicked.
|
||||
/// Public to allow external setup of button listeners.
|
||||
/// </summary>
|
||||
public void OnSkipClicked() {
|
||||
var callback = _onSkip;
|
||||
Hide();
|
||||
callback?.Invoke();
|
||||
}
|
||||
|
||||
private void OnSkipAllClicked() {
|
||||
/// <summary>
|
||||
/// Called when Skip All button is clicked.
|
||||
/// Public to allow external setup of button listeners.
|
||||
/// </summary>
|
||||
public void OnSkipAllClicked() {
|
||||
Hide();
|
||||
TutorialManager.Instance?.SkipAllOnboarding();
|
||||
}
|
||||
|
||||
+184
-5
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Eagle0.Tutorial {
|
||||
@@ -22,17 +23,189 @@ namespace Eagle0.Tutorial {
|
||||
[Tooltip("Canvas for tutorial UI (should be above game UI)")]
|
||||
public Canvas TutorialCanvas;
|
||||
|
||||
[Header("Fonts")]
|
||||
[Tooltip("TextMeshPro font for Canvas UI (assign Stoke-Regular-SDF)")]
|
||||
public TMP_FontAsset CanvasFont;
|
||||
|
||||
[Header("Fallback UI")]
|
||||
[Tooltip("Use IMGUI fallback when Canvas UI cannot be built")]
|
||||
public bool UseFallbackUI = true;
|
||||
|
||||
[Tooltip("Font for IMGUI fallback (assign Stoke-Regular)")]
|
||||
public Font FallbackFont;
|
||||
|
||||
[Header("Runtime Options")]
|
||||
[Tooltip("Auto-build Canvas UI at runtime if ModalPanel is not assigned")]
|
||||
public bool AutoBuildCanvasUI = true;
|
||||
|
||||
// Active hints
|
||||
private Dictionary<string, TutorialHintIndicator> _activeHints =
|
||||
new Dictionary<string, TutorialHintIndicator>();
|
||||
|
||||
// Fallback modal state
|
||||
private bool _fallbackModalVisible;
|
||||
private TutorialStep _fallbackStep;
|
||||
private Action _fallbackOnContinue;
|
||||
private Action _fallbackOnSkip;
|
||||
private int _fallbackCurrentIndex;
|
||||
private int _fallbackTotalSteps;
|
||||
private bool _fallbackIsOnboarding;
|
||||
|
||||
private void Awake() {
|
||||
// Auto-build Canvas UI if needed
|
||||
if (ModalPanel == null && AutoBuildCanvasUI) { BuildCanvasUI(); }
|
||||
|
||||
// Ensure canvas is set up correctly
|
||||
if (TutorialCanvas != null) {
|
||||
TutorialCanvas.sortingOrder = 1000; // Above most game UI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the Canvas-based tutorial UI at runtime.
|
||||
/// Called automatically if AutoBuildCanvasUI is true and ModalPanel is not assigned.
|
||||
/// </summary>
|
||||
private void BuildCanvasUI() {
|
||||
TutorialCanvas = TutorialCanvasBuilder.BuildTutorialCanvas(CanvasFont);
|
||||
if (TutorialCanvas != null) {
|
||||
// Make canvas a child of this object so it persists with TutorialManager
|
||||
TutorialCanvas.transform.SetParent(transform, false);
|
||||
|
||||
// Get the modal panel that was created
|
||||
ModalPanel = TutorialCanvas.GetComponent<TutorialModalPanel>();
|
||||
|
||||
Debug.Log("TutorialUIManager: Built Canvas UI at runtime");
|
||||
} else {
|
||||
Debug.LogWarning("TutorialUIManager: Failed to build Canvas UI");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI() {
|
||||
if (!_fallbackModalVisible || _fallbackStep == null) return;
|
||||
|
||||
// Semi-transparent background
|
||||
GUI.color = new Color(0, 0, 0, 0.7f);
|
||||
GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), Texture2D.whiteTexture);
|
||||
GUI.color = Color.white;
|
||||
|
||||
// Modal window
|
||||
float windowWidth = Mathf.Min(1200, Screen.width - 40);
|
||||
float windowHeight = 600;
|
||||
float windowX = (Screen.width - windowWidth) / 2;
|
||||
float windowY = (Screen.height - windowHeight) / 2;
|
||||
|
||||
GUIStyle windowStyle = new GUIStyle(GUI.skin.window);
|
||||
windowStyle.fontSize = 48;
|
||||
if (FallbackFont != null) windowStyle.font = FallbackFont;
|
||||
|
||||
GUI.Window(
|
||||
12345,
|
||||
new Rect(windowX, windowY, windowWidth, windowHeight),
|
||||
DrawFallbackModal,
|
||||
_fallbackStep.Title ?? "Tutorial",
|
||||
windowStyle);
|
||||
}
|
||||
|
||||
private void DrawFallbackModal(int windowId) {
|
||||
GUILayout.Space(60);
|
||||
|
||||
// Description
|
||||
GUIStyle descStyle = new GUIStyle(GUI.skin.label);
|
||||
descStyle.wordWrap = true;
|
||||
descStyle.fontSize = 40;
|
||||
if (FallbackFont != null) descStyle.font = FallbackFont;
|
||||
GUILayout.Label(
|
||||
_fallbackStep.Description ?? "",
|
||||
descStyle,
|
||||
GUILayout.ExpandHeight(true));
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
// Progress
|
||||
if (_fallbackTotalSteps > 1) {
|
||||
GUIStyle progressStyle = new GUIStyle(GUI.skin.label);
|
||||
progressStyle.fontSize = 32;
|
||||
if (FallbackFont != null) progressStyle.font = FallbackFont;
|
||||
GUILayout.Label(
|
||||
$"Step {_fallbackCurrentIndex + 1} of {_fallbackTotalSteps}",
|
||||
progressStyle,
|
||||
GUILayout.ExpandWidth(true));
|
||||
}
|
||||
|
||||
GUILayout.Space(30);
|
||||
|
||||
// Button style
|
||||
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button);
|
||||
buttonStyle.fontSize = 36;
|
||||
if (FallbackFont != null) buttonStyle.font = FallbackFont;
|
||||
|
||||
// Buttons
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
if (_fallbackStep.AllowSkip) {
|
||||
if (GUILayout.Button(
|
||||
"Skip",
|
||||
buttonStyle,
|
||||
GUILayout.Width(200),
|
||||
GUILayout.Height(80))) {
|
||||
var callback = _fallbackOnSkip;
|
||||
HideFallbackModal();
|
||||
callback?.Invoke();
|
||||
}
|
||||
|
||||
if (_fallbackIsOnboarding) {
|
||||
if (GUILayout.Button(
|
||||
"Skip All",
|
||||
buttonStyle,
|
||||
GUILayout.Width(200),
|
||||
GUILayout.Height(80))) {
|
||||
HideFallbackModal();
|
||||
TutorialManager.Instance?.SkipAllOnboarding();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
string buttonText =
|
||||
_fallbackCurrentIndex >= _fallbackTotalSteps - 1 ? "Got it!" : "Continue";
|
||||
if (GUILayout.Button(
|
||||
buttonText,
|
||||
buttonStyle,
|
||||
GUILayout.Width(240),
|
||||
GUILayout.Height(80))) {
|
||||
var callback = _fallbackOnContinue;
|
||||
HideFallbackModal();
|
||||
callback?.Invoke();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.Space(30);
|
||||
}
|
||||
|
||||
private void ShowFallbackModal(
|
||||
TutorialStep step,
|
||||
int currentIndex,
|
||||
int totalSteps,
|
||||
Action onContinue,
|
||||
Action onSkip,
|
||||
bool isOnboarding) {
|
||||
_fallbackStep = step;
|
||||
_fallbackCurrentIndex = currentIndex;
|
||||
_fallbackTotalSteps = totalSteps;
|
||||
_fallbackOnContinue = onContinue;
|
||||
_fallbackOnSkip = onSkip;
|
||||
_fallbackIsOnboarding = isOnboarding;
|
||||
_fallbackModalVisible = true;
|
||||
}
|
||||
|
||||
private void HideFallbackModal() {
|
||||
_fallbackModalVisible = false;
|
||||
_fallbackStep = null;
|
||||
_fallbackOnContinue = null;
|
||||
_fallbackOnSkip = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a modal dialog for a tutorial step.
|
||||
/// </summary>
|
||||
@@ -43,13 +216,18 @@ namespace Eagle0.Tutorial {
|
||||
Action onComplete,
|
||||
Action onSkip,
|
||||
bool isOnboarding) {
|
||||
if (ModalPanel == null) {
|
||||
Debug.LogWarning("TutorialUIManager: No ModalPanel assigned");
|
||||
if (ModalPanel != null) {
|
||||
// Ensure canvas is active
|
||||
if (TutorialCanvas != null && !TutorialCanvas.gameObject.activeSelf) {
|
||||
TutorialCanvas.gameObject.SetActive(true);
|
||||
}
|
||||
ModalPanel.Show(step, currentIndex, totalSteps, onComplete, onSkip, isOnboarding);
|
||||
} else if (UseFallbackUI) {
|
||||
ShowFallbackModal(step, currentIndex, totalSteps, onComplete, onSkip, isOnboarding);
|
||||
} else {
|
||||
Debug.LogWarning("TutorialUIManager: No ModalPanel assigned and fallback disabled");
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
ModalPanel.Show(step, currentIndex, totalSteps, onComplete, onSkip, isOnboarding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -148,6 +326,7 @@ namespace Eagle0.Tutorial {
|
||||
public void HideAll() {
|
||||
ModalPanel?.Hide();
|
||||
OverlayController?.HideOverlay();
|
||||
HideFallbackModal();
|
||||
// Note: Don't hide hints automatically - they persist until dismissed
|
||||
}
|
||||
|
||||
|
||||
@@ -277,6 +277,7 @@ namespace EagleInstaller {
|
||||
async Task<FetchAttempt>
|
||||
FetchAndWriteOne(string remotePath, string localDir, string expectedSha) {
|
||||
string writePath = WritePathFromRemotePath(remotePath, localDir);
|
||||
string tempPath = writePath + ".downloading";
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(writePath));
|
||||
|
||||
@@ -288,41 +289,67 @@ namespace EagleInstaller {
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using Stream responseStream = await response.Content.ReadAsStreamAsync();
|
||||
await using FileStream fileStream = File.Create(writePath);
|
||||
using MemoryStream memStream = new MemoryStream();
|
||||
await responseStream.CopyToAsync(memStream);
|
||||
memStream.Position = 0;
|
||||
|
||||
var sha = System.Security.Cryptography.SHA256.Create();
|
||||
string computedSha = BitConverter.ToString(sha.ComputeHash(memStream))
|
||||
.Replace("-", string.Empty)
|
||||
.ToLower();
|
||||
// Stream directly to disk while computing SHA256 incrementally
|
||||
using var sha = System.Security.Cryptography.SHA256.Create();
|
||||
await using (FileStream fileStream = File.Create(tempPath)) {
|
||||
var buffer = new byte[81920]; // 80KB buffer
|
||||
int bytesRead;
|
||||
while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length)) >
|
||||
0) {
|
||||
// Write to disk
|
||||
await fileStream.WriteAsync(buffer, 0, bytesRead);
|
||||
// Update hash incrementally
|
||||
sha.TransformBlock(buffer, 0, bytesRead, null, 0);
|
||||
}
|
||||
}
|
||||
// Finalize hash computation
|
||||
sha.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
|
||||
string computedSha =
|
||||
BitConverter.ToString(sha.Hash).Replace("-", string.Empty).ToLower();
|
||||
|
||||
if (computedSha.Equals(expectedSha)) {
|
||||
// SHA matches - rename temp file to final location
|
||||
if (File.Exists(writePath)) { File.Delete(writePath); }
|
||||
File.Move(tempPath, writePath);
|
||||
Logger.WriteLine($"✓ Verified and saved: {remotePath}");
|
||||
memStream.Position = 0;
|
||||
await memStream.CopyToAsync(fileStream);
|
||||
return new FetchAttempt {
|
||||
Success = true,
|
||||
Kvp = new KeyValuePair<String, String>(remotePath, computedSha)
|
||||
};
|
||||
} else {
|
||||
// SHA mismatch - delete temp file
|
||||
try {
|
||||
File.Delete(tempPath);
|
||||
} catch {}
|
||||
Logger.WriteError($"✗ SHA mismatch for {remotePath}");
|
||||
return new FetchAttempt() { Success = false };
|
||||
}
|
||||
} catch (TaskCanceledException e) {
|
||||
// Clean up temp file on failure
|
||||
try {
|
||||
File.Delete(tempPath);
|
||||
} catch {}
|
||||
Logger.WriteError($"Download cancelled: {e.Message}");
|
||||
return new FetchAttempt() { Success = false };
|
||||
} catch (HttpRequestException e) {
|
||||
try {
|
||||
File.Delete(tempPath);
|
||||
} catch {}
|
||||
Logger.WriteError($"Network error: {e.Message}");
|
||||
return new FetchAttempt() { Success = false };
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
File.Delete(tempPath);
|
||||
} catch {}
|
||||
Logger.WriteError($"Download failed: {e.Message}");
|
||||
return new FetchAttempt() { Success = false };
|
||||
} finally { Pool.Release(); }
|
||||
}
|
||||
|
||||
public async Task<bool> DownloadAndLaunchNewInstaller(string installerUrl) {
|
||||
public async Task<bool> DownloadAndLaunchNewInstaller(
|
||||
string installerUrl,
|
||||
string expectedSha) {
|
||||
try {
|
||||
Logger.UpdateStatus("Downloading new installer...");
|
||||
|
||||
@@ -372,6 +399,28 @@ namespace EagleInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
// Verify SHA256 before launching
|
||||
Logger.UpdateStatus("Verifying installer integrity...");
|
||||
string computedSha;
|
||||
using (var sha256 = System.Security.Cryptography.SHA256.Create()) using (
|
||||
var stream = File.OpenRead(newInstallerPath)) {
|
||||
var hashBytes = sha256.ComputeHash(stream);
|
||||
computedSha =
|
||||
BitConverter.ToString(hashBytes).Replace("-", string.Empty).ToLower();
|
||||
}
|
||||
|
||||
if (!computedSha.Equals(expectedSha, StringComparison.OrdinalIgnoreCase)) {
|
||||
Logger.WriteError($"SECURITY ERROR: Installer SHA mismatch!");
|
||||
Logger.WriteError($" Expected: {expectedSha}");
|
||||
Logger.WriteError($" Got: {computedSha}");
|
||||
// Delete the corrupted/tampered file
|
||||
try {
|
||||
File.Delete(newInstallerPath);
|
||||
} catch {}
|
||||
return false;
|
||||
}
|
||||
Logger.WriteLine($"✓ Installer verified: SHA256 matches");
|
||||
|
||||
// Wait a moment to ensure file handles are released
|
||||
await Task.Delay(500);
|
||||
|
||||
|
||||
@@ -110,7 +110,8 @@ namespace EagleInstaller {
|
||||
if (installerUpdateInfo.InstallerNeedsUpdate) {
|
||||
Logger.UpdateStatus("Installer update required...");
|
||||
bool downloadSuccess = await updater.DownloadAndLaunchNewInstaller(
|
||||
installerUpdateInfo.NewInstallerUrl);
|
||||
installerUpdateInfo.NewInstallerUrl,
|
||||
installerUpdateInfo.NewInstallerVersion);
|
||||
if (downloadSuccess) {
|
||||
Logger.UpdateStatus("New installer launched. Exiting current version.");
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
type InvitationHTTPHandler struct {
|
||||
invitationService *InvitationService
|
||||
installerURL string
|
||||
macInstallerURL string
|
||||
}
|
||||
|
||||
// NewInvitationHTTPHandler creates a new invitation HTTP handler
|
||||
@@ -25,9 +26,14 @@ func NewInvitationHTTPHandler(invitationService *InvitationService) *InvitationH
|
||||
if installerURL == "" {
|
||||
installerURL = "https://assets.eagle0.net/installer/EagleInstaller.exe"
|
||||
}
|
||||
macInstallerURL := os.Getenv("MAC_INSTALLER_URL")
|
||||
if macInstallerURL == "" {
|
||||
macInstallerURL = "https://assets.eagle0.net/mac/builds/eagle0-latest.zip"
|
||||
}
|
||||
return &InvitationHTTPHandler{
|
||||
invitationService: invitationService,
|
||||
installerURL: installerURL,
|
||||
macInstallerURL: macInstallerURL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +44,7 @@ func (h *InvitationHTTPHandler) RegisterRoutes() {
|
||||
|
||||
// handleInvite routes requests to the appropriate handler
|
||||
func (h *InvitationHTTPHandler) handleInvite(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse path: /invite/{code} or /invite/{code}/install.bat
|
||||
// Parse path: /invite/{code} or /invite/{code}/install.bat or /invite/{code}/install.sh
|
||||
path := strings.TrimPrefix(r.URL.Path, "/invite/")
|
||||
parts := strings.Split(path, "/")
|
||||
|
||||
@@ -51,6 +57,8 @@ func (h *InvitationHTTPHandler) handleInvite(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
if len(parts) == 2 && parts[1] == "install.bat" {
|
||||
h.handleInstallBat(w, r, code)
|
||||
} else if len(parts) == 2 && parts[1] == "install.sh" {
|
||||
h.handleInstallSh(w, r, code)
|
||||
} else if len(parts) == 1 {
|
||||
h.handleLandingPage(w, r, code)
|
||||
} else {
|
||||
@@ -58,21 +66,34 @@ func (h *InvitationHTTPHandler) handleInvite(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
}
|
||||
|
||||
// isMacUserAgent returns true if the User-Agent indicates a Mac browser
|
||||
func isMacUserAgent(userAgent string) bool {
|
||||
ua := strings.ToLower(userAgent)
|
||||
return strings.Contains(ua, "macintosh") || strings.Contains(ua, "mac os x")
|
||||
}
|
||||
|
||||
// handleLandingPage serves the invitation landing page
|
||||
func (h *InvitationHTTPHandler) handleLandingPage(w http.ResponseWriter, r *http.Request, code string) {
|
||||
invitation := h.invitationService.FindByCode(code)
|
||||
isMac := isMacUserAgent(r.UserAgent())
|
||||
|
||||
data := struct {
|
||||
Valid bool
|
||||
Code string
|
||||
ExpiresAt string
|
||||
ErrorMessage string
|
||||
InstallBatURL string
|
||||
InstallerURL string
|
||||
Valid bool
|
||||
Code string
|
||||
ExpiresAt string
|
||||
ErrorMessage string
|
||||
InstallBatURL string
|
||||
InstallerURL string
|
||||
IsMac bool
|
||||
InstallShURL string
|
||||
MacInstallerURL string
|
||||
}{
|
||||
Code: code,
|
||||
InstallBatURL: fmt.Sprintf("/invite/%s/install.bat", code),
|
||||
InstallerURL: h.installerURL,
|
||||
Code: code,
|
||||
InstallBatURL: fmt.Sprintf("/invite/%s/install.bat", code),
|
||||
InstallerURL: h.installerURL,
|
||||
IsMac: isMac,
|
||||
InstallShURL: fmt.Sprintf("/invite/%s/install.sh", code),
|
||||
MacInstallerURL: h.macInstallerURL,
|
||||
}
|
||||
|
||||
if invitation == nil {
|
||||
@@ -160,6 +181,88 @@ timeout /t 5
|
||||
log.Printf("[InviteHTTP] Served install.bat for code %s...", code[:8])
|
||||
}
|
||||
|
||||
// handleInstallSh serves the Mac shell installer script
|
||||
func (h *InvitationHTTPHandler) handleInstallSh(w http.ResponseWriter, r *http.Request, code string) {
|
||||
// Validate the code exists and is valid
|
||||
if !h.invitationService.IsValidCode(code) {
|
||||
http.Error(w, "Invalid or expired invitation code", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate shell script
|
||||
script := fmt.Sprintf(`#!/bin/bash
|
||||
#
|
||||
# Eagle0 Mac Installer
|
||||
# This script downloads Eagle0 and sets up your invitation code.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
echo ""
|
||||
echo "===================================="
|
||||
echo " Eagle0 Mac Installer"
|
||||
echo "===================================="
|
||||
echo ""
|
||||
|
||||
# Create app support directory
|
||||
APP_SUPPORT_DIR="$HOME/Library/Application Support/eagle0"
|
||||
mkdir -p "$APP_SUPPORT_DIR"
|
||||
|
||||
# Write invitation code
|
||||
echo "Setting up invitation code..."
|
||||
cat > "$APP_SUPPORT_DIR/invitation.json" << 'INVITATION_EOF'
|
||||
{"invitationCode": "%s"}
|
||||
INVITATION_EOF
|
||||
|
||||
echo "Invitation code saved."
|
||||
echo ""
|
||||
|
||||
# Download location
|
||||
DOWNLOAD_DIR="$HOME/Downloads"
|
||||
ZIP_PATH="$DOWNLOAD_DIR/eagle0.zip"
|
||||
APP_PATH="/Applications/eagle0.app"
|
||||
|
||||
echo "Downloading Eagle0..."
|
||||
curl -L -o "$ZIP_PATH" "%s"
|
||||
|
||||
echo ""
|
||||
echo "Extracting to /Applications..."
|
||||
|
||||
# Remove old version if exists
|
||||
if [ -d "$APP_PATH" ]; then
|
||||
echo "Removing previous version..."
|
||||
rm -rf "$APP_PATH"
|
||||
fi
|
||||
|
||||
# Unzip to Applications
|
||||
unzip -q "$ZIP_PATH" -d /Applications/
|
||||
|
||||
# Clean up zip
|
||||
rm "$ZIP_PATH"
|
||||
|
||||
# Remove quarantine attribute (app is notarized but downloaded via curl)
|
||||
xattr -dr com.apple.quarantine "$APP_PATH" 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "===================================="
|
||||
echo " Installation Complete!"
|
||||
echo "===================================="
|
||||
echo ""
|
||||
echo "Eagle0 has been installed to /Applications/eagle0.app"
|
||||
echo "Your invitation code has been saved."
|
||||
echo ""
|
||||
echo "Starting Eagle0..."
|
||||
open "$APP_PATH"
|
||||
|
||||
`, code, h.macInstallerURL)
|
||||
|
||||
w.Header().Set("Content-Type", "application/x-sh")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=eagle0-install.sh")
|
||||
w.Write([]byte(script))
|
||||
|
||||
log.Printf("[InviteHTTP] Served install.sh for code %s...", code[:8])
|
||||
}
|
||||
|
||||
var landingPageTemplate = template.Must(template.New("landing").Parse(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -277,6 +380,21 @@ var landingPageTemplate = template.Must(template.New("landing").Parse(`<!DOCTYPE
|
||||
font-size: 12px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
code {
|
||||
background: #e9ecef;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
.other-platform {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
.other-platform a {
|
||||
color: #1a5f7a;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -294,17 +412,33 @@ var landingPageTemplate = template.Must(template.New("landing").Parse(`<!DOCTYPE
|
||||
<div class="content">
|
||||
<p>You've been invited to play Eagle0, a strategic turn-based game with tactical hex-based combat.</p>
|
||||
|
||||
<a href="{{.InstallBatURL}}" class="button">Download & Install</a>
|
||||
{{if .IsMac}}
|
||||
<a href="{{.InstallShURL}}" class="button">Download for Mac</a>
|
||||
|
||||
<div class="instructions">
|
||||
<h3>How to install:</h3>
|
||||
<h3>How to install on Mac:</h3>
|
||||
<ol>
|
||||
<li>Click the "Download & Install" button above</li>
|
||||
<li>Click the "Download for Mac" button above</li>
|
||||
<li>Open Terminal and navigate to your Downloads folder:<br>
|
||||
<code>cd ~/Downloads</code></li>
|
||||
<li>Run the installer script:<br>
|
||||
<code>bash eagle0-install.sh</code></li>
|
||||
<li>Eagle0 will be installed to /Applications and launched automatically</li>
|
||||
</ol>
|
||||
</div>
|
||||
{{else}}
|
||||
<a href="{{.InstallBatURL}}" class="button">Download for Windows</a>
|
||||
|
||||
<div class="instructions">
|
||||
<h3>How to install on Windows:</h3>
|
||||
<ol>
|
||||
<li>Click the "Download for Windows" button above</li>
|
||||
<li>Open the downloaded <strong>eagle0-install.bat</strong> file</li>
|
||||
<li>If Windows shows a security prompt, click "More info" then "Run anyway"</li>
|
||||
<li>The installer will download and start automatically with your invitation code</li>
|
||||
</ol>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .ExpiresAt}}
|
||||
<div class="expires">
|
||||
@@ -313,10 +447,23 @@ var landingPageTemplate = template.Must(template.New("landing").Parse(`<!DOCTYPE
|
||||
{{end}}
|
||||
|
||||
<div class="manual-section">
|
||||
{{if .IsMac}}
|
||||
<h3>Alternative: Manual Installation</h3>
|
||||
<p>If the script doesn't work, you can <a href="{{.MacInstallerURL}}">download the ZIP manually</a>, extract it to /Applications, and enter this code when prompted:</p>
|
||||
{{else}}
|
||||
<h3>Alternative: Manual Installation</h3>
|
||||
<p>If the automatic installer doesn't work, you can <a href="{{.InstallerURL}}">download the installer manually</a> and enter this code when prompted:</p>
|
||||
{{end}}
|
||||
<div class="code-box">{{.Code}}</div>
|
||||
</div>
|
||||
|
||||
<div class="other-platform">
|
||||
{{if .IsMac}}
|
||||
<p><small>Looking for Windows? <a href="{{.InstallBatURL}}">Download Windows installer</a></small></p>
|
||||
{{else}}
|
||||
<p><small>Looking for Mac? <a href="{{.InstallShURL}}">Download Mac installer</a></small></p>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="error-box">
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "mac_build_handler_lib",
|
||||
srcs = ["mac_build_handler.go"],
|
||||
importpath = "github.com/nolen777/eagle0/src/main/go/net/eagle0/build/mac_build_handler",
|
||||
visibility = ["//visibility:private"],
|
||||
deps = ["//src/main/go/net/eagle0/util/aws"],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "mac_build_handler",
|
||||
embed = [":mac_build_handler_lib"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -0,0 +1,351 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/sha256"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nolen777/eagle0/src/main/go/net/eagle0/util/aws"
|
||||
)
|
||||
|
||||
var bucketName = "eagle0-windows" // Using existing bucket with /mac/ prefix
|
||||
var appcastPath = "mac/appcast.xml"
|
||||
var buildsRoot = "mac/builds/"
|
||||
|
||||
// Sparkle Appcast XML structures
|
||||
type Appcast struct {
|
||||
XMLName xml.Name `xml:"rss"`
|
||||
Version string `xml:"version,attr"`
|
||||
Xmlns string `xml:"xmlns:sparkle,attr"`
|
||||
Channel Channel `xml:"channel"`
|
||||
}
|
||||
|
||||
type Channel struct {
|
||||
Title string `xml:"title"`
|
||||
Link string `xml:"link"`
|
||||
Description string `xml:"description"`
|
||||
Language string `xml:"language"`
|
||||
Items []Item `xml:"item"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Title string `xml:"title"`
|
||||
PubDate string `xml:"pubDate"`
|
||||
SparkleVersion string `xml:"sparkle:version"`
|
||||
SparkleShortVersion string `xml:"sparkle:shortVersionString"`
|
||||
Description string `xml:"description,omitempty"`
|
||||
Enclosure Enclosure `xml:"enclosure"`
|
||||
}
|
||||
|
||||
type Enclosure struct {
|
||||
URL string `xml:"url,attr"`
|
||||
Length int64 `xml:"length,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
EdSig string `xml:"sparkle:edSignature,attr"`
|
||||
}
|
||||
|
||||
func zipApp(appPath string, zipPath string) error {
|
||||
zipFile, err := os.Create(zipPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create zip file: %w", err)
|
||||
}
|
||||
defer zipFile.Close()
|
||||
|
||||
zipWriter := zip.NewWriter(zipFile)
|
||||
defer zipWriter.Close()
|
||||
|
||||
appBase := filepath.Base(appPath)
|
||||
|
||||
// Use WalkDir with Lstat to properly handle symlinks
|
||||
err = filepath.WalkDir(appPath, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the relative path from the app's parent directory
|
||||
relPath, err := filepath.Rel(filepath.Dir(appPath), path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip if it's the root
|
||||
if relPath == appBase && d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use Lstat to get info without following symlinks
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if it's a symlink
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
// Read the symlink target
|
||||
linkTarget, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create symlink entry in zip
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = relPath
|
||||
header.Method = zip.Store
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write([]byte(linkTarget))
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Use forward slashes and preserve the .app directory structure
|
||||
header.Name = relPath
|
||||
if info.IsDir() {
|
||||
header.Name += "/"
|
||||
} else {
|
||||
header.Method = zip.Deflate
|
||||
}
|
||||
|
||||
// Preserve executable permissions
|
||||
header.SetMode(info.Mode())
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(writer, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func signWithSparkle(filePath string, privateKeyPath string) (string, error) {
|
||||
// Use Sparkle's sign_update tool
|
||||
// First, try to find it in the Sparkle cache
|
||||
sparkleSignTool := "/tmp/sparkle-cache/Sparkle-2.6.4/bin/sign_update"
|
||||
|
||||
// If not found, download Sparkle
|
||||
if _, err := os.Stat(sparkleSignTool); os.IsNotExist(err) {
|
||||
log.Println("Sparkle sign_update not found, downloading...")
|
||||
cmd := exec.Command("bash", "-c", `
|
||||
mkdir -p /tmp/sparkle-cache
|
||||
curl -sL https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz | tar -xJ -C /tmp/sparkle-cache
|
||||
`)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return "", fmt.Errorf("failed to download Sparkle: %s: %w", output, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Sign the file using -f to specify private key file path
|
||||
// Note: -s is deprecated and expects key as string argument, not file path
|
||||
cmd := exec.Command(sparkleSignTool, filePath, "-f", privateKeyPath)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign file: %s: %w", string(output), err)
|
||||
}
|
||||
|
||||
// sign_update outputs: sparkle:edSignature="<signature>" length="<length>"
|
||||
// We need to extract just the signature
|
||||
signature := strings.TrimSpace(string(output))
|
||||
// Parse out the signature from the output
|
||||
if strings.Contains(signature, "sparkle:edSignature=\"") {
|
||||
start := strings.Index(signature, "sparkle:edSignature=\"") + len("sparkle:edSignature=\"")
|
||||
end := strings.Index(signature[start:], "\"")
|
||||
if end > 0 {
|
||||
signature = signature[start : start+end]
|
||||
}
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func getFileSize(path string) (int64, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return info.Size(), nil
|
||||
}
|
||||
|
||||
func sha256File(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func fetchAppcast(bb aws.BucketBasics) (*Appcast, error) {
|
||||
content, err := bb.FetchString(bucketName, appcastPath)
|
||||
if err != nil {
|
||||
// Return empty appcast if doesn't exist
|
||||
return &Appcast{
|
||||
Version: "2.0",
|
||||
Xmlns: "http://www.andymatuschak.org/xml-namespaces/sparkle",
|
||||
Channel: Channel{
|
||||
Title: "Eagle0",
|
||||
Link: "https://assets.eagle0.net/mac/appcast.xml",
|
||||
Description: "Eagle0 game updates",
|
||||
Language: "en",
|
||||
Items: []Item{},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var appcast Appcast
|
||||
err = xml.Unmarshal([]byte(content), &appcast)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse appcast: %w", err)
|
||||
}
|
||||
|
||||
return &appcast, nil
|
||||
}
|
||||
|
||||
func uploadAppcast(bb aws.BucketBasics, appcast *Appcast) error {
|
||||
output, err := xml.MarshalIndent(appcast, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal appcast: %w", err)
|
||||
}
|
||||
|
||||
xmlContent := xml.Header + string(output)
|
||||
return bb.UploadBytesPublic(bucketName, appcastPath, []byte(xmlContent))
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 5 {
|
||||
fmt.Println("Usage: mac_build_handler <app_path> <version> <build_number> <sparkle_private_key_path>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
appPath := os.Args[1]
|
||||
version := os.Args[2]
|
||||
buildNumber := os.Args[3]
|
||||
privateKeyPath := os.Args[4]
|
||||
|
||||
if _, err := os.Stat(appPath); os.IsNotExist(err) {
|
||||
log.Fatalf("App not found: %s", appPath)
|
||||
}
|
||||
|
||||
bb, err := aws.NewBucketBasics()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Create ZIP of the app
|
||||
zipFileName := fmt.Sprintf("eagle0-%s.zip", version)
|
||||
zipPath := filepath.Join("/tmp", zipFileName)
|
||||
|
||||
log.Printf("Creating ZIP: %s", zipPath)
|
||||
if err := zipApp(appPath, zipPath); err != nil {
|
||||
log.Fatalf("Failed to create ZIP: %v", err)
|
||||
}
|
||||
|
||||
// Get file size
|
||||
fileSize, err := getFileSize(zipPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get file size: %v", err)
|
||||
}
|
||||
log.Printf("ZIP size: %d bytes", fileSize)
|
||||
|
||||
// Sign the ZIP with Sparkle EdDSA
|
||||
log.Println("Signing ZIP with Sparkle...")
|
||||
signature, err := signWithSparkle(zipPath, privateKeyPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to sign: %v", err)
|
||||
}
|
||||
log.Printf("Signature: %s", signature)
|
||||
|
||||
// Upload ZIP to S3
|
||||
remotePath := buildsRoot + zipFileName
|
||||
log.Printf("Uploading to S3: %s", remotePath)
|
||||
if err := bb.UploadFilePublic(bucketName, remotePath, zipPath); err != nil {
|
||||
log.Fatalf("Failed to upload: %v", err)
|
||||
}
|
||||
|
||||
// Also upload as "latest"
|
||||
latestPath := buildsRoot + "eagle0-latest.zip"
|
||||
log.Printf("Copying to: %s", latestPath)
|
||||
if err := bb.UploadFilePublic(bucketName, latestPath, zipPath); err != nil {
|
||||
log.Fatalf("Failed to upload latest: %v", err)
|
||||
}
|
||||
|
||||
// Update appcast.xml
|
||||
log.Println("Updating appcast.xml...")
|
||||
appcast, err := fetchAppcast(bb)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch appcast: %v", err)
|
||||
}
|
||||
|
||||
// Create new item
|
||||
downloadURL := fmt.Sprintf("https://assets.eagle0.net/%s%s", buildsRoot, zipFileName)
|
||||
newItem := Item{
|
||||
Title: fmt.Sprintf("Version %s", version),
|
||||
PubDate: time.Now().Format(time.RFC1123Z),
|
||||
SparkleVersion: buildNumber,
|
||||
SparkleShortVersion: version,
|
||||
Description: "",
|
||||
Enclosure: Enclosure{
|
||||
URL: downloadURL,
|
||||
Length: fileSize,
|
||||
Type: "application/octet-stream",
|
||||
EdSig: signature,
|
||||
},
|
||||
}
|
||||
|
||||
// Prepend new item (most recent first)
|
||||
appcast.Channel.Items = append([]Item{newItem}, appcast.Channel.Items...)
|
||||
|
||||
// Keep only last 10 versions
|
||||
if len(appcast.Channel.Items) > 10 {
|
||||
appcast.Channel.Items = appcast.Channel.Items[:10]
|
||||
}
|
||||
|
||||
if err := uploadAppcast(bb, appcast); err != nil {
|
||||
log.Fatalf("Failed to upload appcast: %v", err)
|
||||
}
|
||||
|
||||
// Clean up local ZIP
|
||||
os.Remove(zipPath)
|
||||
|
||||
log.Printf("=== Mac build deployed successfully ===")
|
||||
log.Printf("Version: %s (build %s)", version, buildNumber)
|
||||
log.Printf("Download URL: %s", downloadURL)
|
||||
log.Printf("Appcast URL: https://assets.eagle0.net/%s", appcastPath)
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/nolen777/eagle0/src/main/go/net/eagle0/util/aws"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nolen777/eagle0/src/main/go/net/eagle0/util/aws"
|
||||
)
|
||||
|
||||
const bucketName = "eagle0-windows"
|
||||
@@ -64,7 +67,7 @@ func (mm *ManifestManager) parseManifest(content string) map[string]string {
|
||||
return sections
|
||||
}
|
||||
|
||||
func (mm *ManifestManager) buildManifest(sections map[string]string) string {
|
||||
func (mm *ManifestManager) buildManifest(sections map[string]string, privateKey ed25519.PrivateKey) string {
|
||||
var manifest strings.Builder
|
||||
|
||||
manifest.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().Format(time.RFC3339)))
|
||||
@@ -90,10 +93,21 @@ func (mm *ManifestManager) buildManifest(sections map[string]string) string {
|
||||
}
|
||||
}
|
||||
|
||||
return manifest.String()
|
||||
// The content to sign is everything we've built so far
|
||||
contentToSign := manifest.String()
|
||||
|
||||
// If we have a private key, sign the manifest
|
||||
if privateKey != nil {
|
||||
signature := ed25519.Sign(privateKey, []byte(contentToSign))
|
||||
signatureB64 := base64.StdEncoding.EncodeToString(signature)
|
||||
// Prepend signature line at the very beginning
|
||||
return fmt.Sprintf("# signature=%s\n%s", signatureB64, contentToSign)
|
||||
}
|
||||
|
||||
return contentToSign
|
||||
}
|
||||
|
||||
func (mm *ManifestManager) UpdateSection(sectionName, sectionContent string) error {
|
||||
func (mm *ManifestManager) UpdateSection(sectionName, sectionContent string, privateKey ed25519.PrivateKey) error {
|
||||
log.Printf("Updating manifest section: %s", sectionName)
|
||||
|
||||
// Fetch existing manifest
|
||||
@@ -103,14 +117,14 @@ func (mm *ManifestManager) UpdateSection(sectionName, sectionContent string) err
|
||||
existing = ""
|
||||
}
|
||||
|
||||
// Parse sections
|
||||
// Parse sections (strips out old signature line if present)
|
||||
sections := mm.parseManifest(existing)
|
||||
|
||||
// Update the specified section
|
||||
sections[sectionName] = sectionContent
|
||||
|
||||
// Build new manifest
|
||||
newManifest := mm.buildManifest(sections)
|
||||
// Build new manifest (with signature if key provided)
|
||||
newManifest := mm.buildManifest(sections, privateKey)
|
||||
|
||||
// Upload updated manifest with public ACL
|
||||
err = mm.bucketBasics.UploadBytesPublic(bucketName, manifestPath, []byte(newManifest))
|
||||
@@ -118,13 +132,36 @@ func (mm *ManifestManager) UpdateSection(sectionName, sectionContent string) err
|
||||
return fmt.Errorf("failed to upload manifest: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Successfully updated manifest section: %s", sectionName)
|
||||
if privateKey != nil {
|
||||
log.Printf("Successfully updated and signed manifest section: %s", sectionName)
|
||||
} else {
|
||||
log.Printf("Successfully updated manifest section: %s (unsigned)", sectionName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadPrivateKey(keyPath string) (ed25519.PrivateKey, error) {
|
||||
keyData, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read private key file: %v", err)
|
||||
}
|
||||
|
||||
// Key should be base64-encoded 64-byte Ed25519 private key
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(keyData)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode private key: %v", err)
|
||||
}
|
||||
|
||||
if len(keyBytes) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("invalid private key size: expected %d bytes, got %d", ed25519.PrivateKeySize, len(keyBytes))
|
||||
}
|
||||
|
||||
return ed25519.PrivateKey(keyBytes), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 3 {
|
||||
log.Fatal("Usage: manifest_manager <section_name> <section_content_file>")
|
||||
if len(os.Args) < 3 || len(os.Args) > 4 {
|
||||
log.Fatal("Usage: manifest_manager <section_name> <section_content_file> [private_key_file]")
|
||||
}
|
||||
|
||||
sectionName := os.Args[1]
|
||||
@@ -136,6 +173,16 @@ func main() {
|
||||
log.Fatalf("Failed to read content file: %v", err)
|
||||
}
|
||||
|
||||
// Load private key if provided
|
||||
var privateKey ed25519.PrivateKey
|
||||
if len(os.Args) == 4 {
|
||||
privateKey, err = loadPrivateKey(os.Args[3])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load private key: %v", err)
|
||||
}
|
||||
log.Println("Ed25519 signing key loaded")
|
||||
}
|
||||
|
||||
// Create manifest manager
|
||||
mm, err := NewManifestManager()
|
||||
if err != nil {
|
||||
@@ -143,7 +190,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Update the section
|
||||
err = mm.UpdateSection(sectionName, string(content))
|
||||
err = mm.UpdateSection(sectionName, string(content), privateKey)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to update manifest: %v", err)
|
||||
}
|
||||
|
||||
@@ -136,24 +136,50 @@ func runWarmup(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to send stream game request: %w", err)
|
||||
}
|
||||
|
||||
// Wait for subscription ack
|
||||
_, err = waitForResponse(stream, func(resp *eagle.UpdateStreamResponse) bool {
|
||||
return resp.GetSubscriptionAck() != nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get subscription ack: %w", err)
|
||||
}
|
||||
log.Println(" Subscription acknowledged")
|
||||
|
||||
// Wait for initial game state with available commands
|
||||
// Note: ActionResultResponse arrives BEFORE SubscriptionAck, so we can't wait for ack first
|
||||
streamingTextCount := 0
|
||||
gotSubscriptionAck := false
|
||||
gameUpdateResp, err := waitForResponse(stream, func(resp *eagle.UpdateStreamResponse) bool {
|
||||
// Track subscription ack but don't require it before ActionResultResponse
|
||||
if resp.GetSubscriptionAck() != nil {
|
||||
gotSubscriptionAck = true
|
||||
log.Println(" Subscription acknowledged")
|
||||
}
|
||||
gu := resp.GetGameUpdate()
|
||||
if gu == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check what type of update this is
|
||||
switch {
|
||||
case gu.GetStreamingTextResponse() != nil:
|
||||
streamingTextCount++
|
||||
if streamingTextCount%100 == 0 {
|
||||
log.Printf(" Received %d streaming text updates so far...", streamingTextCount)
|
||||
}
|
||||
case gu.GetShardokActionResultResponse() != nil:
|
||||
log.Printf(" Got ShardokActionResultResponse")
|
||||
case gu.GetErrorResponse() != nil:
|
||||
log.Printf(" Got ErrorResponse: %v", gu.GetErrorResponse())
|
||||
case gu.GetActionResultResponse() != nil:
|
||||
log.Printf(" Got ActionResultResponse!")
|
||||
default:
|
||||
log.Printf(" Got GameUpdate with no recognized content")
|
||||
}
|
||||
|
||||
ar := gu.GetActionResultResponse()
|
||||
return ar != nil && ar.GetAvailableCommands() != nil
|
||||
if ar == nil {
|
||||
return false
|
||||
}
|
||||
if ar.GetAvailableCommands() == nil {
|
||||
log.Printf(" ActionResultResponse has no AvailableCommands (has %d results)", len(ar.GetActionResultViews()))
|
||||
return false
|
||||
}
|
||||
log.Printf(" Found AvailableCommands with %d provinces", len(ar.GetAvailableCommands().GetCommandsByProvince()))
|
||||
return true
|
||||
})
|
||||
log.Printf(" Total streaming text updates received: %d, got subscription ack: %v", streamingTextCount, gotSubscriptionAck)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get initial game state: %w", err)
|
||||
}
|
||||
@@ -174,12 +200,14 @@ func runWarmup(ctx context.Context) error {
|
||||
|
||||
var improveCmd *eagle.AvailableCommand
|
||||
var provinceID int32
|
||||
var actingHeroID int32
|
||||
|
||||
for pid, provCmds := range commandsByProvince {
|
||||
for _, cmd := range provCmds.GetCommands() {
|
||||
if cmd.GetImproveCommand() != nil {
|
||||
if ic := cmd.GetImproveCommand(); ic != nil {
|
||||
improveCmd = cmd
|
||||
provinceID = pid
|
||||
actingHeroID = ic.GetRecommendedHeroId()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -200,11 +228,11 @@ func runWarmup(ctx context.Context) error {
|
||||
}
|
||||
// We'll still try to post an Improve command even if not explicitly available
|
||||
} else {
|
||||
log.Printf(" Found Improve command for province %d", provinceID)
|
||||
log.Printf(" Found Improve command for province %d with recommended hero %d", provinceID, actingHeroID)
|
||||
}
|
||||
|
||||
// Post the Improve command
|
||||
log.Println(" Posting Improve command...")
|
||||
log.Printf(" Posting Improve command with token=%d province=%d heroId=%d...", eagleToken, provinceID, actingHeroID)
|
||||
if err := stream.Send(&eagle.UpdateStreamRequest{
|
||||
RequestDetails: &eagle.UpdateStreamRequest_PostCommandRequest{
|
||||
PostCommandRequest: &eagle.PostCommandRequest{
|
||||
@@ -218,7 +246,7 @@ func runWarmup(ctx context.Context) error {
|
||||
SealedValue: &eagle.SelectedCommand_ImproveCommand{
|
||||
ImproveCommand: &eagle.ImproveSelectedCommand{
|
||||
ImprovementType: eaglecommon.ImprovementType_ECONOMY,
|
||||
ActingHeroId: 0, // Default acting hero
|
||||
ActingHeroId: actingHeroID,
|
||||
LockType: false,
|
||||
},
|
||||
},
|
||||
@@ -233,32 +261,58 @@ func runWarmup(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// Step 5: Verify we get ActionResults
|
||||
log.Println("Step 5: Waiting for command response...")
|
||||
log.Println("Step 5: Waiting for command response and collecting results...")
|
||||
|
||||
// Wait for post command response
|
||||
postResp, err := waitForResponse(stream, func(resp *eagle.UpdateStreamResponse) bool {
|
||||
return resp.GetPostCommandResponse() != nil
|
||||
// Track results while waiting for PostCommandResponse
|
||||
// ActionResultResponse arrives as GameUpdate BEFORE PostCommandResponse
|
||||
foundNewRound := false
|
||||
resultCount := 0
|
||||
commandCount := 0
|
||||
postStatus := eagle.PostCommandResponse_UNKNOWN
|
||||
|
||||
_, err = waitForResponse(stream, func(resp *eagle.UpdateStreamResponse) bool {
|
||||
if resp.GetPostCommandResponse() != nil {
|
||||
log.Printf(" Got PostCommandResponse: %v", resp.GetPostCommandResponse().GetStatus())
|
||||
postStatus = resp.GetPostCommandResponse().GetStatus()
|
||||
return true
|
||||
}
|
||||
// Process GameUpdates while waiting - they contain our action results!
|
||||
if gu := resp.GetGameUpdate(); gu != nil {
|
||||
if ar := gu.GetActionResultResponse(); ar != nil {
|
||||
for _, arv := range ar.GetActionResultViews() {
|
||||
resultCount++
|
||||
if arv.GetType() == eaglecommon.ActionResultType_NEW_ROUND_ACTION {
|
||||
foundNewRound = true
|
||||
log.Printf(" Found NEW_ROUND_ACTION (date change)!")
|
||||
}
|
||||
}
|
||||
if ar.GetAvailableCommands() != nil {
|
||||
commandCount = len(ar.GetAvailableCommands().GetCommandsByProvince())
|
||||
log.Printf(" Got %d action results, %d provinces with commands available", len(ar.GetActionResultViews()), commandCount)
|
||||
}
|
||||
}
|
||||
} else if resp.GetSubscriptionAck() != nil {
|
||||
log.Println(" (skipping late SubscriptionAck)")
|
||||
}
|
||||
return false
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get post command response: %w", err)
|
||||
}
|
||||
|
||||
if postResp.GetPostCommandResponse().GetStatus() != eagle.PostCommandResponse_SUCCESS {
|
||||
log.Printf(" Command was rejected: %v", postResp.GetPostCommandResponse().GetStatus())
|
||||
// Continue anyway - the JIT is still warming up
|
||||
} else {
|
||||
if postStatus == eagle.PostCommandResponse_SUCCESS {
|
||||
log.Println(" Command accepted!")
|
||||
} else if postStatus == eagle.PostCommandResponse_BAD_TOKEN {
|
||||
log.Printf(" Command rejected: bad token")
|
||||
} else {
|
||||
log.Printf(" Command status: %v (continuing anyway - JIT warming up)", postStatus)
|
||||
}
|
||||
|
||||
// Collect action results with a timeout
|
||||
log.Println(" Collecting action results...")
|
||||
// Continue collecting any remaining action results with a timeout
|
||||
log.Println(" Collecting any remaining action results...")
|
||||
resultCtx, resultCancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer resultCancel()
|
||||
|
||||
foundNewRound := false
|
||||
resultCount := 0
|
||||
commandCount := 0
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-resultCtx.Done():
|
||||
@@ -306,6 +360,7 @@ done:
|
||||
log.Printf(" Total action results received: %d", resultCount)
|
||||
log.Printf(" Found NEW_ROUND_ACTION (date change): %v", foundNewRound)
|
||||
log.Printf(" New commands available: %d provinces", commandCount)
|
||||
log.Printf(" Post command status: %v", postStatus)
|
||||
|
||||
// Step 6: Drop the game to clean up
|
||||
log.Println("Step 6: Dropping test game...")
|
||||
@@ -333,6 +388,9 @@ done:
|
||||
if resultCount == 0 {
|
||||
return fmt.Errorf("no action results received")
|
||||
}
|
||||
if commandCount == 0 {
|
||||
return fmt.Errorf("no new commands received after posting command")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
load("@build_bazel_rules_swift//proto:proto.bzl", "swift_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")
|
||||
@@ -57,16 +56,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "hostility_swift_proto",
|
||||
protos = [":hostility_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:__pkg__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "hostility_scala_proto",
|
||||
visibility = [
|
||||
@@ -183,15 +172,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "victory_condition_swift_proto",
|
||||
protos = [":victory_condition_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "victory_condition_scala_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
load("@build_bazel_rules_swift//proto:proto.bzl", "swift_proto_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library")
|
||||
@@ -50,26 +49,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "available_command_swift_proto",
|
||||
protos = [":available_command_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:armed_battalion_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:attack_decision_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:captured_hero_option_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:control_weather_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:diplomacy_option_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:prisoner_management_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:province_orders_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:combat_unit_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:improvement_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:tribute_amount_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "available_command_scala_proto",
|
||||
visibility = [
|
||||
@@ -108,16 +87,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "command_swift_proto",
|
||||
protos = [":command_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":available_command_swift_proto",
|
||||
":selected_command_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "command_scala_proto",
|
||||
visibility = [
|
||||
@@ -153,30 +122,6 @@ proto_library(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "selected_command_swift_proto",
|
||||
protos = [":selected_command_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:armed_battalion_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:attack_decision_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:captured_hero_option_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:control_weather_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:diplomacy_option_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:prisoner_management_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:province_orders_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:combat_unit_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_status_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:improvement_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:tribute_amount_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "selected_command_scala_proto",
|
||||
visibility = [
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
load("@build_bazel_rules_swift//proto:proto.bzl", "swift_proto_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library")
|
||||
|
||||
swift_proto_library(
|
||||
name = "appropriate_battalions_swift_proto",
|
||||
protos = [":appropriate_battalions_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "appropriate_battalions_scala_proto",
|
||||
visibility = [
|
||||
@@ -32,16 +21,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "armed_battalion_swift_proto",
|
||||
protos = [":armed_battalion_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "armed_battalion_scala_proto",
|
||||
visibility = [
|
||||
@@ -63,16 +42,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "army_stats_swift_proto",
|
||||
protos = [":army_stats_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "army_stats_scala_proto",
|
||||
visibility = [
|
||||
@@ -93,18 +62,6 @@ proto_library(
|
||||
deps = ["//src/main/protobuf/net/eagle0/common:hostility_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "attack_decision_type_swift_proto",
|
||||
protos = [":attack_decision_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:tribute_amount_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "attack_decision_type_scala_proto",
|
||||
visibility = [
|
||||
@@ -127,16 +84,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "battalion_with_food_cost_swift_proto",
|
||||
protos = [":battalion_with_food_cost_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "battalion_with_food_cost_scala_proto",
|
||||
visibility = [
|
||||
@@ -158,16 +105,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "captured_hero_option_swift_proto",
|
||||
protos = [":captured_hero_option_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "captured_hero_option_scala_proto",
|
||||
visibility = [
|
||||
@@ -189,16 +126,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "control_weather_type_swift_proto",
|
||||
protos = [":control_weather_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "control_weather_type_scala_proto",
|
||||
visibility = [
|
||||
@@ -220,18 +147,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "diplomacy_option_swift_proto",
|
||||
protos = [":diplomacy_option_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "diplomacy_option_scala_proto",
|
||||
visibility = [
|
||||
@@ -254,19 +169,6 @@ proto_library(
|
||||
deps = ["//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "expanded_combat_unit_swift_proto",
|
||||
protos = [":expanded_combat_unit_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:battalion_view_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:hero_view_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "expanded_combat_unit_scala_proto",
|
||||
visibility = [
|
||||
@@ -292,20 +194,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "expanded_unaffiliated_hero_swift_proto",
|
||||
protos = [":expanded_unaffiliated_hero_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:unaffiliated_hero_quest_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:unaffiliated_hero_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:hero_view_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "expanded_unaffiliated_hero_scala_proto",
|
||||
visibility = [
|
||||
@@ -332,16 +220,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "prisoner_management_type_swift_proto",
|
||||
protos = [":prisoner_management_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "prisoner_management_type_scala_proto",
|
||||
visibility = [
|
||||
@@ -363,18 +241,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "province_orders_swift_proto",
|
||||
protos = [":province_orders_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "province_orders_scala_proto",
|
||||
visibility = [
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
load("@build_bazel_rules_swift//proto:proto.bzl", "swift_proto_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library")
|
||||
@@ -8,19 +7,6 @@ package(default_visibility = [
|
||||
"//src/test/scala/net/eagle0:__subpackages__",
|
||||
])
|
||||
|
||||
swift_proto_library(
|
||||
name = "action_result_notification_details_swift_proto",
|
||||
protos = [":action_result_notification_details_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":profession_swift_proto",
|
||||
":unaffiliated_hero_quest_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "action_result_notification_details_scala_proto",
|
||||
deps = [":action_result_notification_details_proto"],
|
||||
@@ -41,15 +27,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "action_result_type_swift_proto",
|
||||
protos = [":action_result_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "action_result_type_scala_proto",
|
||||
deps = [":action_result_type_proto"],
|
||||
@@ -65,15 +42,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "battalion_type_swift_proto",
|
||||
protos = [":battalion_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "battalion_type_scala_proto",
|
||||
deps = [":battalion_type_proto"],
|
||||
@@ -89,15 +57,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "beast_info_swift_proto",
|
||||
protos = [":beast_info_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "beast_info_scala_proto",
|
||||
deps = [":beast_info_proto"],
|
||||
@@ -113,16 +72,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "chronicle_entry_swift_proto",
|
||||
protos = [":chronicle_entry_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [":date_swift_proto"],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "chronicle_entry_scala_proto",
|
||||
deps = ["chronicle_entry_proto"],
|
||||
@@ -141,12 +90,18 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "combat_unit_swift_proto",
|
||||
protos = [":combat_unit_proto"],
|
||||
scala_proto_library(
|
||||
name = "command_type_scala_proto",
|
||||
deps = [":command_type_proto"],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "command_type_proto",
|
||||
srcs = [
|
||||
"command_type.proto",
|
||||
],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -166,15 +121,6 @@ proto_library(
|
||||
deps = ["@com_google_protobuf//:wrappers_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "date_swift_proto",
|
||||
protos = [":date_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "date_scala_proto",
|
||||
deps = [":date_proto"],
|
||||
@@ -190,19 +136,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "diplomacy_offer_swift_proto",
|
||||
protos = [":diplomacy_offer_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":date_swift_proto",
|
||||
":diplomacy_offer_status_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "diplomacy_offer_scala_proto",
|
||||
visibility = [
|
||||
@@ -226,15 +159,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "diplomacy_offer_status_swift_proto",
|
||||
protos = [":diplomacy_offer_status_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "diplomacy_offer_status_scala_proto",
|
||||
deps = [":diplomacy_offer_status_proto"],
|
||||
@@ -250,15 +174,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "gender_swift_proto",
|
||||
protos = [":gender_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "gender_scala_proto",
|
||||
deps = [":gender_proto"],
|
||||
@@ -272,18 +187,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "hero_backstory_version_swift_proto",
|
||||
protos = [":hero_backstory_version_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":date_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "hero_backstory_version_scala_proto",
|
||||
deps = [":hero_backstory_version_proto"],
|
||||
@@ -300,15 +203,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "improvement_type_swift_proto",
|
||||
protos = [":improvement_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "improvement_type_scala_proto",
|
||||
visibility = [
|
||||
@@ -330,15 +224,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "profession_swift_proto",
|
||||
protos = [":profession_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "profession_scala_proto",
|
||||
deps = [":profession_proto"],
|
||||
@@ -354,19 +239,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "province_event_swift_proto",
|
||||
protos = [":province_event_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":beast_info_swift_proto",
|
||||
":date_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "province_event_scala_proto",
|
||||
deps = [":province_event_proto"],
|
||||
@@ -386,15 +258,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "province_order_type_swift_proto",
|
||||
protos = [":province_order_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "province_order_type_scala_proto",
|
||||
deps = [":province_order_type_proto"],
|
||||
@@ -410,18 +273,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "recruitment_info_swift_proto",
|
||||
protos = [":recruitment_info_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":unaffiliated_hero_quest_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "recruitment_info_scala_proto",
|
||||
deps = [":recruitment_info_proto"],
|
||||
@@ -438,15 +289,6 @@ proto_library(
|
||||
deps = [":unaffiliated_hero_quest_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "round_phase_swift_proto",
|
||||
protos = [":round_phase_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "round_phase_scala_proto",
|
||||
deps = [":round_phase_proto"],
|
||||
@@ -462,15 +304,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "tribute_amount_swift_proto",
|
||||
protos = [":tribute_amount_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "tribute_amount_scala_proto",
|
||||
visibility = [
|
||||
@@ -494,16 +327,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "unaffiliated_hero_quest_swift_proto",
|
||||
protos = [":unaffiliated_hero_quest_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [":battalion_type_swift_proto"],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "unaffiliated_hero_quest_scala_proto",
|
||||
deps = [":unaffiliated_hero_quest_proto"],
|
||||
@@ -520,16 +343,6 @@ proto_library(
|
||||
deps = [":battalion_type_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "unaffiliated_hero_type_swift_proto",
|
||||
protos = [":unaffiliated_hero_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "unaffiliated_hero_type_scala_proto",
|
||||
deps = [":unaffiliated_hero_type_proto"],
|
||||
@@ -555,6 +368,7 @@ go_proto_library(
|
||||
":beast_info_proto",
|
||||
":chronicle_entry_proto",
|
||||
":combat_unit_proto",
|
||||
":command_type_proto",
|
||||
":date_proto",
|
||||
":diplomacy_offer_proto",
|
||||
":diplomacy_offer_status_proto",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package net.eagle0.eagle.common.command_type;
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "net.eagle0.eagle.common.command_type";
|
||||
|
||||
// Enum representing the type of a command, without any command-specific data.
|
||||
// Used to track which command type was last executed on a province.
|
||||
enum CommandType {
|
||||
COMMAND_TYPE_UNKNOWN = 0;
|
||||
COMMAND_TYPE_ALMS = 1;
|
||||
COMMAND_TYPE_APPREHEND_OUTLAW = 2;
|
||||
COMMAND_TYPE_ARM_TROOPS = 3;
|
||||
COMMAND_TYPE_ATTACK_DECISION = 4;
|
||||
COMMAND_TYPE_CONTROL_WEATHER = 5;
|
||||
COMMAND_TYPE_DECLINE_QUEST = 6;
|
||||
COMMAND_TYPE_DEFEND = 7;
|
||||
COMMAND_TYPE_DIPLOMACY = 8;
|
||||
COMMAND_TYPE_DIVINE = 9;
|
||||
COMMAND_TYPE_EXILE_VASSAL = 10;
|
||||
COMMAND_TYPE_FEAST = 11;
|
||||
COMMAND_TYPE_FREE_FOR_ALL_DECISION = 12;
|
||||
COMMAND_TYPE_HANDLE_CAPTURED_HERO = 13;
|
||||
COMMAND_TYPE_HANDLE_RIOT_CRACK_DOWN = 14;
|
||||
COMMAND_TYPE_HANDLE_RIOT_DO_NOTHING = 15;
|
||||
COMMAND_TYPE_HANDLE_RIOT_GIVE = 16;
|
||||
COMMAND_TYPE_HERO_GIFT = 17;
|
||||
COMMAND_TYPE_IMPROVE = 18;
|
||||
COMMAND_TYPE_ISSUE_ORDERS = 19;
|
||||
COMMAND_TYPE_MANAGE_PRISONERS = 20;
|
||||
COMMAND_TYPE_MARCH = 21;
|
||||
COMMAND_TYPE_ORGANIZE_TROOPS = 22;
|
||||
COMMAND_TYPE_PLEASE_RECRUIT_ME = 23;
|
||||
COMMAND_TYPE_RECON = 24;
|
||||
COMMAND_TYPE_RECRUIT_HEROES = 25;
|
||||
COMMAND_TYPE_RESOLVE_ALLIANCE_OFFER = 26;
|
||||
COMMAND_TYPE_RESOLVE_BREAK_ALLIANCE = 27;
|
||||
COMMAND_TYPE_RESOLVE_INVITATION = 28;
|
||||
COMMAND_TYPE_RESOLVE_RANSOM_OFFER = 29;
|
||||
COMMAND_TYPE_RESOLVE_TRUCE_OFFER = 30;
|
||||
COMMAND_TYPE_RESOLVE_TRIBUTE = 31;
|
||||
COMMAND_TYPE_REST = 32;
|
||||
COMMAND_TYPE_RETURN = 33;
|
||||
COMMAND_TYPE_SEND_SUPPLIES = 34;
|
||||
COMMAND_TYPE_START_EPIDEMIC = 35;
|
||||
COMMAND_TYPE_SUPPRESS_BEASTS = 36;
|
||||
COMMAND_TYPE_SWEAR_BROTHERHOOD = 37;
|
||||
COMMAND_TYPE_TRADE = 38;
|
||||
COMMAND_TYPE_TRAIN = 39;
|
||||
COMMAND_TYPE_TRAVEL = 40;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
load("@build_bazel_rules_swift//proto:proto.bzl", "swift_proto_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library")
|
||||
@@ -8,31 +7,6 @@ package(default_visibility = [
|
||||
"//src/test/scala/net/eagle0:__subpackages__",
|
||||
])
|
||||
|
||||
swift_proto_library(
|
||||
name = "action_result_swift_proto",
|
||||
protos = [":action_result_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":battalion_swift_proto",
|
||||
":changed_faction_swift_proto",
|
||||
":changed_hero_swift_proto",
|
||||
":changed_province_swift_proto",
|
||||
":faction_swift_proto",
|
||||
":hero_swift_proto",
|
||||
":llm_request_swift_proto",
|
||||
":llm_response_swift_proto",
|
||||
":province_swift_proto",
|
||||
":shardok_battle_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_notification_details_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:chronicle_entry_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "action_result_scala_proto",
|
||||
deps = [":action_result_proto"],
|
||||
@@ -58,24 +32,13 @@ proto_library(
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:chronicle_entry_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:command_type_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_proto",
|
||||
"@com_google_protobuf//:wrappers_proto",
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "army_swift_proto",
|
||||
protos = [":army_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":supplies_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:combat_unit_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:tribute_amount_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "army_scala_proto",
|
||||
deps = [":army_proto"],
|
||||
@@ -94,15 +57,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "battalion_swift_proto",
|
||||
protos = [":battalion_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "battalion_scala_proto",
|
||||
deps = [":battalion_proto"],
|
||||
@@ -116,12 +70,6 @@ proto_library(
|
||||
deps = ["//src/main/protobuf/net/eagle0/eagle/common:battalion_type_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "battle_revelation_swift_proto",
|
||||
protos = [":battle_revelation_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "battle_revelation_scala_proto",
|
||||
deps = [":battle_revelation_proto"],
|
||||
@@ -134,20 +82,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "changed_faction_swift_proto",
|
||||
protos = [":changed_faction_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":changed_hero_swift_proto",
|
||||
":changed_province_swift_proto",
|
||||
":faction_relationship_swift_proto",
|
||||
":faction_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:province_view_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "changed_faction_scala_proto",
|
||||
deps = [
|
||||
@@ -169,16 +103,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "changed_hero_swift_proto",
|
||||
protos = [":changed_hero_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":event_for_hero_backstory_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:profession_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "changed_hero_scala_proto",
|
||||
deps = ["changed_hero_proto"],
|
||||
@@ -196,23 +120,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "changed_province_swift_proto",
|
||||
protos = [":changed_province_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":deferred_change_swift_proto",
|
||||
":province_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:improvement_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_event_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:army_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:battle_revelation_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:supplies_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:unaffiliated_hero_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "changed_province_scala_proto",
|
||||
deps = ["changed_province_proto"],
|
||||
@@ -237,15 +144,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "client_text_swift_proto",
|
||||
protos = [":client_text_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:llm_request_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "client_text_scala_proto",
|
||||
deps = ["client_text_proto"],
|
||||
@@ -259,12 +157,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "deferred_change_swift_proto",
|
||||
protos = [":deferred_change_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "deferred_change_scala_proto",
|
||||
deps = [":deferred_change_proto"],
|
||||
@@ -275,15 +167,6 @@ proto_library(
|
||||
srcs = ["deferred_change.proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "event_for_chronicle_swift_proto",
|
||||
protos = [":event_for_chronicle_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "event_for_chronicle_scala_proto",
|
||||
deps = [":event_for_chronicle_proto"],
|
||||
@@ -298,19 +181,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "event_for_hero_backstory_swift_proto",
|
||||
protos = [":event_for_hero_backstory_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_status_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:profession_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:unaffiliated_hero_quest_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:battalion_view_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "event_for_hero_backstory_scala_proto",
|
||||
deps = [":event_for_hero_backstory_proto"],
|
||||
@@ -328,19 +198,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "faction_swift_proto",
|
||||
protos = [":faction_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":faction_relationship_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_notification_details_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:province_view_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "faction_scala_proto",
|
||||
deps = [":faction_proto"],
|
||||
@@ -359,15 +216,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "faction_relationship_swift_proto",
|
||||
protos = [":faction_relationship_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "faction_relationship_scala_proto",
|
||||
deps = [":faction_relationship_proto"],
|
||||
@@ -384,17 +232,6 @@ proto_library(
|
||||
deps = ["//src/main/protobuf/net/eagle0/eagle/common:date_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "game_parameters_swift_proto",
|
||||
protos = [":game_parameters_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:battalion_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "game_parameters_scala_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -413,18 +250,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "game_swift_proto",
|
||||
protos = [":game_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":action_result_swift_proto",
|
||||
":game_state_swift_proto",
|
||||
":shardok_results_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "game_scala_proto",
|
||||
deps = [":game_proto"],
|
||||
@@ -442,27 +267,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "game_state_swift_proto",
|
||||
protos = [":game_state_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":battalion_swift_proto",
|
||||
":faction_swift_proto",
|
||||
":hero_swift_proto",
|
||||
":llm_request_swift_proto",
|
||||
":llm_response_swift_proto",
|
||||
":province_swift_proto",
|
||||
":run_status_swift_proto",
|
||||
":shardok_battle_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_notification_details_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:chronicle_entry_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "game_state_scala_proto",
|
||||
deps = [":game_state_proto"],
|
||||
@@ -489,18 +293,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "hero_swift_proto",
|
||||
protos = [":hero_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":event_for_hero_backstory_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:gender_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:hero_backstory_version_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:profession_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "hero_scala_proto",
|
||||
deps = [":hero_proto"],
|
||||
@@ -518,13 +310,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "llm_response_swift_proto",
|
||||
protos = [":llm_response_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [":llm_request_swift_proto"],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "llm_response_scala_proto",
|
||||
deps = [":llm_response_proto"],
|
||||
@@ -540,21 +325,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "llm_request_swift_proto",
|
||||
protos = [":llm_request_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":event_for_chronicle_swift_proto",
|
||||
":event_for_hero_backstory_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_status_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:unaffiliated_hero_quest_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:hero_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:battalion_view_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "llm_request_scala_proto",
|
||||
deps = [":llm_request_proto"],
|
||||
@@ -578,27 +348,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "province_swift_proto",
|
||||
protos = [":province_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":army_swift_proto",
|
||||
":battle_revelation_swift_proto",
|
||||
":deferred_change_swift_proto",
|
||||
":supplies_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:improvement_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_event_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:tribute_amount_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:unaffiliated_hero_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "province_scala_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -616,8 +365,8 @@ proto_library(
|
||||
":deferred_change_proto",
|
||||
":supplies_proto",
|
||||
":unaffiliated_hero_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:command_type_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:improvement_type_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_event_proto",
|
||||
@@ -626,13 +375,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "run_status_swift_proto",
|
||||
protos = [":run_status_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "run_status_scala_proto",
|
||||
deps = [":run_status_proto"],
|
||||
@@ -645,13 +387,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "running_games_swift_proto",
|
||||
protos = [":running_games_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "running_games_scala_proto",
|
||||
deps = [":running_games_proto"],
|
||||
@@ -662,16 +397,6 @@ proto_library(
|
||||
srcs = ["running_games.proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "shardok_battle_swift_proto",
|
||||
protos = [":shardok_battle_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":army_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/common:victory_condition_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "shardok_battle_scala_proto",
|
||||
deps = [":shardok_battle_proto"],
|
||||
@@ -688,17 +413,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "shardok_results_swift_proto",
|
||||
protos = [":shardok_results_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/shardok/api:action_result_view_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/api:command_descriptor_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/storage:action_result_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "shardok_results_scala_proto",
|
||||
deps = [":shardok_results_proto"],
|
||||
@@ -716,14 +430,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "supplies_swift_proto",
|
||||
protos = [":supplies_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "supplies_scala_proto",
|
||||
deps = [":supplies_proto"],
|
||||
@@ -737,20 +443,6 @@ proto_library(
|
||||
deps = ["@com_google_protobuf//:wrappers_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "unaffiliated_hero_swift_proto",
|
||||
protos = [":unaffiliated_hero_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:recruitment_info_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:unaffiliated_hero_type_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "unaffiliated_hero_scala_proto",
|
||||
deps = [":unaffiliated_hero_proto"],
|
||||
|
||||
@@ -12,6 +12,7 @@ import "src/main/protobuf/net/eagle0/eagle/common/action_result_notification_det
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/action_result_type.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/battalion_type.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/chronicle_entry.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/command_type.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/date.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/round_phase.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/internal/battalion.proto";
|
||||
@@ -63,7 +64,9 @@ message ActionResult {
|
||||
|
||||
.google.protobuf.Int32Value province_acted = 17;
|
||||
|
||||
.net.eagle0.eagle.api.SelectedCommand last_command_type_for_acting_province = 28;
|
||||
// Deprecated: Use last_command_type_for_acting_province instead. Kept for backwards compatibility with saved games.
|
||||
.net.eagle0.eagle.api.SelectedCommand deprecated_last_command_type_for_acting_province = 28;
|
||||
.net.eagle0.eagle.common.command_type.CommandType last_command_type_for_acting_province = 43;
|
||||
|
||||
.google.protobuf.Int32Value leader = 18;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ syntax = "proto3";
|
||||
package net.eagle0.eagle.internal;
|
||||
|
||||
import "google/protobuf/wrappers.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/api/selected_command.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/battalion_type.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/command_type.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/date.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/improvement_type.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/province_event.proto";
|
||||
@@ -104,7 +104,7 @@ message Province {
|
||||
|
||||
.net.eagle0.eagle.common.Date last_beasts_date = 25;
|
||||
.net.eagle0.eagle.common.Date last_riot_date = 35;
|
||||
.net.eagle0.eagle.api.SelectedCommand last_command = 26;
|
||||
.net.eagle0.eagle.common.command_type.CommandType last_command = 26;
|
||||
|
||||
int32 castle_count = 33;
|
||||
int32 hero_cap = 39;
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
load("@build_bazel_rules_swift//proto:proto.bzl", "swift_proto_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library")
|
||||
|
||||
swift_proto_library(
|
||||
name = "action_result_view_swift_proto",
|
||||
protos = [":action_result_view_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_notification_details_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "action_result_view_scala_proto",
|
||||
visibility = [
|
||||
@@ -36,15 +25,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "army_view_swift_proto",
|
||||
protos = [":army_view_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:combat_unit_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "army_view_scala_proto",
|
||||
visibility = [
|
||||
@@ -65,19 +45,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "battalion_view_swift_proto",
|
||||
protos = [":battalion_view_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:__pkg__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "battalion_view_scala_proto",
|
||||
visibility = [
|
||||
@@ -102,15 +69,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "faction_relationship_view_swift_proto",
|
||||
protos = [":faction_relationship_view_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "faction_relationship_view_scala_proto",
|
||||
visibility = [
|
||||
@@ -131,16 +89,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "faction_view_swift_proto",
|
||||
protos = [":faction_view_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":faction_relationship_view_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "faction_view_scala_proto",
|
||||
visibility = [
|
||||
@@ -163,23 +111,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "game_state_view_swift_proto",
|
||||
protos = [":game_state_view_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
":battalion_view_swift_proto",
|
||||
":faction_view_swift_proto",
|
||||
":hero_view_swift_proto",
|
||||
":province_view_swift_proto",
|
||||
":shardok_battle_view_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:chronicle_entry_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "game_state_view_scala_proto",
|
||||
visibility = [
|
||||
@@ -208,20 +139,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "hero_view_swift_proto",
|
||||
protos = [":hero_view_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":stat_with_condition_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:gender_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:profession_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "hero_view_scala_proto",
|
||||
visibility = [
|
||||
@@ -247,13 +164,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "incoming_army_view_swift_proto",
|
||||
protos = [":incoming_army_view_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [":battalion_view_swift_proto"],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "incoming_army_view_scala_proto",
|
||||
visibility = [
|
||||
@@ -270,27 +180,6 @@ proto_library(
|
||||
deps = [":battalion_view_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "province_view_swift_proto",
|
||||
protos = [":province_view_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":army_view_swift_proto",
|
||||
":battalion_view_swift_proto",
|
||||
":incoming_army_view_swift_proto",
|
||||
":stat_with_condition_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:profession_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_event_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:recruitment_info_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:unaffiliated_hero_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "province_view_scala_proto",
|
||||
visibility = [
|
||||
@@ -323,13 +212,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "stat_with_condition_swift_proto",
|
||||
protos = [":stat_with_condition_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "stat_with_condition_scala_proto",
|
||||
visibility = [
|
||||
@@ -349,15 +231,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "shardok_battle_view_swift_proto",
|
||||
protos = [":shardok_battle_view_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/common:hostility_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "shardok_battle_view_scala_proto",
|
||||
visibility = [
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
load("@build_bazel_rules_swift//proto:proto.bzl", "swift_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")
|
||||
|
||||
swift_proto_library(
|
||||
name = "action_result_view_swift_proto",
|
||||
protos = [":action_result_view_proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":game_state_view_swift_proto",
|
||||
":odds_view_swift_proto",
|
||||
":unit_view_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:action_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:coords_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:game_status_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "action_result_view_scala_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -42,18 +27,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "command_descriptor_swift_proto",
|
||||
protos = [":command_descriptor_proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":odds_view_swift_proto",
|
||||
":roll_request_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:command_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:coords_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "command_descriptor_scala_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -82,20 +55,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "game_state_view_swift_proto",
|
||||
protos = [":game_state_view_proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":unit_view_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:game_status_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:hex_map_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:player_info_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:tile_modifier_with_coords_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:weather_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "game_state_view_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -119,13 +78,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "odds_view_swift_proto",
|
||||
protos = [":odds_view_proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":unit_view_swift_proto"],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "odds_view_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -153,16 +105,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "roll_request_swift_proto",
|
||||
protos = [":roll_request_proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":odds_view_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:command_type_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "roll_request_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -179,17 +121,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "unit_view_swift_proto",
|
||||
protos = [":unit_view_proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:command_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:coords_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:hostility_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "unit_view_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
load("@build_bazel_rules_swift//proto:proto.bzl", "swift_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")
|
||||
|
||||
swift_proto_library(
|
||||
name = "action_type_swift_proto",
|
||||
protos = [":action_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "action_type_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -26,16 +15,6 @@ proto_library(
|
||||
visibility = ["//src/main/protobuf/net/eagle0/shardok:__subpackages__"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "command_type_swift_proto",
|
||||
protos = [":command_type_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "command_type_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -48,16 +27,6 @@ proto_library(
|
||||
visibility = ["//src/main/protobuf/net/eagle0/shardok:__subpackages__"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "coords_swift_proto",
|
||||
protos = [":coords_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "coords_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -70,16 +39,6 @@ proto_library(
|
||||
visibility = ["//src/main/protobuf/net/eagle0/shardok:__subpackages__"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "game_status_swift_proto",
|
||||
protos = [":game_status_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [":victory_condition_swift_proto"],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "game_status_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -93,21 +52,6 @@ proto_library(
|
||||
deps = [":victory_condition_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "hex_map_swift_proto",
|
||||
protos = [":hex_map_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":coords_swift_proto",
|
||||
":hex_map_direction_swift_proto",
|
||||
":terrain_swift_proto",
|
||||
":tile_modifier_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "hex_map_scala_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -134,16 +78,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "hex_map_direction_swift_proto",
|
||||
protos = [":hex_map_direction_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "hex_map_direction_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -156,16 +90,6 @@ proto_library(
|
||||
visibility = ["//src/main/protobuf/net/eagle0/shardok:__subpackages__"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "hostility_swift_proto",
|
||||
protos = [":hostility_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "hostility_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -178,16 +102,6 @@ proto_library(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "player_info_swift_proto",
|
||||
protos = [":player_info_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [":victory_condition_swift_proto"],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "player_info_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -205,16 +119,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "terrain_swift_proto",
|
||||
protos = [":terrain_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [":tile_modifier_swift_proto"],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "terrain_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -234,16 +138,6 @@ proto_library(
|
||||
deps = [":tile_modifier_proto"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "tile_modifier_swift_proto",
|
||||
protos = [":tile_modifier_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "tile_modifier_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -265,19 +159,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "tile_modifier_with_coords_swift_proto",
|
||||
protos = [":tile_modifier_with_coords_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":coords_swift_proto",
|
||||
":tile_modifier_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "tile_modifier_with_coords_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -294,16 +175,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "victory_condition_swift_proto",
|
||||
protos = [":victory_condition_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "victory_condition_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -316,16 +187,6 @@ proto_library(
|
||||
visibility = ["//src/main/protobuf/net/eagle0/shardok:__subpackages__"],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "weather_swift_proto",
|
||||
protos = [":weather_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0:__subpackages__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [":hex_map_direction_swift_proto"],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "weather_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
@@ -1,26 +1,8 @@
|
||||
load("@build_bazel_rules_swift//proto:proto.bzl", "swift_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")
|
||||
|
||||
swift_proto_library(
|
||||
name = "action_result_swift_proto",
|
||||
protos = [":action_result_proto"],
|
||||
visibility = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:__pkg__",
|
||||
"//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":odds_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:action_type_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:coords_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:game_status_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:tile_modifier_with_coords_swift_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/common:weather_swift_proto",
|
||||
],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "action_result_scala_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -67,12 +49,6 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_proto_library(
|
||||
name = "odds_swift_proto",
|
||||
protos = [":odds_proto"],
|
||||
visibility = ["//src/main/swift/net/eagle0/EagleGameHistoryViewer:__subpackages__"],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "odds_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
@@ -154,7 +154,6 @@ scala_library(
|
||||
":ai_client_utils",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:battalion_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
|
||||
|
||||
@@ -11,6 +11,7 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/service/controller:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/shardok_interface:battle_update",
|
||||
],
|
||||
@@ -21,6 +22,7 @@ scala_library(
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:action_result_view_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/shardok_interface:battle_resolution",
|
||||
"//src/main/scala/net/eagle0/eagle/shardok_interface:battle_update",
|
||||
@@ -75,6 +77,8 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/command/available:one_province_available_commands_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
@@ -170,6 +174,7 @@ scala_library(
|
||||
name = "round_phase_advancer",
|
||||
srcs = ["RoundPhaseAdvancer.scala"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
|
||||
@@ -2,8 +2,8 @@ package net.eagle0.eagle.library
|
||||
|
||||
import net.eagle0.eagle.{FactionId, GameId, ProvinceId}
|
||||
import net.eagle0.eagle.api.command.AvailableCommands
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.shardok_interface.{BattleResolution, BattleUpdate}
|
||||
import net.eagle0.eagle.views.action_result_view.ActionResultView
|
||||
|
||||
@@ -5,7 +5,6 @@ import scala.annotation.tailrec
|
||||
import net.eagle0.common.SeededRandom
|
||||
import net.eagle0.eagle.{FactionId, GameId, ProvinceId}
|
||||
import net.eagle0.eagle.api.command.AvailableCommands
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.library.actions.applier.{ActionResultApplierImpl, ActionResultWithResultingState}
|
||||
import net.eagle0.eagle.library.actions.availability.AvailableCommandsFactory
|
||||
@@ -24,6 +23,8 @@ import net.eagle0.eagle.library.EngineImpl.{appliedResults, appliedResultsScala,
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.command.available.OneProvinceAvailableCommandsConverter
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand as ScalaAvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.shardok_interface.{BattleResolution, BattleUpdate}
|
||||
import net.eagle0.eagle.views.action_result_view.ActionResultView
|
||||
@@ -286,34 +287,35 @@ case class EngineImpl(
|
||||
selectedProvinceId: ProvinceId,
|
||||
selectedCommand: SelectedCommand
|
||||
): EngineAndResults = {
|
||||
val maybeAvailableCommands = getAvailablePlayerCommands(factionId)
|
||||
// Get Scala commands directly from factory
|
||||
val scalaCommandsByProvince =
|
||||
availableCommandsFactory.availablePlayerCommands(currentState, factionId)
|
||||
|
||||
commandRequire(
|
||||
maybeAvailableCommands.nonEmpty,
|
||||
scalaCommandsByProvince.nonEmpty,
|
||||
s"Attempted to post a command for faction $factionId, but there are none"
|
||||
)
|
||||
|
||||
val availableCommands = maybeAvailableCommands.get
|
||||
|
||||
commandRequire(
|
||||
availableCommands.commandsByProvince.contains(selectedProvinceId),
|
||||
s"Attempted to select province $selectedProvinceId out of ${availableCommands.commandsByProvince.keys}"
|
||||
scalaCommandsByProvince.contains(selectedProvinceId),
|
||||
s"Attempted to select province $selectedProvinceId out of ${scalaCommandsByProvince.keys}"
|
||||
)
|
||||
|
||||
val availableCommandsForProvince =
|
||||
availableCommands.commandsByProvince(selectedProvinceId)
|
||||
val scalaCommandsForProvince = scalaCommandsByProvince(selectedProvinceId)
|
||||
|
||||
val availableCommandOpt = AvailableCommandTypeMap.matchingAvailableCommand(
|
||||
availableCommandsForProvince.commands,
|
||||
// Match using CommandType
|
||||
val scalaAvailableCommandOpt = AvailableCommandTypeMap.matchingAvailableCommand(
|
||||
scalaCommandsForProvince.commands,
|
||||
selectedCommand
|
||||
)
|
||||
commandRequire(
|
||||
availableCommandOpt.isDefined,
|
||||
scalaAvailableCommandOpt.isDefined,
|
||||
s"No matching available command for selected command $selectedCommand"
|
||||
)
|
||||
val availableCommand = availableCommandOpt.get
|
||||
val scalaAvailableCommand = scalaAvailableCommandOpt.get
|
||||
|
||||
val sequencer = RandomStateSequencer(
|
||||
val commandType = selectedCommand.commandType
|
||||
val sequencer = RandomStateSequencer(
|
||||
initialState = this.currentState,
|
||||
actionResultApplier = ActionResultApplierImpl(Some(ScalaRuntimeValidator)),
|
||||
functionalRandom = SeededRandom(this.currentState.randomSeed)
|
||||
@@ -322,10 +324,10 @@ case class EngineImpl(
|
||||
commandFactory.makeTCommand(
|
||||
actingFactionId = factionId,
|
||||
gameState = gs,
|
||||
availableCommand = availableCommand,
|
||||
availableCommand = scalaAvailableCommand,
|
||||
selectedCommand = selectedCommand
|
||||
),
|
||||
selectedCommand = selectedCommand
|
||||
commandType = commandType
|
||||
).withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator(gs))
|
||||
|
||||
// Validate that the first result has an acting faction set (required for player commands)
|
||||
|
||||
@@ -54,12 +54,12 @@ scala_library(
|
||||
deps = [
|
||||
":province_update_helpers",
|
||||
":province_update_helpers2",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//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/concrete",
|
||||
@@ -155,13 +155,13 @@ scala_library(
|
||||
":game_state_hero_extensions",
|
||||
":game_state_misc_extensions",
|
||||
":game_state_province_extensions",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
deps = [
|
||||
@@ -171,7 +171,6 @@ scala_library(
|
||||
":game_state_hero_extensions",
|
||||
":game_state_misc_extensions",
|
||||
":game_state_province_extensions",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:extra_xp_for_stat_bump_over100",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:xp_for_stat_bump",
|
||||
@@ -180,6 +179,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
package net.eagle0.eagle.library.actions.applier
|
||||
|
||||
import net.eagle0.eagle.*
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
import net.eagle0.eagle.library.EagleInternalException
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.action_result.changed_province.ChangedProvinceT
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
@@ -79,10 +79,10 @@ object GameStateProvinceExtensions {
|
||||
|
||||
def applyLastCommand(
|
||||
provinceId: Option[ProvinceId],
|
||||
lastCommand: Option[SelectedCommand]
|
||||
lastCommand: Option[CommandType]
|
||||
): GameState =
|
||||
provinceId
|
||||
.filter(_ => lastCommand.exists(!_.isEmpty))
|
||||
.filter(_ => lastCommand.isDefined)
|
||||
.map { pid =>
|
||||
val province = gameState.provinces(pid) match {
|
||||
case p: ProvinceC => p
|
||||
|
||||
+18
-63
@@ -7,11 +7,10 @@ import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.library.util.IncomingArmyUtils
|
||||
import net.eagle0.eagle.model.proto_converters.command.selected.SelectedCommandConverter
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.*
|
||||
import net.eagle0.eagle.model.state.command.available.OneProvinceAvailableCommands
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
@@ -19,66 +18,25 @@ import net.eagle0.eagle.model.state.RoundPhase.*
|
||||
|
||||
object AvailableCommandsFactory {
|
||||
|
||||
/** Command types that should not suggest a follow-up of the same type. */
|
||||
private val noFollowCommands: Set[CommandType] = Set(
|
||||
CommandType.March,
|
||||
CommandType.Diplomacy,
|
||||
CommandType.Rest,
|
||||
CommandType.Trade,
|
||||
CommandType.ArmTroops,
|
||||
CommandType.OrganizeTroops,
|
||||
CommandType.ExileVassal,
|
||||
CommandType.SwearBrotherhood
|
||||
)
|
||||
|
||||
/** Returns true if after executing `last`, the UI should suggest `next` as a follow-up. */
|
||||
def shouldFollow(
|
||||
last: SelectedCommand,
|
||||
last: CommandType,
|
||||
next: AvailableCommand
|
||||
): Boolean = (last, next) match {
|
||||
case (_: SelectedCommand.MarchSelected, _) => false
|
||||
case (_: SelectedCommand.DiplomacySelected, _) => false
|
||||
case (SelectedCommand.RestSelected, _) => false
|
||||
case (_: SelectedCommand.TradeSelected, _) => false
|
||||
case (_: SelectedCommand.ArmTroopsSelected, _) => false
|
||||
case (_: SelectedCommand.OrganizeTroopsSelected, _) => false
|
||||
case (_: SelectedCommand.ExileVassalSelected, _) => false
|
||||
case (_: SelectedCommand.SwearBrotherhoodSelected, _) => false
|
||||
case (last, next) => commandTypesMatch(last, next)
|
||||
}
|
||||
|
||||
/** Compares ordinal values to check if the selected and available commands are the same type. */
|
||||
private def commandTypesMatch(selected: SelectedCommand, available: AvailableCommand): Boolean =
|
||||
(selected, available) match {
|
||||
case (_: SelectedCommand.AlmsSelected, _: AlmsAvailable) => true
|
||||
case (_: SelectedCommand.ApprehendOutlawSelected, _: ApprehendOutlawAvailable) => true
|
||||
case (_: SelectedCommand.ArmTroopsSelected, _: ArmTroopsAvailable) => true
|
||||
case (_: SelectedCommand.AttackDecisionSelected, _: AttackDecisionAvailable) => true
|
||||
case (_: SelectedCommand.ControlWeatherSelected, _: ControlWeatherAvailable) => true
|
||||
case (_: SelectedCommand.DeclineQuestSelected, _: DeclineQuestAvailable) => true
|
||||
case (_: SelectedCommand.DefendSelected, _: DefendAvailable) => true
|
||||
case (_: SelectedCommand.DiplomacySelected, _: DiplomacyAvailable) => true
|
||||
case (_: SelectedCommand.DivineSelected, _: DivineAvailable) => true
|
||||
case (_: SelectedCommand.ExileVassalSelected, _: ExileVassalAvailable) => true
|
||||
case (SelectedCommand.FeastSelected, _: FeastAvailable) => true
|
||||
case (_: SelectedCommand.FreeForAllDecisionSelected, _: FreeForAllDecisionAvailable) => true
|
||||
case (_: SelectedCommand.HandleCapturedHeroSelected, _: HandleCapturedHeroAvailable) => true
|
||||
case (_: SelectedCommand.HandleRiotCrackDownSelected, _: HandleRiotCrackDownAvailable) => true
|
||||
case (SelectedCommand.HandleRiotDoNothingSelected, _: HandleRiotDoNothingAvailable) => true
|
||||
case (_: SelectedCommand.HandleRiotGiveSelected, _: HandleRiotGiveAvailable) => true
|
||||
case (_: SelectedCommand.HeroGiftSelected, _: HeroGiftAvailable) => true
|
||||
case (_: SelectedCommand.ImproveSelected, _: ImproveAvailable) => true
|
||||
case (_: SelectedCommand.IssueOrdersSelected, _: IssueOrdersAvailable) => true
|
||||
case (_: SelectedCommand.ManagePrisonersSelected, _: ManagePrisonersAvailable) => true
|
||||
case (_: SelectedCommand.MarchSelected, _: MarchAvailable) => true
|
||||
case (_: SelectedCommand.OrganizeTroopsSelected, _: OrganizeTroopsAvailable) => true
|
||||
case (_: SelectedCommand.PleaseRecruitMeSelected, _: PleaseRecruitMeAvailable) => true
|
||||
case (_: SelectedCommand.ReconSelected, _: ReconAvailable) => true
|
||||
case (_: SelectedCommand.RecruitHeroesSelected, _: RecruitHeroesAvailable) => true
|
||||
case (_: SelectedCommand.ResolveAllianceOfferSelected, _: ResolveAllianceOfferAvailable) => true
|
||||
case (_: SelectedCommand.ResolveBreakAllianceSelected, _: ResolveBreakAllianceAvailable) => true
|
||||
case (_: SelectedCommand.ResolveInvitationSelected, _: ResolveInvitationAvailable) => true
|
||||
case (_: SelectedCommand.ResolveRansomOfferSelected, _: ResolveRansomOfferAvailable) => true
|
||||
case (_: SelectedCommand.ResolveTruceOfferSelected, _: ResolveTruceOfferAvailable) => true
|
||||
case (_: SelectedCommand.ResolveTributeSelected, _: ResolveTributeAvailable) => true
|
||||
case (SelectedCommand.RestSelected, _: RestAvailable) => true
|
||||
case (SelectedCommand.ReturnSelected, _: ReturnAvailable) => true
|
||||
case (_: SelectedCommand.SendSuppliesSelected, _: SendSuppliesAvailable) => true
|
||||
case (_: SelectedCommand.StartEpidemicSelected, _: StartEpidemicAvailable) => true
|
||||
case (_: SelectedCommand.SuppressBeastsSelected, _: SuppressBeastsAvailable) => true
|
||||
case (_: SelectedCommand.SwearBrotherhoodSelected, _: SwearBrotherhoodAvailable) => true
|
||||
case (_: SelectedCommand.TradeSelected, _: TradeAvailable) => true
|
||||
case (_: SelectedCommand.TrainSelected, _: TrainAvailable) => true
|
||||
case _ => false
|
||||
}
|
||||
): Boolean =
|
||||
if noFollowCommands.contains(last) then false
|
||||
else last == next.commandType
|
||||
|
||||
def suggestedProvinceId(
|
||||
fid: FactionId,
|
||||
@@ -210,10 +168,7 @@ class AvailableCommandsFactory(
|
||||
): Int =
|
||||
commands.zipWithIndex.find {
|
||||
case (ac, _) =>
|
||||
province.lastCommand.forall { lastProto =>
|
||||
val last = SelectedCommandConverter.fromProto(lastProto)
|
||||
AvailableCommandsFactory.shouldFollow(last, ac)
|
||||
}
|
||||
province.lastCommand.forall(last => AvailableCommandsFactory.shouldFollow(last, ac))
|
||||
}
|
||||
.map(_._2)
|
||||
.getOrElse(0)
|
||||
|
||||
@@ -71,10 +71,9 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:incoming_army_utils",
|
||||
"//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/model/proto_converters/command/selected:selected_command_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
],
|
||||
|
||||
@@ -444,6 +444,7 @@ scala_library(
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
@@ -1306,6 +1307,7 @@ scala_library(
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/common:more_option",
|
||||
@@ -1344,6 +1346,7 @@ scala_library(
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
|
||||
+3
-2
@@ -52,11 +52,12 @@ case class EndHandleRiotsPhaseAction(
|
||||
)
|
||||
.continue {
|
||||
case (Some(cs), nextFr) =>
|
||||
val scs = cs.toScala(opac.commands)
|
||||
val cmd = commandFactory.makeTCommand(
|
||||
actingFactionId = province.rulingFactionId.get,
|
||||
gameState = gs,
|
||||
availableCommand = cs.available,
|
||||
selectedCommand = cs.selected
|
||||
availableCommand = scs.available,
|
||||
selectedCommand = scs.selected
|
||||
)
|
||||
cmd match {
|
||||
case TCommand.Simple(action) =>
|
||||
|
||||
+3
-2
@@ -50,11 +50,12 @@ case class PerformVassalCommandsPhaseAction(
|
||||
sequencer.withRandomActionResults { (gs, fr) =>
|
||||
chooseCommand(gs, province.id, fr).continue {
|
||||
case (Some(cs), nextFr) =>
|
||||
val scs = cs.toScala(opac.commands)
|
||||
val cmd = commandFactory.makeTCommand(
|
||||
actingFactionId = cs.actingFactionId,
|
||||
gameState = gs,
|
||||
availableCommand = cs.available,
|
||||
selectedCommand = cs.selected
|
||||
availableCommand = scs.available,
|
||||
selectedCommand = scs.selected
|
||||
)
|
||||
cmd match {
|
||||
case TCommand.Simple(action) =>
|
||||
|
||||
+3
-2
@@ -50,11 +50,12 @@ case class PerformVassalDefenseDecisionsAction(
|
||||
}
|
||||
chooseCommand(gs, province.id, protoCommands, fr).continue {
|
||||
case (Some(cs), nextFr) =>
|
||||
val scs = cs.toScala(opac.commands)
|
||||
val cmd = commandFactory.makeTCommand(
|
||||
actingFactionId = cs.actingFactionId,
|
||||
gameState = gs,
|
||||
availableCommand = cs.available,
|
||||
selectedCommand = cs.selected
|
||||
availableCommand = scs.available,
|
||||
selectedCommand = scs.selected
|
||||
)
|
||||
cmd match {
|
||||
case TCommand.Simple(action) =>
|
||||
|
||||
+6
-7
@@ -1,17 +1,16 @@
|
||||
package net.eagle0.eagle.library.actions
|
||||
package impl.command
|
||||
|
||||
import net.eagle0.eagle.api.available_command.*
|
||||
import net.eagle0.eagle.api.selected_command.*
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand
|
||||
|
||||
object AvailableCommandTypeMap {
|
||||
def matchingAvailableCommand(
|
||||
acs: Iterable[AvailableCommand],
|
||||
sc: SelectedCommand
|
||||
availableCommands: Iterable[AvailableCommand],
|
||||
selectedCommand: SelectedCommand
|
||||
): Option[AvailableCommand] =
|
||||
acs.find(ac => matches(ac, sc))
|
||||
availableCommands.find(_.commandType == selectedCommand.commandType)
|
||||
|
||||
// This is pretty hacky but avoids a long match that has to be kept up to date.
|
||||
def matches(ac: AvailableCommand, sc: SelectedCommand): Boolean =
|
||||
ac.asMessage.sealedValue.number == sc.asMessage.sealedValue.number
|
||||
ac.commandType == sc.commandType
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ scala_library(
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
@@ -71,10 +73,6 @@ scala_library(
|
||||
":trade_command",
|
||||
":train_command",
|
||||
":travel_command",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:control_weather_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:province_orders_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_client_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
@@ -90,25 +88,14 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_battalion_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_id_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:combat_unit_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:improvement_type_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:proto_conversion_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:tribute_amount_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:unaffiliated_hero_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/diplomacy_offer",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/diplomacy_offer/status",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/diplomacy_option",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province:order_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:combat_unit",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:improvement_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:tribute_amount",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/common",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
"//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/diplomacy_option",
|
||||
@@ -223,8 +210,8 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
+307
-429
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.eagle.api.available_command.AvailableCommand
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.library.actions.impl.common.TCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.FactionId
|
||||
|
||||
|
||||
+4
-4
@@ -1,7 +1,6 @@
|
||||
package net.eagle0.eagle.library.actions.random_state_sequencer
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.library.actions.applier.{ActionResultApplier, ActionResultWithResultingState}
|
||||
import net.eagle0.eagle.library.actions.impl.common.{
|
||||
ProtolessRandomSimpleAction,
|
||||
@@ -10,6 +9,7 @@ import net.eagle0.eagle.library.actions.impl.common.{
|
||||
TCommand
|
||||
}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,7 @@ trait RandomStateSequencer {
|
||||
*/
|
||||
def withTCommandAndLastCommand(
|
||||
commandGen: GameState => TCommand,
|
||||
selectedCommand: SelectedCommand
|
||||
commandType: CommandType
|
||||
): RandomStateSequencer
|
||||
}
|
||||
|
||||
@@ -189,10 +189,10 @@ private case class RandomStateSequencerImpl(
|
||||
|
||||
override def withTCommandAndLastCommand(
|
||||
commandGen: GameState => TCommand,
|
||||
selectedCommand: SelectedCommand
|
||||
commandType: CommandType
|
||||
): RandomStateSequencer = {
|
||||
def wrapWithLastCommand(result: ActionResultT): ActionResultT =
|
||||
result.withLastCommandTypeForActingProvince(selectedCommand)
|
||||
result.withLastCommandTypeForActingProvince(commandType)
|
||||
|
||||
commandGen(lastState) match {
|
||||
case TCommand.Simple(action) =>
|
||||
|
||||
@@ -127,6 +127,9 @@ scala_library(
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/command/selected:selected_command_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,16 +1,51 @@
|
||||
package net.eagle0.eagle.library.util
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.AvailableCommand
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.api.available_command.AvailableCommand as ProtoAvailableCommand
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand as ProtoSelectedCommand
|
||||
import net.eagle0.eagle.model.proto_converters.command.selected.SelectedCommandConverter
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand
|
||||
|
||||
// Proto-based command selection (legacy, for gradual migration)
|
||||
case class CommandSelection(
|
||||
actingFactionId: FactionId,
|
||||
actingProvinceId: ProvinceId,
|
||||
available: ProtoAvailableCommand,
|
||||
selected: ProtoSelectedCommand,
|
||||
reason: String
|
||||
) {
|
||||
def withReason(reason: String): CommandSelection =
|
||||
this.copy(reason = reason)
|
||||
|
||||
/** Convert to ScalaCommandSelection by finding the matching Scala AvailableCommand */
|
||||
def toScala(scalaAvailableCommands: Iterable[AvailableCommand]): ScalaCommandSelection = {
|
||||
val scalaSelectedCommand = SelectedCommandConverter.fromProto(selected)
|
||||
val scalaAvailableCommand = scalaAvailableCommands
|
||||
.find(_.commandType == scalaSelectedCommand.commandType)
|
||||
.getOrElse(
|
||||
throw new IllegalStateException(
|
||||
s"No matching Scala available command for selected command: ${scalaSelectedCommand.commandType}"
|
||||
)
|
||||
)
|
||||
ScalaCommandSelection(
|
||||
actingFactionId = actingFactionId,
|
||||
actingProvinceId = actingProvinceId,
|
||||
available = scalaAvailableCommand,
|
||||
selected = scalaSelectedCommand,
|
||||
reason = reason
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Scala-based command selection
|
||||
case class ScalaCommandSelection(
|
||||
actingFactionId: FactionId,
|
||||
actingProvinceId: ProvinceId,
|
||||
available: AvailableCommand,
|
||||
selected: SelectedCommand,
|
||||
reason: String
|
||||
) {
|
||||
def withReason(reason: String): CommandSelection =
|
||||
def withReason(reason: String): ScalaCommandSelection =
|
||||
this.copy(reason = reason)
|
||||
}
|
||||
|
||||
@@ -188,6 +188,7 @@ scala_library(
|
||||
":ransom_offer_helpers",
|
||||
"//src/main/protobuf/net/eagle0/common:hostility_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:armed_battalion_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:battalion_with_food_cost_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
@@ -260,7 +261,6 @@ scala_library(
|
||||
":province_gold_surplus_calculator",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:max_combat_unit_count_per_side",
|
||||
@@ -395,7 +395,6 @@ scala_library(
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/battalion_type_finder",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package net.eagle0.eagle.model.action_result
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId, RoundId}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.model.action_result.changed_province.ChangedProvinceT
|
||||
import net.eagle0.eagle.model.action_result.generated_text_request.GeneratedTextRequestT
|
||||
import net.eagle0.eagle.model.action_result.types.ActionResultType
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.chronicle_entry.ChronicleEntry
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
@@ -29,7 +29,7 @@ trait ActionResultT {
|
||||
def newRoundId: Option[RoundId]
|
||||
def newDate: Option[Date]
|
||||
|
||||
def lastCommandTypeForActingProvince: Option[SelectedCommand]
|
||||
def lastCommandTypeForActingProvince: Option[CommandType]
|
||||
|
||||
def newBattle: Option[ShardokBattle]
|
||||
def resolvedBattle: Option[String]
|
||||
@@ -75,7 +75,7 @@ trait ActionResultT {
|
||||
newRoundPhase: Option[RoundPhase] = newRoundPhase,
|
||||
newRoundId: Option[RoundId] = newRoundId,
|
||||
newDate: Option[Date] = newDate,
|
||||
lastCommandTypeForActingProvince: Option[SelectedCommand] = lastCommandTypeForActingProvince,
|
||||
lastCommandTypeForActingProvince: Option[CommandType] = lastCommandTypeForActingProvince,
|
||||
newBattle: Option[ShardokBattle] = newBattle,
|
||||
resolvedBattle: Option[String] = resolvedBattle,
|
||||
// resolvedImminentBattle: Option[Int] = None,
|
||||
@@ -160,9 +160,9 @@ trait ActionResultT {
|
||||
copy(newRandomSeed = Some(seed))
|
||||
|
||||
def withLastCommandTypeForActingProvince(
|
||||
selectedCommand: SelectedCommand
|
||||
commandType: CommandType
|
||||
): ActionResultT =
|
||||
copy(lastCommandTypeForActingProvince = Some(selectedCommand))
|
||||
copy(lastCommandTypeForActingProvince = Some(commandType))
|
||||
|
||||
def update(updater: ActionResultT.ActionResultUpdater): ActionResultT =
|
||||
updater(this)
|
||||
|
||||
@@ -32,7 +32,6 @@ scala_library(
|
||||
":changed_hero_trait",
|
||||
":client_text_visibility_extension_trait",
|
||||
":notification_trait",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
|
||||
@@ -40,6 +39,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/chronicle_entry",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
@@ -53,7 +53,6 @@ scala_library(
|
||||
":changed_hero_trait",
|
||||
":client_text_visibility_extension_trait",
|
||||
":notification_trait",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
@@ -62,6 +61,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/chronicle_entry",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.eagle0.eagle.model.action_result.concrete
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId, RoundId}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.model.action_result.{
|
||||
ActionResultT,
|
||||
ChangedBattalionT,
|
||||
@@ -15,6 +14,7 @@ import net.eagle0.eagle.model.action_result.generated_text_request.GeneratedText
|
||||
import net.eagle0.eagle.model.action_result.types.ActionResultType
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.chronicle_entry.ChronicleEntry
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
@@ -36,7 +36,7 @@ case class ActionResultC(
|
||||
newRoundPhase: Option[RoundPhase] = None,
|
||||
newRoundId: Option[RoundId] = None,
|
||||
newDate: Option[Date] = None,
|
||||
lastCommandTypeForActingProvince: Option[SelectedCommand] = None,
|
||||
lastCommandTypeForActingProvince: Option[CommandType] = None,
|
||||
newBattle: Option[ShardokBattle] = None,
|
||||
resolvedBattle: Option[String] = None,
|
||||
newChronicleEntry: Option[ChronicleEntry] = None,
|
||||
@@ -73,7 +73,7 @@ case class ActionResultC(
|
||||
newRoundPhase: Option[RoundPhase] = newRoundPhase,
|
||||
newRoundId: Option[RoundId] = newRoundId,
|
||||
newDate: Option[Date] = newDate,
|
||||
lastCommandTypeForActingProvince: Option[SelectedCommand] = lastCommandTypeForActingProvince,
|
||||
lastCommandTypeForActingProvince: Option[CommandType] = lastCommandTypeForActingProvince,
|
||||
newBattle: Option[ShardokBattle] = newBattle,
|
||||
resolvedBattle: Option[String] = resolvedBattle,
|
||||
newChronicleEntry: Option[ChronicleEntry] = newChronicleEntry,
|
||||
|
||||
@@ -16,7 +16,6 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
@@ -31,6 +30,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/chronicle_entry",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
|
||||
+19
-6
@@ -1,8 +1,8 @@
|
||||
package net.eagle0.eagle.model.proto_converters
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId, RoundId}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType as ActionResultTypeProto
|
||||
import net.eagle0.eagle.common.command_type.command_type.CommandType as CommandTypeProto
|
||||
import net.eagle0.eagle.common.round_phase.NewRoundPhase as NewRoundPhaseProto
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult as ActionResultProto
|
||||
import net.eagle0.eagle.internal.generated_text_request.{
|
||||
@@ -28,6 +28,7 @@ import net.eagle0.eagle.model.action_result.generated_text_request.{
|
||||
}
|
||||
import net.eagle0.eagle.model.action_result.types.ActionResultType
|
||||
import net.eagle0.eagle.model.proto_converters.chronicle_entry.ChronicleEntryConverter
|
||||
import net.eagle0.eagle.model.proto_converters.command.selected.SelectedCommandConverter
|
||||
import net.eagle0.eagle.model.proto_converters.date.DateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
|
||||
import net.eagle0.eagle.model.proto_converters.generated_text_request.LlmRequestConverter
|
||||
@@ -35,6 +36,7 @@ import net.eagle0.eagle.model.proto_converters.hero.{GenderConverter, HeroConver
|
||||
import net.eagle0.eagle.model.proto_converters.shardok_battle.ShardokBattleConverter
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.chronicle_entry.ChronicleEntry
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
@@ -55,7 +57,7 @@ object ActionResultProtoConverter {
|
||||
newRoundPhase: Option[RoundPhase],
|
||||
newRoundId: Option[RoundId],
|
||||
newDate: Option[Date],
|
||||
lastCommandTypeForActingProvince: Option[SelectedCommand],
|
||||
lastCommandTypeForActingProvince: Option[CommandType],
|
||||
newBattle: Option[ShardokBattle],
|
||||
resolvedBattle: Option[String],
|
||||
newChronicleEntry: Option[ChronicleEntry],
|
||||
@@ -91,7 +93,9 @@ object ActionResultProtoConverter {
|
||||
newRoundPhase = newRoundPhase.map(rp => NewRoundPhaseProto(RoundPhaseConverter.toProto(rp))),
|
||||
newRoundId = newRoundId,
|
||||
newDate = newDate.map(DateConverter.toProto),
|
||||
lastCommandTypeForActingProvince = lastCommandTypeForActingProvince.getOrElse(SelectedCommand.Empty),
|
||||
lastCommandTypeForActingProvince = lastCommandTypeForActingProvince
|
||||
.map(CommandTypeConverter.toProto)
|
||||
.getOrElse(CommandTypeProto.COMMAND_TYPE_UNKNOWN),
|
||||
newBattle = newBattle.map(ShardokBattleConverter.toProto),
|
||||
resolvedBattle = resolvedBattle,
|
||||
newChronicleEntry = newChronicleEntry.map(ChronicleEntryConverter.toProto),
|
||||
@@ -186,9 +190,18 @@ object ActionResultProtoConverter {
|
||||
newRoundPhase = proto.newRoundPhase.map(nrp => RoundPhaseConverter.fromProto(nrp.value)),
|
||||
newRoundId = proto.newRoundId,
|
||||
newDate = proto.newDate.map(d => DateConverter.fromProto(Some(d))),
|
||||
lastCommandTypeForActingProvince = Option.when(proto.lastCommandTypeForActingProvince != SelectedCommand.Empty)(
|
||||
proto.lastCommandTypeForActingProvince
|
||||
),
|
||||
lastCommandTypeForActingProvince = {
|
||||
// Backwards compatibility: first check new CommandType field, fall back to deprecated SelectedCommand field
|
||||
val fromNewField = CommandTypeConverter.fromProto(proto.lastCommandTypeForActingProvince)
|
||||
fromNewField.orElse {
|
||||
// Check if the deprecated SelectedCommand has a command set
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand as SelectedCommandProto
|
||||
val deprecatedCommand = proto.deprecatedLastCommandTypeForActingProvince
|
||||
Option.when(deprecatedCommand != SelectedCommandProto.defaultInstance) {
|
||||
SelectedCommandConverter.fromProto(deprecatedCommand).commandType
|
||||
}
|
||||
}
|
||||
},
|
||||
newBattle = proto.newBattle.map(ShardokBattleConverter.fromProto),
|
||||
resolvedBattle = proto.resolvedBattle,
|
||||
newChronicleEntry = proto.newChronicleEntry.map(ChronicleEntryConverter.fromProto),
|
||||
|
||||
@@ -24,10 +24,12 @@ scala_library(
|
||||
":changed_faction_converter",
|
||||
":changed_hero_converter",
|
||||
":changed_province_converter",
|
||||
":command_type_converter",
|
||||
":notification_converter",
|
||||
":round_phase_converter",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:command_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
@@ -39,6 +41,7 @@ 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/types",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/chronicle_entry:chronicle_entry_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/command/selected:selected_command_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
@@ -49,6 +52,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/chronicle_entry",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//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/province",
|
||||
@@ -273,6 +277,22 @@ scala_library(
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "command_type_converter",
|
||||
srcs = ["CommandTypeConverter.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province:__pkg__",
|
||||
"//src/test/scala/net/eagle0/eagle/model/proto_converters:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
":proto_conversion_exception",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:command_type_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "combat_unit_converter",
|
||||
srcs = ["CombatUnitConverter.scala"],
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package net.eagle0.eagle.model.proto_converters
|
||||
|
||||
import net.eagle0.eagle.common.command_type.command_type.CommandType as CommandTypeProto
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
|
||||
/**
|
||||
* Converter between Scala CommandType enum and proto CommandType enum.
|
||||
*
|
||||
* Both toProto and fromProto use exhaustive pattern matching, so adding a new command type without updating this
|
||||
* converter will cause a compile error.
|
||||
*/
|
||||
object CommandTypeConverter:
|
||||
/**
|
||||
* Converts Scala CommandType to proto CommandType. Exhaustive matching ensures compile-time safety when adding new
|
||||
* command types.
|
||||
*/
|
||||
def toProto(commandType: CommandType): CommandTypeProto =
|
||||
commandType match
|
||||
case CommandType.Alms => CommandTypeProto.COMMAND_TYPE_ALMS
|
||||
case CommandType.ApprehendOutlaw => CommandTypeProto.COMMAND_TYPE_APPREHEND_OUTLAW
|
||||
case CommandType.ArmTroops => CommandTypeProto.COMMAND_TYPE_ARM_TROOPS
|
||||
case CommandType.AttackDecision => CommandTypeProto.COMMAND_TYPE_ATTACK_DECISION
|
||||
case CommandType.ControlWeather => CommandTypeProto.COMMAND_TYPE_CONTROL_WEATHER
|
||||
case CommandType.DeclineQuest => CommandTypeProto.COMMAND_TYPE_DECLINE_QUEST
|
||||
case CommandType.Defend => CommandTypeProto.COMMAND_TYPE_DEFEND
|
||||
case CommandType.Diplomacy => CommandTypeProto.COMMAND_TYPE_DIPLOMACY
|
||||
case CommandType.Divine => CommandTypeProto.COMMAND_TYPE_DIVINE
|
||||
case CommandType.ExileVassal => CommandTypeProto.COMMAND_TYPE_EXILE_VASSAL
|
||||
case CommandType.Feast => CommandTypeProto.COMMAND_TYPE_FEAST
|
||||
case CommandType.FreeForAllDecision => CommandTypeProto.COMMAND_TYPE_FREE_FOR_ALL_DECISION
|
||||
case CommandType.HandleCapturedHero => CommandTypeProto.COMMAND_TYPE_HANDLE_CAPTURED_HERO
|
||||
case CommandType.HandleRiotCrackDown => CommandTypeProto.COMMAND_TYPE_HANDLE_RIOT_CRACK_DOWN
|
||||
case CommandType.HandleRiotDoNothing => CommandTypeProto.COMMAND_TYPE_HANDLE_RIOT_DO_NOTHING
|
||||
case CommandType.HandleRiotGive => CommandTypeProto.COMMAND_TYPE_HANDLE_RIOT_GIVE
|
||||
case CommandType.HeroGift => CommandTypeProto.COMMAND_TYPE_HERO_GIFT
|
||||
case CommandType.Improve => CommandTypeProto.COMMAND_TYPE_IMPROVE
|
||||
case CommandType.IssueOrders => CommandTypeProto.COMMAND_TYPE_ISSUE_ORDERS
|
||||
case CommandType.ManagePrisoners => CommandTypeProto.COMMAND_TYPE_MANAGE_PRISONERS
|
||||
case CommandType.March => CommandTypeProto.COMMAND_TYPE_MARCH
|
||||
case CommandType.OrganizeTroops => CommandTypeProto.COMMAND_TYPE_ORGANIZE_TROOPS
|
||||
case CommandType.PleaseRecruitMe => CommandTypeProto.COMMAND_TYPE_PLEASE_RECRUIT_ME
|
||||
case CommandType.Recon => CommandTypeProto.COMMAND_TYPE_RECON
|
||||
case CommandType.RecruitHeroes => CommandTypeProto.COMMAND_TYPE_RECRUIT_HEROES
|
||||
case CommandType.ResolveAllianceOffer => CommandTypeProto.COMMAND_TYPE_RESOLVE_ALLIANCE_OFFER
|
||||
case CommandType.ResolveBreakAlliance => CommandTypeProto.COMMAND_TYPE_RESOLVE_BREAK_ALLIANCE
|
||||
case CommandType.ResolveInvitation => CommandTypeProto.COMMAND_TYPE_RESOLVE_INVITATION
|
||||
case CommandType.ResolveRansomOffer => CommandTypeProto.COMMAND_TYPE_RESOLVE_RANSOM_OFFER
|
||||
case CommandType.ResolveTruceOffer => CommandTypeProto.COMMAND_TYPE_RESOLVE_TRUCE_OFFER
|
||||
case CommandType.ResolveTribute => CommandTypeProto.COMMAND_TYPE_RESOLVE_TRIBUTE
|
||||
case CommandType.Rest => CommandTypeProto.COMMAND_TYPE_REST
|
||||
case CommandType.Return => CommandTypeProto.COMMAND_TYPE_RETURN
|
||||
case CommandType.SendSupplies => CommandTypeProto.COMMAND_TYPE_SEND_SUPPLIES
|
||||
case CommandType.StartEpidemic => CommandTypeProto.COMMAND_TYPE_START_EPIDEMIC
|
||||
case CommandType.SuppressBeasts => CommandTypeProto.COMMAND_TYPE_SUPPRESS_BEASTS
|
||||
case CommandType.SwearBrotherhood => CommandTypeProto.COMMAND_TYPE_SWEAR_BROTHERHOOD
|
||||
case CommandType.Trade => CommandTypeProto.COMMAND_TYPE_TRADE
|
||||
case CommandType.Train => CommandTypeProto.COMMAND_TYPE_TRAIN
|
||||
case CommandType.Travel => CommandTypeProto.COMMAND_TYPE_TRAVEL
|
||||
|
||||
/**
|
||||
* Converts proto CommandType to Scala CommandType. Exhaustive matching ensures compile-time safety when adding new
|
||||
* command types. Returns None for UNKNOWN.
|
||||
*/
|
||||
def fromProto(commandTypeProto: CommandTypeProto): Option[CommandType] =
|
||||
commandTypeProto match
|
||||
case CommandTypeProto.COMMAND_TYPE_ALMS => Some(CommandType.Alms)
|
||||
case CommandTypeProto.COMMAND_TYPE_APPREHEND_OUTLAW => Some(CommandType.ApprehendOutlaw)
|
||||
case CommandTypeProto.COMMAND_TYPE_ARM_TROOPS => Some(CommandType.ArmTroops)
|
||||
case CommandTypeProto.COMMAND_TYPE_ATTACK_DECISION => Some(CommandType.AttackDecision)
|
||||
case CommandTypeProto.COMMAND_TYPE_CONTROL_WEATHER => Some(CommandType.ControlWeather)
|
||||
case CommandTypeProto.COMMAND_TYPE_DECLINE_QUEST => Some(CommandType.DeclineQuest)
|
||||
case CommandTypeProto.COMMAND_TYPE_DEFEND => Some(CommandType.Defend)
|
||||
case CommandTypeProto.COMMAND_TYPE_DIPLOMACY => Some(CommandType.Diplomacy)
|
||||
case CommandTypeProto.COMMAND_TYPE_DIVINE => Some(CommandType.Divine)
|
||||
case CommandTypeProto.COMMAND_TYPE_EXILE_VASSAL => Some(CommandType.ExileVassal)
|
||||
case CommandTypeProto.COMMAND_TYPE_FEAST => Some(CommandType.Feast)
|
||||
case CommandTypeProto.COMMAND_TYPE_FREE_FOR_ALL_DECISION => Some(CommandType.FreeForAllDecision)
|
||||
case CommandTypeProto.COMMAND_TYPE_HANDLE_CAPTURED_HERO => Some(CommandType.HandleCapturedHero)
|
||||
case CommandTypeProto.COMMAND_TYPE_HANDLE_RIOT_CRACK_DOWN => Some(CommandType.HandleRiotCrackDown)
|
||||
case CommandTypeProto.COMMAND_TYPE_HANDLE_RIOT_DO_NOTHING => Some(CommandType.HandleRiotDoNothing)
|
||||
case CommandTypeProto.COMMAND_TYPE_HANDLE_RIOT_GIVE => Some(CommandType.HandleRiotGive)
|
||||
case CommandTypeProto.COMMAND_TYPE_HERO_GIFT => Some(CommandType.HeroGift)
|
||||
case CommandTypeProto.COMMAND_TYPE_IMPROVE => Some(CommandType.Improve)
|
||||
case CommandTypeProto.COMMAND_TYPE_ISSUE_ORDERS => Some(CommandType.IssueOrders)
|
||||
case CommandTypeProto.COMMAND_TYPE_MANAGE_PRISONERS => Some(CommandType.ManagePrisoners)
|
||||
case CommandTypeProto.COMMAND_TYPE_MARCH => Some(CommandType.March)
|
||||
case CommandTypeProto.COMMAND_TYPE_ORGANIZE_TROOPS => Some(CommandType.OrganizeTroops)
|
||||
case CommandTypeProto.COMMAND_TYPE_PLEASE_RECRUIT_ME => Some(CommandType.PleaseRecruitMe)
|
||||
case CommandTypeProto.COMMAND_TYPE_RECON => Some(CommandType.Recon)
|
||||
case CommandTypeProto.COMMAND_TYPE_RECRUIT_HEROES => Some(CommandType.RecruitHeroes)
|
||||
case CommandTypeProto.COMMAND_TYPE_RESOLVE_ALLIANCE_OFFER => Some(CommandType.ResolveAllianceOffer)
|
||||
case CommandTypeProto.COMMAND_TYPE_RESOLVE_BREAK_ALLIANCE => Some(CommandType.ResolveBreakAlliance)
|
||||
case CommandTypeProto.COMMAND_TYPE_RESOLVE_INVITATION => Some(CommandType.ResolveInvitation)
|
||||
case CommandTypeProto.COMMAND_TYPE_RESOLVE_RANSOM_OFFER => Some(CommandType.ResolveRansomOffer)
|
||||
case CommandTypeProto.COMMAND_TYPE_RESOLVE_TRUCE_OFFER => Some(CommandType.ResolveTruceOffer)
|
||||
case CommandTypeProto.COMMAND_TYPE_RESOLVE_TRIBUTE => Some(CommandType.ResolveTribute)
|
||||
case CommandTypeProto.COMMAND_TYPE_REST => Some(CommandType.Rest)
|
||||
case CommandTypeProto.COMMAND_TYPE_RETURN => Some(CommandType.Return)
|
||||
case CommandTypeProto.COMMAND_TYPE_SEND_SUPPLIES => Some(CommandType.SendSupplies)
|
||||
case CommandTypeProto.COMMAND_TYPE_START_EPIDEMIC => Some(CommandType.StartEpidemic)
|
||||
case CommandTypeProto.COMMAND_TYPE_SUPPRESS_BEASTS => Some(CommandType.SuppressBeasts)
|
||||
case CommandTypeProto.COMMAND_TYPE_SWEAR_BROTHERHOOD => Some(CommandType.SwearBrotherhood)
|
||||
case CommandTypeProto.COMMAND_TYPE_TRADE => Some(CommandType.Trade)
|
||||
case CommandTypeProto.COMMAND_TYPE_TRAIN => Some(CommandType.Train)
|
||||
case CommandTypeProto.COMMAND_TYPE_TRAVEL => Some(CommandType.Travel)
|
||||
case CommandTypeProto.COMMAND_TYPE_UNKNOWN => None
|
||||
case CommandTypeProto.Unrecognized(_) =>
|
||||
throw ProtoConversionException(s"Unrecognized command type: $commandTypeProto")
|
||||
end CommandTypeConverter
|
||||
+4
-1
@@ -7,6 +7,9 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
":available_command_converter",
|
||||
],
|
||||
deps = [
|
||||
":available_command_converter",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:command_scala_proto",
|
||||
@@ -20,7 +23,7 @@ scala_library(
|
||||
name = "available_command_converter",
|
||||
srcs = ["AvailableCommandConverter.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/service:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl/action:__pkg__",
|
||||
"//src/test/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
|
||||
@@ -4,10 +4,16 @@ scala_library(
|
||||
name = "selected_command_converter",
|
||||
srcs = ["SelectedCommandConverter.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/service:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:armed_battalion_scala_proto",
|
||||
|
||||
@@ -23,12 +23,14 @@ scala_library(
|
||||
deps = [
|
||||
":event",
|
||||
":order_type",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:command_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:province_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:army_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_id_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battle_revelation_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:captured_hero_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:command_type_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:deferred_change_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:improvement_type_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:incoming_end_turn_action_converter",
|
||||
@@ -36,6 +38,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:supplies_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:unaffiliated_hero_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:orders",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
|
||||
|
||||
+9
-6
@@ -1,9 +1,8 @@
|
||||
package net.eagle0.eagle.model.proto_converters.province
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand as SelectedCommandProto
|
||||
import net.eagle0.eagle.common.battalion_type.BattalionTypeId as BattalionTypeIdProto
|
||||
import net.eagle0.eagle.common.command_type.command_type.CommandType as CommandTypeProto
|
||||
import net.eagle0.eagle.common.date.Date as DateProto
|
||||
import net.eagle0.eagle.common.improvement_type.ImprovementType as ImprovementTypeProto
|
||||
import net.eagle0.eagle.common.province_event.ProvinceEvent as ProvinceEventProto
|
||||
@@ -30,6 +29,7 @@ import net.eagle0.eagle.model.proto_converters.{
|
||||
BattalionTypeIdConverter,
|
||||
BattleRevelationConverter,
|
||||
CapturedHeroConverter,
|
||||
CommandTypeConverter,
|
||||
DeferredChangeConverter,
|
||||
ImprovementTypeConverter,
|
||||
IncomingEndTurnActionConverter,
|
||||
@@ -48,6 +48,7 @@ import net.eagle0.eagle.model.state.{
|
||||
MovingArmy,
|
||||
MovingSupplies
|
||||
}
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.province.{
|
||||
DeferredChangeT,
|
||||
@@ -100,7 +101,7 @@ object ProvinceConverter {
|
||||
hexMapName: String,
|
||||
castleCount: Int,
|
||||
heroCap: Int,
|
||||
lastCommand: Option[SelectedCommand]
|
||||
lastCommand: Option[CommandType]
|
||||
) =>
|
||||
ProvinceProto(
|
||||
id = id,
|
||||
@@ -131,7 +132,9 @@ object ProvinceConverter {
|
||||
activeEvents = activeEvents.map(ProvinceEventConverter.toProto),
|
||||
lastBeastsDate = lastBeastsDate.map(DateConverter.toProto),
|
||||
lastRiotDate = lastRiotDate.map(DateConverter.toProto),
|
||||
lastCommand = lastCommand.getOrElse(SelectedCommand.Empty),
|
||||
lastCommand = lastCommand
|
||||
.map(CommandTypeConverter.toProto)
|
||||
.getOrElse(CommandTypeProto.COMMAND_TYPE_UNKNOWN),
|
||||
incomingArmies = incomingArmies.map(ArmyConverter.toProto),
|
||||
hostileArmies = hostileArmies.map(ArmyConverter.toProto),
|
||||
defendingArmy = defendingArmy.map(ArmyConverter.toProto),
|
||||
@@ -182,7 +185,7 @@ object ProvinceConverter {
|
||||
activeEvents: Seq[ProvinceEventProto],
|
||||
lastBeastsDate: Option[DateProto],
|
||||
lastRiotDate: Option[DateProto],
|
||||
lastCommand: SelectedCommandProto,
|
||||
lastCommand: CommandTypeProto,
|
||||
castleCount: Int,
|
||||
heroCap: Int,
|
||||
deferredChanges: Seq[DeferredChangeProto],
|
||||
@@ -220,7 +223,7 @@ object ProvinceConverter {
|
||||
activeEvents = activeEvents.map(ProvinceEventConverter.fromProto).toVector,
|
||||
lastBeastsDate = lastBeastsDate.map(date => DateConverter.fromProto(Some(date))),
|
||||
lastRiotDate = lastRiotDate.map(date => DateConverter.fromProto(Some(date))),
|
||||
lastCommand = Option.when(!lastCommand.isEmpty)(lastCommand),
|
||||
lastCommand = CommandTypeConverter.fromProto(lastCommand),
|
||||
incomingArmies = incomingArmies.map(ArmyConverter.fromProto).toVector,
|
||||
hostileArmies = hostileArmies.map(ArmyConverter.fromProto).toVector,
|
||||
defendingArmy = defendingArmy.map(ArmyConverter.fromProto),
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
load("@rules_scala//scala:scala.bzl", "scala_library")
|
||||
|
||||
scala_library(
|
||||
name = "command_type",
|
||||
srcs = ["CommandType.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle:__subpackages__",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
package net.eagle0.eagle.model.state.command
|
||||
|
||||
/**
|
||||
* Enum representing the type of a command, without any command-specific data.
|
||||
*
|
||||
* This is used to track which command type was last executed on a province, without needing to serialize the full
|
||||
* SelectedCommand with all its parameters.
|
||||
*
|
||||
* Both SelectedCommand and AvailableCommand have a `toCommandType` method that converts to this enum using exhaustive
|
||||
* pattern matching. Adding a new command variant without updating the `toCommandType` method will cause a compile
|
||||
* error.
|
||||
*/
|
||||
enum CommandType:
|
||||
case Alms
|
||||
case ApprehendOutlaw
|
||||
case ArmTroops
|
||||
case AttackDecision
|
||||
case ControlWeather
|
||||
case DeclineQuest
|
||||
case Defend
|
||||
case Diplomacy
|
||||
case Divine
|
||||
case ExileVassal
|
||||
case Feast
|
||||
case FreeForAllDecision
|
||||
case HandleCapturedHero
|
||||
case HandleRiotCrackDown
|
||||
case HandleRiotDoNothing
|
||||
case HandleRiotGive
|
||||
case HeroGift
|
||||
case Improve
|
||||
case IssueOrders
|
||||
case ManagePrisoners
|
||||
case March
|
||||
case OrganizeTroops
|
||||
case PleaseRecruitMe
|
||||
case Recon
|
||||
case RecruitHeroes
|
||||
case ResolveAllianceOffer
|
||||
case ResolveBreakAlliance
|
||||
case ResolveInvitation
|
||||
case ResolveRansomOffer
|
||||
case ResolveTruceOffer
|
||||
case ResolveTribute
|
||||
case Rest
|
||||
case Return
|
||||
case SendSupplies
|
||||
case StartEpidemic
|
||||
case SuppressBeasts
|
||||
case SwearBrotherhood
|
||||
case Trade
|
||||
case Train
|
||||
case Travel
|
||||
end CommandType
|
||||
+48
-36
@@ -3,44 +3,50 @@ package net.eagle0.eagle.model.state.command.available
|
||||
import net.eagle0.common.hostility.Hostility
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.command.common.*
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.diplomacy_offer.status.Status
|
||||
import AvailableCommand.*
|
||||
|
||||
/** Scala 3 enum representing available command options for a player. */
|
||||
enum AvailableCommand:
|
||||
/**
|
||||
* Scala 3 enum representing available command options for a player.
|
||||
*
|
||||
* Each case must provide a CommandType via the enum parameter, ensuring compile-time safety: adding a new command
|
||||
* variant without specifying its CommandType will cause a compile error.
|
||||
*/
|
||||
enum AvailableCommand(val commandType: CommandType):
|
||||
case AlmsAvailable(
|
||||
foodAvailable: Int,
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.Alms)
|
||||
case ApprehendOutlawAvailable(
|
||||
outlaws: Vector[ResidentOutlaw],
|
||||
availableBattalionIds: Vector[BattalionId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.ApprehendOutlaw)
|
||||
case ArmTroopsAvailable(
|
||||
armamentCosts: Vector[ArmamentCost],
|
||||
availableBattalions: Vector[BattalionId],
|
||||
maxArmament: Int,
|
||||
actingProvinceId: ProvinceId,
|
||||
availableBattalionTypeIds: Vector[Int]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.ArmTroops)
|
||||
case AttackDecisionAvailable(
|
||||
armies: Vector[ArmyStats],
|
||||
actingUnits: Vector[ExpandedCombatUnit],
|
||||
availableDecisions: Vector[AttackDecisionType],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.AttackDecision)
|
||||
case ControlWeatherAvailable(
|
||||
options: Vector[TargetProvinceOptions],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.ControlWeather)
|
||||
case DeclineQuestAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
declinableHeroes: Vector[ExpandedUnaffiliatedHero]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.DeclineQuest)
|
||||
case DefendAvailable(
|
||||
suitableBattalionsForHeroes: Map[HeroId, SuitableBattalions],
|
||||
availableFleeProvinceIds: Vector[ProvinceId],
|
||||
@@ -51,138 +57,144 @@ enum AvailableCommand:
|
||||
hostileFactionIds: Vector[FactionId],
|
||||
hostileHeroCount: Int,
|
||||
hostileTroopCount: Int
|
||||
)
|
||||
) extends AvailableCommand(CommandType.Defend)
|
||||
case DiplomacyAvailable(
|
||||
options: Vector[DiplomacyOption],
|
||||
availableHeroIds: Vector[HeroId],
|
||||
actingProvinceId: ProvinceId,
|
||||
recommendedHeroId: HeroId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.Diplomacy)
|
||||
case DivineAvailable(
|
||||
costPerHero: Int,
|
||||
divinableHeroes: Vector[ExpandedUnaffiliatedHero],
|
||||
unavailableHeroes: Vector[ExpandedUnaffiliatedHero],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.Divine)
|
||||
case ExileVassalAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
exilableHeroIds: Vector[HeroId]
|
||||
)
|
||||
case FeastAvailable(goldCost: Int, actingProvinceId: ProvinceId)
|
||||
) extends AvailableCommand(CommandType.ExileVassal)
|
||||
case FeastAvailable(goldCost: Int, actingProvinceId: ProvinceId) extends AvailableCommand(CommandType.Feast)
|
||||
case FreeForAllDecisionAvailable(
|
||||
provinceId: ProvinceId,
|
||||
armies: Vector[ArmyStats],
|
||||
actingUnits: Vector[ExpandedCombatUnit],
|
||||
availableDecisions: Vector[AttackDecisionType]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.FreeForAllDecision)
|
||||
case HandleCapturedHeroAvailable(
|
||||
availableHeroes: Vector[ExpandedCapturedHero],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.HandleCapturedHero)
|
||||
case HandleRiotCrackDownAvailable(
|
||||
availableHeroIds: Vector[HeroId],
|
||||
battalionIdsAvailable: Vector[BattalionId],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.HandleRiotCrackDown)
|
||||
case HandleRiotDoNothingAvailable(actingProvinceId: ProvinceId)
|
||||
extends AvailableCommand(CommandType.HandleRiotDoNothing)
|
||||
case HandleRiotGiveAvailable(
|
||||
foodAvailable: Int,
|
||||
goldAvailable: Int,
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.HandleRiotGive)
|
||||
case HeroGiftAvailable(
|
||||
eligibleGifts: Vector[EligibleGift],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.HeroGift)
|
||||
case ImproveAvailable(
|
||||
availableTypes: Vector[ImprovementType],
|
||||
availableHeroIds: Vector[HeroId],
|
||||
recommendedHeroId: HeroId,
|
||||
actingProvinceId: ProvinceId,
|
||||
lockedType: Option[ImprovementType]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.Improve)
|
||||
case IssueOrdersAvailable(
|
||||
currentOrders: Vector[ProvinceOrders],
|
||||
availableOrders: Vector[ProvinceOrderType],
|
||||
availableFocusProvinces: Vector[ProvinceId],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.IssueOrders)
|
||||
case ManagePrisonersAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
prisoners: Vector[PrisonerToManage]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.ManagePrisoners)
|
||||
case MarchAvailable(
|
||||
oneProvinceCommands: Vector[MarchCommandFromOneProvince],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableTypes: Vector[BattalionType]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.March)
|
||||
case OrganizeTroopsAvailable(
|
||||
troopCosts: Vector[TroopCost],
|
||||
existingBattalions: Vector[BattalionId],
|
||||
maxBattalions: Int,
|
||||
actingProvinceId: ProvinceId,
|
||||
availableBattalionTypes: Vector[BattalionTypeStatus]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.OrganizeTroops)
|
||||
case PleaseRecruitMeAvailable(
|
||||
availableProvinces: Vector[OneProvincePleaseRecruitMe],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.PleaseRecruitMe)
|
||||
case ReconAvailable(
|
||||
availableTargetProvinces: Vector[ProvinceId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.Recon)
|
||||
case RecruitHeroesAvailable(
|
||||
availableHeroes: Vector[ExpandedUnaffiliatedHero],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.RecruitHeroes)
|
||||
case ResolveAllianceOfferAvailable(offers: Vector[DiplomacyOfferInfo])
|
||||
extends AvailableCommand(CommandType.ResolveAllianceOffer)
|
||||
case ResolveBreakAllianceAvailable(offers: Vector[DiplomacyOfferInfo])
|
||||
extends AvailableCommand(CommandType.ResolveBreakAlliance)
|
||||
case ResolveInvitationAvailable(invitations: Vector[DiplomacyOfferInfo])
|
||||
extends AvailableCommand(CommandType.ResolveInvitation)
|
||||
case ResolveRansomOfferAvailable(offers: Vector[DiplomacyOfferInfo])
|
||||
extends AvailableCommand(CommandType.ResolveRansomOffer)
|
||||
case ResolveTruceOfferAvailable(offers: Vector[DiplomacyOfferInfo])
|
||||
extends AvailableCommand(CommandType.ResolveTruceOffer)
|
||||
case ResolveTributeAvailable(
|
||||
demands: Vector[TributeAndFaction],
|
||||
availableGold: Int,
|
||||
availableFood: Int,
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case RestAvailable(actingProvinceId: ProvinceId)
|
||||
case ReturnAvailable(actingProvinceId: ProvinceId)
|
||||
) extends AvailableCommand(CommandType.ResolveTribute)
|
||||
case RestAvailable(actingProvinceId: ProvinceId) extends AvailableCommand(CommandType.Rest)
|
||||
case ReturnAvailable(actingProvinceId: ProvinceId) extends AvailableCommand(CommandType.Return)
|
||||
case SendSuppliesAvailable(
|
||||
goldAvailable: Int,
|
||||
foodAvailable: Int,
|
||||
availableDestinationProvinceIds: Vector[ProvinceId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.SendSupplies)
|
||||
case StartEpidemicAvailable(
|
||||
options: Vector[StartEpidemicOptions],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.StartEpidemic)
|
||||
case SuppressBeastsAvailable(
|
||||
availableHeroIds: Vector[HeroId],
|
||||
availableBattalionIds: Vector[BattalionId],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.SuppressBeasts)
|
||||
case SwearBrotherhoodAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroes: Vector[HeroAndBackstory]
|
||||
)
|
||||
) extends AvailableCommand(CommandType.SwearBrotherhood)
|
||||
case TradeAvailable(
|
||||
foodAvailable: Int,
|
||||
goldAvailable: Int,
|
||||
foodBuyPrice: Double,
|
||||
foodSellPrice: Double,
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
) extends AvailableCommand(CommandType.Trade)
|
||||
case TrainAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId],
|
||||
recommendedHeroId: HeroId
|
||||
)
|
||||
case TravelAvailable(actingProvinceId: ProvinceId)
|
||||
) extends AvailableCommand(CommandType.Train)
|
||||
case TravelAvailable(actingProvinceId: ProvinceId) extends AvailableCommand(CommandType.Travel)
|
||||
end AvailableCommand
|
||||
|
||||
/** Supporting types for AvailableCommand. */
|
||||
|
||||
@@ -9,20 +9,21 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/ai:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/command:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:__pkg__",
|
||||
"//src/test/scala/net/eagle0/eagle/ai:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/model/state/command:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/protobuf/net/eagle0/common:hostility_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/common:hostility_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/common",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer/status",
|
||||
],
|
||||
|
||||
@@ -4,13 +4,18 @@ scala_library(
|
||||
name = "selected",
|
||||
srcs = ["SelectedCommand.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/command:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/common",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer/status",
|
||||
],
|
||||
|
||||
+48
-29
@@ -2,87 +2,106 @@ package net.eagle0.eagle.model.state.command.selected
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.command.common.*
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.diplomacy_offer.status.Status
|
||||
|
||||
/** Scala 3 enum representing a player's selected command. */
|
||||
enum SelectedCommand:
|
||||
case AlmsSelected(amount: Int, actingHeroId: HeroId)
|
||||
/**
|
||||
* Scala 3 enum representing a player's selected command.
|
||||
*
|
||||
* Each case must provide a CommandType via the enum parameter, ensuring compile-time safety: adding a new command
|
||||
* variant without specifying its CommandType will cause a compile error.
|
||||
*/
|
||||
enum SelectedCommand(val commandType: CommandType):
|
||||
case AlmsSelected(amount: Int, actingHeroId: HeroId) extends SelectedCommand(CommandType.Alms)
|
||||
case ApprehendOutlawSelected(
|
||||
heroIdToApprehend: HeroId,
|
||||
battalionId: Option[BattalionId],
|
||||
actingHeroId: HeroId
|
||||
)
|
||||
case ArmTroopsSelected(armedBattalions: Vector[ArmedBattalion])
|
||||
case AttackDecisionSelected(decision: AttackDecisionType)
|
||||
) extends SelectedCommand(CommandType.ApprehendOutlaw)
|
||||
case ArmTroopsSelected(armedBattalions: Vector[ArmedBattalion]) extends SelectedCommand(CommandType.ArmTroops)
|
||||
case AttackDecisionSelected(decision: AttackDecisionType) extends SelectedCommand(CommandType.AttackDecision)
|
||||
case ControlWeatherSelected(
|
||||
selectedType: ControlWeatherType,
|
||||
selectedProvinceId: ProvinceId,
|
||||
actingHeroId: HeroId
|
||||
)
|
||||
case DeclineQuestSelected(heroId: HeroId)
|
||||
) extends SelectedCommand(CommandType.ControlWeather)
|
||||
case DeclineQuestSelected(heroId: HeroId) extends SelectedCommand(CommandType.DeclineQuest)
|
||||
case DefendSelected(
|
||||
fleeProvinceId: Option[ProvinceId],
|
||||
defendingUnits: Vector[CombatUnit]
|
||||
)
|
||||
) extends SelectedCommand(CommandType.Defend)
|
||||
case DiplomacySelected(
|
||||
selectedOption: DiplomacyOptionType,
|
||||
targetFactionId: FactionId,
|
||||
sentHeroId: HeroId
|
||||
)
|
||||
case DivineSelected(heroIds: Vector[HeroId])
|
||||
case ExileVassalSelected(exiledHeroId: HeroId)
|
||||
case FeastSelected
|
||||
) extends SelectedCommand(CommandType.Diplomacy)
|
||||
case DivineSelected(heroIds: Vector[HeroId]) extends SelectedCommand(CommandType.Divine)
|
||||
case ExileVassalSelected(exiledHeroId: HeroId) extends SelectedCommand(CommandType.ExileVassal)
|
||||
case FeastSelected extends SelectedCommand(CommandType.Feast)
|
||||
case FreeForAllDecisionSelected(provinceId: ProvinceId, decision: AttackDecisionType)
|
||||
extends SelectedCommand(CommandType.FreeForAllDecision)
|
||||
case HandleCapturedHeroSelected(heroId: HeroId, selectedOption: CapturedHeroOption)
|
||||
extends SelectedCommand(CommandType.HandleCapturedHero)
|
||||
case HandleRiotCrackDownSelected(
|
||||
heroId: HeroId,
|
||||
battalionId: Option[BattalionId],
|
||||
actingHeroId: HeroId
|
||||
)
|
||||
case HandleRiotDoNothingSelected
|
||||
case HandleRiotGiveSelected(foodAmount: Int, goldAmount: Int)
|
||||
case HeroGiftSelected(recipientHeroId: HeroId, amount: Int)
|
||||
) extends SelectedCommand(CommandType.HandleRiotCrackDown)
|
||||
case HandleRiotDoNothingSelected extends SelectedCommand(CommandType.HandleRiotDoNothing)
|
||||
case HandleRiotGiveSelected(foodAmount: Int, goldAmount: Int) extends SelectedCommand(CommandType.HandleRiotGive)
|
||||
case HeroGiftSelected(recipientHeroId: HeroId, amount: Int) extends SelectedCommand(CommandType.HeroGift)
|
||||
case ImproveSelected(improvementType: ImprovementType, actingHeroId: HeroId, lockType: Boolean)
|
||||
extends SelectedCommand(CommandType.Improve)
|
||||
case IssueOrdersSelected(newOrders: Vector[ProvinceOrder], newFocusProvince: Option[ProvinceId])
|
||||
extends SelectedCommand(CommandType.IssueOrders)
|
||||
case ManagePrisonersSelected(prisonerHeroId: HeroId, chosenOption: PrisonerManagementOption)
|
||||
extends SelectedCommand(CommandType.ManagePrisoners)
|
||||
case MarchSelected(
|
||||
gold: Int,
|
||||
food: Int,
|
||||
originProvince: ProvinceId,
|
||||
destinationProvinceId: ProvinceId,
|
||||
marchingUnits: Vector[CombatUnit]
|
||||
)
|
||||
) extends SelectedCommand(CommandType.March)
|
||||
case OrganizeTroopsSelected(
|
||||
changedBattalions: Vector[ChangedBattalion],
|
||||
newBattalions: Vector[NewBattalion]
|
||||
)
|
||||
) extends SelectedCommand(CommandType.OrganizeTroops)
|
||||
case PleaseRecruitMeSelected(provinceId: ProvinceId, heroId: HeroId, accept: Boolean)
|
||||
case ReconSelected(actingHeroId: HeroId, targetProvinceId: ProvinceId)
|
||||
case RecruitHeroesSelected(heroIds: Vector[HeroId])
|
||||
extends SelectedCommand(CommandType.PleaseRecruitMe)
|
||||
case ReconSelected(actingHeroId: HeroId, targetProvinceId: ProvinceId) extends SelectedCommand(CommandType.Recon)
|
||||
case RecruitHeroesSelected(heroIds: Vector[HeroId]) extends SelectedCommand(CommandType.RecruitHeroes)
|
||||
case ResolveAllianceOfferSelected(originatingFactionId: FactionId, resolution: Status)
|
||||
extends SelectedCommand(CommandType.ResolveAllianceOffer)
|
||||
case ResolveBreakAllianceSelected(originatingFactionId: FactionId, resolution: Status)
|
||||
extends SelectedCommand(CommandType.ResolveBreakAlliance)
|
||||
case ResolveInvitationSelected(originatingFactionId: FactionId, resolution: Status)
|
||||
extends SelectedCommand(CommandType.ResolveInvitation)
|
||||
case ResolveRansomOfferSelected(
|
||||
offeringFactionId: FactionId,
|
||||
prisonerHeroId: HeroId,
|
||||
resolution: Status
|
||||
)
|
||||
) extends SelectedCommand(CommandType.ResolveRansomOffer)
|
||||
case ResolveTruceOfferSelected(originatingFactionId: FactionId, resolution: Status)
|
||||
extends SelectedCommand(CommandType.ResolveTruceOffer)
|
||||
case ResolveTributeSelected(demandingFactionId: FactionId, paid: Boolean)
|
||||
case RestSelected
|
||||
case ReturnSelected
|
||||
extends SelectedCommand(CommandType.ResolveTribute)
|
||||
case RestSelected extends SelectedCommand(CommandType.Rest)
|
||||
case ReturnSelected extends SelectedCommand(CommandType.Return)
|
||||
case SendSuppliesSelected(
|
||||
gold: Int,
|
||||
food: Int,
|
||||
actingHeroId: HeroId,
|
||||
destinationProvinceId: ProvinceId
|
||||
)
|
||||
) extends SelectedCommand(CommandType.SendSupplies)
|
||||
case StartEpidemicSelected(selectedProvinceId: ProvinceId, actingHeroId: HeroId)
|
||||
extends SelectedCommand(CommandType.StartEpidemic)
|
||||
case SuppressBeastsSelected(heroId: HeroId, battalionId: Option[BattalionId])
|
||||
case SwearBrotherhoodSelected(newBrotherHeroId: HeroId)
|
||||
case TradeSelected(tradeType: TradeType, amount: Int)
|
||||
case TrainSelected(actingHeroId: HeroId)
|
||||
case TravelSelected
|
||||
extends SelectedCommand(CommandType.SuppressBeasts)
|
||||
case SwearBrotherhoodSelected(newBrotherHeroId: HeroId) extends SelectedCommand(CommandType.SwearBrotherhood)
|
||||
case TradeSelected(tradeType: TradeType, amount: Int) extends SelectedCommand(CommandType.Trade)
|
||||
case TrainSelected(actingHeroId: HeroId) extends SelectedCommand(CommandType.Train)
|
||||
case TravelSelected extends SelectedCommand(CommandType.Travel)
|
||||
end SelectedCommand
|
||||
|
||||
// Supporting enums specific to SelectedCommand
|
||||
|
||||
@@ -65,13 +65,13 @@ scala_library(
|
||||
":event",
|
||||
":incoming_end_turn_action",
|
||||
":orders",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type_id",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battle_revelation",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:captured_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:improvement_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.eagle0.eagle.model.state.province
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.model.state.{
|
||||
Army,
|
||||
BattalionTypeId,
|
||||
@@ -12,6 +11,7 @@ import net.eagle0.eagle.model.state.{
|
||||
MovingArmy,
|
||||
MovingSupplies
|
||||
}
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroT
|
||||
|
||||
@@ -87,7 +87,7 @@ trait ProvinceT {
|
||||
def castleCount: Int
|
||||
def heroCap: Int
|
||||
|
||||
def lastCommand: Option[SelectedCommand]
|
||||
def lastCommand: Option[CommandType]
|
||||
|
||||
def updateWith(
|
||||
name: String = name,
|
||||
@@ -126,7 +126,7 @@ trait ProvinceT {
|
||||
hexMapName: String = hexMapName,
|
||||
castleCount: Int = castleCount,
|
||||
heroCap: Int = heroCap,
|
||||
lastCommand: Option[SelectedCommand] = lastCommand
|
||||
lastCommand: Option[CommandType] = lastCommand
|
||||
): ProvinceT
|
||||
|
||||
def withRulingFactionId(rulingFactionId: FactionId): ProvinceT =
|
||||
|
||||
@@ -21,12 +21,12 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:orders",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battle_revelation",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:captured_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:improvement_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:deferred_change_trait",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.eagle0.eagle.model.state.province.concrete
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.model.state.{
|
||||
Army,
|
||||
BattalionTypeId,
|
||||
@@ -12,6 +11,7 @@ import net.eagle0.eagle.model.state.{
|
||||
MovingArmy,
|
||||
MovingSupplies
|
||||
}
|
||||
import net.eagle0.eagle.model.state.command.CommandType
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.province.{
|
||||
DeferredChangeT,
|
||||
@@ -62,7 +62,7 @@ case class ProvinceC(
|
||||
hexMapName: String = "",
|
||||
castleCount: Int = 0,
|
||||
heroCap: Int = 0,
|
||||
lastCommand: Option[SelectedCommand] = None
|
||||
lastCommand: Option[CommandType] = None
|
||||
) extends ProvinceT {
|
||||
override def updateWith(
|
||||
name: String = name,
|
||||
@@ -101,7 +101,7 @@ case class ProvinceC(
|
||||
hexMapName: String = hexMapName,
|
||||
castleCount: Int = castleCount,
|
||||
heroCap: Int = heroCap,
|
||||
lastCommand: Option[SelectedCommand] = lastCommand
|
||||
lastCommand: Option[CommandType] = lastCommand
|
||||
): ProvinceT =
|
||||
ProvinceC(
|
||||
id = this.id,
|
||||
|
||||
@@ -51,12 +51,19 @@ class AuthorizationInterceptor(jwtService: Option[JwtService] = None) extends Se
|
||||
Option(headers.get(IMPERSONATE_USER_KEY))
|
||||
.filter(_.nonEmpty)
|
||||
|
||||
private def isLocalhost[ReqT, RespT](call: ServerCall[ReqT, RespT]): Boolean = {
|
||||
// Check if the connection is from a trusted local source (localhost or Docker bridge network).
|
||||
// This is used to allow warmup/testing without JWT authentication.
|
||||
private def isLocalOrDocker[ReqT, RespT](call: ServerCall[ReqT, RespT]): Boolean = {
|
||||
val remoteAddr = call.getAttributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR)
|
||||
remoteAddr match {
|
||||
case inet: InetSocketAddress =>
|
||||
val addr = inet.getAddress
|
||||
addr != null && (addr.isLoopbackAddress || addr.getHostAddress == "127.0.0.1" || addr.getHostAddress == "::1")
|
||||
addr != null && (
|
||||
addr.isLoopbackAddress || // 127.0.0.1, ::1
|
||||
addr.getHostAddress == "127.0.0.1" ||
|
||||
addr.getHostAddress == "::1" ||
|
||||
addr.isSiteLocalAddress // 10.x.x.x, 172.16-31.x.x, 192.168.x.x (Docker bridge networks)
|
||||
)
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
@@ -116,10 +123,10 @@ class AuthorizationInterceptor(jwtService: Option[JwtService] = None) extends Se
|
||||
Contexts.interceptCall(effectiveContext, call, headers, next)
|
||||
|
||||
case None =>
|
||||
// No valid JWT token - check for warmup user header (localhost only)
|
||||
// No valid JWT token - check for warmup user header (localhost or Docker bridge only)
|
||||
Option(headers.get(WARMUP_USER_KEY)).filter(_.nonEmpty) match {
|
||||
case Some(warmupUser) if isLocalhost(call) =>
|
||||
// Warmup/test user from localhost - set a simple context without JWT
|
||||
case Some(warmupUser) if isLocalOrDocker(call) =>
|
||||
// Warmup/test user from localhost or Docker bridge - set a simple context without JWT
|
||||
val warmupContext = AuthorizationUtils.contextWithJwtClaims(
|
||||
userId = s"warmup-$warmupUser",
|
||||
displayName = warmupUser,
|
||||
@@ -127,10 +134,10 @@ class AuthorizationInterceptor(jwtService: Option[JwtService] = None) extends Se
|
||||
accessToken = ""
|
||||
)
|
||||
Contexts.interceptCall(warmupContext, call, headers, next)
|
||||
case Some(_) =>
|
||||
// X-Warmup-User from non-localhost - ignore it, treat as unauthenticated
|
||||
case Some(_) =>
|
||||
// X-Warmup-User from non-local network - ignore it, treat as unauthenticated
|
||||
next.startCall(call, headers)
|
||||
case None =>
|
||||
case None =>
|
||||
// No auth at all - let endpoint handle unauthenticated request
|
||||
next.startCall(call, headers)
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ class EagleServiceImpl(
|
||||
case UniversalCommand.Empty =>
|
||||
throw new EagleClientException("Must include a command")
|
||||
}
|
||||
}.map(_ => PostCommandResponse())
|
||||
}.map(_ => PostCommandResponse(status = PostCommandResponse.Status.SUCCESS))
|
||||
|
||||
private class SyncStreamObserver(
|
||||
responseObserver: SyncResponseObserver
|
||||
@@ -626,14 +626,14 @@ class EagleServiceImpl(
|
||||
|
||||
val players = gameState.factions.map {
|
||||
case (factionId, faction) =>
|
||||
val leader = gameState.heroes(faction.factionHeadId)
|
||||
val leader = gameState.heroes.get(faction.factionHeadId)
|
||||
val userName = controller.userNameToFactionId.find { case (_, fid) => fid == factionId }
|
||||
.map(_._1)
|
||||
|
||||
RunningGamePlayerInfo(
|
||||
factionId = factionId,
|
||||
factionName = faction.name,
|
||||
leaderName = leader.nameTextId,
|
||||
factionName = Option(faction.name).filter(_.nonEmpty).getOrElse("[MISSING FACTION NAME]"),
|
||||
leaderName = leader.map(_.nameTextId).flatMap(Option(_)).filter(_.nonEmpty).getOrElse("[MISSING LEADER]"),
|
||||
isHuman = userName.isDefined,
|
||||
userName = userName.getOrElse("")
|
||||
)
|
||||
|
||||
@@ -36,6 +36,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library:game_history",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/command/selected:selected_command_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/service:post_results",
|
||||
"//src/main/scala/net/eagle0/eagle/service:sync_response_observer",
|
||||
"//src/main/scala/net/eagle0/eagle/shardok_interface:battle_resolution",
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.eagle0.eagle.internal.llm_response.LLMResponse
|
||||
import net.eagle0.eagle.library.{EagleInternalException, Engine, EngineAndResults}
|
||||
import net.eagle0.eagle.library.util.CommandSelection
|
||||
import net.eagle0.eagle.library.util.EagleRequire.{clientRequire, internalRequire}
|
||||
import net.eagle0.eagle.model.proto_converters.command.selected.SelectedCommandConverter
|
||||
import net.eagle0.eagle.service.{LlmRequestAfterHistoryCount, PostResults, SyncResponseObserver}
|
||||
import net.eagle0.eagle.shardok_interface.{BattleResolution, BattleUpdate}
|
||||
|
||||
@@ -287,7 +288,7 @@ final case class GameController(
|
||||
.postCommand(
|
||||
factionId = factionId,
|
||||
selectedProvinceId = selectedProvinceId,
|
||||
selectedCommand = command
|
||||
selectedCommand = SelectedCommandConverter.fromProto(command)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ scala_library(
|
||||
":loaded_hero_conversion",
|
||||
":start_date",
|
||||
":start_game_action_result_utils",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:chronicle_entry_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:battalion_scala_proto",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user