mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 04:05:44 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad974357f4 | ||
|
|
d9dfb5cbb2 |
@@ -53,26 +53,26 @@ jobs:
|
||||
run: |
|
||||
# Upload sysroot tarball to DO Spaces
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_amd64_sysroot.tar.xz \
|
||||
s3://eagle0-sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz \
|
||||
s3://eagle0-windows/sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
# Upload sha256 file
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256 \
|
||||
s3://eagle0-sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.sha256 \
|
||||
s3://eagle0-windows/sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.sha256 \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
echo ""
|
||||
echo "=== AMD64 Sysroot uploaded ==="
|
||||
echo "URL: https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz"
|
||||
echo "URL: https://eagle0-windows.sfo3.digitaloceanspaces.com/sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz"
|
||||
echo "SHA256: $(cat tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256)"
|
||||
echo ""
|
||||
echo "Update MODULE.bazel with:"
|
||||
echo "sysroot("
|
||||
echo " name = \"linux_sysroot\","
|
||||
echo " sha256 = \"$(cat tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256)\","
|
||||
echo " urls = [\"https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz\"],"
|
||||
echo " urls = [\"https://eagle0-windows.sfo3.digitaloceanspaces.com/sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz\"],"
|
||||
echo ")"
|
||||
|
||||
build-sysroot-arm64:
|
||||
@@ -114,24 +114,24 @@ jobs:
|
||||
run: |
|
||||
# Upload sysroot tarball to DO Spaces
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_arm64_sysroot.tar.xz \
|
||||
s3://eagle0-sysroot/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.tar.xz \
|
||||
s3://eagle0-windows/sysroot/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.tar.xz \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
# Upload sha256 file
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_arm64_sysroot.sha256 \
|
||||
s3://eagle0-sysroot/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.sha256 \
|
||||
s3://eagle0-windows/sysroot/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.sha256 \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
echo ""
|
||||
echo "=== ARM64 Sysroot uploaded ==="
|
||||
echo "URL: https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.tar.xz"
|
||||
echo "URL: https://eagle0-windows.sfo3.digitaloceanspaces.com/sysroot/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.tar.xz"
|
||||
echo "SHA256: $(cat tools/sysroot/output/ubuntu_noble_arm64_sysroot.sha256)"
|
||||
echo ""
|
||||
echo "Update MODULE.bazel with:"
|
||||
echo "sysroot("
|
||||
echo " name = \"linux_sysroot_arm64\","
|
||||
echo " sha256 = \"$(cat tools/sysroot/output/ubuntu_noble_arm64_sysroot.sha256)\","
|
||||
echo " urls = [\"https://eagle0-sysroot.sfo3.digitaloceanspaces.com/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.tar.xz\"],"
|
||||
echo " urls = [\"https://eagle0-windows.sfo3.digitaloceanspaces.com/sysroot/${{ inputs.version }}/ubuntu_noble_arm64_sysroot.tar.xz\"],"
|
||||
echo ")"
|
||||
|
||||
@@ -40,15 +40,13 @@ jobs:
|
||||
echo ""
|
||||
|
||||
# List of repositories to clean
|
||||
# Use tail to skip header row in case --no-header doesn't work
|
||||
REPOS=$(doctl registry repository list-v2 --format Name --no-header | grep -v '^Name$' | grep -v '^$')
|
||||
REPOS=$(doctl registry repository list-v2 --format Name --no-header)
|
||||
|
||||
for REPO in $REPOS; do
|
||||
echo "=== Processing repository: ${REPO} ==="
|
||||
|
||||
# Get all manifests with their tags and dates
|
||||
# Filter out header row and empty lines
|
||||
MANIFESTS=$(doctl registry repository list-manifests "${REPO}" --format Digest,UpdatedAt,Tags --no-header 2>/dev/null | grep -v '^Digest' | grep -v '^$' || echo "")
|
||||
MANIFESTS=$(doctl registry repository list-manifests "${REPO}" --format Digest,UpdatedAt,Tags --no-header 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$MANIFESTS" ]; then
|
||||
echo " No manifests found"
|
||||
@@ -56,8 +54,8 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "$MANIFESTS" | while read -r DIGEST UPDATED_AT TAGS; do
|
||||
# Skip if no digest or if it doesn't look like a valid digest (sha256:...)
|
||||
if [ -z "$DIGEST" ] || ! echo "$DIGEST" | grep -q '^sha256:'; then
|
||||
# Skip if no digest
|
||||
if [ -z "$DIGEST" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
@@ -159,63 +159,51 @@ jobs:
|
||||
echo "Also tagged as: registry.digitalocean.com/eagle0/shardok-server:arm64-latest"
|
||||
|
||||
deploy-hetzner:
|
||||
runs-on: self-hosted
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-shardok-arm64]
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
environment: production
|
||||
env:
|
||||
SHARDOK_IMAGE: ${{ needs.build-shardok-arm64.outputs.image_tag }}
|
||||
steps:
|
||||
- name: Setup SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.HETZNER_SSH_KEY }}" > ~/.ssh/hetzner_deploy
|
||||
chmod 600 ~/.ssh/hetzner_deploy
|
||||
# Add host key to known_hosts to avoid prompt
|
||||
ssh-keyscan -H ${{ secrets.HETZNER_IP }} >> ~/.ssh/known_hosts 2>/dev/null || true
|
||||
|
||||
- name: Deploy to Hetzner
|
||||
run: |
|
||||
ssh -i ~/.ssh/hetzner_deploy -o StrictHostKeyChecking=accept-new deploy@${{ secrets.HETZNER_IP }} << 'ENDSSH'
|
||||
set -ex
|
||||
cd /opt/eagle0
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ secrets.HETZNER_IP }}
|
||||
username: deploy
|
||||
key: ${{ secrets.HETZNER_SSH_KEY }}
|
||||
protocol: tcp6
|
||||
script_stop: true
|
||||
envs: SHARDOK_IMAGE
|
||||
script: |
|
||||
set -ex
|
||||
cd /opt/eagle0
|
||||
|
||||
# Login to registry
|
||||
echo "${{ secrets.DO_REGISTRY_TOKEN }}" | docker login registry.digitalocean.com -u "${{ secrets.DO_REGISTRY_TOKEN }}" --password-stdin
|
||||
# Login to registry
|
||||
echo "${{ secrets.DO_REGISTRY_TOKEN }}" | docker login registry.digitalocean.com -u "${{ secrets.DO_REGISTRY_TOKEN }}" --password-stdin
|
||||
|
||||
echo "Deploying Shardok ARM64: ${{ needs.build-shardok-arm64.outputs.image_tag }}"
|
||||
echo "Deploying Shardok ARM64: $SHARDOK_IMAGE"
|
||||
|
||||
# Pull the new image
|
||||
docker pull "${{ needs.build-shardok-arm64.outputs.image_tag }}"
|
||||
# Pull the new image
|
||||
docker pull "$SHARDOK_IMAGE"
|
||||
|
||||
# Stop and remove any container using port 40042 or named shardok*
|
||||
docker ps -q --filter "publish=40042" | xargs -r docker stop
|
||||
docker ps -aq --filter "name=shardok" | xargs -r docker rm -f
|
||||
docker ps -aq --filter "publish=40042" | xargs -r docker rm -f
|
||||
# Stop and remove existing container
|
||||
docker stop shardok-ai 2>/dev/null || true
|
||||
docker rm shardok-ai 2>/dev/null || true
|
||||
|
||||
# Run new container
|
||||
docker run -d \
|
||||
--name shardok-ai \
|
||||
--restart unless-stopped \
|
||||
-p 40042:40042 \
|
||||
-v /opt/eagle0/data:/data \
|
||||
-v /etc/shardok:/etc/shardok:ro \
|
||||
-v /etc/letsencrypt:/etc/letsencrypt:ro \
|
||||
-v /usr/local/share/eagle0:/usr/local/share/eagle0:ro \
|
||||
-e SHARDOK_RESOURCES_PATH=/app/resources \
|
||||
-e SHARDOK_MAPS_PATH=/app/resources/maps \
|
||||
"${{ needs.build-shardok-arm64.outputs.image_tag }}"
|
||||
# Run new container
|
||||
docker run -d \
|
||||
--name shardok-ai \
|
||||
--restart unless-stopped \
|
||||
-p 40042:40042 \
|
||||
-v /opt/eagle0/data:/data \
|
||||
"$SHARDOK_IMAGE"
|
||||
|
||||
# Wait and verify
|
||||
sleep 5
|
||||
docker ps | grep shardok-ai
|
||||
# Wait and verify
|
||||
sleep 5
|
||||
docker ps | grep shardok-ai
|
||||
|
||||
# Cleanup old images
|
||||
docker image prune -f
|
||||
# Cleanup old images
|
||||
docker image prune -f
|
||||
|
||||
echo "=== Hetzner deployment complete ==="
|
||||
ENDSSH
|
||||
|
||||
- name: Cleanup SSH key
|
||||
if: always()
|
||||
run: rm -f ~/.ssh/hetzner_deploy
|
||||
echo "=== Hetzner deployment complete ==="
|
||||
|
||||
+2
-2
@@ -88,14 +88,14 @@ sysroot = use_repo_rule("@toolchains_llvm//toolchain:sysroot.bzl", "sysroot")
|
||||
sysroot(
|
||||
name = "linux_sysroot",
|
||||
sha256 = "a06475004fe8003ae7ccb4fe1d5511feb9b27cce4a8826eb1dfd686ed83f3dba",
|
||||
urls = ["https://eagle0-sysroot.sfo3.digitaloceanspaces.com/v3/ubuntu_noble_amd64_sysroot.tar.xz"],
|
||||
urls = ["https://eagle0-windows.sfo3.digitaloceanspaces.com/sysroot/v3/ubuntu_noble_amd64_sysroot.tar.xz"],
|
||||
)
|
||||
|
||||
# ARM64 sysroot
|
||||
sysroot(
|
||||
name = "linux_sysroot_arm64",
|
||||
sha256 = "87469137737e09bc73855007dab835477eb10a7b3ce3f725f93f64e25747f3f9",
|
||||
urls = ["https://eagle0-sysroot.sfo3.digitaloceanspaces.com/v4/ubuntu_noble_arm64_sysroot.tar.xz"],
|
||||
urls = ["https://eagle0-windows.sfo3.digitaloceanspaces.com/sysroot/v4/ubuntu_noble_arm64_sysroot.tar.xz"],
|
||||
)
|
||||
|
||||
#
|
||||
|
||||
@@ -170,16 +170,18 @@ namespace Auth {
|
||||
|
||||
/// <summary>
|
||||
/// Logout - invalidates refresh token on server.
|
||||
/// Does NOT clear local tokens (OAuthManager handles that decision).
|
||||
/// Routed to Eagle.
|
||||
/// </summary>
|
||||
public async Task LogoutAsync() {
|
||||
try {
|
||||
await _eagleClient.LogoutAsync(new LogoutRequest());
|
||||
Debug.Log("[AuthClient] Server logout successful");
|
||||
} catch (Exception ex) {
|
||||
Debug.LogWarning($"[AuthClient] Logout RPC failed (may be expected): {ex.Message}");
|
||||
}
|
||||
|
||||
// Clear local tokens regardless of server response
|
||||
TokenStorage.Clear();
|
||||
Debug.Log("[AuthClient] Logged out, tokens cleared");
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Net.Eagle0.Eagle.Api.Auth;
|
||||
using UnityEngine;
|
||||
@@ -10,7 +9,6 @@ namespace Auth {
|
||||
/// - Opens system browser for OAuth consent
|
||||
/// - Polls server for OAuth completion (no deep links needed)
|
||||
/// - Token storage and refresh
|
||||
/// - Multi-account support
|
||||
/// </summary>
|
||||
public class OAuthManager : MonoBehaviour {
|
||||
public static OAuthManager Instance { get; private set; }
|
||||
@@ -18,7 +16,6 @@ namespace Auth {
|
||||
private AuthClient _authClient;
|
||||
private string _currentAuthServiceUrl;
|
||||
private string _currentEagleUrl;
|
||||
private OAuthProvider _currentLoginProvider; // Track provider during login
|
||||
|
||||
// Events for UI updates
|
||||
public event Action<UserInfo> OnLoginSuccess;
|
||||
@@ -83,8 +80,6 @@ namespace Auth {
|
||||
/// </summary>
|
||||
public async Task<CheckOAuthStatusResponse> LoginAsync(OAuthProvider provider) {
|
||||
EnsureConfigured();
|
||||
_currentLoginProvider = provider; // Track for later storage
|
||||
|
||||
try {
|
||||
// Get OAuth URL and state from server
|
||||
var (authUrl, state) = await _authClient.GetOAuthUrlAsync(provider);
|
||||
@@ -96,15 +91,13 @@ namespace Auth {
|
||||
// Poll for OAuth completion (server handles the callback)
|
||||
var response = await _authClient.PollForOAuthCompletionAsync(state);
|
||||
|
||||
// Store tokens with provider info
|
||||
var providerName = provider.ToString().ToLowerInvariant();
|
||||
// Store tokens
|
||||
TokenStorage.StoreTokens(
|
||||
response.AccessToken,
|
||||
response.RefreshToken,
|
||||
response.ExpiresAt,
|
||||
response.User.UserId,
|
||||
response.User.DisplayName ?? "",
|
||||
providerName);
|
||||
response.User.DisplayName ?? "");
|
||||
|
||||
// Notify listeners
|
||||
if (response.IsNewUser) {
|
||||
@@ -163,15 +156,6 @@ namespace Auth {
|
||||
// Validate session with server
|
||||
try {
|
||||
var response = await _authClient.GetCurrentUserAsync();
|
||||
|
||||
// Check if user still needs to set display name (e.g., closed app before setting
|
||||
// it)
|
||||
if (string.IsNullOrEmpty(response.User.DisplayName)) {
|
||||
Debug.Log("[OAuthManager] Session restored but user needs display name");
|
||||
OnNewUserNeedsDisplayName?.Invoke(false); // false = not a brand new user
|
||||
return true;
|
||||
}
|
||||
|
||||
Debug.Log($"[OAuthManager] Session restored for {response.User.DisplayName}");
|
||||
OnLoginSuccess?.Invoke(response.User);
|
||||
return true;
|
||||
@@ -194,89 +178,16 @@ namespace Auth {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logout - clears current session but preserves stored tokens for quick re-login.
|
||||
/// Logout and clear stored tokens.
|
||||
/// </summary>
|
||||
public async Task LogoutAsync() {
|
||||
// Notify server of logout if connected
|
||||
// LogoutAsync can work even without server connection - just clear local tokens
|
||||
if (_authClient != null) {
|
||||
try {
|
||||
await _authClient.LogoutAsync();
|
||||
} catch (Exception ex) {
|
||||
Debug.LogWarning($"[OAuthManager] Server logout failed: {ex.Message}");
|
||||
}
|
||||
await _authClient.LogoutAsync();
|
||||
} else {
|
||||
TokenStorage.Clear();
|
||||
}
|
||||
|
||||
// Clear current account selection but keep tokens stored
|
||||
TokenStorage.ClearCurrentAccount();
|
||||
OnLogout?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all stored accounts for display in UI.
|
||||
/// </summary>
|
||||
public IReadOnlyList<StoredAccount> GetStoredAccounts() {
|
||||
return TokenStorage.GetAllAccounts();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select and connect with a stored account.
|
||||
/// If token is expired or about to expire, refreshes it first.
|
||||
/// </summary>
|
||||
public async Task<bool> ConnectWithStoredAccountAsync(StoredAccount account) {
|
||||
EnsureConfigured();
|
||||
|
||||
// Select this account as current
|
||||
TokenStorage.SelectAccount(account.AccountKey);
|
||||
|
||||
// Check if token needs refresh
|
||||
if (!account.HasValidToken || account.NeedsRefresh) {
|
||||
if (account.HasRefreshToken) {
|
||||
try {
|
||||
await _authClient.RefreshTokenAsync();
|
||||
Debug.Log($"[OAuthManager] Refreshed token for {account.DisplayName}");
|
||||
} catch (Exception ex) {
|
||||
Debug.LogWarning($"[OAuthManager] Token refresh failed: {ex.Message}");
|
||||
// Token refresh failed - need to re-authenticate
|
||||
// Parse provider from account key
|
||||
var provider = account.Provider.ToLowerInvariant() == "google"
|
||||
? OAuthProvider.Google
|
||||
: OAuthProvider.Discord;
|
||||
OnLoginFailed?.Invoke("Session expired. Please sign in again.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
OnLoginFailed?.Invoke("Session expired. Please sign in again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate session with server
|
||||
try {
|
||||
var response = await _authClient.GetCurrentUserAsync();
|
||||
|
||||
// Check if user still needs to set display name
|
||||
if (string.IsNullOrEmpty(response.User.DisplayName)) {
|
||||
Debug.Log("[OAuthManager] Stored account needs display name");
|
||||
OnNewUserNeedsDisplayName?.Invoke(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
Debug.Log(
|
||||
$"[OAuthManager] Connected with stored account: {response.User.DisplayName}");
|
||||
OnLoginSuccess?.Invoke(response.User);
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
Debug.LogWarning($"[OAuthManager] Session validation failed: {ex.Message}");
|
||||
OnLoginFailed?.Invoke("Session invalid. Please sign in again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a stored account permanently.
|
||||
/// </summary>
|
||||
public void RemoveStoredAccount(StoredAccount account) {
|
||||
TokenStorage.RemoveAccount(account.AccountKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Auth {
|
||||
/// <summary>
|
||||
/// Represents a stored OAuth account with tokens.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class StoredAccount {
|
||||
public string Provider; // "discord" or "google"
|
||||
public string UserId; // OAuth provider user ID
|
||||
public string DisplayName; // User's display name
|
||||
public string AccessToken;
|
||||
public string RefreshToken;
|
||||
public long ExpiresAt; // Unix timestamp
|
||||
|
||||
public string AccountKey => $"{Provider}:{UserId}";
|
||||
|
||||
public bool HasValidToken => !string.IsNullOrEmpty(AccessToken) &&
|
||||
ExpiresAt > DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
|
||||
public bool HasRefreshToken => !string.IsNullOrEmpty(RefreshToken);
|
||||
|
||||
public bool NeedsRefresh {
|
||||
get {
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
return ExpiresAt > 0 && ExpiresAt - now < 300; // 5 minutes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Container for all stored accounts, serialized to JSON.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class StoredAccountsData {
|
||||
public List<StoredAccount> Accounts = new();
|
||||
public string CurrentAccountKey; // Provider:UserId of active account
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Secure storage for OAuth tokens using Unity's PlayerPrefs.
|
||||
/// Supports multiple accounts - tokens are preserved on logout.
|
||||
/// Values are cached in memory to allow access from background threads.
|
||||
/// In production, consider using platform-specific secure storage
|
||||
/// (Keychain on iOS, Keystore on Android).
|
||||
/// </summary>
|
||||
public static class TokenStorage {
|
||||
private const string AccountsDataKey = "eagle0_accounts_data";
|
||||
private const string AccessTokenKey = "eagle0_access_token";
|
||||
private const string RefreshTokenKey = "eagle0_refresh_token";
|
||||
private const string ExpiresAtKey = "eagle0_expires_at";
|
||||
private const string UserIdKey = "eagle0_user_id";
|
||||
private const string DisplayNameKey = "eagle0_display_name";
|
||||
|
||||
// Legacy keys for migration
|
||||
private const string LegacyAccessTokenKey = "eagle0_access_token";
|
||||
private const string LegacyRefreshTokenKey = "eagle0_refresh_token";
|
||||
private const string LegacyExpiresAtKey = "eagle0_expires_at";
|
||||
private const string LegacyUserIdKey = "eagle0_user_id";
|
||||
private const string LegacyDisplayNameKey = "eagle0_display_name";
|
||||
|
||||
// In-memory cache
|
||||
private static StoredAccountsData _accountsData;
|
||||
// In-memory cache for thread-safe access from background threads
|
||||
// PlayerPrefs can only be accessed from the main thread
|
||||
private static string _cachedAccessToken;
|
||||
private static string _cachedRefreshToken;
|
||||
private static long _cachedExpiresAt;
|
||||
private static string _cachedUserId;
|
||||
private static string _cachedDisplayName;
|
||||
private static bool _cacheInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
@@ -65,245 +29,147 @@ namespace Auth {
|
||||
/// Call this early in app startup (e.g., in Awake or Start).
|
||||
/// </summary>
|
||||
public static void InitializeCache() {
|
||||
var json = PlayerPrefs.GetString(AccountsDataKey, "");
|
||||
|
||||
if (!string.IsNullOrEmpty(json)) {
|
||||
try {
|
||||
_accountsData = JsonUtility.FromJson<StoredAccountsData>(json);
|
||||
} catch (Exception ex) {
|
||||
Debug.LogWarning($"[TokenStorage] Failed to parse accounts data: {ex.Message}");
|
||||
_accountsData = new StoredAccountsData();
|
||||
}
|
||||
} else {
|
||||
_accountsData = new StoredAccountsData();
|
||||
// Try to migrate legacy single-account data
|
||||
MigrateLegacyData();
|
||||
}
|
||||
|
||||
_cachedAccessToken = PlayerPrefs.GetString(AccessTokenKey, null);
|
||||
_cachedRefreshToken = PlayerPrefs.GetString(RefreshTokenKey, null);
|
||||
_cachedExpiresAt =
|
||||
long.TryParse(PlayerPrefs.GetString(ExpiresAtKey, "0"), out var val) ? val : 0;
|
||||
_cachedUserId = PlayerPrefs.GetString(UserIdKey, null);
|
||||
_cachedDisplayName = PlayerPrefs.GetString(DisplayNameKey, null);
|
||||
_cacheInitialized = true;
|
||||
|
||||
Debug.Log(
|
||||
$"[TokenStorage] Cache initialized, {_accountsData.Accounts.Count} accounts stored");
|
||||
$"[TokenStorage] Cache initialized, hasToken={!string.IsNullOrEmpty(_cachedAccessToken)}");
|
||||
}
|
||||
|
||||
private static void MigrateLegacyData() {
|
||||
var legacyAccessToken = PlayerPrefs.GetString(LegacyAccessTokenKey, "");
|
||||
if (string.IsNullOrEmpty(legacyAccessToken)) return;
|
||||
|
||||
var legacyRefreshToken = PlayerPrefs.GetString(LegacyRefreshTokenKey, "");
|
||||
var legacyExpiresAt =
|
||||
long.TryParse(PlayerPrefs.GetString(LegacyExpiresAtKey, "0"), out var val) ? val
|
||||
: 0;
|
||||
var legacyUserId = PlayerPrefs.GetString(LegacyUserIdKey, "");
|
||||
var legacyDisplayName = PlayerPrefs.GetString(LegacyDisplayNameKey, "");
|
||||
|
||||
if (!string.IsNullOrEmpty(legacyUserId)) {
|
||||
// Assume discord for legacy data (most common)
|
||||
var account = new StoredAccount {
|
||||
Provider = "discord",
|
||||
UserId = legacyUserId,
|
||||
DisplayName = legacyDisplayName,
|
||||
AccessToken = legacyAccessToken,
|
||||
RefreshToken = legacyRefreshToken,
|
||||
ExpiresAt = legacyExpiresAt
|
||||
};
|
||||
_accountsData.Accounts.Add(account);
|
||||
_accountsData.CurrentAccountKey = account.AccountKey;
|
||||
SaveAccountsData();
|
||||
|
||||
// Clear legacy keys
|
||||
PlayerPrefs.DeleteKey(LegacyAccessTokenKey);
|
||||
PlayerPrefs.DeleteKey(LegacyRefreshTokenKey);
|
||||
PlayerPrefs.DeleteKey(LegacyExpiresAtKey);
|
||||
PlayerPrefs.DeleteKey(LegacyUserIdKey);
|
||||
PlayerPrefs.DeleteKey(LegacyDisplayNameKey);
|
||||
PlayerPrefs.Save();
|
||||
|
||||
Debug.Log($"[TokenStorage] Migrated legacy account: {account.DisplayName}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveAccountsData() {
|
||||
var json = JsonUtility.ToJson(_accountsData);
|
||||
PlayerPrefs.SetString(AccountsDataKey, json);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all stored accounts.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<StoredAccount> GetAllAccounts() {
|
||||
EnsureInitialized();
|
||||
return _accountsData.Accounts.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the currently active account, or null if none selected.
|
||||
/// </summary>
|
||||
public static StoredAccount CurrentAccount {
|
||||
public static bool HasValidToken {
|
||||
get {
|
||||
EnsureInitialized();
|
||||
if (string.IsNullOrEmpty(_accountsData.CurrentAccountKey)) return null;
|
||||
return _accountsData.Accounts.Find(
|
||||
a => a.AccountKey == _accountsData.CurrentAccountKey);
|
||||
var token = AccessToken;
|
||||
var expiresAt = ExpiresAt;
|
||||
return !string.IsNullOrEmpty(token) &&
|
||||
expiresAt > DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasRefreshToken => !string.IsNullOrEmpty(RefreshToken);
|
||||
|
||||
public static string AccessToken {
|
||||
get {
|
||||
if (!_cacheInitialized) {
|
||||
Debug.LogWarning(
|
||||
"[TokenStorage] Cache not initialized, returning cached value anyway");
|
||||
}
|
||||
return _cachedAccessToken;
|
||||
}
|
||||
private
|
||||
set {
|
||||
_cachedAccessToken = value;
|
||||
PlayerPrefs.SetString(AccessTokenKey, value ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
public static string RefreshToken {
|
||||
get => _cachedRefreshToken;
|
||||
private
|
||||
set {
|
||||
_cachedRefreshToken = value;
|
||||
PlayerPrefs.SetString(RefreshTokenKey, value ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
public static long ExpiresAt {
|
||||
get => _cachedExpiresAt;
|
||||
private
|
||||
set {
|
||||
_cachedExpiresAt = value;
|
||||
PlayerPrefs.SetString(ExpiresAtKey, value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static string UserId {
|
||||
get => _cachedUserId;
|
||||
private
|
||||
set {
|
||||
_cachedUserId = value;
|
||||
PlayerPrefs.SetString(UserIdKey, value ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
public static string DisplayName {
|
||||
get => _cachedDisplayName;
|
||||
private
|
||||
set {
|
||||
_cachedDisplayName = value;
|
||||
PlayerPrefs.SetString(DisplayNameKey, value ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select an account as the current active account.
|
||||
/// </summary>
|
||||
public static void SelectAccount(string accountKey) {
|
||||
EnsureInitialized();
|
||||
var account = _accountsData.Accounts.Find(a => a.AccountKey == accountKey);
|
||||
if (account != null) {
|
||||
_accountsData.CurrentAccountKey = accountKey;
|
||||
SaveAccountsData();
|
||||
Debug.Log($"[TokenStorage] Selected account: {account.DisplayName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the current account selection (logout without deleting tokens).
|
||||
/// </summary>
|
||||
public static void ClearCurrentAccount() {
|
||||
EnsureInitialized();
|
||||
_accountsData.CurrentAccountKey = null;
|
||||
SaveAccountsData();
|
||||
Debug.Log("[TokenStorage] Cleared current account selection");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a specific account and its tokens.
|
||||
/// </summary>
|
||||
public static void RemoveAccount(string accountKey) {
|
||||
EnsureInitialized();
|
||||
var removed = _accountsData.Accounts.RemoveAll(a => a.AccountKey == accountKey);
|
||||
if (_accountsData.CurrentAccountKey == accountKey) {
|
||||
_accountsData.CurrentAccountKey = null;
|
||||
}
|
||||
if (removed > 0) {
|
||||
SaveAccountsData();
|
||||
Debug.Log($"[TokenStorage] Removed account: {accountKey}");
|
||||
}
|
||||
}
|
||||
|
||||
// Compatibility properties that reference the current account
|
||||
|
||||
public static bool HasValidToken => CurrentAccount?.HasValidToken ?? false;
|
||||
|
||||
public static bool HasRefreshToken => CurrentAccount?.HasRefreshToken ?? false;
|
||||
|
||||
public static string AccessToken => CurrentAccount?.AccessToken;
|
||||
|
||||
public static string RefreshToken => CurrentAccount?.RefreshToken;
|
||||
|
||||
public static long ExpiresAt => CurrentAccount?.ExpiresAt ?? 0;
|
||||
|
||||
public static string UserId => CurrentAccount?.UserId;
|
||||
|
||||
public static string DisplayName => CurrentAccount?.DisplayName;
|
||||
|
||||
public static bool NeedsRefresh => CurrentAccount?.NeedsRefresh ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Store tokens for an account. Creates new or updates existing.
|
||||
/// Store tokens received from OAuth exchange.
|
||||
/// </summary>
|
||||
public static void StoreTokens(
|
||||
string accessToken,
|
||||
string refreshToken,
|
||||
long expiresAt,
|
||||
string userId,
|
||||
string displayName,
|
||||
string provider = "discord") {
|
||||
EnsureInitialized();
|
||||
string displayName) {
|
||||
AccessToken = accessToken;
|
||||
RefreshToken = refreshToken;
|
||||
ExpiresAt = expiresAt;
|
||||
UserId = userId;
|
||||
DisplayName = displayName;
|
||||
PlayerPrefs.Save();
|
||||
|
||||
var accountKey = $"{provider}:{userId}";
|
||||
var existingAccount = _accountsData.Accounts.Find(a => a.AccountKey == accountKey);
|
||||
|
||||
if (existingAccount != null) {
|
||||
existingAccount.AccessToken = accessToken;
|
||||
existingAccount.RefreshToken = refreshToken;
|
||||
existingAccount.ExpiresAt = expiresAt;
|
||||
existingAccount.DisplayName = displayName;
|
||||
} else {
|
||||
var newAccount = new StoredAccount {
|
||||
Provider = provider,
|
||||
UserId = userId,
|
||||
DisplayName = displayName,
|
||||
AccessToken = accessToken,
|
||||
RefreshToken = refreshToken,
|
||||
ExpiresAt = expiresAt
|
||||
};
|
||||
_accountsData.Accounts.Add(newAccount);
|
||||
}
|
||||
|
||||
// Set as current account
|
||||
_accountsData.CurrentAccountKey = accountKey;
|
||||
SaveAccountsData();
|
||||
|
||||
Debug.Log($"[TokenStorage] Stored tokens for {displayName} ({provider})");
|
||||
Debug.Log(
|
||||
$"[TokenStorage] Stored tokens for user {displayName} (expires at {expiresAt})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update access token after refresh for the current account.
|
||||
/// Update access token after refresh.
|
||||
/// </summary>
|
||||
public static void UpdateAccessToken(string accessToken, long expiresAt) {
|
||||
EnsureInitialized();
|
||||
var account = CurrentAccount;
|
||||
if (account != null) {
|
||||
account.AccessToken = accessToken;
|
||||
account.ExpiresAt = expiresAt;
|
||||
SaveAccountsData();
|
||||
Debug.Log($"[TokenStorage] Updated access token (expires at {expiresAt})");
|
||||
}
|
||||
AccessToken = accessToken;
|
||||
ExpiresAt = expiresAt;
|
||||
PlayerPrefs.Save();
|
||||
|
||||
Debug.Log($"[TokenStorage] Updated access token (expires at {expiresAt})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update access token for a specific account (by key).
|
||||
/// </summary>
|
||||
public static void
|
||||
UpdateAccessTokenForAccount(string accountKey, string accessToken, long expiresAt) {
|
||||
EnsureInitialized();
|
||||
var account = _accountsData.Accounts.Find(a => a.AccountKey == accountKey);
|
||||
if (account != null) {
|
||||
account.AccessToken = accessToken;
|
||||
account.ExpiresAt = expiresAt;
|
||||
SaveAccountsData();
|
||||
Debug.Log(
|
||||
$"[TokenStorage] Updated access token for {account.DisplayName} (expires at {expiresAt})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update display name for the current account.
|
||||
/// Update display name after user sets it.
|
||||
/// </summary>
|
||||
public static void UpdateDisplayName(string displayName) {
|
||||
EnsureInitialized();
|
||||
var account = CurrentAccount;
|
||||
if (account != null) {
|
||||
account.DisplayName = displayName;
|
||||
SaveAccountsData();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all stored accounts (full reset).
|
||||
/// </summary>
|
||||
public static void ClearAll() {
|
||||
_accountsData = new StoredAccountsData();
|
||||
PlayerPrefs.DeleteKey(AccountsDataKey);
|
||||
DisplayName = displayName;
|
||||
PlayerPrefs.Save();
|
||||
Debug.Log("[TokenStorage] Cleared all accounts");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Legacy Clear method - now just clears current selection, not tokens.
|
||||
/// Clear all stored tokens (logout).
|
||||
/// </summary>
|
||||
public static void Clear() { ClearCurrentAccount(); }
|
||||
public static void Clear() {
|
||||
_cachedAccessToken = null;
|
||||
_cachedRefreshToken = null;
|
||||
_cachedExpiresAt = 0;
|
||||
_cachedUserId = null;
|
||||
_cachedDisplayName = null;
|
||||
|
||||
private static void EnsureInitialized() {
|
||||
if (!_cacheInitialized) {
|
||||
Debug.LogWarning("[TokenStorage] Cache not initialized, initializing now");
|
||||
InitializeCache();
|
||||
PlayerPrefs.DeleteKey(AccessTokenKey);
|
||||
PlayerPrefs.DeleteKey(RefreshTokenKey);
|
||||
PlayerPrefs.DeleteKey(ExpiresAtKey);
|
||||
PlayerPrefs.DeleteKey(UserIdKey);
|
||||
PlayerPrefs.DeleteKey(DisplayNameKey);
|
||||
PlayerPrefs.Save();
|
||||
|
||||
Debug.Log("[TokenStorage] Cleared all tokens");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if token needs refresh (expires within 5 minutes).
|
||||
/// </summary>
|
||||
public static bool NeedsRefresh {
|
||||
get {
|
||||
var expiresAt = ExpiresAt;
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
return expiresAt > 0 && expiresAt - now < 300; // 5 minutes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+87
-133
@@ -80,6 +80,9 @@ public class AuthInterceptor : Interceptor {
|
||||
};
|
||||
|
||||
public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
public TMP_Dropdown environmentDropdown;
|
||||
public TMP_InputField nameField;
|
||||
public TMP_InputField passwordField;
|
||||
public TMP_Dropdown resolutionDropdown;
|
||||
|
||||
[Header("OAuth UI")]
|
||||
@@ -88,18 +91,17 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
public Button googleLoginButton;
|
||||
public TextMeshProUGUI oauthStatusText;
|
||||
|
||||
[Header("Stored Accounts")]
|
||||
public GameObject storedAccountsContainer;
|
||||
public GameObject storedAccountButtonPrefab;
|
||||
public Sprite discordProviderIcon;
|
||||
public Sprite googleProviderIcon;
|
||||
|
||||
[Header("Display Name Setup")]
|
||||
public GameObject displayNamePanel;
|
||||
public TMP_InputField displayNameField;
|
||||
public Button setDisplayNameButton;
|
||||
public TextMeshProUGUI displayNameErrorText;
|
||||
|
||||
[Header("Legacy Auth (for testing)")]
|
||||
public GameObject legacyAuthPanel;
|
||||
public Button useLegacyAuthButton;
|
||||
public TextMeshProUGUI useLegacyAuthButtonText;
|
||||
|
||||
[Header("Status Display")]
|
||||
public TextMeshProUGUI connectionStatusText;
|
||||
|
||||
@@ -112,7 +114,7 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
public Button logoutButton;
|
||||
public Button customBattleButton;
|
||||
public Button cancelCustomBattleButton;
|
||||
public TMP_Dropdown lobbyEnvironmentDropdown;
|
||||
public TextMeshProUGUI lobbyEnvironmentText;
|
||||
public TextMeshProUGUI lobbyUserText;
|
||||
|
||||
public GameObject runningGamesListArea;
|
||||
@@ -140,7 +142,7 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private readonly Object pendingReplyLock = new Object();
|
||||
private int _connectedEnvironmentIndex = -1; // -1 means not connected
|
||||
private List<GameObject> _storedAccountButtons = new List<GameObject>();
|
||||
private bool _useOAuth = false; // Default to legacy until server-side OAuth is ready
|
||||
|
||||
public ClientPregeneratedText clientPregeneratedText;
|
||||
|
||||
@@ -164,7 +166,7 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
: null;
|
||||
|
||||
private string GetUrlFromEnvironment() {
|
||||
int envIndex = PlayerPrefs.GetInt(EnvironmentKey, 0);
|
||||
int envIndex = environmentDropdown.value;
|
||||
string prefix = EnvironmentOptions[envIndex];
|
||||
return prefix + BaseDomain;
|
||||
}
|
||||
@@ -197,6 +199,14 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
resolutionDropdown.AddOptions(resolutions.Select(r => $"{r.width} x {r.height}").ToList());
|
||||
resolutionDropdown.value = resolutions.ToList().IndexOf(currentResolution);
|
||||
|
||||
// Set up environment dropdown
|
||||
environmentDropdown.ClearOptions();
|
||||
environmentDropdown.AddOptions(EnvironmentDisplayNames);
|
||||
environmentDropdown.value = PlayerPrefs.GetInt(EnvironmentKey, 0);
|
||||
|
||||
nameField.text = PlayerPrefs.GetString(NameKey, "sample_name");
|
||||
passwordField.text = PlayerPrefs.GetString(PasswordKey, "sample_password");
|
||||
|
||||
connectionCanvas.gameObject.SetActive(true);
|
||||
eagleCanvas.gameObject.SetActive(false);
|
||||
shardokCanvas.gameObject.SetActive(false);
|
||||
@@ -222,7 +232,7 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
}
|
||||
|
||||
private void SetupOAuthUI() {
|
||||
// Set up OAuth button click handlers for new sign-ins
|
||||
// Set up OAuth button click handlers
|
||||
if (discordLoginButton != null) {
|
||||
discordLoginButton.onClick.AddListener(
|
||||
() => OnOAuthLoginClicked(OAuthProvider.Discord));
|
||||
@@ -233,6 +243,9 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
if (setDisplayNameButton != null) {
|
||||
setDisplayNameButton.onClick.AddListener(OnSetDisplayNameClicked);
|
||||
}
|
||||
if (useLegacyAuthButton != null) {
|
||||
useLegacyAuthButton.onClick.AddListener(OnUseLegacyAuthClicked);
|
||||
}
|
||||
|
||||
// Subscribe to OAuthManager events
|
||||
if (OAuthManager.Instance != null) {
|
||||
@@ -242,79 +255,24 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
OAuthManager.Instance.OnLogout += OnOAuthLogout;
|
||||
}
|
||||
|
||||
// Show auth panel with stored accounts
|
||||
// Show appropriate panel based on auth state
|
||||
ShowAuthPanel();
|
||||
}
|
||||
|
||||
private void ShowAuthPanel() {
|
||||
// Hide display name panel
|
||||
// Hide all auth panels first
|
||||
if (oauthPanel != null) oauthPanel.SetActive(false);
|
||||
if (displayNamePanel != null) displayNamePanel.SetActive(false);
|
||||
if (legacyAuthPanel != null) legacyAuthPanel.SetActive(false);
|
||||
|
||||
// Show OAuth panel
|
||||
if (oauthPanel != null) oauthPanel.SetActive(true);
|
||||
|
||||
// Refresh stored account buttons
|
||||
RefreshStoredAccountButtons();
|
||||
}
|
||||
|
||||
private void RefreshStoredAccountButtons() {
|
||||
// Clear existing buttons
|
||||
foreach (var btn in _storedAccountButtons) {
|
||||
if (btn != null) Destroy(btn);
|
||||
}
|
||||
_storedAccountButtons.Clear();
|
||||
|
||||
if (storedAccountsContainer == null || storedAccountButtonPrefab == null) return;
|
||||
|
||||
// Get stored accounts
|
||||
var accounts = OAuthManager.Instance?.GetStoredAccounts();
|
||||
if (accounts == null || accounts.Count == 0) return;
|
||||
|
||||
// Create button for each stored account
|
||||
foreach (var account in accounts) {
|
||||
var buttonObj =
|
||||
Instantiate(storedAccountButtonPrefab, storedAccountsContainer.transform);
|
||||
buttonObj.transform.localScale = Vector3.one;
|
||||
|
||||
// Set button text (just display name, icon shows provider)
|
||||
var buttonText = buttonObj.GetComponentInChildren<TextMeshProUGUI>();
|
||||
if (buttonText != null) { buttonText.text = account.DisplayName; }
|
||||
|
||||
// Set provider icon (look for child named "ProviderIcon")
|
||||
var providerIconTransform = buttonObj.transform.Find("ProviderIcon");
|
||||
if (providerIconTransform != null) {
|
||||
var providerImage = providerIconTransform.GetComponent<Image>();
|
||||
if (providerImage != null) {
|
||||
var isGoogle = account.Provider.ToLowerInvariant() == "google";
|
||||
providerImage.sprite = isGoogle ? googleProviderIcon : discordProviderIcon;
|
||||
}
|
||||
}
|
||||
|
||||
// Set click handler
|
||||
var button = buttonObj.GetComponent<Button>();
|
||||
if (button != null) {
|
||||
var accountCopy = account; // Capture for lambda
|
||||
button.onClick.AddListener(() => OnStoredAccountClicked(accountCopy));
|
||||
}
|
||||
|
||||
_storedAccountButtons.Add(buttonObj);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnStoredAccountClicked(StoredAccount account) {
|
||||
// Configure OAuthManager with URLs
|
||||
OAuthManager.Instance.SetServerUrls(GetAuthServiceUrl(), GetEagleUrl());
|
||||
|
||||
if (oauthStatusText != null) {
|
||||
oauthStatusText.text = $"Connecting as {account.DisplayName}...";
|
||||
}
|
||||
|
||||
var success = await OAuthManager.Instance.ConnectWithStoredAccountAsync(account);
|
||||
if (success) {
|
||||
// OnLoginSuccess will handle connection to lobby
|
||||
// Show appropriate panel based on _useOAuth
|
||||
if (_useOAuth) {
|
||||
if (oauthPanel != null) oauthPanel.SetActive(true);
|
||||
if (useLegacyAuthButtonText != null)
|
||||
useLegacyAuthButtonText.text = "Use Classic Sign-in";
|
||||
} else {
|
||||
// Failed - might need re-auth, refresh the buttons
|
||||
RefreshStoredAccountButtons();
|
||||
if (legacyAuthPanel != null) legacyAuthPanel.SetActive(true);
|
||||
if (useLegacyAuthButtonText != null) useLegacyAuthButtonText.text = "Use OAuth Sign-in";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,56 +282,22 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
if (cancelCustomBattleButton != null) {
|
||||
cancelCustomBattleButton.onClick.AddListener(CancelCustomBattle);
|
||||
}
|
||||
|
||||
// Set up environment dropdown
|
||||
if (lobbyEnvironmentDropdown != null) {
|
||||
lobbyEnvironmentDropdown.ClearOptions();
|
||||
lobbyEnvironmentDropdown.AddOptions(EnvironmentDisplayNames);
|
||||
lobbyEnvironmentDropdown.value = PlayerPrefs.GetInt(EnvironmentKey, 0);
|
||||
lobbyEnvironmentDropdown.onValueChanged.AddListener(OnLobbyEnvironmentChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLobbyStatusDisplays() {
|
||||
// Update environment dropdown selection
|
||||
if (lobbyEnvironmentDropdown != null && _connectedEnvironmentIndex >= 0) {
|
||||
// Temporarily remove listener to avoid triggering reconnect
|
||||
lobbyEnvironmentDropdown.onValueChanged.RemoveListener(OnLobbyEnvironmentChanged);
|
||||
lobbyEnvironmentDropdown.value = _connectedEnvironmentIndex;
|
||||
lobbyEnvironmentDropdown.onValueChanged.AddListener(OnLobbyEnvironmentChanged);
|
||||
// Show environment (prod/qa)
|
||||
if (lobbyEnvironmentText != null) {
|
||||
lobbyEnvironmentText.text = ConnectedEnvironmentName ?? "";
|
||||
}
|
||||
|
||||
// Show current user from OAuth
|
||||
if (lobbyUserText != null) { lobbyUserText.text = TokenStorage.DisplayName ?? ""; }
|
||||
}
|
||||
|
||||
private void OnLobbyEnvironmentChanged(int newEnvironmentIndex) {
|
||||
if (newEnvironmentIndex == _connectedEnvironmentIndex) return;
|
||||
|
||||
Debug.Log(
|
||||
$"[ConnectionHandler] Switching environment from {ConnectedEnvironmentName} to {EnvironmentDisplayNames[newEnvironmentIndex]}");
|
||||
|
||||
// Disconnect from current environment
|
||||
_persistentClientConnection?.Dispose();
|
||||
eagleConnection?.Dispose();
|
||||
_httpClient?.Dispose();
|
||||
|
||||
_persistentClientConnection = null;
|
||||
eagleConnection = null;
|
||||
_httpClient = null;
|
||||
|
||||
// Update environment index and reconnect
|
||||
_connectedEnvironmentIndex = newEnvironmentIndex;
|
||||
PlayerPrefs.SetInt(EnvironmentKey, newEnvironmentIndex);
|
||||
|
||||
// Clear current game lists while reconnecting
|
||||
foreach (Transform row in runningGamesListArea.transform) { Destroy(row.gameObject); }
|
||||
foreach (Transform row in availableGamesListArea.transform) { Destroy(row.gameObject); }
|
||||
|
||||
// Reconnect to new environment
|
||||
_createConnection();
|
||||
RequestMaps();
|
||||
StartListeningForLobbyUpdates();
|
||||
// Show current user - prefer OAuth display name, fall back to classic login name
|
||||
if (lobbyUserText != null) {
|
||||
if (_useOAuth && !string.IsNullOrEmpty(TokenStorage.DisplayName)) {
|
||||
lobbyUserText.text = TokenStorage.DisplayName;
|
||||
} else {
|
||||
lobbyUserText.text = nameField.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnLogoutClicked() {
|
||||
@@ -486,7 +410,27 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
});
|
||||
}
|
||||
|
||||
private void ConnectWithOAuth() { _internalConnectEagle(); }
|
||||
private void OnUseLegacyAuthClicked() {
|
||||
_useOAuth = !_useOAuth; // Toggle instead of just setting false
|
||||
|
||||
if (_useOAuth) {
|
||||
// Show OAuth panel
|
||||
if (oauthPanel != null) oauthPanel.SetActive(true);
|
||||
if (legacyAuthPanel != null) legacyAuthPanel.SetActive(false);
|
||||
if (useLegacyAuthButtonText != null)
|
||||
useLegacyAuthButtonText.text = "Use Classic Sign-in";
|
||||
} else {
|
||||
// Show legacy panel
|
||||
if (oauthPanel != null) oauthPanel.SetActive(false);
|
||||
if (legacyAuthPanel != null) legacyAuthPanel.SetActive(true);
|
||||
if (useLegacyAuthButtonText != null) useLegacyAuthButtonText.text = "Use OAuth Sign-in";
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectWithOAuth() {
|
||||
_useOAuth = true;
|
||||
_internalConnectEagle();
|
||||
}
|
||||
|
||||
private float _statusUpdateTimer = 0f;
|
||||
private const float StatusUpdateInterval = 0.5f;
|
||||
@@ -690,7 +634,10 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
}
|
||||
|
||||
private void _createConnection() {
|
||||
_connectedEnvironmentIndex = PlayerPrefs.GetInt(EnvironmentKey, 0);
|
||||
_connectedEnvironmentIndex = environmentDropdown.value;
|
||||
PlayerPrefs.SetInt(EnvironmentKey, _connectedEnvironmentIndex);
|
||||
PlayerPrefs.SetString(NameKey, nameField.text);
|
||||
PlayerPrefs.SetString(PasswordKey, passwordField.text);
|
||||
|
||||
// Dispose existing connections before creating new ones
|
||||
_httpClient?.Dispose();
|
||||
@@ -703,18 +650,25 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
|
||||
string url = GetUrlFromEnvironment();
|
||||
|
||||
// OAuth JWT-based connection
|
||||
if (!TokenStorage.HasValidToken) {
|
||||
Debug.LogError("[ConnectionHandler] No valid token available for connection");
|
||||
return;
|
||||
if (_useOAuth && TokenStorage.HasValidToken) {
|
||||
// Use JWT-based connection
|
||||
eagleConnection = EagleConnection.CreateWithJwt(url);
|
||||
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Bearer", TokenStorage.AccessToken);
|
||||
} else {
|
||||
// Legacy Basic Auth connection
|
||||
PlayerPrefs.SetString(NameKey, nameField.text);
|
||||
PlayerPrefs.SetString(PasswordKey, passwordField.text);
|
||||
|
||||
eagleConnection = new EagleConnection(nameField.text, passwordField.text, url);
|
||||
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Basic", eagleConnection.authHeader);
|
||||
}
|
||||
|
||||
eagleConnection = EagleConnection.CreateWithJwt(url);
|
||||
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Bearer", TokenStorage.AccessToken);
|
||||
|
||||
_persistentClientConnection = new PersistentClientConnection(
|
||||
eagleConnection.EagleGrpcClient,
|
||||
eagleConnection.credentials,
|
||||
|
||||
LFS
BIN
Binary file not shown.
-117
@@ -1,117 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af9f637b3a1f6433f819a0bcdb326453
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
-427
@@ -1,427 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1262787222422545696
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6242087783640990751}
|
||||
- component: {fileID: 5444015614922696687}
|
||||
- component: {fileID: 8871991993173924918}
|
||||
- component: {fileID: 6965305925514130928}
|
||||
m_Layer: 5
|
||||
m_Name: ProviderIcon
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6242087783640990751
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1262787222422545696}
|
||||
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: 157686408557551303}
|
||||
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!222 &5444015614922696687
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1262787222422545696}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &8871991993173924918
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1262787222422545696}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 80
|
||||
m_PreferredHeight: 50
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &6965305925514130928
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1262787222422545696}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
|
||||
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: 21300000, guid: b6b68c8c37d5cf1419267a80c426ac83, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 1
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &5166012670666351313
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 157686408557551303}
|
||||
- component: {fileID: 3634401029247236684}
|
||||
- component: {fileID: 7790188191322585408}
|
||||
- component: {fileID: 646268409010340088}
|
||||
- component: {fileID: 3097176747511135857}
|
||||
- component: {fileID: 7691398338434978342}
|
||||
m_Layer: 5
|
||||
m_Name: StoredAccountButton
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &157686408557551303
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5166012670666351313}
|
||||
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: 7974514965529143181}
|
||||
- {fileID: 6242087783640990751}
|
||||
m_Father: {fileID: 0}
|
||||
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!222 &3634401029247236684
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5166012670666351313}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &7790188191322585408
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5166012670666351313}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
|
||||
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!114 &646268409010340088
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5166012670666351313}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button
|
||||
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: 7790188191322585408}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &3097176747511135857
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5166012670666351313}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 400
|
||||
m_PreferredHeight: 60
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &7691398338434978342
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5166012670666351313}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.HorizontalLayoutGroup
|
||||
m_Padding:
|
||||
m_Left: 0
|
||||
m_Right: 0
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 5
|
||||
m_Spacing: 0
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 1
|
||||
m_ChildControlHeight: 1
|
||||
m_ChildScaleWidth: 0
|
||||
m_ChildScaleHeight: 0
|
||||
m_ReverseArrangement: 0
|
||||
--- !u!1 &5617793748733146591
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7974514965529143181}
|
||||
- component: {fileID: 4513536456760004536}
|
||||
- component: {fileID: 6210669843299902677}
|
||||
- component: {fileID: 572895541873054588}
|
||||
m_Layer: 5
|
||||
m_Name: Label
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &7974514965529143181
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5617793748733146591}
|
||||
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: 157686408557551303}
|
||||
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!222 &4513536456760004536
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5617793748733146591}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &6210669843299902677
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5617793748733146591}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
|
||||
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: nolen (dan@danielcrosby.net)
|
||||
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!114 &572895541873054588
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5617793748733146591}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: 60
|
||||
m_FlexibleWidth: 1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6440904d10a244010b3a91e0aa247749
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+8
-8
@@ -6,7 +6,7 @@ TextureImporter:
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
@@ -37,13 +37,13 @@ TextureImporter:
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
@@ -52,9 +52,9 @@ TextureImporter:
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
@@ -100,7 +100,7 @@ TextureImporter:
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
|
||||
+8
-8
@@ -6,7 +6,7 @@ TextureImporter:
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
@@ -37,13 +37,13 @@ TextureImporter:
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
@@ -52,9 +52,9 @@ TextureImporter:
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
@@ -100,7 +100,7 @@ TextureImporter:
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ using System.IO;
|
||||
using common;
|
||||
using eagle;
|
||||
using Net.Eagle0.Shardok.Api;
|
||||
using Shardok;
|
||||
using TMPro;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
@@ -29,9 +28,6 @@ public class SettingsPanelController : MonoBehaviour {
|
||||
public Toggle tooltipHugsCursorToggle;
|
||||
public HoveringTooltip hoveringTooltip;
|
||||
|
||||
public Toggle showAnimationTestsToggle;
|
||||
public AnimationTestController animationTestController;
|
||||
|
||||
public Slider autoScrollSpeedSlider;
|
||||
public TMP_Text autoScrollSpeedLabel;
|
||||
|
||||
@@ -43,7 +39,6 @@ public class SettingsPanelController : MonoBehaviour {
|
||||
private const string MusicVolumeKey = "musicVolume";
|
||||
public const string UseGoDiceKey = "useGoDice";
|
||||
private const string TooltipHugsCursorKey = "tooltipHugsCursor";
|
||||
private const string AnimationToggleKey = "animationToggle";
|
||||
|
||||
public EagleGameController eagleGameController;
|
||||
public GameObject ShardokCanvas;
|
||||
@@ -64,9 +59,6 @@ public class SettingsPanelController : MonoBehaviour {
|
||||
tooltipHugsCursorToggle.isOn = PlayerPrefs.GetInt(TooltipHugsCursorKey, 0) == 1;
|
||||
hoveringTooltip.HugsCursor = tooltipHugsCursorToggle.isOn;
|
||||
|
||||
showAnimationTestsToggle.isOn = PlayerPrefs.GetInt(AnimationToggleKey, 0) == 1;
|
||||
animationTestController.gameObject.SetActive(showAnimationTestsToggle.isOn);
|
||||
|
||||
if (autoScrollSpeedSlider != null) {
|
||||
autoScrollSpeedSlider.value = AutoScrollingText.GlobalScrollSpeedMultiplier;
|
||||
UpdateAutoScrollSpeedLabel(autoScrollSpeedSlider.value);
|
||||
@@ -124,11 +116,6 @@ public class SettingsPanelController : MonoBehaviour {
|
||||
hoveringTooltip.HugsCursor = val;
|
||||
}
|
||||
|
||||
public void OnAnimationTestToggleChange(bool val) {
|
||||
PlayerPrefs.SetInt(AnimationToggleKey, val ? 1 : 0);
|
||||
animationTestController.gameObject.SetActive(val);
|
||||
}
|
||||
|
||||
public void OnAutoScrollSpeedSliderChange(float val) {
|
||||
AutoScrollingText.GlobalScrollSpeedMultiplier = val;
|
||||
UpdateAutoScrollSpeedLabel(val);
|
||||
|
||||
BIN
Binary file not shown.
-169
@@ -1,169 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32f7f3d380ec1413590676a13c22324c
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 7482667652216324306
|
||||
second: Square
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 256
|
||||
spriteBorder: {x: 4, y: 4, z: 4, w: 4}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: 0
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: Triangle
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 256
|
||||
height: 256
|
||||
alignment: 9
|
||||
pivot: {x: 0.5, y: 0.28866667}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
outline:
|
||||
- - {x: 0, y: 93.7025033688}
|
||||
- {x: -128, y: -128}
|
||||
- {x: 128, y: -128}
|
||||
physicsShape:
|
||||
- - {x: 0, y: 93.7025033688}
|
||||
- {x: -128, y: -128}
|
||||
- {x: 128, y: -128}
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 2d009a6b596c7d760800000000000000
|
||||
internalID: 7482667652216324306
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
physicsShape:
|
||||
- - {x: -128, y: 128}
|
||||
- {x: -128, y: -128}
|
||||
- {x: 128, y: -128}
|
||||
- {x: 128, y: 128}
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable:
|
||||
Triangle: 7482667652216324306
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
LFS
BIN
Binary file not shown.
-117
@@ -1,117 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fee21f21c4f146f69dd2eae14a94b4b
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
LFS
BIN
Binary file not shown.
-117
@@ -1,117 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfa8cdc5e73d54ed1876c17a47ff7346
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
LFS
BIN
Binary file not shown.
-117
@@ -1,117 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd23b09c6f27b4754a769858a2020668
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
LFS
BIN
Binary file not shown.
-117
@@ -1,117 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fe839b58f5fd46d695ece8ccf2412a5
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+3
-21
@@ -1,4 +1,3 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Shardok {
|
||||
@@ -126,30 +125,13 @@ namespace Shardok {
|
||||
Debug.Log("Melee sequence complete!");
|
||||
}
|
||||
|
||||
public void TestMove() { StartCoroutine(TestMoveSequence()); }
|
||||
|
||||
private IEnumerator TestMoveSequence() {
|
||||
Debug.Log($"Testing Move (Infantry): {sourceCell} -> {targetCell}");
|
||||
public void TestMove() {
|
||||
Debug.Log($"Testing Move: {sourceCell} -> {targetCell}");
|
||||
if (_moveAnimator != null) {
|
||||
_moveAnimator.AnimateMove(
|
||||
sourceCell,
|
||||
targetCell,
|
||||
Net.Eagle0.Shardok.Api.BattalionView.Types.BattalionTypeId.LightInfantry);
|
||||
_moveAnimator.AnimateMove(sourceCell, targetCell);
|
||||
PlaySound(Net.Eagle0.Shardok.Common.ActionType.Move);
|
||||
} else {
|
||||
Debug.LogWarning("MoveAnimator not available");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(1.5f);
|
||||
|
||||
Debug.Log($"Testing Move (Cavalry): {sourceCell} -> {targetCell}");
|
||||
if (_moveAnimator != null) {
|
||||
_moveAnimator.AnimateMove(
|
||||
sourceCell,
|
||||
targetCell,
|
||||
Net.Eagle0.Shardok.Api.BattalionView.Types.BattalionTypeId.LightCavalry);
|
||||
PlaySound(Net.Eagle0.Shardok.Common.ActionType.Move);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -59,8 +59,8 @@ namespace Shardok {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2? sourcePos = __hexGrid.GetCellCenterPosition(sourceCellIndex);
|
||||
Vector2? targetPos = __hexGrid.GetCellCenterPosition(targetCellIndex);
|
||||
Vector2? sourcePos = __hexGrid.GetCellLocalPosition(sourceCellIndex);
|
||||
Vector2? targetPos = __hexGrid.GetCellLocalPosition(targetCellIndex);
|
||||
|
||||
if (sourcePos == null || targetPos == null) {
|
||||
Debug.LogWarning(
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Shardok {
|
||||
}
|
||||
|
||||
Vector2? sourcePos = __hexGrid.GetCellLocalPosition(sourceCellIndex);
|
||||
Vector2? targetPos = __hexGrid.GetCellCenterPosition(targetCellIndex);
|
||||
Vector2? targetPos = __hexGrid.GetCellLocalPosition(targetCellIndex);
|
||||
|
||||
if (sourcePos == null || targetPos == null) {
|
||||
Debug.LogWarning(
|
||||
|
||||
+217
-137
@@ -7,8 +7,7 @@ using static Net.Eagle0.Shardok.Api.BattalionView.Types;
|
||||
namespace Shardok {
|
||||
/// <summary>
|
||||
/// Animates charge attacks between two hexes.
|
||||
/// Shows attacker's weapon thrusting toward defender with accelerating motion
|
||||
/// and a violent flash on impact.
|
||||
/// Similar to MeleeAnimator but with more dramatic motion for charging units.
|
||||
/// </summary>
|
||||
public class ChargeAnimator : MonoBehaviour {
|
||||
[Header("Weapon Sprites")]
|
||||
@@ -30,41 +29,28 @@ namespace Shardok {
|
||||
[Tooltip("Bone/claw sprite for undead charge")]
|
||||
public Sprite boneSprite;
|
||||
|
||||
[Header("Impact Flash Settings")]
|
||||
[Tooltip("Sprite for impact flash")]
|
||||
public Sprite flashSprite;
|
||||
|
||||
[Tooltip("Color of impact flash")]
|
||||
public Color flashColor = new Color(1f, 1f, 0.8f, 0.9f);
|
||||
|
||||
[Tooltip("Scale of impact flash")]
|
||||
public float flashScale = 35f;
|
||||
|
||||
[Tooltip("Duration of flash")]
|
||||
public float flashDuration = 0.1f;
|
||||
|
||||
[Header("Animation Settings")]
|
||||
[Tooltip("Base scale of weapon sprites")]
|
||||
public float weaponScale = 25.0f;
|
||||
|
||||
[Tooltip("Number of thrusts")]
|
||||
public int thrustCount = 2;
|
||||
[Tooltip("Duration of the charge thrust in seconds")]
|
||||
public float chargeDuration = 0.15f;
|
||||
|
||||
[Tooltip("Duration of each thrust (slow to fast)")]
|
||||
public float thrustDuration = 0.25f;
|
||||
[Tooltip("Number of impact cycles")]
|
||||
public int impactCount = 2;
|
||||
|
||||
[Tooltip("Duration of pullback between thrusts")]
|
||||
public float pullbackDuration = 0.15f;
|
||||
[Tooltip("Distance weapons travel toward each other (in local units)")]
|
||||
public float thrustDistance = 35f;
|
||||
|
||||
[Tooltip("Starting position offset from midpoint (in local units)")]
|
||||
public float startOffset = 60f;
|
||||
|
||||
[Tooltip("End position offset from midpoint (in local units)")]
|
||||
public float endOffset = 15f;
|
||||
[Tooltip("Offset from center point along attack axis (in local units)")]
|
||||
public float weaponOffset = 50f;
|
||||
|
||||
[Tooltip("Base rotation offset for diagonal sprites (degrees)")]
|
||||
public float baseRotationOffset = -45f;
|
||||
|
||||
[Tooltip("Amount of random shake (in local units)")]
|
||||
public float shakeIntensity = 12f;
|
||||
|
||||
private HexGrid __hexGrid;
|
||||
private Coroutine _activeAnimation;
|
||||
|
||||
@@ -73,6 +59,10 @@ namespace Shardok {
|
||||
/// <summary>
|
||||
/// Starts a charge animation between two hex cells.
|
||||
/// </summary>
|
||||
/// <param name="attackerCellIndex">Grid index of the charging hex</param>
|
||||
/// <param name="defenderCellIndex">Grid index of the defending hex</param>
|
||||
/// <param name="attackerType">Battalion type of the attacker</param>
|
||||
/// <param name="defenderType">Battalion type of the defender</param>
|
||||
public void AnimateCharge(
|
||||
int attackerCellIndex,
|
||||
int defenderCellIndex,
|
||||
@@ -89,8 +79,8 @@ namespace Shardok {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2? attackerPos = __hexGrid.GetCellCenterPosition(attackerCellIndex);
|
||||
Vector2? defenderPos = __hexGrid.GetCellCenterPosition(defenderCellIndex);
|
||||
Vector2? attackerPos = __hexGrid.GetCellLocalPosition(attackerCellIndex);
|
||||
Vector2? defenderPos = __hexGrid.GetCellLocalPosition(defenderCellIndex);
|
||||
|
||||
if (attackerPos == null || defenderPos == null) {
|
||||
Debug.LogWarning(
|
||||
@@ -100,148 +90,238 @@ namespace Shardok {
|
||||
|
||||
if (_activeAnimation != null) { StopCoroutine(_activeAnimation); }
|
||||
|
||||
_activeAnimation = StartCoroutine(
|
||||
AnimateChargeThrusts(attackerPos.Value, defenderPos.Value, attackerType));
|
||||
Debug.Log(
|
||||
$"ChargeAnimator: Starting charge from cell {attackerCellIndex} ({attackerType}) to {defenderCellIndex} ({defenderType})");
|
||||
_activeAnimation = StartCoroutine(AnimateChargeClash(
|
||||
attackerPos.Value,
|
||||
defenderPos.Value,
|
||||
attackerType,
|
||||
defenderType));
|
||||
}
|
||||
|
||||
private Sprite GetWeaponSprite(BattalionTypeId type) {
|
||||
private struct WeaponConfig {
|
||||
public Sprite sprite;
|
||||
public float scaleMultiplier;
|
||||
public float violenceMultiplier;
|
||||
}
|
||||
|
||||
private WeaponConfig GetWeaponConfig(BattalionTypeId type) {
|
||||
switch (type) {
|
||||
case BattalionTypeId.LightInfantry: return swordSprite;
|
||||
case BattalionTypeId.HeavyInfantry: return maceSprite ?? swordSprite;
|
||||
case BattalionTypeId.LightInfantry:
|
||||
return new WeaponConfig {
|
||||
sprite = swordSprite,
|
||||
scaleMultiplier = 1.0f,
|
||||
violenceMultiplier = 1.2f
|
||||
};
|
||||
case BattalionTypeId.HeavyInfantry:
|
||||
return new WeaponConfig {
|
||||
sprite = maceSprite ?? swordSprite,
|
||||
scaleMultiplier = 1.2f,
|
||||
violenceMultiplier = 1.4f
|
||||
};
|
||||
case BattalionTypeId.LightCavalry:
|
||||
return smallSpearSprite ?? largeLanceSprite ?? swordSprite;
|
||||
return new WeaponConfig {
|
||||
sprite = smallSpearSprite ?? largeLanceSprite ?? swordSprite,
|
||||
scaleMultiplier = 1.1f,
|
||||
violenceMultiplier = 1.6f
|
||||
};
|
||||
case BattalionTypeId.HeavyCavalry:
|
||||
return largeLanceSprite ?? smallSpearSprite ?? swordSprite;
|
||||
case BattalionTypeId.Longbowmen: return daggerSprite ?? swordSprite;
|
||||
case BattalionTypeId.Undead: return boneSprite ?? swordSprite;
|
||||
default: return swordSprite;
|
||||
return new WeaponConfig {
|
||||
sprite = largeLanceSprite ?? smallSpearSprite ?? swordSprite,
|
||||
scaleMultiplier = 1.4f,
|
||||
violenceMultiplier = 2.0f
|
||||
};
|
||||
case BattalionTypeId.Longbowmen:
|
||||
return new WeaponConfig {
|
||||
sprite = daggerSprite ?? swordSprite,
|
||||
scaleMultiplier = 0.8f,
|
||||
violenceMultiplier = 1.0f
|
||||
};
|
||||
case BattalionTypeId.Undead:
|
||||
return new WeaponConfig {
|
||||
sprite = boneSprite ?? swordSprite,
|
||||
scaleMultiplier = 1.0f,
|
||||
violenceMultiplier = 1.3f
|
||||
};
|
||||
default:
|
||||
return new WeaponConfig {
|
||||
sprite = swordSprite,
|
||||
scaleMultiplier = 1.0f,
|
||||
violenceMultiplier = 1.2f
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private float GetScaleMultiplier(BattalionTypeId type) {
|
||||
switch (type) {
|
||||
case BattalionTypeId.HeavyInfantry: return 1.2f;
|
||||
case BattalionTypeId.LightCavalry: return 1.1f;
|
||||
case BattalionTypeId.HeavyCavalry: return 1.4f;
|
||||
case BattalionTypeId.Longbowmen: return 0.8f;
|
||||
default: return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator AnimateChargeThrusts(
|
||||
private IEnumerator AnimateChargeClash(
|
||||
Vector2 attackerPos,
|
||||
Vector2 defenderPos,
|
||||
BattalionTypeId attackerType) {
|
||||
// Calculate direction toward defender
|
||||
BattalionTypeId attackerType,
|
||||
BattalionTypeId defenderType) {
|
||||
// Calculate midpoint and direction
|
||||
Vector2 midpoint = (attackerPos + defenderPos) / 2f;
|
||||
Vector2 direction = (defenderPos - attackerPos).normalized;
|
||||
float baseAngle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
|
||||
|
||||
// Create attacker weapon
|
||||
Sprite weaponSprite = GetWeaponSprite(attackerType);
|
||||
float scale = weaponScale * GetScaleMultiplier(attackerType);
|
||||
// Get weapon configs
|
||||
WeaponConfig attackerConfig = GetWeaponConfig(attackerType);
|
||||
WeaponConfig defenderConfig = GetWeaponConfig(defenderType);
|
||||
|
||||
// Create weapons
|
||||
GameObject attackerWeapon = CreateWeapon(attackerConfig, baseAngle);
|
||||
GameObject defenderWeapon = CreateWeapon(defenderConfig, baseAngle + 180f);
|
||||
|
||||
float attackerViolence = shakeIntensity * attackerConfig.violenceMultiplier;
|
||||
float defenderViolence = shakeIntensity * defenderConfig.violenceMultiplier;
|
||||
|
||||
// Starting positions offset from midpoint (further back for charge buildup)
|
||||
Vector3 attackerStart = new Vector3(
|
||||
midpoint.x - direction.x * weaponOffset,
|
||||
midpoint.y - direction.y * weaponOffset,
|
||||
10f);
|
||||
Vector3 defenderStart = new Vector3(
|
||||
midpoint.x + direction.x * weaponOffset,
|
||||
midpoint.y + direction.y * weaponOffset,
|
||||
10f);
|
||||
|
||||
// Clash point (closer to midpoint)
|
||||
Vector3 attackerClash = new Vector3(
|
||||
midpoint.x - direction.x * (weaponOffset - thrustDistance),
|
||||
midpoint.y - direction.y * (weaponOffset - thrustDistance),
|
||||
10f);
|
||||
Vector3 defenderClash = new Vector3(
|
||||
midpoint.x + direction.x * (weaponOffset - thrustDistance),
|
||||
midpoint.y + direction.y * (weaponOffset - thrustDistance),
|
||||
10f);
|
||||
|
||||
attackerWeapon.transform.localPosition = attackerStart;
|
||||
defenderWeapon.transform.localPosition = defenderStart;
|
||||
|
||||
// Animate impact cycles
|
||||
for (int i = 0; i < impactCount; i++) {
|
||||
// Charge thrust toward clash point
|
||||
yield return StartCoroutine(AnimateWeaponThrust(
|
||||
attackerWeapon,
|
||||
attackerStart,
|
||||
attackerClash,
|
||||
chargeDuration / 2f,
|
||||
true,
|
||||
attackerViolence));
|
||||
|
||||
yield return StartCoroutine(AnimateWeaponThrust(
|
||||
defenderWeapon,
|
||||
defenderStart,
|
||||
defenderClash,
|
||||
chargeDuration / 2f,
|
||||
true,
|
||||
defenderViolence));
|
||||
|
||||
// Violent impact shake
|
||||
float impactViolence = Mathf.Max(attackerViolence, defenderViolence);
|
||||
yield return StartCoroutine(ImpactShake(
|
||||
attackerWeapon,
|
||||
defenderWeapon,
|
||||
attackerClash,
|
||||
defenderClash,
|
||||
0.08f,
|
||||
impactViolence));
|
||||
|
||||
// Pull back
|
||||
yield return StartCoroutine(AnimateWeaponThrust(
|
||||
attackerWeapon,
|
||||
attackerClash,
|
||||
attackerStart,
|
||||
chargeDuration / 2f,
|
||||
false,
|
||||
attackerViolence * 0.5f));
|
||||
|
||||
yield return StartCoroutine(AnimateWeaponThrust(
|
||||
defenderWeapon,
|
||||
defenderClash,
|
||||
defenderStart,
|
||||
chargeDuration / 2f,
|
||||
false,
|
||||
defenderViolence * 0.5f));
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if (attackerWeapon != null) { Destroy(attackerWeapon); }
|
||||
if (defenderWeapon != null) { Destroy(defenderWeapon); }
|
||||
_activeAnimation = null;
|
||||
}
|
||||
|
||||
private GameObject CreateWeapon(WeaponConfig config, float angle) {
|
||||
GameObject weapon = new GameObject("ChargeWeapon");
|
||||
weapon.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
|
||||
SpriteRenderer renderer = weapon.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = weaponSprite;
|
||||
renderer.sprite = config.sprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
|
||||
weapon.transform.localRotation = Quaternion.Euler(0, 0, baseAngle + baseRotationOffset);
|
||||
weapon.transform.localRotation = Quaternion.Euler(0, 0, angle + baseRotationOffset);
|
||||
float scale = weaponScale * config.scaleMultiplier;
|
||||
weapon.transform.localScale = new Vector3(scale, scale, scale);
|
||||
|
||||
// Calculate positions
|
||||
Vector3 startPos = new Vector3(
|
||||
midpoint.x - direction.x * startOffset,
|
||||
midpoint.y - direction.y * startOffset,
|
||||
10f);
|
||||
Vector3 endPos = new Vector3(
|
||||
midpoint.x + direction.x * endOffset,
|
||||
midpoint.y + direction.y * endOffset,
|
||||
10f);
|
||||
|
||||
weapon.transform.localPosition = startPos;
|
||||
|
||||
// Animate thrusts
|
||||
for (int i = 0; i < thrustCount; i++) {
|
||||
// Thrust forward - starts slow, accelerates
|
||||
float elapsed = 0f;
|
||||
while (elapsed < thrustDuration) {
|
||||
if (weapon == null) yield break;
|
||||
|
||||
float t = elapsed / thrustDuration;
|
||||
// Cubic ease-in for accelerating thrust
|
||||
float easeT = t * t * t;
|
||||
|
||||
weapon.transform.localPosition = Vector3.Lerp(startPos, endPos, easeT);
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
weapon.transform.localPosition = endPos;
|
||||
|
||||
// Impact flash
|
||||
if (flashSprite != null) {
|
||||
yield return StartCoroutine(ShowImpactFlash(endPos, direction));
|
||||
}
|
||||
|
||||
// Pullback - smooth ease out
|
||||
elapsed = 0f;
|
||||
while (elapsed < pullbackDuration) {
|
||||
if (weapon == null) yield break;
|
||||
|
||||
float t = elapsed / pullbackDuration;
|
||||
// Ease out for smooth pullback
|
||||
float easeT = t * (2f - t);
|
||||
|
||||
weapon.transform.localPosition = Vector3.Lerp(endPos, startPos, easeT);
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
weapon.transform.localPosition = startPos;
|
||||
|
||||
// Brief pause between thrusts
|
||||
if (i < thrustCount - 1) { yield return new WaitForSeconds(0.05f); }
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if (weapon != null) { Destroy(weapon); }
|
||||
_activeAnimation = null;
|
||||
return weapon;
|
||||
}
|
||||
|
||||
private IEnumerator ShowImpactFlash(Vector3 position, Vector2 direction) {
|
||||
GameObject flash = new GameObject("ChargeFlash");
|
||||
flash.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
flash.transform.localPosition = position;
|
||||
|
||||
SpriteRenderer renderer = flash.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = flashSprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.color = flashColor;
|
||||
|
||||
private IEnumerator AnimateWeaponThrust(
|
||||
GameObject weapon,
|
||||
Vector3 fromPos,
|
||||
Vector3 toPos,
|
||||
float duration,
|
||||
bool isCharging,
|
||||
float violence) {
|
||||
float elapsed = 0f;
|
||||
while (elapsed < flashDuration) {
|
||||
float t = elapsed / flashDuration;
|
||||
|
||||
// Quick expand then fade
|
||||
float expandT = t < 0.3f ? t / 0.3f : 1f;
|
||||
float scale = flashScale * (0.5f + 0.5f * expandT);
|
||||
flash.transform.localScale = Vector3.one * scale;
|
||||
while (elapsed < duration) {
|
||||
if (weapon == null) { yield break; }
|
||||
|
||||
// Fade out
|
||||
Color c = flashColor;
|
||||
c.a = flashColor.a * (1f - t);
|
||||
renderer.color = c;
|
||||
float t = elapsed / duration;
|
||||
// More aggressive ease for charge, smoother for retreat
|
||||
float smoothT = isCharging ? t * t * t : t * (2f - t);
|
||||
|
||||
// Position with shake
|
||||
Vector3 shake = new Vector3(
|
||||
Random.Range(-violence, violence),
|
||||
Random.Range(-violence, violence),
|
||||
0f);
|
||||
weapon.transform.localPosition = Vector3.Lerp(fromPos, toPos, smoothT) + shake;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Destroy(flash);
|
||||
if (weapon != null) { weapon.transform.localPosition = toPos; }
|
||||
}
|
||||
|
||||
private IEnumerator ImpactShake(
|
||||
GameObject weapon1,
|
||||
GameObject weapon2,
|
||||
Vector3 pos1,
|
||||
Vector3 pos2,
|
||||
float duration,
|
||||
float violence) {
|
||||
float elapsed = 0f;
|
||||
float intensity = violence * 2.5f; // Extra intense for charge impact
|
||||
|
||||
while (elapsed < duration) {
|
||||
if (weapon1 == null || weapon2 == null) { yield break; }
|
||||
|
||||
Vector3 shake1 = new Vector3(
|
||||
Random.Range(-intensity, intensity),
|
||||
Random.Range(-intensity, intensity),
|
||||
0f);
|
||||
Vector3 shake2 = new Vector3(
|
||||
Random.Range(-intensity, intensity),
|
||||
Random.Range(-intensity, intensity),
|
||||
0f);
|
||||
|
||||
weapon1.transform.localPosition = pos1 + shake1;
|
||||
weapon2.transform.localPosition = pos2 + shake2;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+17
-40
@@ -12,13 +12,7 @@ namespace Shardok {
|
||||
public Sprite dropletSprite;
|
||||
|
||||
[Tooltip("Number of droplets")]
|
||||
public int dropletCount = 25;
|
||||
|
||||
[Tooltip("Minimum droplet scale")]
|
||||
public float dropletScaleMin = 5f;
|
||||
|
||||
[Tooltip("Maximum droplet scale")]
|
||||
public float dropletScaleMax = 15f;
|
||||
public int dropletCount = 12;
|
||||
|
||||
[Tooltip("Color of water")]
|
||||
public Color waterColor = new Color(0.3f, 0.6f, 0.9f, 0.8f);
|
||||
@@ -28,29 +22,23 @@ namespace Shardok {
|
||||
public Sprite steamSprite;
|
||||
|
||||
[Tooltip("Number of steam puffs")]
|
||||
public int steamCount = 8;
|
||||
public int steamCount = 5;
|
||||
|
||||
[Tooltip("Color of steam")]
|
||||
public Color steamColor = new Color(0.9f, 0.9f, 0.9f, 0.6f);
|
||||
|
||||
[Header("Animation Settings")]
|
||||
[Tooltip("Height water falls from")]
|
||||
public float fallHeight = 100f;
|
||||
public float fallHeight = 60f;
|
||||
|
||||
[Tooltip("Spread radius of the water")]
|
||||
public float waterSpread = 40f;
|
||||
public float waterSpread = 25f;
|
||||
|
||||
[Tooltip("Duration of the water fall")]
|
||||
public float fallDuration = 0.5f;
|
||||
|
||||
[Tooltip("Time spread for staggered droplet launch")]
|
||||
public float launchSpread = 0.25f;
|
||||
|
||||
[Tooltip("Horizontal chaos during fall")]
|
||||
public float fallChaos = 15f;
|
||||
public float fallDuration = 0.3f;
|
||||
|
||||
[Tooltip("Duration of the steam effect")]
|
||||
public float steamDuration = 0.5f;
|
||||
public float steamDuration = 0.4f;
|
||||
|
||||
private HexGrid __hexGrid;
|
||||
|
||||
@@ -84,7 +72,6 @@ namespace Shardok {
|
||||
// Create droplets
|
||||
GameObject[] droplets = new GameObject[dropletCount];
|
||||
Vector2[] dropletOffsets = new Vector2[dropletCount];
|
||||
Vector2[] dropletChaos = new Vector2[dropletCount];
|
||||
float[] dropletDelays = new float[dropletCount];
|
||||
|
||||
for (int i = 0; i < dropletCount; i++) {
|
||||
@@ -97,23 +84,17 @@ namespace Shardok {
|
||||
renderer.sprite = dropletSprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.color = waterColor;
|
||||
droplet.transform.localScale =
|
||||
Vector3.one * Random.Range(dropletScaleMin, dropletScaleMax);
|
||||
droplet.transform.localScale = Vector3.one * Random.Range(4f, 8f);
|
||||
|
||||
float angle = Random.Range(0f, Mathf.PI * 2f);
|
||||
float radius = waterSpread * Random.Range(0f, 1f);
|
||||
dropletOffsets[i] = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius;
|
||||
dropletDelays[i] = Random.Range(0f, launchSpread);
|
||||
dropletDelays[i] = Random.Range(0f, fallDuration * 0.3f);
|
||||
|
||||
// Random horizontal chaos direction for each droplet
|
||||
float chaosAngle = Random.Range(0f, Mathf.PI * 2f);
|
||||
dropletChaos[i] =
|
||||
new Vector2(Mathf.Cos(chaosAngle), Mathf.Sin(chaosAngle)) * fallChaos;
|
||||
|
||||
// Start above with some horizontal spread
|
||||
// Start above
|
||||
droplet.transform.localPosition = new Vector3(
|
||||
target.x + dropletOffsets[i].x * 0.3f,
|
||||
target.y + fallHeight + Random.Range(-10f, 10f),
|
||||
target.x + dropletOffsets[i].x,
|
||||
target.y + fallHeight + dropletOffsets[i].y * 0.5f,
|
||||
5f);
|
||||
|
||||
droplets[i] = droplet;
|
||||
@@ -121,27 +102,23 @@ namespace Shardok {
|
||||
|
||||
// Animate falling water
|
||||
float elapsed = 0f;
|
||||
float totalDuration = fallDuration + launchSpread;
|
||||
|
||||
while (elapsed < totalDuration) {
|
||||
while (elapsed < fallDuration) {
|
||||
float t = elapsed / fallDuration;
|
||||
|
||||
for (int i = 0; i < droplets.Length; i++) {
|
||||
if (droplets[i] == null) continue;
|
||||
|
||||
float dropletT = Mathf.Clamp01(
|
||||
(elapsed - dropletDelays[i]) /
|
||||
(fallDuration - dropletDelays[i] * 0.5f));
|
||||
(elapsed - dropletDelays[i]) / (fallDuration - dropletDelays[i]));
|
||||
if (dropletT <= 0) continue;
|
||||
|
||||
// Accelerating fall
|
||||
float fallT = dropletT * dropletT;
|
||||
|
||||
// Add chaotic horizontal movement (sine wave pattern)
|
||||
float chaosT = Mathf.Sin(dropletT * Mathf.PI * 2f) * (1f - dropletT);
|
||||
Vector2 chaos = dropletChaos[i] * chaosT;
|
||||
|
||||
float x = target.x + dropletOffsets[i].x + chaos.x;
|
||||
float y = target.y + fallHeight * (1f - fallT);
|
||||
droplets[i].transform.localPosition = new Vector3(x, y, 5f);
|
||||
droplets[i].transform.localPosition =
|
||||
new Vector3(target.x + dropletOffsets[i].x, y, 5f);
|
||||
}
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace Shardok {
|
||||
[Tooltip("Mace/hammer sprite for heavy infantry")]
|
||||
public Sprite maceSprite;
|
||||
|
||||
[Tooltip("Falchion sprite for light cavalry")]
|
||||
public Sprite falchionSprite;
|
||||
[Tooltip("Small spear sprite for light cavalry")]
|
||||
public Sprite smallSpearSprite;
|
||||
|
||||
[Tooltip("Two-handed sword sprite for heavy cavalry")]
|
||||
public Sprite twoHandedSwordSprite;
|
||||
[Tooltip("Large spear/lance sprite for heavy cavalry")]
|
||||
public Sprite largeSpearSprite;
|
||||
|
||||
[Tooltip("Dagger sprite for longbowmen")]
|
||||
public Sprite daggerSprite;
|
||||
@@ -30,57 +30,15 @@ namespace Shardok {
|
||||
[Tooltip("Bone/fist sprite for undead")]
|
||||
public Sprite boneSprite;
|
||||
|
||||
[Header("Weapon Orientation - Sword")]
|
||||
[Tooltip("Base rotation offset for sword (degrees)")]
|
||||
public float swordRotationOffset = -45f;
|
||||
|
||||
[Tooltip("Flip sword horizontally")]
|
||||
public bool swordFlipHorizontal = false;
|
||||
|
||||
[Header("Weapon Orientation - Mace")]
|
||||
[Tooltip("Base rotation offset for mace (degrees)")]
|
||||
public float maceRotationOffset = -45f;
|
||||
|
||||
[Tooltip("Flip mace horizontally")]
|
||||
public bool maceFlipHorizontal = false;
|
||||
|
||||
[Header("Weapon Orientation - Falchion")]
|
||||
[Tooltip("Base rotation offset for falchion (degrees)")]
|
||||
public float falchionRotationOffset = -45f;
|
||||
|
||||
[Tooltip("Flip falchion horizontally")]
|
||||
public bool falchionFlipHorizontal = false;
|
||||
|
||||
[Header("Weapon Orientation - Two-Handed Sword")]
|
||||
[Tooltip("Base rotation offset for two-handed sword (degrees)")]
|
||||
public float twoHandedSwordRotationOffset = -45f;
|
||||
|
||||
[Tooltip("Flip two-handed sword horizontally")]
|
||||
public bool twoHandedSwordFlipHorizontal = false;
|
||||
|
||||
[Header("Weapon Orientation - Dagger")]
|
||||
[Tooltip("Base rotation offset for dagger (degrees)")]
|
||||
public float daggerRotationOffset = -45f;
|
||||
|
||||
[Tooltip("Flip dagger horizontally")]
|
||||
public bool daggerFlipHorizontal = false;
|
||||
|
||||
[Header("Weapon Orientation - Bone")]
|
||||
[Tooltip("Base rotation offset for bone (degrees)")]
|
||||
public float boneRotationOffset = -45f;
|
||||
|
||||
[Tooltip("Flip bone horizontally")]
|
||||
public bool boneFlipHorizontal = false;
|
||||
|
||||
[Header("Animation Settings")]
|
||||
[Tooltip("Base scale of weapon sprites")]
|
||||
public float weaponScale = 20.0f;
|
||||
|
||||
[Tooltip("Duration of a single swing cycle in seconds")]
|
||||
public float clashDuration = 0.3f;
|
||||
[Tooltip("Duration of a single clash cycle in seconds")]
|
||||
public float clashDuration = 0.12f;
|
||||
|
||||
[Tooltip("Number of swing cycles")]
|
||||
public int clashCount = 2;
|
||||
[Tooltip("Number of clash cycles")]
|
||||
public int clashCount = 3;
|
||||
|
||||
[Tooltip("Distance weapons travel toward each other (in local units)")]
|
||||
public float thrustDistance = 25f;
|
||||
@@ -88,11 +46,14 @@ namespace Shardok {
|
||||
[Tooltip("Offset from center point along attack axis (in local units)")]
|
||||
public float weaponOffset = 40f;
|
||||
|
||||
[Tooltip("Swing arc angle for swinging weapons (degrees)")]
|
||||
public float swingArc = 90f;
|
||||
[Tooltip("Base rotation offset for diagonal sprites (degrees)")]
|
||||
public float baseRotationOffset = -45f;
|
||||
|
||||
[Tooltip("Amount of shake at impact only (in local units)")]
|
||||
public float shakeIntensity = 3f;
|
||||
[Tooltip("Swing arc angle for swinging weapons (degrees)")]
|
||||
public float swingArc = 60f;
|
||||
|
||||
[Tooltip("Amount of random shake (in local units)")]
|
||||
public float shakeIntensity = 8f;
|
||||
|
||||
private HexGrid __hexGrid;
|
||||
private Coroutine _activeAnimation;
|
||||
@@ -111,8 +72,8 @@ namespace Shardok {
|
||||
int defenderCellIndex,
|
||||
BattalionTypeId attackerType,
|
||||
BattalionTypeId defenderType) {
|
||||
if (swordSprite == null && maceSprite == null && falchionSprite == null &&
|
||||
twoHandedSwordSprite == null && daggerSprite == null && boneSprite == null) {
|
||||
if (swordSprite == null && maceSprite == null && smallSpearSprite == null &&
|
||||
largeSpearSprite == null && daggerSprite == null && boneSprite == null) {
|
||||
Debug.LogWarning("MeleeAnimator: No weapon sprites assigned");
|
||||
return;
|
||||
}
|
||||
@@ -143,8 +104,7 @@ namespace Shardok {
|
||||
public Sprite sprite;
|
||||
public bool swings; // true = swing arc, false = thrust
|
||||
public float scaleMultiplier;
|
||||
public float rotationOffset;
|
||||
public bool flipHorizontal;
|
||||
public float violenceMultiplier; // shake intensity multiplier
|
||||
}
|
||||
|
||||
private WeaponConfig GetWeaponConfig(BattalionTypeId type) {
|
||||
@@ -154,66 +114,49 @@ namespace Shardok {
|
||||
sprite = swordSprite,
|
||||
swings = true,
|
||||
scaleMultiplier = 1.0f,
|
||||
rotationOffset = swordRotationOffset,
|
||||
flipHorizontal = swordFlipHorizontal
|
||||
violenceMultiplier = 0.3f
|
||||
};
|
||||
case BattalionTypeId.HeavyInfantry:
|
||||
return new WeaponConfig {
|
||||
sprite = maceSprite ?? swordSprite,
|
||||
swings = true,
|
||||
scaleMultiplier = 1.2f,
|
||||
rotationOffset =
|
||||
maceSprite != null ? maceRotationOffset : swordRotationOffset,
|
||||
flipHorizontal =
|
||||
maceSprite != null ? maceFlipHorizontal : swordFlipHorizontal
|
||||
violenceMultiplier = 0.4f
|
||||
};
|
||||
case BattalionTypeId.LightCavalry:
|
||||
return new WeaponConfig {
|
||||
sprite = falchionSprite ?? swordSprite,
|
||||
swings = true,
|
||||
scaleMultiplier = 1.0f,
|
||||
rotationOffset = falchionSprite != null ? falchionRotationOffset
|
||||
: swordRotationOffset,
|
||||
flipHorizontal = falchionSprite != null ? falchionFlipHorizontal
|
||||
: swordFlipHorizontal
|
||||
sprite = smallSpearSprite ?? largeSpearSprite ?? swordSprite,
|
||||
swings = false,
|
||||
scaleMultiplier = 0.9f,
|
||||
violenceMultiplier = 1.0f
|
||||
};
|
||||
case BattalionTypeId.HeavyCavalry:
|
||||
return new WeaponConfig {
|
||||
sprite = twoHandedSwordSprite ?? swordSprite,
|
||||
swings = true,
|
||||
sprite = largeSpearSprite ?? smallSpearSprite ?? swordSprite,
|
||||
swings = false,
|
||||
scaleMultiplier = 1.4f,
|
||||
rotationOffset = twoHandedSwordSprite != null ? twoHandedSwordRotationOffset
|
||||
: swordRotationOffset,
|
||||
flipHorizontal = twoHandedSwordSprite != null ? twoHandedSwordFlipHorizontal
|
||||
: swordFlipHorizontal
|
||||
violenceMultiplier = 1.8f
|
||||
};
|
||||
case BattalionTypeId.Longbowmen:
|
||||
return new WeaponConfig {
|
||||
sprite = daggerSprite ?? swordSprite,
|
||||
swings = false,
|
||||
scaleMultiplier = 0.7f,
|
||||
rotationOffset =
|
||||
daggerSprite != null ? daggerRotationOffset : swordRotationOffset,
|
||||
flipHorizontal =
|
||||
daggerSprite != null ? daggerFlipHorizontal : swordFlipHorizontal
|
||||
violenceMultiplier = 0.8f
|
||||
};
|
||||
case BattalionTypeId.Undead:
|
||||
return new WeaponConfig {
|
||||
sprite = boneSprite ?? swordSprite,
|
||||
swings = true,
|
||||
scaleMultiplier = 1.0f,
|
||||
rotationOffset =
|
||||
boneSprite != null ? boneRotationOffset : swordRotationOffset,
|
||||
flipHorizontal =
|
||||
boneSprite != null ? boneFlipHorizontal : swordFlipHorizontal
|
||||
violenceMultiplier = 0.5f
|
||||
};
|
||||
default:
|
||||
return new WeaponConfig {
|
||||
sprite = swordSprite,
|
||||
swings = true,
|
||||
scaleMultiplier = 1.0f,
|
||||
rotationOffset = swordRotationOffset,
|
||||
flipHorizontal = swordFlipHorizontal
|
||||
violenceMultiplier = 0.3f
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -238,6 +181,8 @@ namespace Shardok {
|
||||
|
||||
bool attackerSwings = attackerConfig.swings;
|
||||
bool defenderSwings = defenderConfig.swings;
|
||||
float attackerViolence = shakeIntensity * attackerConfig.violenceMultiplier;
|
||||
float defenderViolence = shakeIntensity * defenderConfig.violenceMultiplier;
|
||||
|
||||
// Starting positions offset from midpoint
|
||||
Vector3 attackerStart = new Vector3(
|
||||
@@ -262,15 +207,15 @@ namespace Shardok {
|
||||
attackerWeapon.transform.localPosition = attackerStart;
|
||||
defenderWeapon.transform.localPosition = defenderStart;
|
||||
|
||||
float attackerBaseAngle = baseAngle + attackerConfig.rotationOffset;
|
||||
float defenderBaseAngle = baseAngle + 180f + defenderConfig.rotationOffset;
|
||||
float attackerBaseAngle = baseAngle + baseRotationOffset;
|
||||
float defenderBaseAngle = baseAngle + 180f + baseRotationOffset;
|
||||
|
||||
// Animate swing cycles with alternating swing directions
|
||||
// Animate clash cycles with alternating swing directions
|
||||
for (int i = 0; i < clashCount; i++) {
|
||||
// Alternate swing direction each cycle
|
||||
float swingDirection = (i % 2 == 0) ? 1f : -1f;
|
||||
|
||||
// Swing toward clash point
|
||||
// Swing/thrust toward clash point
|
||||
yield return StartCoroutine(AnimateWeaponSwing(
|
||||
attackerWeapon,
|
||||
attackerStart,
|
||||
@@ -279,7 +224,8 @@ namespace Shardok {
|
||||
attackerSwings ? -swingArc * swingDirection : 0f,
|
||||
attackerSwings ? 0f : 0f,
|
||||
clashDuration / 2f,
|
||||
true));
|
||||
true,
|
||||
attackerViolence));
|
||||
|
||||
yield return StartCoroutine(AnimateWeaponSwing(
|
||||
defenderWeapon,
|
||||
@@ -289,18 +235,20 @@ namespace Shardok {
|
||||
defenderSwings ? swingArc * swingDirection : 0f,
|
||||
defenderSwings ? 0f : 0f,
|
||||
clashDuration / 2f,
|
||||
true));
|
||||
true,
|
||||
defenderViolence));
|
||||
|
||||
// Brief shake at impact
|
||||
// Brief violent shake at impact (use max violence of both)
|
||||
float impactViolence = Mathf.Max(attackerViolence, defenderViolence);
|
||||
yield return StartCoroutine(ImpactShake(
|
||||
attackerWeapon,
|
||||
defenderWeapon,
|
||||
attackerClash,
|
||||
defenderClash,
|
||||
0.05f,
|
||||
shakeIntensity));
|
||||
impactViolence));
|
||||
|
||||
// Swing back
|
||||
// Swing/pull back
|
||||
yield return StartCoroutine(AnimateWeaponSwing(
|
||||
attackerWeapon,
|
||||
attackerClash,
|
||||
@@ -309,7 +257,8 @@ namespace Shardok {
|
||||
0f,
|
||||
attackerSwings ? -swingArc * swingDirection : 0f,
|
||||
clashDuration / 2f,
|
||||
false));
|
||||
false,
|
||||
attackerViolence));
|
||||
|
||||
yield return StartCoroutine(AnimateWeaponSwing(
|
||||
defenderWeapon,
|
||||
@@ -319,7 +268,8 @@ namespace Shardok {
|
||||
0f,
|
||||
defenderSwings ? swingArc * swingDirection : 0f,
|
||||
clashDuration / 2f,
|
||||
false));
|
||||
false,
|
||||
defenderViolence));
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
@@ -336,10 +286,9 @@ namespace Shardok {
|
||||
renderer.sprite = config.sprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
|
||||
weapon.transform.localRotation = Quaternion.Euler(0, 0, angle + config.rotationOffset);
|
||||
weapon.transform.localRotation = Quaternion.Euler(0, 0, angle + baseRotationOffset);
|
||||
float scale = weaponScale * config.scaleMultiplier;
|
||||
float xScale = config.flipHorizontal ? -scale : scale;
|
||||
weapon.transform.localScale = new Vector3(xScale, scale, scale);
|
||||
weapon.transform.localScale = new Vector3(scale, scale, scale);
|
||||
|
||||
return weapon;
|
||||
}
|
||||
@@ -352,20 +301,25 @@ namespace Shardok {
|
||||
float fromAngleOffset,
|
||||
float toAngleOffset,
|
||||
float duration,
|
||||
bool isAttacking) {
|
||||
bool isAttacking,
|
||||
float violence) {
|
||||
float elapsed = 0f;
|
||||
|
||||
while (elapsed < duration) {
|
||||
if (weapon == null) { yield break; }
|
||||
|
||||
float t = elapsed / duration;
|
||||
// Smooth ease for deliberate swing motion
|
||||
// Aggressive ease for attack, smoother for retreat
|
||||
float smoothT = isAttacking ? t * t * (3f - 2f * t) : t * (2f - t);
|
||||
|
||||
// Clean position interpolation - no shake during swing
|
||||
weapon.transform.localPosition = Vector3.Lerp(fromPos, toPos, smoothT);
|
||||
// Position with shake (scaled by violence)
|
||||
Vector3 shake = new Vector3(
|
||||
Random.Range(-violence, violence),
|
||||
Random.Range(-violence, violence),
|
||||
0f);
|
||||
weapon.transform.localPosition = Vector3.Lerp(fromPos, toPos, smoothT) + shake;
|
||||
|
||||
// Rotation arc for swinging weapons
|
||||
// Rotation (for swinging weapons)
|
||||
float currentAngle =
|
||||
Mathf.Lerp(baseAngle + fromAngleOffset, baseAngle + toAngleOffset, smoothT);
|
||||
weapon.transform.localRotation = Quaternion.Euler(0, 0, currentAngle);
|
||||
|
||||
@@ -28,102 +28,40 @@ namespace Shardok {
|
||||
[Tooltip("Sprite for trail particles")]
|
||||
public Sprite trailSprite;
|
||||
|
||||
[Tooltip("Number of trail particles in continuous trail")]
|
||||
public int trailParticleCount = 20;
|
||||
[Tooltip("Number of trail particles")]
|
||||
public int trailParticleCount = 8;
|
||||
|
||||
[Tooltip("Scale of trail particles")]
|
||||
public float trailScale = 50f;
|
||||
public float trailScale = 8f;
|
||||
|
||||
[Tooltip("Color of trail at front (hottest)")]
|
||||
public Color trailColorHot = new Color(1f, 0.9f, 0.6f, 0.9f);
|
||||
|
||||
[Tooltip("Color of trail at back (cooler)")]
|
||||
public Color trailColorCool = new Color(1f, 0.4f, 0.1f, 0.6f);
|
||||
|
||||
[Tooltip("Trail spread width")]
|
||||
public float trailSpread = 12f;
|
||||
[Tooltip("Color of trail")]
|
||||
public Color trailColor = new Color(1f, 0.7f, 0.3f, 0.8f);
|
||||
|
||||
[Header("Explosion Settings")]
|
||||
[Tooltip("Sprite for explosion")]
|
||||
public Sprite explosionSprite;
|
||||
|
||||
[Tooltip("Initial scale of explosion")]
|
||||
public float explosionStartScale = 20f;
|
||||
public float explosionStartScale = 15f;
|
||||
|
||||
[Tooltip("Final scale of explosion")]
|
||||
public float explosionEndScale = 250f;
|
||||
public float explosionEndScale = 50f;
|
||||
|
||||
[Tooltip("Color of explosion core")]
|
||||
public Color explosionColor = new Color(1f, 0.6f, 0.2f, 1f);
|
||||
[Tooltip("Color of explosion")]
|
||||
public Color explosionColor = new Color(1f, 0.4f, 0.1f, 1f);
|
||||
|
||||
[Tooltip("Number of fire sparks")]
|
||||
public int sparkCount = 8;
|
||||
[Tooltip("Number of sparks")]
|
||||
public int sparkCount = 6;
|
||||
|
||||
[Tooltip("Spark spread distance")]
|
||||
public float sparkSpread = 50f;
|
||||
|
||||
[Header("Debris Settings")]
|
||||
[Tooltip("Sprite for rock debris")]
|
||||
public Sprite debrisSprite;
|
||||
|
||||
[Tooltip("Number of debris rocks")]
|
||||
public int debrisCount = 6;
|
||||
|
||||
[Tooltip("Scale of debris rocks")]
|
||||
public float debrisScale = 6f;
|
||||
|
||||
[Tooltip("Debris spread distance")]
|
||||
public float debrisSpread = 60f;
|
||||
|
||||
[Tooltip("Color of debris")]
|
||||
public Color debrisColor = new Color(0.4f, 0.3f, 0.2f, 1f);
|
||||
|
||||
[Header("Meteor Rotation")]
|
||||
[Tooltip("Rotation speed in degrees per second")]
|
||||
public float rotationSpeed = 360f;
|
||||
|
||||
[Header("Charging Settings (MeteorStart)")]
|
||||
[Tooltip("Sprite for charging glow")]
|
||||
public Sprite chargeSprite;
|
||||
|
||||
[Tooltip("Color of charging effect")]
|
||||
public Color chargeColor = new Color(1f, 0.6f, 0.2f, 0.7f);
|
||||
|
||||
[Tooltip("Starting scale of charge glow")]
|
||||
public float chargeStartScale = 5f;
|
||||
|
||||
[Tooltip("Ending scale of charge glow")]
|
||||
public float chargeEndScale = 25f;
|
||||
|
||||
[Tooltip("Duration of charge animation")]
|
||||
public float chargeDuration = 0.5f;
|
||||
|
||||
[Header("Targeting Settings (MeteorTarget)")]
|
||||
[Tooltip("Sprite for target indicator")]
|
||||
public Sprite targetSprite;
|
||||
|
||||
[Tooltip("Color of target indicator")]
|
||||
public Color targetColor = new Color(1f, 0.3f, 0.1f, 0.6f);
|
||||
|
||||
[Tooltip("Scale of target indicator")]
|
||||
public float targetScale = 30f;
|
||||
|
||||
[Tooltip("Duration of target animation")]
|
||||
public float targetDuration = 0.4f;
|
||||
|
||||
[Header("Cancel Settings (MeteorCancel)")]
|
||||
[Tooltip("Color of fizzle effect")]
|
||||
public Color cancelColor = new Color(0.5f, 0.5f, 0.5f, 0.8f);
|
||||
|
||||
[Tooltip("Duration of cancel animation")]
|
||||
public float cancelDuration = 0.3f;
|
||||
public float sparkSpread = 40f;
|
||||
|
||||
[Header("Animation Settings")]
|
||||
[Tooltip("Duration of meteor fall")]
|
||||
public float fallDuration = 0.8f;
|
||||
public float fallDuration = 0.4f;
|
||||
|
||||
[Tooltip("Duration of explosion")]
|
||||
public float explosionDuration = 0.6f;
|
||||
public float explosionDuration = 0.5f;
|
||||
|
||||
private HexGrid __hexGrid;
|
||||
|
||||
@@ -149,315 +87,6 @@ namespace Shardok {
|
||||
StartCoroutine(AnimateMeteor(targetPos.Value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animates meteor charging effect at the caster's position.
|
||||
/// Shows a growing fiery glow gathering energy.
|
||||
/// </summary>
|
||||
public void AnimateMeteorStart(int sourceCellIndex) {
|
||||
if (__hexGrid == null || __hexGrid.gridCanvas == null) {
|
||||
Debug.LogWarning("MeteorAnimator: No _hexGrid or gridCanvas available");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2? sourcePos = __hexGrid.GetCellCenterPosition(sourceCellIndex);
|
||||
|
||||
if (sourcePos == null) {
|
||||
Debug.LogWarning($"MeteorAnimator: Invalid cell index {sourceCellIndex}");
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(AnimateCharge(sourcePos.Value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animates meteor targeting indicator at destination.
|
||||
/// Shows a pulsing circle/crosshairs marking the target.
|
||||
/// </summary>
|
||||
public void AnimateMeteorTarget(int targetCellIndex) {
|
||||
if (__hexGrid == null || __hexGrid.gridCanvas == null) {
|
||||
Debug.LogWarning("MeteorAnimator: No _hexGrid or gridCanvas available");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2? targetPos = __hexGrid.GetCellCenterPosition(targetCellIndex);
|
||||
|
||||
if (targetPos == null) {
|
||||
Debug.LogWarning($"MeteorAnimator: Invalid cell index {targetCellIndex}");
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(AnimateTarget(targetPos.Value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animates meteor cancel/fizzle effect at the caster's position.
|
||||
/// Shows the charging energy dissipating.
|
||||
/// </summary>
|
||||
public void AnimateMeteorCancel(int sourceCellIndex) {
|
||||
if (__hexGrid == null || __hexGrid.gridCanvas == null) {
|
||||
Debug.LogWarning("MeteorAnimator: No _hexGrid or gridCanvas available");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2? sourcePos = __hexGrid.GetCellCenterPosition(sourceCellIndex);
|
||||
|
||||
if (sourcePos == null) {
|
||||
Debug.LogWarning($"MeteorAnimator: Invalid cell index {sourceCellIndex}");
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(AnimateCancel(sourcePos.Value));
|
||||
}
|
||||
|
||||
private IEnumerator AnimateCharge(Vector2 source) {
|
||||
Sprite sprite = chargeSprite ?? explosionSprite;
|
||||
if (sprite == null) yield break;
|
||||
|
||||
// Create charging glow
|
||||
GameObject glow = new GameObject("MeteorCharge");
|
||||
glow.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
glow.transform.localPosition = new Vector3(source.x, source.y, 5f);
|
||||
|
||||
SpriteRenderer renderer = glow.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = sprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.color = chargeColor;
|
||||
|
||||
// Create rising particles
|
||||
int particleCount = 5;
|
||||
GameObject[] particles = new GameObject[particleCount];
|
||||
Vector2[] particleOffsets = new Vector2[particleCount];
|
||||
|
||||
for (int i = 0; i < particleCount; i++) {
|
||||
if (trailSprite == null) break;
|
||||
|
||||
GameObject particle = new GameObject($"ChargeParticle_{i}");
|
||||
particle.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
|
||||
SpriteRenderer pRenderer = particle.AddComponent<SpriteRenderer>();
|
||||
pRenderer.sprite = trailSprite;
|
||||
pRenderer.sortingLayerName = "Effects";
|
||||
pRenderer.color = chargeColor;
|
||||
particle.transform.localScale = Vector3.one * trailScale;
|
||||
|
||||
float angle = (float)i / particleCount * Mathf.PI * 2f;
|
||||
particleOffsets[i] = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * 20f;
|
||||
particles[i] = particle;
|
||||
}
|
||||
|
||||
// Animation
|
||||
float elapsed = 0f;
|
||||
while (elapsed < chargeDuration) {
|
||||
float t = elapsed / chargeDuration;
|
||||
float easeT = t * (2f - t); // Ease out
|
||||
|
||||
// Grow glow
|
||||
float scale = Mathf.Lerp(chargeStartScale, chargeEndScale, easeT);
|
||||
glow.transform.localScale = Vector3.one * scale;
|
||||
|
||||
// Pulse alpha
|
||||
Color c = chargeColor;
|
||||
c.a = chargeColor.a * (0.7f + 0.3f * Mathf.Sin(elapsed * 12f));
|
||||
renderer.color = c;
|
||||
|
||||
// Particles spiral inward and upward
|
||||
for (int i = 0; i < particles.Length; i++) {
|
||||
if (particles[i] == null) continue;
|
||||
|
||||
float particleT = (t + (float)i / particleCount) % 1f;
|
||||
float radius = 20f * (1f - particleT);
|
||||
float angle =
|
||||
particleT * Mathf.PI * 4f + (float)i / particleCount * Mathf.PI * 2f;
|
||||
Vector2 offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius;
|
||||
|
||||
particles[i].transform.localPosition = new Vector3(
|
||||
source.x + offset.x,
|
||||
source.y + offset.y + particleT * 15f,
|
||||
4f);
|
||||
|
||||
var pRenderer = particles[i].GetComponent<SpriteRenderer>();
|
||||
if (pRenderer != null) {
|
||||
Color pc = chargeColor;
|
||||
pc.a = chargeColor.a * (1f - particleT * 0.5f);
|
||||
pRenderer.color = pc;
|
||||
}
|
||||
}
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Brief flash at end
|
||||
glow.transform.localScale = Vector3.one * chargeEndScale * 1.2f;
|
||||
Color flashColor = chargeColor;
|
||||
flashColor.a = 1f;
|
||||
renderer.color = flashColor;
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
|
||||
// Cleanup
|
||||
Destroy(glow);
|
||||
foreach (var p in particles) {
|
||||
if (p != null) Destroy(p);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator AnimateTarget(Vector2 target) {
|
||||
Sprite sprite = targetSprite ?? explosionSprite;
|
||||
if (sprite == null) yield break;
|
||||
|
||||
// Create target indicator
|
||||
GameObject indicator = new GameObject("MeteorTarget");
|
||||
indicator.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
indicator.transform.localPosition = new Vector3(target.x, target.y, 5f);
|
||||
|
||||
SpriteRenderer renderer = indicator.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = sprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.color = targetColor;
|
||||
|
||||
// Create outer ring that contracts
|
||||
GameObject ring = null;
|
||||
SpriteRenderer ringRenderer = null;
|
||||
if (sprite != null) {
|
||||
ring = new GameObject("TargetRing");
|
||||
ring.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
ring.transform.localPosition = new Vector3(target.x, target.y, 6f);
|
||||
|
||||
ringRenderer = ring.AddComponent<SpriteRenderer>();
|
||||
ringRenderer.sprite = sprite;
|
||||
ringRenderer.sortingLayerName = "Effects";
|
||||
Color ringColor = targetColor;
|
||||
ringColor.a = targetColor.a * 0.5f;
|
||||
ringRenderer.color = ringColor;
|
||||
}
|
||||
|
||||
// Animation - indicator appears, ring contracts to it
|
||||
float elapsed = 0f;
|
||||
while (elapsed < targetDuration) {
|
||||
float t = elapsed / targetDuration;
|
||||
|
||||
// Main indicator pulses
|
||||
float pulse = 0.8f + 0.2f * Mathf.Sin(elapsed * 15f);
|
||||
indicator.transform.localScale = Vector3.one * targetScale * pulse;
|
||||
|
||||
Color c = targetColor;
|
||||
c.a = targetColor.a * pulse;
|
||||
renderer.color = c;
|
||||
|
||||
// Ring contracts inward
|
||||
if (ring != null) {
|
||||
float ringScale = targetScale * (2f - t);
|
||||
ring.transform.localScale = Vector3.one * ringScale;
|
||||
|
||||
Color rc = targetColor;
|
||||
rc.a = targetColor.a * 0.5f * (1f - t);
|
||||
ringRenderer.color = rc;
|
||||
}
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Final pulse and fade
|
||||
float fadeDuration = 0.15f;
|
||||
elapsed = 0f;
|
||||
while (elapsed < fadeDuration) {
|
||||
float t = elapsed / fadeDuration;
|
||||
|
||||
indicator.transform.localScale = Vector3.one * targetScale * (1f + t * 0.3f);
|
||||
|
||||
Color c = targetColor;
|
||||
c.a = targetColor.a * (1f - t);
|
||||
renderer.color = c;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
Destroy(indicator);
|
||||
if (ring != null) Destroy(ring);
|
||||
}
|
||||
|
||||
private IEnumerator AnimateCancel(Vector2 source) {
|
||||
Sprite sprite = chargeSprite ?? explosionSprite;
|
||||
if (sprite == null) yield break;
|
||||
|
||||
// Create fizzling glow (starts at charge end size)
|
||||
GameObject glow = new GameObject("MeteorCancel");
|
||||
glow.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
glow.transform.localPosition = new Vector3(source.x, source.y, 5f);
|
||||
glow.transform.localScale = Vector3.one * chargeEndScale;
|
||||
|
||||
SpriteRenderer renderer = glow.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = sprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.color = cancelColor;
|
||||
|
||||
// Create dispersing particles
|
||||
int particleCount = 6;
|
||||
GameObject[] particles = new GameObject[particleCount];
|
||||
Vector2[] directions = new Vector2[particleCount];
|
||||
|
||||
for (int i = 0; i < particleCount; i++) {
|
||||
if (trailSprite == null) break;
|
||||
|
||||
GameObject particle = new GameObject($"CancelParticle_{i}");
|
||||
particle.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
particle.transform.localPosition = new Vector3(source.x, source.y, 4f);
|
||||
|
||||
SpriteRenderer pRenderer = particle.AddComponent<SpriteRenderer>();
|
||||
pRenderer.sprite = trailSprite;
|
||||
pRenderer.sortingLayerName = "Effects";
|
||||
pRenderer.color = cancelColor;
|
||||
particle.transform.localScale = Vector3.one * trailScale;
|
||||
|
||||
float angle = (float)i / particleCount * Mathf.PI * 2f + Random.Range(-0.2f, 0.2f);
|
||||
directions[i] = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
|
||||
particles[i] = particle;
|
||||
}
|
||||
|
||||
// Animation - shrink and fade while particles disperse
|
||||
float elapsed = 0f;
|
||||
while (elapsed < cancelDuration) {
|
||||
float t = elapsed / cancelDuration;
|
||||
|
||||
// Shrink and fade glow
|
||||
float scale = chargeEndScale * (1f - t * 0.7f);
|
||||
glow.transform.localScale = Vector3.one * scale;
|
||||
|
||||
Color c = cancelColor;
|
||||
c.a = cancelColor.a * (1f - t);
|
||||
renderer.color = c;
|
||||
|
||||
// Particles drift outward and fade
|
||||
for (int i = 0; i < particles.Length; i++) {
|
||||
if (particles[i] == null) continue;
|
||||
|
||||
Vector2 pos = source + directions[i] * 30f * t;
|
||||
particles[i].transform.localPosition = new Vector3(pos.x, pos.y, 4f);
|
||||
|
||||
var pRenderer = particles[i].GetComponent<SpriteRenderer>();
|
||||
if (pRenderer != null) {
|
||||
Color pc = cancelColor;
|
||||
pc.a = cancelColor.a * (1f - t);
|
||||
pRenderer.color = pc;
|
||||
particles[i].transform.localScale =
|
||||
Vector3.one * trailScale * (1f - t * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
Destroy(glow);
|
||||
foreach (var p in particles) {
|
||||
if (p != null) Destroy(p);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator AnimateMeteor(Vector2 target) {
|
||||
List<GameObject> objects = new List<GameObject>();
|
||||
|
||||
@@ -466,24 +95,25 @@ namespace Shardok {
|
||||
Vector2 fallDirection = new Vector2(Mathf.Sin(angleRad), Mathf.Cos(angleRad));
|
||||
Vector2 startPos = target + fallDirection * fallDistance;
|
||||
|
||||
// Create meteor rock
|
||||
// Create meteor
|
||||
GameObject meteor = null;
|
||||
SpriteRenderer meteorRenderer = null;
|
||||
float currentRotation = 0f;
|
||||
if (meteorSprite != null) {
|
||||
meteor = new GameObject("Meteor");
|
||||
meteor.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
|
||||
meteorRenderer = meteor.AddComponent<SpriteRenderer>();
|
||||
SpriteRenderer meteorRenderer = meteor.AddComponent<SpriteRenderer>();
|
||||
meteorRenderer.sprite = meteorSprite;
|
||||
meteorRenderer.sortingLayerName = "Effects";
|
||||
meteorRenderer.color = meteorColor;
|
||||
meteor.transform.localScale = Vector3.one * meteorScale;
|
||||
meteor.transform.localPosition = new Vector3(startPos.x, startPos.y, 3f);
|
||||
|
||||
// Rotate meteor to face direction of travel
|
||||
float rotAngle = Mathf.Atan2(-fallDirection.x, -fallDirection.y) * Mathf.Rad2Deg;
|
||||
meteor.transform.localRotation = Quaternion.Euler(0, 0, rotAngle);
|
||||
objects.Add(meteor);
|
||||
}
|
||||
|
||||
// Pre-create trail particles (continuous trail behind meteor)
|
||||
// Trail particles
|
||||
List<TrailParticle> trailParticles = new List<TrailParticle>();
|
||||
for (int i = 0; i < trailParticleCount; i++) {
|
||||
if (trailSprite == null) break;
|
||||
@@ -494,75 +124,58 @@ namespace Shardok {
|
||||
SpriteRenderer renderer = particle.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = trailSprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
particle.transform.localPosition = new Vector3(startPos.x, startPos.y, 4f);
|
||||
|
||||
// Particles further back are smaller and cooler colored
|
||||
float trailT = (float)i / trailParticleCount;
|
||||
float particleScale = trailScale * (1f - trailT * 0.6f);
|
||||
particle.transform.localScale = Vector3.one * particleScale;
|
||||
|
||||
Color particleColor = Color.Lerp(trailColorHot, trailColorCool, trailT);
|
||||
renderer.color = particleColor;
|
||||
|
||||
// Random offset perpendicular to fall direction
|
||||
float perpOffset = Random.Range(-trailSpread, trailSpread) * trailT;
|
||||
renderer.color = trailColor;
|
||||
particle.transform.localScale = Vector3.one * trailScale;
|
||||
particle.SetActive(false);
|
||||
|
||||
trailParticles.Add(new TrailParticle {
|
||||
gameObject = particle,
|
||||
delay = trailT * 0.3f, // How far behind the meteor this particle trails
|
||||
offset = perpOffset
|
||||
delay = (float)i / trailParticleCount * 0.15f,
|
||||
offset = Random.Range(-5f, 5f)
|
||||
});
|
||||
objects.Add(particle);
|
||||
}
|
||||
|
||||
// Fall phase with rotation and continuous trail
|
||||
// Fall phase
|
||||
float elapsed = 0f;
|
||||
int nextTrailIndex = 0;
|
||||
|
||||
while (elapsed < fallDuration) {
|
||||
float t = elapsed / fallDuration;
|
||||
// Ease-in for accelerating fall (starts slow, speeds up)
|
||||
float easeT = t * t;
|
||||
float easeT = t * t; // Accelerating fall
|
||||
|
||||
Vector2 currentPos = Vector2.Lerp(startPos, target, easeT);
|
||||
|
||||
// Update meteor position and rotation
|
||||
if (meteor != null) {
|
||||
meteor.transform.localPosition = new Vector3(currentPos.x, currentPos.y, 3f);
|
||||
|
||||
// Continuous rotation
|
||||
currentRotation += rotationSpeed * Time.deltaTime;
|
||||
meteor.transform.localRotation = Quaternion.Euler(0, 0, currentRotation);
|
||||
|
||||
// Slight glow intensification as it approaches
|
||||
Color c = meteorColor;
|
||||
c.r = Mathf.Min(1f, meteorColor.r + t * 0.2f);
|
||||
c.g = Mathf.Min(1f, meteorColor.g + t * 0.1f);
|
||||
meteorRenderer.color = c;
|
||||
}
|
||||
|
||||
// Update trail particles to follow behind meteor
|
||||
Vector2 perpDir = new Vector2(-fallDirection.y, fallDirection.x);
|
||||
for (int i = 0; i < trailParticles.Count; i++) {
|
||||
var tp = trailParticles[i];
|
||||
if (tp.gameObject == null) continue;
|
||||
// Spawn trail particles
|
||||
while (nextTrailIndex < trailParticles.Count &&
|
||||
elapsed >= trailParticles[nextTrailIndex].delay) {
|
||||
var tp = trailParticles[nextTrailIndex];
|
||||
tp.gameObject.SetActive(true);
|
||||
tp.spawnPosition =
|
||||
currentPos + new Vector2(tp.offset, tp.offset * 0.5f) * fallDirection.x;
|
||||
tp.gameObject.transform.localPosition =
|
||||
new Vector3(tp.spawnPosition.x, tp.spawnPosition.y, 4f);
|
||||
nextTrailIndex++;
|
||||
}
|
||||
|
||||
// Calculate position along the trail (behind the meteor)
|
||||
float trailT = (float)i / trailParticleCount;
|
||||
float trailDistance = trailT * 50f; // How far behind
|
||||
|
||||
// Position is behind meteor along fall direction
|
||||
Vector2 trailPos = currentPos + fallDirection * trailDistance;
|
||||
// Add perpendicular wobble
|
||||
trailPos += perpDir * tp.offset * Mathf.Sin(elapsed * 8f + i);
|
||||
|
||||
tp.gameObject.transform.localPosition = new Vector3(trailPos.x, trailPos.y, 4f);
|
||||
|
||||
// Flicker effect
|
||||
var renderer = tp.gameObject.GetComponent<SpriteRenderer>();
|
||||
if (renderer != null) {
|
||||
Color baseColor = Color.Lerp(trailColorHot, trailColorCool, trailT);
|
||||
baseColor.a *= 0.8f + 0.2f * Mathf.Sin(elapsed * 15f + i * 0.5f);
|
||||
renderer.color = baseColor;
|
||||
// Fade trail particles
|
||||
foreach (var tp in trailParticles) {
|
||||
if (!tp.gameObject.activeSelf) continue;
|
||||
float age = elapsed - tp.delay;
|
||||
if (age > 0) {
|
||||
float fadeT = Mathf.Clamp01(age / 0.2f);
|
||||
var renderer = tp.gameObject.GetComponent<SpriteRenderer>();
|
||||
if (renderer != null) {
|
||||
Color c = trailColor;
|
||||
c.a = trailColor.a * (1f - fadeT);
|
||||
renderer.color = c;
|
||||
tp.gameObject.transform.localScale =
|
||||
Vector3.one * trailScale * (1f - fadeT * 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,7 +190,7 @@ namespace Shardok {
|
||||
}
|
||||
objects.Clear();
|
||||
|
||||
// Explosion phase - bigger and with debris
|
||||
// Explosion phase
|
||||
GameObject explosion = null;
|
||||
SpriteRenderer explosionRenderer = null;
|
||||
|
||||
@@ -590,11 +203,10 @@ namespace Shardok {
|
||||
explosionRenderer.sprite = explosionSprite;
|
||||
explosionRenderer.sortingLayerName = "Effects";
|
||||
explosionRenderer.color = explosionColor;
|
||||
explosion.transform.localScale = Vector3.one * explosionStartScale;
|
||||
objects.Add(explosion);
|
||||
}
|
||||
|
||||
// Fire sparks (fast moving, fiery)
|
||||
// Sparks
|
||||
List<SparkParticle> sparks = new List<SparkParticle>();
|
||||
for (int i = 0; i < sparkCount; i++) {
|
||||
if (trailSprite == null) break;
|
||||
@@ -613,61 +225,27 @@ namespace Shardok {
|
||||
sparks.Add(new SparkParticle {
|
||||
gameObject = spark,
|
||||
direction = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)),
|
||||
speed = sparkSpread* Random.Range(0.8f, 1.4f)
|
||||
speed = sparkSpread* Random.Range(0.7f, 1.3f)
|
||||
});
|
||||
objects.Add(spark);
|
||||
}
|
||||
|
||||
// Rock debris (slower, with gravity arc, rotating)
|
||||
List<DebrisParticle> debris = new List<DebrisParticle>();
|
||||
Sprite actualDebrisSprite = debrisSprite ?? meteorSprite;
|
||||
if (actualDebrisSprite != null) {
|
||||
for (int i = 0; i < debrisCount; i++) {
|
||||
GameObject rock = new GameObject($"Debris_{i}");
|
||||
rock.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
rock.transform.localPosition = new Vector3(target.x, target.y, 3f);
|
||||
|
||||
SpriteRenderer renderer = rock.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = actualDebrisSprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.color = debrisColor;
|
||||
|
||||
float randomScale = debrisScale * Random.Range(0.6f, 1.4f);
|
||||
rock.transform.localScale = Vector3.one * randomScale;
|
||||
|
||||
// Debris flies outward in all directions
|
||||
float angle =
|
||||
(float)i / debrisCount * Mathf.PI * 2f + Random.Range(-0.4f, 0.4f);
|
||||
Vector2 dir = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
|
||||
|
||||
debris.Add(new DebrisParticle {
|
||||
gameObject = rock,
|
||||
direction = dir,
|
||||
speed = debrisSpread * Random.Range(0.6f, 1.2f),
|
||||
rotationSpeed = Random.Range(-400f, 400f),
|
||||
currentRotation = Random.Range(0f, 360f)
|
||||
});
|
||||
objects.Add(rock);
|
||||
}
|
||||
}
|
||||
|
||||
// Explosion animation
|
||||
elapsed = 0f;
|
||||
while (elapsed < explosionDuration) {
|
||||
float t = elapsed / explosionDuration;
|
||||
|
||||
// Expand and fade explosion with initial flash
|
||||
// Expand and fade explosion
|
||||
if (explosion != null) {
|
||||
float flashT = t < 0.1f ? 1f + (0.1f - t) * 5f : 1f;
|
||||
float scale = Mathf.Lerp(explosionStartScale, explosionEndScale, t) * flashT;
|
||||
float scale = Mathf.Lerp(explosionStartScale, explosionEndScale, t);
|
||||
explosion.transform.localScale = Vector3.one * scale;
|
||||
|
||||
Color c = explosionColor;
|
||||
c.a = (1f - t * t); // Slower fade at start
|
||||
c.a = 1f - t;
|
||||
explosionRenderer.color = c;
|
||||
}
|
||||
|
||||
// Animate fire sparks (fast, straight lines)
|
||||
// Animate sparks
|
||||
foreach (var spark in sparks) {
|
||||
if (spark.gameObject == null) continue;
|
||||
|
||||
@@ -680,31 +258,7 @@ namespace Shardok {
|
||||
c.a = 1f - t;
|
||||
renderer.color = c;
|
||||
spark.gameObject.transform.localScale =
|
||||
Vector3.one * trailScale * 1.5f * (1f - t * 0.6f);
|
||||
}
|
||||
}
|
||||
|
||||
// Animate debris (flies outward in all directions, rotating)
|
||||
for (int di = 0; di < debris.Count; di++) {
|
||||
var d = debris[di];
|
||||
if (d.gameObject == null) continue;
|
||||
|
||||
// Debris flies outward, slowing down over time
|
||||
float easeT = t * (2f - t); // Ease out - fast start, slow end
|
||||
Vector2 pos = target + d.direction * d.speed * easeT;
|
||||
d.gameObject.transform.localPosition = new Vector3(pos.x, pos.y, 3f);
|
||||
|
||||
// Spin the debris
|
||||
d.currentRotation += d.rotationSpeed * Time.deltaTime;
|
||||
d.gameObject.transform.localRotation =
|
||||
Quaternion.Euler(0, 0, d.currentRotation);
|
||||
debris[di] = d; // Write back modified struct
|
||||
|
||||
var renderer = d.gameObject.GetComponent<SpriteRenderer>();
|
||||
if (renderer != null) {
|
||||
Color c = debrisColor;
|
||||
c.a = 1f - t * 0.8f; // Debris fades slower
|
||||
renderer.color = c;
|
||||
Vector3.one * trailScale * 1.5f * (1f - t * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,13 +284,5 @@ namespace Shardok {
|
||||
public Vector2 direction;
|
||||
public float speed;
|
||||
}
|
||||
|
||||
private struct DebrisParticle {
|
||||
public GameObject gameObject;
|
||||
public Vector2 direction;
|
||||
public float speed;
|
||||
public float rotationSpeed;
|
||||
public float currentRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +1,41 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using static Net.Eagle0.Shardok.Api.BattalionView.Types;
|
||||
|
||||
namespace Shardok {
|
||||
/// <summary>
|
||||
/// Animates unit movement between hex cells using a trail of footprints/hoofprints.
|
||||
/// Boot prints for infantry, horseshoe prints for cavalry.
|
||||
/// Prints appear sequentially then fade out FIFO.
|
||||
/// Animates unit movement from source to target hex with smooth interpolation.
|
||||
/// Creates a temporary indicator that slides across the grid.
|
||||
/// </summary>
|
||||
public class MoveAnimator : MonoBehaviour {
|
||||
[Header("Print Sprites")]
|
||||
[Tooltip("Boot print sprite (will be flipped for left/right)")]
|
||||
public Sprite bootPrintSprite;
|
||||
|
||||
[Tooltip("Paired horseshoe prints sprite")]
|
||||
public Sprite hoofPrintSprite;
|
||||
|
||||
[Header("Animation Settings")]
|
||||
[Tooltip("Number of prints between hex centers")]
|
||||
public int printsPerHex = 5;
|
||||
[Tooltip("Duration of the movement in seconds")]
|
||||
public float moveDuration = 0.3f;
|
||||
|
||||
[Tooltip("Time between each print appearing (seconds)")]
|
||||
public float printInterval = 0.06f;
|
||||
[Tooltip("Optional sprite to show during movement")]
|
||||
public Sprite moveIndicatorSprite;
|
||||
|
||||
[Tooltip("How long each print stays fully visible before fading (seconds)")]
|
||||
public float printLingerTime = 0.3f;
|
||||
[Tooltip("Scale of the movement indicator")]
|
||||
public float indicatorScale = 15f;
|
||||
|
||||
[Tooltip("Duration of each print's fade out (seconds)")]
|
||||
public float fadeDuration = 0.4f;
|
||||
[Tooltip("Height offset for arc movement (0 = straight line)")]
|
||||
public float arcHeight = 0.05f;
|
||||
|
||||
[Tooltip("Scale of print sprites")]
|
||||
public float printScale = 1.5f;
|
||||
private HexGrid __hexGrid;
|
||||
|
||||
[Tooltip("Lateral offset for alternating prints")]
|
||||
public float lateralOffset = 6f;
|
||||
|
||||
[Tooltip("Color tint for boot prints")]
|
||||
public Color bootPrintColor = new Color(0.3f, 0.2f, 0.1f, 0.8f);
|
||||
|
||||
[Tooltip("Color tint for hoof prints")]
|
||||
public Color hoofPrintColor = new Color(0.25f, 0.15f, 0.05f, 0.8f);
|
||||
|
||||
private HexGrid _hexGrid;
|
||||
private readonly List<Coroutine> _activeCoroutines = new List<Coroutine>();
|
||||
|
||||
private void Start() { _hexGrid = FindObjectOfType<HexGrid>(); }
|
||||
private void Start() { __hexGrid = FindObjectOfType<HexGrid>(); }
|
||||
|
||||
/// <summary>
|
||||
/// Animate movement from source to target hex.
|
||||
/// Starts a move animation between two hex cells.
|
||||
/// Multiple animations can run simultaneously.
|
||||
/// </summary>
|
||||
/// <param name="sourceCellIndex">Starting hex cell index</param>
|
||||
/// <param name="targetCellIndex">Destination hex cell index</param>
|
||||
/// <param name="unitType">Type of battalion (determines boot vs hoof prints)</param>
|
||||
public void
|
||||
AnimateMove(int sourceCellIndex, int targetCellIndex, BattalionTypeId unitType) {
|
||||
if (bootPrintSprite == null && hoofPrintSprite == null) {
|
||||
Debug.LogWarning("MoveAnimator: No print sprites assigned");
|
||||
public void AnimateMove(int sourceCellIndex, int targetCellIndex) {
|
||||
if (__hexGrid == null || __hexGrid.gridCanvas == null) {
|
||||
Debug.LogWarning("MoveAnimator: No _hexGrid or gridCanvas available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hexGrid == null || _hexGrid.gridCanvas == null) {
|
||||
Debug.LogWarning("MoveAnimator: No HexGrid or gridCanvas available");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2? sourcePos = _hexGrid.GetCellLocalPosition(sourceCellIndex);
|
||||
Vector2? targetPos = _hexGrid.GetCellLocalPosition(targetCellIndex);
|
||||
Vector2? sourcePos = __hexGrid.GetCellLocalPosition(sourceCellIndex);
|
||||
Vector2? targetPos = __hexGrid.GetCellLocalPosition(targetCellIndex);
|
||||
|
||||
if (sourcePos == null || targetPos == null) {
|
||||
Debug.LogWarning(
|
||||
@@ -74,122 +43,45 @@ namespace Shardok {
|
||||
return;
|
||||
}
|
||||
|
||||
bool isMounted = unitType == BattalionTypeId.LightCavalry ||
|
||||
unitType == BattalionTypeId.HeavyCavalry;
|
||||
|
||||
StartCoroutine(SpawnPrintTrail(sourcePos.Value, targetPos.Value, isMounted));
|
||||
StartCoroutine(AnimateMovement(sourcePos.Value, targetPos.Value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overload for when unit type is not available - defaults to boot prints.
|
||||
/// </summary>
|
||||
public void AnimateMove(int sourceCellIndex, int targetCellIndex) {
|
||||
AnimateMove(sourceCellIndex, targetCellIndex, BattalionTypeId.LightInfantry);
|
||||
}
|
||||
private IEnumerator AnimateMovement(Vector2 source, Vector2 target) {
|
||||
// Create movement indicator if sprite assigned
|
||||
GameObject indicator = null;
|
||||
if (moveIndicatorSprite != null) {
|
||||
indicator = new GameObject("MoveIndicator");
|
||||
indicator.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
|
||||
private IEnumerator SpawnPrintTrail(Vector2 source, Vector2 target, bool isMounted) {
|
||||
var prints = new List<GameObject>();
|
||||
Vector2 direction = (target - source).normalized;
|
||||
float totalDistance = Vector2.Distance(source, target);
|
||||
|
||||
// Calculate rotation angle for prints to face direction of travel
|
||||
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg - 90f;
|
||||
|
||||
Sprite printSprite = isMounted ? (hoofPrintSprite ?? bootPrintSprite)
|
||||
: (bootPrintSprite ?? hoofPrintSprite);
|
||||
Color printColor = isMounted ? hoofPrintColor : bootPrintColor;
|
||||
|
||||
// Spawn prints along the path
|
||||
for (int i = 0; i < printsPerHex; i++) {
|
||||
float t = (i + 1f) / (printsPerHex + 1f);
|
||||
Vector2 position = Vector2.Lerp(source, target, t);
|
||||
|
||||
// Alternate left/right for boot prints (flip horizontally)
|
||||
bool flipX = !isMounted && (i % 2 == 1);
|
||||
|
||||
// Lateral offset for alternating prints (feet or front/rear hooves)
|
||||
Vector2 perpendicular = new Vector2(-direction.y, direction.x);
|
||||
float offset = (i % 2 == 0) ? -lateralOffset : lateralOffset;
|
||||
position += perpendicular * offset;
|
||||
|
||||
GameObject print = CreatePrint(position, angle, printSprite, printColor, flipX);
|
||||
prints.Add(print);
|
||||
|
||||
// Start FIFO fade coroutine for this print
|
||||
var fadeCoroutine =
|
||||
StartCoroutine(FadePrintFIFO(print, printLingerTime + i * printInterval));
|
||||
_activeCoroutines.Add(fadeCoroutine);
|
||||
|
||||
yield return new WaitForSeconds(printInterval);
|
||||
var renderer = indicator.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = moveIndicatorSprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
indicator.transform.localScale = Vector3.one * indicatorScale;
|
||||
}
|
||||
|
||||
// Wait for all fades to complete
|
||||
float totalWaitTime = printLingerTime + fadeDuration + 0.1f;
|
||||
yield return new WaitForSeconds(totalWaitTime);
|
||||
|
||||
// Cleanup any remaining prints
|
||||
foreach (var print in prints) {
|
||||
if (print != null) { Destroy(print); }
|
||||
}
|
||||
|
||||
_activeCoroutines.Clear();
|
||||
}
|
||||
|
||||
private GameObject
|
||||
CreatePrint(Vector2 position, float angle, Sprite sprite, Color color, bool flipX) {
|
||||
GameObject print = new GameObject("Footprint");
|
||||
print.transform.SetParent(_hexGrid.gridCanvas.transform, false);
|
||||
|
||||
SpriteRenderer renderer = print.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = sprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.sortingOrder = -1; // Below other effects
|
||||
renderer.color = color;
|
||||
|
||||
print.transform.localPosition = new Vector3(position.x, position.y, 0);
|
||||
print.transform.localRotation = Quaternion.Euler(0, 0, angle);
|
||||
|
||||
Vector3 scale = new Vector3(flipX ? -printScale : printScale, printScale, printScale);
|
||||
print.transform.localScale = scale;
|
||||
|
||||
return print;
|
||||
}
|
||||
|
||||
private IEnumerator FadePrintFIFO(GameObject print, float delay) {
|
||||
// Wait before starting fade (FIFO - earlier prints fade first)
|
||||
yield return new WaitForSeconds(delay);
|
||||
|
||||
if (print == null) yield break;
|
||||
|
||||
SpriteRenderer renderer = print.GetComponent<SpriteRenderer>();
|
||||
if (renderer == null) yield break;
|
||||
|
||||
Color startColor = renderer.color;
|
||||
float elapsed = 0f;
|
||||
float distance = Vector2.Distance(source, target);
|
||||
|
||||
while (elapsed < fadeDuration) {
|
||||
if (print == null || renderer == null) yield break;
|
||||
while (elapsed < moveDuration) {
|
||||
float t = elapsed / moveDuration;
|
||||
float smoothT = t * t * (3f - 2f * t); // Smooth step
|
||||
|
||||
float t = elapsed / fadeDuration;
|
||||
Color newColor = startColor;
|
||||
newColor.a = Mathf.Lerp(startColor.a, 0f, t);
|
||||
renderer.color = newColor;
|
||||
Vector3 pos = Vector3.Lerp(
|
||||
new Vector3(source.x, source.y, 5f),
|
||||
new Vector3(target.x, target.y, 5f),
|
||||
smoothT);
|
||||
|
||||
// Add slight arc on Y axis for visible height in top-down view
|
||||
float arc = 4f * arcHeight * distance * t * (1f - t);
|
||||
pos.y += arc;
|
||||
|
||||
if (indicator != null) { indicator.transform.localPosition = pos; }
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (print != null) { Destroy(print); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels all active print animations.
|
||||
/// </summary>
|
||||
public void CancelAllAnimations() {
|
||||
foreach (var coroutine in _activeCoroutines) {
|
||||
if (coroutine != null) { StopCoroutine(coroutine); }
|
||||
}
|
||||
_activeCoroutines.Clear();
|
||||
if (indicator != null) { Destroy(indicator); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ using UnityEngine;
|
||||
|
||||
namespace Shardok {
|
||||
/// <summary>
|
||||
/// Animates scouting action with an eye at source, a vision cone extending
|
||||
/// toward target, and a glow that travels with the cone tip and grows to
|
||||
/// cover the target hex and its neighbors.
|
||||
/// Animates scouting action with an eye/binoculars effect and scanning beam.
|
||||
/// Creates a sweeping vision cone from source toward target.
|
||||
/// </summary>
|
||||
public class ScoutAnimator : MonoBehaviour {
|
||||
[Header("Eye Settings")]
|
||||
@@ -19,40 +18,34 @@ namespace Shardok {
|
||||
public float eyeScale = 15f;
|
||||
|
||||
[Header("Vision Cone Settings")]
|
||||
[Tooltip("Sprite for vision cone/beam (triangle pointing right)")]
|
||||
[Tooltip("Sprite for vision cone/beam")]
|
||||
public Sprite coneSprite;
|
||||
|
||||
[Tooltip("Color of the vision cone")]
|
||||
public Color coneColor = new Color(1f, 1f, 0.5f, 0.3f);
|
||||
|
||||
[Tooltip("Width of the vision cone at base")]
|
||||
public float coneWidth = 30f;
|
||||
[Tooltip("Length of the vision cone")]
|
||||
public float coneLength = 60f;
|
||||
|
||||
[Tooltip("Rotation offset for cone sprite (degrees). +90 for upward-pointing triangle")]
|
||||
public float coneRotationOffset = 90f;
|
||||
[Tooltip("Width of the vision cone")]
|
||||
public float coneWidth = 40f;
|
||||
|
||||
[Header("Glow Settings")]
|
||||
[Tooltip("Sprite for the traveling glow (circle)")]
|
||||
public Sprite glowSprite;
|
||||
[Header("Scan Lines Settings")]
|
||||
[Tooltip("Sprite for scan line particles")]
|
||||
public Sprite scanSprite;
|
||||
|
||||
[Tooltip("Color of the glow")]
|
||||
public Color glowColor = new Color(1f, 1f, 0.7f, 0.5f);
|
||||
[Tooltip("Number of scan lines")]
|
||||
public int scanLineCount = 3;
|
||||
|
||||
[Tooltip("Starting scale of glow (at source)")]
|
||||
public float glowStartScale = 10f;
|
||||
|
||||
[Tooltip("Final scale of glow in hex radii (2.5 = covers 7-hex area)")]
|
||||
public float glowEndRadii = 2.5f;
|
||||
[Tooltip("Color of scan lines")]
|
||||
public Color scanColor = new Color(1f, 1f, 0.8f, 0.6f);
|
||||
|
||||
[Header("Animation Settings")]
|
||||
[Tooltip("Duration of cone extension")]
|
||||
public float extendDuration = 0.5f;
|
||||
[Tooltip("Duration of the scan sweep")]
|
||||
public float scanDuration = 0.6f;
|
||||
|
||||
[Tooltip("Duration the glow holds at target")]
|
||||
public float holdDuration = 0.4f;
|
||||
|
||||
[Tooltip("Duration of fade out")]
|
||||
public float fadeDuration = 0.3f;
|
||||
[Tooltip("Sweep angle in degrees")]
|
||||
public float sweepAngle = 30f;
|
||||
|
||||
private HexGrid __hexGrid;
|
||||
|
||||
@@ -62,6 +55,11 @@ namespace Shardok {
|
||||
/// Starts a scout animation from source toward target hex.
|
||||
/// </summary>
|
||||
public void AnimateScout(int sourceCellIndex, int targetCellIndex) {
|
||||
if (eyeSprite == null && coneSprite == null) {
|
||||
Debug.LogWarning("ScoutAnimator: No sprites assigned");
|
||||
return;
|
||||
}
|
||||
|
||||
if (__hexGrid == null || __hexGrid.gridCanvas == null) {
|
||||
Debug.LogWarning("ScoutAnimator: No _hexGrid or gridCanvas available");
|
||||
return;
|
||||
@@ -76,62 +74,59 @@ namespace Shardok {
|
||||
return;
|
||||
}
|
||||
|
||||
float hexRadius = __hexGrid.GetHexInnerRadius();
|
||||
float glowEndScale = hexRadius * glowEndRadii * 2f;
|
||||
|
||||
StartCoroutine(AnimateScoutEffect(sourcePos.Value, targetPos.Value, glowEndScale));
|
||||
StartCoroutine(AnimateScoutEffect(sourcePos.Value, targetPos.Value));
|
||||
}
|
||||
|
||||
private IEnumerator AnimateScoutEffect(Vector2 source, Vector2 target, float glowEndScale) {
|
||||
private IEnumerator AnimateScoutEffect(Vector2 source, Vector2 target) {
|
||||
Vector2 direction = (target - source).normalized;
|
||||
float baseAngle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
|
||||
float distance = Vector2.Distance(source, target);
|
||||
|
||||
// Create eye at source
|
||||
GameObject eye = null;
|
||||
SpriteRenderer eyeRenderer = null;
|
||||
if (eyeSprite != null) {
|
||||
eye = new GameObject("ScoutEye");
|
||||
eye.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
eye.transform.localPosition = new Vector3(source.x, source.y, 5f);
|
||||
eye.transform.localScale = Vector3.zero;
|
||||
|
||||
eyeRenderer = eye.AddComponent<SpriteRenderer>();
|
||||
eyeRenderer.sprite = eyeSprite;
|
||||
eyeRenderer.sortingLayerName = "Effects";
|
||||
eyeRenderer.color = eyeColor;
|
||||
SpriteRenderer renderer = eye.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = eyeSprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.color = eyeColor;
|
||||
}
|
||||
|
||||
// Create vision cone
|
||||
GameObject cone = null;
|
||||
SpriteRenderer coneRenderer = null;
|
||||
if (coneSprite != null) {
|
||||
cone = new GameObject("VisionCone");
|
||||
cone.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
cone.transform.localPosition = new Vector3(source.x, source.y, 6f);
|
||||
cone.transform.localRotation =
|
||||
Quaternion.Euler(0, 0, baseAngle + coneRotationOffset);
|
||||
cone.transform.localScale = new Vector3(coneWidth, 0f, 1f);
|
||||
cone.transform.localRotation = Quaternion.Euler(0, 0, baseAngle - sweepAngle);
|
||||
cone.transform.localScale = new Vector3(0f, coneWidth, 1f);
|
||||
|
||||
coneRenderer = cone.AddComponent<SpriteRenderer>();
|
||||
coneRenderer.sprite = coneSprite;
|
||||
coneRenderer.sortingLayerName = "Effects";
|
||||
coneRenderer.color = coneColor;
|
||||
SpriteRenderer renderer = cone.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = coneSprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.color = coneColor;
|
||||
}
|
||||
|
||||
// Create traveling glow
|
||||
GameObject glow = null;
|
||||
SpriteRenderer glowRenderer = null;
|
||||
if (glowSprite != null) {
|
||||
glow = new GameObject("ScoutGlow");
|
||||
glow.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
glow.transform.localPosition = new Vector3(source.x, source.y, 7f);
|
||||
glow.transform.localScale = Vector3.one * glowStartScale;
|
||||
// Create scan lines
|
||||
GameObject[] scanLines = new GameObject[scanLineCount];
|
||||
|
||||
glowRenderer = glow.AddComponent<SpriteRenderer>();
|
||||
glowRenderer.sprite = glowSprite;
|
||||
glowRenderer.sortingLayerName = "Effects";
|
||||
glowRenderer.color = glowColor;
|
||||
for (int i = 0; i < scanLineCount; i++) {
|
||||
if (scanSprite == null) break;
|
||||
|
||||
GameObject scanLine = new GameObject($"ScanLine_{i}");
|
||||
scanLine.transform.SetParent(__hexGrid.gridCanvas.transform, false);
|
||||
scanLine.transform.localPosition = new Vector3(source.x, source.y, 4f);
|
||||
scanLine.transform.localScale = Vector3.one * 5f;
|
||||
|
||||
SpriteRenderer renderer = scanLine.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = scanSprite;
|
||||
renderer.sortingLayerName = "Effects";
|
||||
renderer.color = scanColor;
|
||||
|
||||
scanLines[i] = scanLine;
|
||||
}
|
||||
|
||||
// Eye appearance phase
|
||||
@@ -142,7 +137,7 @@ namespace Shardok {
|
||||
float t = elapsed / appearDuration;
|
||||
|
||||
if (eye != null) {
|
||||
float scale = eyeScale * t * (2f - t);
|
||||
float scale = eyeScale * t * (2f - t); // Overshoot slightly
|
||||
eye.transform.localScale = Vector3.one * scale;
|
||||
}
|
||||
|
||||
@@ -152,90 +147,89 @@ namespace Shardok {
|
||||
|
||||
if (eye != null) { eye.transform.localScale = Vector3.one * eyeScale; }
|
||||
|
||||
// Cone extension phase - cone extends, glow travels and grows
|
||||
// Scan sweep phase
|
||||
elapsed = 0f;
|
||||
|
||||
while (elapsed < extendDuration) {
|
||||
float t = elapsed / extendDuration;
|
||||
float easeT = t * (2f - t); // Ease out
|
||||
while (elapsed < scanDuration) {
|
||||
float t = elapsed / scanDuration;
|
||||
float sweepT = Mathf.Sin(t * Mathf.PI); // Smooth ease in-out
|
||||
|
||||
// Extend cone - Y axis is length after rotation, X is width
|
||||
// Rotate cone through sweep
|
||||
if (cone != null) {
|
||||
float length = distance * easeT;
|
||||
Vector2 conePos = source + direction * (length * 0.5f);
|
||||
cone.transform.localPosition = new Vector3(conePos.x, conePos.y, 6f);
|
||||
cone.transform.localScale =
|
||||
new Vector3(coneWidth * (1f - easeT * 0.3f), length, 1f);
|
||||
float currentAngle = baseAngle - sweepAngle + sweepAngle * 2f * t;
|
||||
cone.transform.localRotation = Quaternion.Euler(0, 0, currentAngle);
|
||||
|
||||
Color c = coneColor;
|
||||
c.a = coneColor.a * (0.8f + 0.2f * Mathf.Sin(elapsed * 15f));
|
||||
coneRenderer.color = c;
|
||||
// Extend cone
|
||||
float length = coneLength * Mathf.Min(t * 3f, 1f);
|
||||
cone.transform.localScale = new Vector3(length, coneWidth, 1f);
|
||||
|
||||
// Pulse alpha
|
||||
var coneRenderer = cone.GetComponent<SpriteRenderer>();
|
||||
if (coneRenderer != null) {
|
||||
Color c = coneColor;
|
||||
c.a = coneColor.a * (0.7f + sweepT * 0.3f);
|
||||
coneRenderer.color = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Move and grow glow at cone tip
|
||||
if (glow != null) {
|
||||
Vector2 glowPos = source + direction * distance * easeT;
|
||||
glow.transform.localPosition = new Vector3(glowPos.x, glowPos.y, 7f);
|
||||
// Animate scan lines along the cone
|
||||
for (int i = 0; i < scanLines.Length; i++) {
|
||||
if (scanLines[i] == null) continue;
|
||||
|
||||
float glowScale = Mathf.Lerp(glowStartScale, glowEndScale, easeT);
|
||||
glow.transform.localScale = Vector3.one * glowScale;
|
||||
float lineT = (t + (float)i / scanLineCount * 0.3f) % 1f;
|
||||
float lineAngle =
|
||||
(baseAngle - sweepAngle + sweepAngle * 2f * t) * Mathf.Deg2Rad;
|
||||
Vector2 lineDir = new Vector2(Mathf.Cos(lineAngle), Mathf.Sin(lineAngle));
|
||||
|
||||
Color c = glowColor;
|
||||
c.a = glowColor.a * (0.7f + 0.3f * Mathf.Sin(elapsed * 12f));
|
||||
glowRenderer.color = c;
|
||||
Vector2 pos = source + lineDir * coneLength * lineT;
|
||||
scanLines[i].transform.localPosition = new Vector3(pos.x, pos.y, 4f);
|
||||
|
||||
var renderer = scanLines[i].GetComponent<SpriteRenderer>();
|
||||
if (renderer != null) {
|
||||
Color c = scanColor;
|
||||
c.a = scanColor.a * (1f - lineT) * sweepT;
|
||||
renderer.color = c;
|
||||
}
|
||||
}
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Ensure final positions
|
||||
if (cone != null) {
|
||||
Vector2 conePos = source + direction * (distance * 0.5f);
|
||||
cone.transform.localPosition = new Vector3(conePos.x, conePos.y, 6f);
|
||||
cone.transform.localScale = new Vector3(coneWidth * 0.7f, distance, 1f);
|
||||
}
|
||||
if (glow != null) {
|
||||
glow.transform.localPosition = new Vector3(target.x, target.y, 7f);
|
||||
glow.transform.localScale = Vector3.one * glowEndScale;
|
||||
}
|
||||
|
||||
// Hold phase - glow pulses at target
|
||||
// Fade out
|
||||
float fadeDuration = 0.2f;
|
||||
elapsed = 0f;
|
||||
while (elapsed < holdDuration) {
|
||||
float t = elapsed / holdDuration;
|
||||
|
||||
if (glowRenderer != null) {
|
||||
Color c = glowColor;
|
||||
c.a = glowColor.a * (0.8f + 0.2f * Mathf.Sin(elapsed * 10f));
|
||||
glowRenderer.color = c;
|
||||
}
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Fade out phase
|
||||
elapsed = 0f;
|
||||
while (elapsed < fadeDuration) {
|
||||
float t = elapsed / fadeDuration;
|
||||
|
||||
if (eyeRenderer != null) {
|
||||
Color c = eyeColor;
|
||||
c.a = eyeColor.a * (1f - t);
|
||||
eyeRenderer.color = c;
|
||||
if (eye != null) {
|
||||
var renderer = eye.GetComponent<SpriteRenderer>();
|
||||
if (renderer != null) {
|
||||
Color c = eyeColor;
|
||||
c.a = eyeColor.a * (1f - t);
|
||||
renderer.color = c;
|
||||
}
|
||||
eye.transform.localScale = Vector3.one * eyeScale * (1f - t * 0.3f);
|
||||
}
|
||||
|
||||
if (coneRenderer != null) {
|
||||
Color c = coneColor;
|
||||
c.a = coneColor.a * (1f - t);
|
||||
coneRenderer.color = c;
|
||||
if (cone != null) {
|
||||
var renderer = cone.GetComponent<SpriteRenderer>();
|
||||
if (renderer != null) {
|
||||
Color c = coneColor;
|
||||
c.a = coneColor.a * (1f - t);
|
||||
renderer.color = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (glowRenderer != null) {
|
||||
Color c = glowColor;
|
||||
c.a = glowColor.a * (1f - t);
|
||||
glowRenderer.color = c;
|
||||
foreach (var scanLine in scanLines) {
|
||||
if (scanLine == null) continue;
|
||||
var renderer = scanLine.GetComponent<SpriteRenderer>();
|
||||
if (renderer != null) {
|
||||
Color c = scanColor;
|
||||
c.a = scanColor.a * (1f - t);
|
||||
renderer.color = c;
|
||||
}
|
||||
}
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
@@ -245,7 +239,9 @@ namespace Shardok {
|
||||
// Cleanup
|
||||
if (eye != null) { Destroy(eye); }
|
||||
if (cone != null) { Destroy(cone); }
|
||||
if (glow != null) { Destroy(glow); }
|
||||
foreach (var scanLine in scanLines) {
|
||||
if (scanLine != null) { Destroy(scanLine); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-51
@@ -35,10 +35,7 @@ namespace Shardok {
|
||||
Fear,
|
||||
// Magic
|
||||
LightningBolt,
|
||||
MeteorStart,
|
||||
MeteorTarget,
|
||||
MeteorCast,
|
||||
MeteorCancel,
|
||||
HolyWave,
|
||||
RaiseDead,
|
||||
Control,
|
||||
@@ -1346,8 +1343,8 @@ namespace Shardok {
|
||||
case CommandType.MoveCommand: return AnimationType.Move;
|
||||
// Magic
|
||||
case CommandType.LightningBoltCommand: return AnimationType.LightningBolt;
|
||||
case CommandType.MeteorStartCommand: return AnimationType.MeteorStart;
|
||||
case CommandType.MeteorTargetCommand: return AnimationType.MeteorTarget;
|
||||
case CommandType.MeteorStartCommand:
|
||||
case CommandType.MeteorTargetCommand: return AnimationType.MeteorCast;
|
||||
case CommandType.HolyWaveCommand: return AnimationType.HolyWave;
|
||||
case CommandType.RaiseDeadCommand: return AnimationType.RaiseDead;
|
||||
case CommandType.ControlCommand: return AnimationType.Control;
|
||||
@@ -1386,10 +1383,9 @@ namespace Shardok {
|
||||
case ActionType.FearAttack: return AnimationType.Fear;
|
||||
// Magic
|
||||
case ActionType.LightningBolt: return AnimationType.LightningBolt;
|
||||
case ActionType.MeteorStart: return AnimationType.MeteorStart;
|
||||
case ActionType.MeteorTarget: return AnimationType.MeteorTarget;
|
||||
case ActionType.MeteorStart:
|
||||
case ActionType.MeteorTarget:
|
||||
case ActionType.MeteorCast: return AnimationType.MeteorCast;
|
||||
case ActionType.MeteorCancel: return AnimationType.MeteorCancel;
|
||||
case ActionType.HolyWave:
|
||||
case ActionType.HolyWaveDamage: return AnimationType.HolyWave;
|
||||
case ActionType.RaisedUndead: return AnimationType.RaiseDead;
|
||||
@@ -1429,10 +1425,7 @@ namespace Shardok {
|
||||
case AnimationType.Fear: return ActionType.FearAttack;
|
||||
// Magic
|
||||
case AnimationType.LightningBolt: return ActionType.LightningBolt;
|
||||
case AnimationType.MeteorStart: return ActionType.MeteorStart;
|
||||
case AnimationType.MeteorTarget: return ActionType.MeteorTarget;
|
||||
case AnimationType.MeteorCast: return ActionType.MeteorCast;
|
||||
case AnimationType.MeteorCancel: return ActionType.MeteorCancel;
|
||||
case AnimationType.HolyWave: return ActionType.HolyWave;
|
||||
case AnimationType.RaiseDead: return ActionType.RaisedUndead;
|
||||
case AnimationType.Control: return ActionType.Controlled;
|
||||
@@ -1556,11 +1549,8 @@ namespace Shardok {
|
||||
}
|
||||
break;
|
||||
case AnimationType.Move:
|
||||
if (moveAnimator != null && attackerUnit != null) {
|
||||
moveAnimator.AnimateMove(
|
||||
sourceGridIndex,
|
||||
targetGridIndex,
|
||||
attackerUnit.Battalion.Type);
|
||||
if (moveAnimator != null) {
|
||||
moveAnimator.AnimateMove(sourceGridIndex, targetGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.LightningBolt:
|
||||
@@ -1578,26 +1568,11 @@ namespace Shardok {
|
||||
raiseDeadAnimator.AnimateRaiseDead(targetGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.MeteorStart:
|
||||
if (meteorAnimator != null) {
|
||||
meteorAnimator.AnimateMeteorStart(sourceGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.MeteorTarget:
|
||||
if (meteorAnimator != null) {
|
||||
meteorAnimator.AnimateMeteorTarget(targetGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.MeteorCast:
|
||||
if (meteorAnimator != null) {
|
||||
meteorAnimator.AnimateMeteorCast(targetGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.MeteorCancel:
|
||||
if (meteorAnimator != null) {
|
||||
meteorAnimator.AnimateMeteorCancel(sourceGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.HolyWave:
|
||||
if (holyWaveAnimator != null) {
|
||||
var undeadCells = FindNearbyUndeadCells(sourceGridIndex, 2);
|
||||
@@ -1719,11 +1694,8 @@ namespace Shardok {
|
||||
}
|
||||
break;
|
||||
case AnimationType.Move:
|
||||
if (moveAnimator != null && attackerUnit != null) {
|
||||
moveAnimator.AnimateMove(
|
||||
sourceGridIndex,
|
||||
targetGridIndex,
|
||||
attackerUnit.Battalion.Type);
|
||||
if (moveAnimator != null) {
|
||||
moveAnimator.AnimateMove(sourceGridIndex, targetGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.LightningBolt:
|
||||
@@ -1741,26 +1713,11 @@ namespace Shardok {
|
||||
raiseDeadAnimator.AnimateRaiseDead(targetGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.MeteorStart:
|
||||
if (meteorAnimator != null) {
|
||||
meteorAnimator.AnimateMeteorStart(sourceGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.MeteorTarget:
|
||||
if (meteorAnimator != null) {
|
||||
meteorAnimator.AnimateMeteorTarget(targetGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.MeteorCast:
|
||||
if (meteorAnimator != null) {
|
||||
meteorAnimator.AnimateMeteorCast(targetGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.MeteorCancel:
|
||||
if (meteorAnimator != null) {
|
||||
meteorAnimator.AnimateMeteorCancel(sourceGridIndex);
|
||||
}
|
||||
break;
|
||||
case AnimationType.HolyWave:
|
||||
if (holyWaveAnimator != null) {
|
||||
var undeadCells = FindNearbyUndeadCells(sourceGridIndex, 2);
|
||||
|
||||
@@ -43,18 +43,6 @@ namespace Shardok {
|
||||
[Tooltip("Rotation arc of the swing (degrees)")]
|
||||
public float swingArc = 45f;
|
||||
|
||||
[Tooltip("Base rotation offset for hammer (degrees)")]
|
||||
public float hammerBaseRotation = 180f;
|
||||
|
||||
[Tooltip("Flip hammer horizontally")]
|
||||
public bool hammerFlipHorizontal = true;
|
||||
|
||||
[Tooltip("Base rotation offset for alternate tool (degrees)")]
|
||||
public float alternateBaseRotation = 0f;
|
||||
|
||||
[Tooltip("Flip alternate tool horizontally")]
|
||||
public bool alternateFlipHorizontal = false;
|
||||
|
||||
private HexGrid __hexGrid;
|
||||
|
||||
private void Start() { __hexGrid = FindObjectOfType<HexGrid>(); }
|
||||
@@ -91,11 +79,9 @@ namespace Shardok {
|
||||
}
|
||||
|
||||
private IEnumerator AnimateStrikes(Vector2 target, bool useBridgeStyle) {
|
||||
bool useAlternate = useBridgeStyle && alternateToolSprite != null;
|
||||
Sprite toolSprite =
|
||||
useAlternate ? alternateToolSprite : hammerSprite ?? alternateToolSprite;
|
||||
float baseRotation = useAlternate ? alternateBaseRotation : hammerBaseRotation;
|
||||
bool flipHorizontal = useAlternate ? alternateFlipHorizontal : hammerFlipHorizontal;
|
||||
Sprite toolSprite = useBridgeStyle && alternateToolSprite != null
|
||||
? alternateToolSprite
|
||||
: hammerSprite ?? alternateToolSprite;
|
||||
|
||||
// Create tool
|
||||
GameObject tool = new GameObject("Tool");
|
||||
@@ -104,8 +90,7 @@ namespace Shardok {
|
||||
SpriteRenderer toolRenderer = tool.AddComponent<SpriteRenderer>();
|
||||
toolRenderer.sprite = toolSprite;
|
||||
toolRenderer.sortingLayerName = "Effects";
|
||||
float xScale = flipHorizontal ? -toolScale : toolScale;
|
||||
tool.transform.localScale = new Vector3(xScale, toolScale, toolScale);
|
||||
tool.transform.localScale = Vector3.one * toolScale;
|
||||
|
||||
// Starting position (raised above target)
|
||||
Vector3 strikePoint = new Vector3(target.x, target.y, 5f);
|
||||
@@ -115,7 +100,7 @@ namespace Shardok {
|
||||
for (int i = 0; i < strikeCount; i++) {
|
||||
// Raise tool
|
||||
tool.transform.localPosition = raisePoint;
|
||||
tool.transform.localRotation = Quaternion.Euler(0, 0, baseRotation - swingArc);
|
||||
tool.transform.localRotation = Quaternion.Euler(0, 0, -swingArc);
|
||||
|
||||
float elapsed = 0f;
|
||||
float downDuration = strikeDuration * 0.4f;
|
||||
@@ -127,14 +112,14 @@ namespace Shardok {
|
||||
|
||||
tool.transform.localPosition = Vector3.Lerp(raisePoint, strikePoint, easeT);
|
||||
float angle = Mathf.Lerp(-swingArc, 0f, easeT);
|
||||
tool.transform.localRotation = Quaternion.Euler(0, 0, baseRotation + angle);
|
||||
tool.transform.localRotation = Quaternion.Euler(0, 0, angle);
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
tool.transform.localPosition = strikePoint;
|
||||
tool.transform.localRotation = Quaternion.Euler(0, 0, baseRotation);
|
||||
tool.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
||||
|
||||
// Spawn sparks on impact
|
||||
yield return StartCoroutine(SpawnSparks(target));
|
||||
@@ -152,7 +137,7 @@ namespace Shardok {
|
||||
|
||||
tool.transform.localPosition = Vector3.Lerp(strikePoint, raisePoint, easeT);
|
||||
float angle = Mathf.Lerp(0f, -swingArc, easeT);
|
||||
tool.transform.localRotation = Quaternion.Euler(0, 0, baseRotation + angle);
|
||||
tool.transform.localRotation = Quaternion.Euler(0, 0, angle);
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
|
||||
+142
-3
@@ -1,5 +1,144 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace EagleInstaller {
|
||||
public class CredentialManager {
|
||||
public const string ServerUrl = "https://assets.eagle0.net";
|
||||
public class Credentials {
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string ServerUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class CredentialManager {
|
||||
private const string RegistryKeyPath = @"SOFTWARE\Eagle0\Launcher";
|
||||
private const string UsernameValueName = "Username";
|
||||
private const string ServerUrlValueName = "ServerUrl";
|
||||
private const string CredentialFileName = "eagle0_credentials.dat";
|
||||
|
||||
private static readonly string CredentialFilePath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"eagle0",
|
||||
CredentialFileName);
|
||||
|
||||
/// <summary>
|
||||
/// Loads saved credentials. Returns null if no valid credentials are found.
|
||||
/// </summary>
|
||||
public static Credentials LoadCredentials() {
|
||||
try {
|
||||
// Try to load from registry (username and server URL)
|
||||
string username = null;
|
||||
string serverUrl = null;
|
||||
|
||||
using (var key = Registry.CurrentUser.OpenSubKey(RegistryKeyPath, false)) {
|
||||
if (key != null) {
|
||||
username = key.GetValue(UsernameValueName) as string;
|
||||
serverUrl = key.GetValue(ServerUrlValueName) as string;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(username)) { return null; }
|
||||
|
||||
// Try to load encrypted password from file
|
||||
string password = LoadEncryptedPassword();
|
||||
if (string.IsNullOrEmpty(password)) { return null; }
|
||||
|
||||
return new Credentials {
|
||||
Username = username,
|
||||
Password = password,
|
||||
ServerUrl = serverUrl ?? "https://eagle0.net"
|
||||
};
|
||||
} catch (Exception) {
|
||||
// If anything fails, return null to prompt for credentials
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves credentials to registry (username/server) and encrypted file (password).
|
||||
/// </summary>
|
||||
public static void SaveCredentials(Credentials credentials) {
|
||||
try {
|
||||
// Save username and server URL to registry
|
||||
using (var key = Registry.CurrentUser.CreateSubKey(RegistryKeyPath)) {
|
||||
key.SetValue(UsernameValueName, credentials.Username);
|
||||
key.SetValue(ServerUrlValueName, credentials.ServerUrl);
|
||||
}
|
||||
|
||||
// Save encrypted password to file
|
||||
SaveEncryptedPassword(credentials.Password);
|
||||
} catch (Exception ex) {
|
||||
throw new Exception($"Failed to save credentials: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all saved credentials.
|
||||
/// </summary>
|
||||
public static void ClearCredentials() {
|
||||
try {
|
||||
// Clear registry entries
|
||||
using (var key = Registry.CurrentUser.OpenSubKey(RegistryKeyPath, true)) {
|
||||
if (key != null) {
|
||||
key.DeleteValue(UsernameValueName, false);
|
||||
key.DeleteValue(ServerUrlValueName, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete password file
|
||||
if (File.Exists(CredentialFilePath)) { File.Delete(CredentialFilePath); }
|
||||
} catch (Exception) {
|
||||
// Ignore errors when clearing
|
||||
}
|
||||
}
|
||||
|
||||
private static string LoadEncryptedPassword() {
|
||||
try {
|
||||
if (!File.Exists(CredentialFilePath)) { return null; }
|
||||
|
||||
byte[] encryptedData = File.ReadAllBytes(CredentialFilePath);
|
||||
byte[] decryptedData = ProtectedData.Unprotect(
|
||||
encryptedData,
|
||||
null,
|
||||
DataProtectionScope.CurrentUser);
|
||||
return Encoding.UTF8.GetString(decryptedData);
|
||||
} catch (Exception) { return null; }
|
||||
}
|
||||
|
||||
private static void SaveEncryptedPassword(string password) {
|
||||
try {
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(CredentialFilePath));
|
||||
|
||||
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
|
||||
byte[] encryptedData =
|
||||
ProtectedData.Protect(passwordBytes, null, DataProtectionScope.CurrentUser);
|
||||
File.WriteAllBytes(CredentialFilePath, encryptedData);
|
||||
} catch (Exception ex) {
|
||||
throw new Exception($"Failed to save encrypted password: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves credentials in the same format used by the Unity client for compatibility.
|
||||
/// </summary>
|
||||
public static void SaveCredentialsForUnityClient(Credentials credentials) {
|
||||
try {
|
||||
// Unity uses PlayerPrefs which on Windows maps to registry entries
|
||||
// We'll save to the same location the Unity client expects
|
||||
const string unityRegistryPath =
|
||||
@"SOFTWARE\Unity\UnityEditor\CompanyName\ProductName";
|
||||
|
||||
using (var key = Registry.CurrentUser.CreateSubKey(unityRegistryPath)) {
|
||||
key.SetValue("urlKey", credentials.ServerUrl);
|
||||
key.SetValue("nameKey", credentials.Username);
|
||||
key.SetValue("passwordKey", credentials.Password);
|
||||
}
|
||||
} catch (Exception) {
|
||||
// If Unity credential sharing fails, continue anyway
|
||||
// The main launcher credentials are still saved
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
@@ -10,6 +13,9 @@ namespace EagleInstaller {
|
||||
internal class UpdaterFailureException
|
||||
(string message) : Exception(message);
|
||||
|
||||
internal class AuthenticationFailedException
|
||||
(string message) : Exception(message);
|
||||
|
||||
public class InstallerUpdateInfo {
|
||||
public bool InstallerNeedsUpdate { get; set; }
|
||||
public string NewInstallerVersion { get; set; }
|
||||
@@ -31,6 +37,8 @@ namespace EagleInstaller {
|
||||
private readonly HttpClient _httpClient;
|
||||
private static readonly SemaphoreSlim Pool = new(DownloadSlots, DownloadSlots);
|
||||
|
||||
// Current credentials and service URL
|
||||
private Credentials CurrentCredentials { get; set; }
|
||||
private Uri ServiceUrl { get; set; }
|
||||
|
||||
private struct FetchAttempt {
|
||||
@@ -74,12 +82,27 @@ namespace EagleInstaller {
|
||||
return dict;
|
||||
}
|
||||
|
||||
public EagleUpdater(string serverUrl) {
|
||||
if (string.IsNullOrEmpty(serverUrl)) {
|
||||
throw new ArgumentNullException(nameof(serverUrl));
|
||||
}
|
||||
ServiceUrl = new Uri(serverUrl);
|
||||
public EagleUpdater(Credentials credentials) {
|
||||
CurrentCredentials =
|
||||
credentials ?? throw new ArgumentNullException(nameof(credentials));
|
||||
ServiceUrl = new Uri(CurrentCredentials.ServerUrl);
|
||||
|
||||
_httpClient = new HttpClient();
|
||||
SetAuthentication(CurrentCredentials.Username, CurrentCredentials.Password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the authentication credentials for future requests.
|
||||
/// </summary>
|
||||
public void UpdateCredentials(Credentials newCredentials) {
|
||||
CurrentCredentials = newCredentials;
|
||||
ServiceUrl = new Uri(CurrentCredentials.ServerUrl);
|
||||
SetAuthentication(CurrentCredentials.Username, CurrentCredentials.Password);
|
||||
}
|
||||
|
||||
private void SetAuthentication(string username, string password) {
|
||||
_httpClient.DefaultRequestHeaders.Authorization =
|
||||
AuthenticationHeader(username, password);
|
||||
}
|
||||
|
||||
private List<string> Discrepancies(
|
||||
@@ -233,6 +256,15 @@ namespace EagleInstaller {
|
||||
return existingFiles;
|
||||
}
|
||||
|
||||
private static AuthenticationHeaderValue AuthenticationHeader(
|
||||
string name,
|
||||
string password) {
|
||||
return new AuthenticationHeaderValue(
|
||||
"Basic",
|
||||
Convert.ToBase64String(
|
||||
System.Text.Encoding.ASCII.GetBytes($"{name}:{password}")));
|
||||
}
|
||||
|
||||
static async Task<string> LocalManifestText(string existingManifestPath) {
|
||||
if (!File.Exists(existingManifestPath)) return null;
|
||||
|
||||
@@ -243,6 +275,11 @@ namespace EagleInstaller {
|
||||
using HttpResponseMessage response =
|
||||
await _httpClient.GetAsync(new Uri(ServiceUrl, RemoteManifestName));
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized) {
|
||||
throw new AuthenticationFailedException(
|
||||
"Authentication failed while fetching manifest. Invalid username or password.");
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using Stream responseStream = await response.Content.ReadAsStreamAsync();
|
||||
using StreamReader reader = new StreamReader(responseStream);
|
||||
@@ -286,6 +323,11 @@ namespace EagleInstaller {
|
||||
using HttpResponseMessage response =
|
||||
await _httpClient.GetAsync(new Uri(ServiceUrl, RemotePrefix + remotePath));
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized) {
|
||||
throw new AuthenticationFailedException(
|
||||
$"Authentication failed while downloading {remotePath}. Invalid username or password.");
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using Stream responseStream = await response.Content.ReadAsStreamAsync();
|
||||
await using FileStream fileStream = File.Create(writePath);
|
||||
@@ -337,6 +379,11 @@ namespace EagleInstaller {
|
||||
using HttpResponseMessage response = await _httpClient.GetAsync(
|
||||
new Uri(ServiceUrl, "assets/" + installerUrl));
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized) {
|
||||
throw new AuthenticationFailedException(
|
||||
"Authentication failed while downloading new installer. Invalid username or password.");
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
long? totalBytes = response.Content.Headers.ContentLength;
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace EagleInstaller {
|
||||
public partial class LoginDialog : Form {
|
||||
public Credentials Credentials { get; private set; }
|
||||
public bool SaveCredentials { get; private set; }
|
||||
|
||||
private TextBox usernameTextBox;
|
||||
private TextBox passwordTextBox;
|
||||
private TextBox serverUrlTextBox;
|
||||
private CheckBox saveCredentialsCheckBox;
|
||||
private Button okButton;
|
||||
private Button cancelButton;
|
||||
private Label titleLabel;
|
||||
private Label usernameLabel;
|
||||
private Label passwordLabel;
|
||||
private Label serverLabel;
|
||||
|
||||
public LoginDialog(string title = "Eagle0 Login", Credentials existingCredentials = null) {
|
||||
InitializeComponent();
|
||||
this.Text = title;
|
||||
titleLabel.Text = title;
|
||||
|
||||
if (existingCredentials != null) {
|
||||
usernameTextBox.Text = existingCredentials.Username ?? "";
|
||||
passwordTextBox.Text = existingCredentials.Password ?? "";
|
||||
serverUrlTextBox.Text = existingCredentials.ServerUrl ?? "https://eagle0.net";
|
||||
} else {
|
||||
serverUrlTextBox.Text = "https://eagle0.net";
|
||||
}
|
||||
|
||||
// Default to saving credentials
|
||||
saveCredentialsCheckBox.Checked = true;
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
// Form properties
|
||||
this.Size = new Size(400, 280);
|
||||
this.StartPosition = FormStartPosition.CenterScreen;
|
||||
this.FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.ShowInTaskbar = false;
|
||||
|
||||
// Title label
|
||||
titleLabel = new Label {
|
||||
Text = "Eagle0 Login",
|
||||
Font = new Font("Microsoft Sans Serif", 12F, FontStyle.Bold),
|
||||
Location = new Point(20, 20),
|
||||
Size = new Size(350, 25),
|
||||
TextAlign = ContentAlignment.MiddleCenter
|
||||
};
|
||||
|
||||
// Username label and textbox
|
||||
usernameLabel = new Label {
|
||||
Text = "Username:",
|
||||
Location = new Point(20, 60),
|
||||
Size = new Size(80, 23),
|
||||
TextAlign = ContentAlignment.MiddleLeft
|
||||
};
|
||||
|
||||
usernameTextBox = new TextBox {
|
||||
Location = new Point(110, 60),
|
||||
Size = new Size(250, 23),
|
||||
TabIndex = 0
|
||||
};
|
||||
|
||||
// Password label and textbox
|
||||
passwordLabel = new Label {
|
||||
Text = "Password:",
|
||||
Location = new Point(20, 90),
|
||||
Size = new Size(80, 23),
|
||||
TextAlign = ContentAlignment.MiddleLeft
|
||||
};
|
||||
|
||||
passwordTextBox = new TextBox {
|
||||
Location = new Point(110, 90),
|
||||
Size = new Size(250, 23),
|
||||
UseSystemPasswordChar = true,
|
||||
TabIndex = 1
|
||||
};
|
||||
|
||||
// Server URL label and textbox
|
||||
serverLabel = new Label {
|
||||
Text = "Server:",
|
||||
Location = new Point(20, 120),
|
||||
Size = new Size(80, 23),
|
||||
TextAlign = ContentAlignment.MiddleLeft
|
||||
};
|
||||
|
||||
serverUrlTextBox = new TextBox {
|
||||
Location = new Point(110, 120),
|
||||
Size = new Size(250, 23),
|
||||
TabIndex = 2
|
||||
};
|
||||
|
||||
// Save credentials checkbox
|
||||
saveCredentialsCheckBox = new CheckBox {
|
||||
Text = "Remember credentials",
|
||||
Location = new Point(20, 160),
|
||||
Size = new Size(200, 23),
|
||||
TabIndex = 3
|
||||
};
|
||||
|
||||
// OK button
|
||||
okButton = new Button {
|
||||
Text = "OK",
|
||||
Location = new Point(200, 200),
|
||||
Size = new Size(80, 30),
|
||||
TabIndex = 4,
|
||||
DialogResult = DialogResult.OK
|
||||
};
|
||||
okButton.Click += OkButton_Click;
|
||||
|
||||
// Cancel button
|
||||
cancelButton = new Button {
|
||||
Text = "Cancel",
|
||||
Location = new Point(290, 200),
|
||||
Size = new Size(80, 30),
|
||||
TabIndex = 5,
|
||||
DialogResult = DialogResult.Cancel
|
||||
};
|
||||
|
||||
// Add controls to form
|
||||
this.Controls.AddRange(new Control[] {
|
||||
titleLabel,
|
||||
usernameLabel,
|
||||
usernameTextBox,
|
||||
passwordLabel,
|
||||
passwordTextBox,
|
||||
serverLabel,
|
||||
serverUrlTextBox,
|
||||
saveCredentialsCheckBox,
|
||||
okButton,
|
||||
cancelButton
|
||||
});
|
||||
|
||||
// Set default button and cancel button
|
||||
this.AcceptButton = okButton;
|
||||
this.CancelButton = cancelButton;
|
||||
|
||||
// Focus on username field
|
||||
this.ActiveControl = usernameTextBox;
|
||||
}
|
||||
|
||||
private void OkButton_Click(object sender, EventArgs e) {
|
||||
// Validate input
|
||||
if (string.IsNullOrWhiteSpace(usernameTextBox.Text)) {
|
||||
MessageBox.Show(
|
||||
"Username is required.",
|
||||
"Validation Error",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
usernameTextBox.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(passwordTextBox.Text)) {
|
||||
MessageBox.Show(
|
||||
"Password is required.",
|
||||
"Validation Error",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
passwordTextBox.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(serverUrlTextBox.Text)) {
|
||||
MessageBox.Show(
|
||||
"Server URL is required.",
|
||||
"Validation Error",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
serverUrlTextBox.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create credentials object
|
||||
Credentials = new Credentials {
|
||||
Username = usernameTextBox.Text.Trim(),
|
||||
Password = passwordTextBox.Text,
|
||||
ServerUrl = serverUrlTextBox.Text.Trim()
|
||||
};
|
||||
|
||||
// Normalize server URL
|
||||
if (!Credentials.ServerUrl.StartsWith("http://") &&
|
||||
!Credentials.ServerUrl.StartsWith("https://")) {
|
||||
Credentials.ServerUrl = "https://" + Credentials.ServerUrl;
|
||||
}
|
||||
|
||||
SaveCredentials = saveCredentialsCheckBox.Checked;
|
||||
|
||||
this.DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the login dialog and returns the entered credentials, or null if cancelled.
|
||||
/// </summary>
|
||||
public static (Credentials credentials, bool saveCredentials) ShowLoginDialog(
|
||||
string title = "Eagle0 Login",
|
||||
Credentials existingCredentials = null) {
|
||||
using (var dialog = new LoginDialog(title, existingCredentials)) {
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
return (dialog.Credentials, dialog.SaveCredentials);
|
||||
}
|
||||
return (null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,29 @@ using System.Windows.Forms;
|
||||
|
||||
namespace EagleInstaller {
|
||||
public partial class MainForm : Form {
|
||||
private Panel _loginPanel;
|
||||
private Panel _progressPanel;
|
||||
private TextBox _progressTextBox;
|
||||
private Label _statusLabel;
|
||||
private Button _changeCredentialsButton;
|
||||
private Button _exitButton;
|
||||
|
||||
public string ServerUrl => CredentialManager.ServerUrl;
|
||||
// Login controls
|
||||
private TextBox _usernameTextBox;
|
||||
private TextBox _passwordTextBox;
|
||||
private TextBox _serverUrlTextBox;
|
||||
private CheckBox _saveCredentialsCheckBox;
|
||||
private Button _loginButton;
|
||||
private Button _cancelButton;
|
||||
|
||||
public MainForm() { InitializeComponent(); }
|
||||
public Credentials EnteredCredentials { get; private set; }
|
||||
public bool SaveCredentials { get; private set; }
|
||||
public bool LoginCancelled { get; private set; }
|
||||
|
||||
public MainForm() {
|
||||
InitializeComponent();
|
||||
ShowLoginView();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
// Form properties
|
||||
@@ -22,15 +37,114 @@ namespace EagleInstaller {
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = true;
|
||||
|
||||
// Create login panel
|
||||
CreateLoginPanel();
|
||||
|
||||
// Create progress panel
|
||||
CreateProgressPanel();
|
||||
|
||||
// Add panel to form
|
||||
// Add panels to form
|
||||
this.Controls.Add(_loginPanel);
|
||||
this.Controls.Add(_progressPanel);
|
||||
}
|
||||
|
||||
private void CreateLoginPanel() {
|
||||
_loginPanel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(20) };
|
||||
|
||||
// Title
|
||||
var titleLabel = new Label {
|
||||
Text = "Eagle0 Login",
|
||||
Font = new Font("Microsoft Sans Serif", 14F, FontStyle.Bold),
|
||||
Location = new Point(20, 20),
|
||||
Size = new Size(540, 30),
|
||||
TextAlign = ContentAlignment.MiddleCenter
|
||||
};
|
||||
|
||||
// Username
|
||||
var usernameLabel = new Label {
|
||||
Text = "Username:",
|
||||
Location = new Point(50, 80),
|
||||
Size = new Size(100, 23)
|
||||
};
|
||||
_usernameTextBox = new TextBox {
|
||||
Location = new Point(160, 80),
|
||||
Size = new Size(300, 23),
|
||||
TabIndex = 0
|
||||
};
|
||||
|
||||
// Password
|
||||
var passwordLabel = new Label {
|
||||
Text = "Password:",
|
||||
Location = new Point(50, 110),
|
||||
Size = new Size(100, 23)
|
||||
};
|
||||
_passwordTextBox = new TextBox {
|
||||
Location = new Point(160, 110),
|
||||
Size = new Size(300, 23),
|
||||
UseSystemPasswordChar = true,
|
||||
TabIndex = 1
|
||||
};
|
||||
|
||||
// Server
|
||||
var serverLabel = new Label {
|
||||
Text = "Server:",
|
||||
Location = new Point(50, 140),
|
||||
Size = new Size(100, 23)
|
||||
};
|
||||
_serverUrlTextBox = new TextBox {
|
||||
Location = new Point(160, 140),
|
||||
Size = new Size(300, 23),
|
||||
TabIndex = 2
|
||||
};
|
||||
|
||||
// Save credentials checkbox
|
||||
_saveCredentialsCheckBox = new CheckBox {
|
||||
Text = "Remember credentials",
|
||||
Location = new Point(50, 180),
|
||||
Size = new Size(200, 23),
|
||||
Checked = true,
|
||||
TabIndex = 3
|
||||
};
|
||||
|
||||
// Buttons
|
||||
_loginButton = new Button {
|
||||
Text = "Login",
|
||||
Location = new Point(280, 220),
|
||||
Size = new Size(80, 30),
|
||||
TabIndex = 4
|
||||
};
|
||||
_loginButton.Click += LoginButton_Click;
|
||||
|
||||
_cancelButton = new Button {
|
||||
Text = "Cancel",
|
||||
Location = new Point(380, 220),
|
||||
Size = new Size(80, 30),
|
||||
TabIndex = 5
|
||||
};
|
||||
_cancelButton.Click += CancelButton_Click;
|
||||
|
||||
// Add controls to login panel
|
||||
_loginPanel.Controls.AddRange(new Control[] {
|
||||
titleLabel,
|
||||
usernameLabel,
|
||||
_usernameTextBox,
|
||||
passwordLabel,
|
||||
_passwordTextBox,
|
||||
serverLabel,
|
||||
_serverUrlTextBox,
|
||||
_saveCredentialsCheckBox,
|
||||
_loginButton,
|
||||
_cancelButton
|
||||
});
|
||||
|
||||
// Set form defaults
|
||||
this.AcceptButton = _loginButton;
|
||||
this.CancelButton = _cancelButton;
|
||||
}
|
||||
|
||||
private void CreateProgressPanel() {
|
||||
_progressPanel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(10) };
|
||||
_progressPanel =
|
||||
new Panel { Dock = DockStyle.Fill, Padding = new Padding(10), Visible = false };
|
||||
|
||||
// Title and status
|
||||
var titleLabel = new Label {
|
||||
@@ -50,7 +164,7 @@ namespace EagleInstaller {
|
||||
// Progress text box with scrollbar
|
||||
_progressTextBox = new TextBox {
|
||||
Location = new Point(10, 75),
|
||||
Size = new Size(560, 310),
|
||||
Size = new Size(560, 280),
|
||||
Multiline = true,
|
||||
ReadOnly = true,
|
||||
ScrollBars = ScrollBars.Vertical,
|
||||
@@ -60,17 +174,52 @@ namespace EagleInstaller {
|
||||
TabIndex = 0
|
||||
};
|
||||
|
||||
// Exit button
|
||||
// Control buttons
|
||||
_changeCredentialsButton = new Button {
|
||||
Text = "Change Credentials",
|
||||
Location = new Point(350, 370),
|
||||
Size = new Size(130, 30),
|
||||
Enabled = false
|
||||
};
|
||||
_changeCredentialsButton.Click += ChangeCredentialsButton_Click;
|
||||
|
||||
_exitButton = new Button {
|
||||
Text = "Exit",
|
||||
Location = new Point(490, 395),
|
||||
Location = new Point(490, 370),
|
||||
Size = new Size(80, 30)
|
||||
};
|
||||
_exitButton.Click += ExitButton_Click;
|
||||
|
||||
// Add controls to progress panel
|
||||
_progressPanel.Controls.AddRange(
|
||||
new Control[] { titleLabel, _statusLabel, _progressTextBox, _exitButton });
|
||||
_progressPanel.Controls.AddRange(new Control[] {
|
||||
titleLabel,
|
||||
_statusLabel,
|
||||
_progressTextBox,
|
||||
_changeCredentialsButton,
|
||||
_exitButton
|
||||
});
|
||||
}
|
||||
|
||||
public void ShowLoginView() {
|
||||
_loginPanel.Visible = true;
|
||||
_progressPanel.Visible = false;
|
||||
this.ActiveControl = _usernameTextBox;
|
||||
}
|
||||
|
||||
public void ShowProgressView() {
|
||||
_loginPanel.Visible = false;
|
||||
_progressPanel.Visible = true;
|
||||
_changeCredentialsButton.Enabled = true;
|
||||
}
|
||||
|
||||
public void SetLoginCredentials(Credentials credentials) {
|
||||
if (credentials != null) {
|
||||
_usernameTextBox.Text = credentials.Username ?? "";
|
||||
_passwordTextBox.Text = credentials.Password ?? "";
|
||||
_serverUrlTextBox.Text = credentials.ServerUrl ?? "https://eagle0.net";
|
||||
} else {
|
||||
_serverUrlTextBox.Text = "https://eagle0.net";
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateStatus(string status) {
|
||||
@@ -92,6 +241,72 @@ namespace EagleInstaller {
|
||||
_progressTextBox.ScrollToCaret();
|
||||
}
|
||||
|
||||
private void LoginButton_Click(object sender, EventArgs e) {
|
||||
// Validate input
|
||||
if (string.IsNullOrWhiteSpace(_usernameTextBox.Text)) {
|
||||
MessageBox.Show(
|
||||
"Username is required.",
|
||||
"Validation Error",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
_usernameTextBox.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_passwordTextBox.Text)) {
|
||||
MessageBox.Show(
|
||||
"Password is required.",
|
||||
"Validation Error",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
_passwordTextBox.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_serverUrlTextBox.Text)) {
|
||||
MessageBox.Show(
|
||||
"Server URL is required.",
|
||||
"Validation Error",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
_serverUrlTextBox.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create credentials
|
||||
var serverUrl = _serverUrlTextBox.Text.Trim();
|
||||
if (!serverUrl.StartsWith("http://") && !serverUrl.StartsWith("https://")) {
|
||||
serverUrl = "https://" + serverUrl;
|
||||
}
|
||||
|
||||
EnteredCredentials = new Credentials {
|
||||
Username = _usernameTextBox.Text.Trim(),
|
||||
Password = _passwordTextBox.Text,
|
||||
ServerUrl = serverUrl
|
||||
};
|
||||
|
||||
SaveCredentials = _saveCredentialsCheckBox.Checked;
|
||||
LoginCancelled = false;
|
||||
|
||||
ShowProgressView();
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, EventArgs e) {
|
||||
LoginCancelled = true;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void ChangeCredentialsButton_Click(object sender, EventArgs e) { ShowLoginView(); }
|
||||
|
||||
private void ExitButton_Click(object sender, EventArgs e) { this.Close(); }
|
||||
|
||||
public static MainForm CreateMainForm(
|
||||
string title = "Eagle0 Login",
|
||||
Credentials existingCredentials = null) {
|
||||
var form = new MainForm();
|
||||
form.Text = title;
|
||||
form.SetLoginCredentials(existingCredentials);
|
||||
return form;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
@@ -35,6 +35,14 @@ namespace EagleInstaller {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Shift key is held during launch to force login
|
||||
bool shiftKeyHeld = (Control.ModifierKeys & Keys.Shift) == Keys.Shift;
|
||||
|
||||
// Check for credential management flags
|
||||
bool forceLogin =
|
||||
shiftKeyHeld || (args.Length > 0 &&
|
||||
(args[0] == "--login" || args[0] == "--change-credentials"));
|
||||
bool clearCredentials = args.Length > 0 && args[0] == "--clear-credentials";
|
||||
bool showHelp = args.Length > 0 && (args[0] == "--help" || args[0] == "-h");
|
||||
|
||||
// Show help if requested
|
||||
@@ -43,8 +51,16 @@ namespace EagleInstaller {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create main form with default server URL
|
||||
var mainForm = new MainForm();
|
||||
// Handle clear credentials flag
|
||||
if (clearCredentials) {
|
||||
Console.WriteLine("Clearing saved credentials...");
|
||||
CredentialManager.ClearCredentials();
|
||||
Console.WriteLine("Credentials cleared successfully.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create main form
|
||||
var mainForm = MainForm.CreateMainForm();
|
||||
Logger.Initialize(mainForm);
|
||||
|
||||
// Show the form and ensure handle is created
|
||||
@@ -52,7 +68,7 @@ namespace EagleInstaller {
|
||||
mainForm.CreateControl();
|
||||
|
||||
// Start the installer logic in the background
|
||||
var installerTask = RunInstallerLogic(mainForm);
|
||||
var installerTask = RunInstallerLogic(mainForm, forceLogin);
|
||||
|
||||
// Run the application with the main form
|
||||
Application.Run(mainForm);
|
||||
@@ -61,45 +77,84 @@ namespace EagleInstaller {
|
||||
await installerTask;
|
||||
}
|
||||
|
||||
private static async Task RunInstallerLogic(MainForm mainForm) {
|
||||
private static async Task RunInstallerLogic(MainForm mainForm, bool forceLogin) {
|
||||
try {
|
||||
string serverUrl = mainForm.ServerUrl;
|
||||
Logger.WriteLine($"Using server: {serverUrl}");
|
||||
|
||||
EagleUpdater updater = new EagleUpdater(serverUrl);
|
||||
|
||||
// Stage 1: Check if installer itself needs update
|
||||
var installerUpdateInfo = await updater.CheckForInstallerUpdate();
|
||||
if (installerUpdateInfo.InstallerNeedsUpdate) {
|
||||
Logger.UpdateStatus("Installer update required...");
|
||||
bool downloadSuccess = await updater.DownloadAndLaunchNewInstaller(
|
||||
installerUpdateInfo.NewInstallerUrl);
|
||||
if (downloadSuccess) {
|
||||
Logger.UpdateStatus("New installer launched. Exiting current version.");
|
||||
|
||||
// Wait a moment to ensure new installer starts before we exit
|
||||
await Task.Delay(1000);
|
||||
|
||||
// Exit the application directly
|
||||
Application.Exit();
|
||||
return; // Exit this version
|
||||
}
|
||||
|
||||
Logger.WriteError(
|
||||
"Failed to download new installer, continuing with current version...");
|
||||
// Get credentials (load saved or prompt user)
|
||||
Credentials credentials = await GetValidCredentials(mainForm, forceLogin);
|
||||
if (credentials == null) {
|
||||
Logger.WriteError("No valid credentials provided. Exiting.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Stage 2: Proceed with normal game update
|
||||
bool success = await updater.MaybePerformUpdateWithRetries(FileLocation);
|
||||
// Switch to progress view
|
||||
SafeInvoke(mainForm, () => mainForm.ShowProgressView());
|
||||
|
||||
if (success) {
|
||||
Logger.UpdateStatus("Launching Eagle0...");
|
||||
Process.Start(ExeLocation);
|
||||
EagleUpdater updater = new EagleUpdater(credentials);
|
||||
|
||||
// Exit the installer after launching the game
|
||||
Application.Exit();
|
||||
} else {
|
||||
Logger.WriteError("Update failed!");
|
||||
bool credentialsAreValid = false;
|
||||
while (!credentialsAreValid) {
|
||||
try {
|
||||
// Stage 1: Check if installer itself needs update
|
||||
var installerUpdateInfo = await updater.CheckForInstallerUpdate();
|
||||
if (installerUpdateInfo.InstallerNeedsUpdate) {
|
||||
Logger.UpdateStatus("Installer update required...");
|
||||
bool downloadSuccess = await updater.DownloadAndLaunchNewInstaller(
|
||||
installerUpdateInfo.NewInstallerUrl);
|
||||
if (downloadSuccess) {
|
||||
Logger.UpdateStatus(
|
||||
"New installer launched. Exiting current version.");
|
||||
|
||||
// Wait a moment to ensure new installer starts before we exit
|
||||
await Task.Delay(1000);
|
||||
|
||||
// Exit the application directly
|
||||
Application.Exit();
|
||||
return; // Exit this version
|
||||
}
|
||||
|
||||
Logger.WriteError(
|
||||
"Failed to download new installer, continuing with current version...");
|
||||
}
|
||||
|
||||
// Stage 2: Proceed with normal game update
|
||||
bool success = await updater.MaybePerformUpdateWithRetries(FileLocation);
|
||||
|
||||
if (success) {
|
||||
Logger.UpdateStatus("Launching Eagle0...");
|
||||
Process.Start(ExeLocation);
|
||||
|
||||
// Exit the installer after launching the game
|
||||
Application.Exit();
|
||||
return;
|
||||
} else {
|
||||
Logger.WriteError("Update failed!");
|
||||
credentialsAreValid = true; // Don't retry on non-auth failures
|
||||
}
|
||||
} catch (AuthenticationFailedException ex) {
|
||||
Logger.WriteError($"Authentication failed: {ex.Message}");
|
||||
|
||||
// Clear saved credentials since they're invalid
|
||||
CredentialManager.ClearCredentials();
|
||||
|
||||
// Show login view again
|
||||
SafeInvoke(mainForm, () => mainForm.ShowLoginView());
|
||||
|
||||
// Wait for new credentials
|
||||
credentials = await GetValidCredentials(
|
||||
mainForm,
|
||||
true,
|
||||
"Authentication Failed - Please re-enter credentials");
|
||||
if (credentials == null) {
|
||||
Logger.WriteError("No valid credentials provided. Exiting.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch back to progress view
|
||||
SafeInvoke(mainForm, () => mainForm.ShowProgressView());
|
||||
|
||||
// Update updater with new credentials
|
||||
updater.UpdateCredentials(credentials);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) { Logger.WriteError($"Unexpected error: {ex.Message}"); }
|
||||
}
|
||||
@@ -236,6 +291,88 @@ namespace EagleInstaller {
|
||||
} finally { progressForm?.Close(); }
|
||||
}
|
||||
|
||||
private static async Task<Credentials> GetValidCredentials(
|
||||
MainForm mainForm,
|
||||
bool forceLogin = false,
|
||||
string title = "Eagle0 Login") {
|
||||
Credentials savedCredentials = null;
|
||||
|
||||
// Try to load saved credentials first (unless forced to login)
|
||||
if (!forceLogin) {
|
||||
savedCredentials = CredentialManager.LoadCredentials();
|
||||
|
||||
if (savedCredentials != null) {
|
||||
Logger.WriteLine(
|
||||
$"Found saved credentials for user: {savedCredentials.Username}");
|
||||
// Return saved credentials immediately for automatic login
|
||||
return savedCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
// If no saved credentials or force login, show login form
|
||||
Logger.WriteLine("No saved credentials found. Please enter your login information.");
|
||||
|
||||
// Set the form title and pre-fill credentials if forcing login
|
||||
SafeInvoke(mainForm, () => {
|
||||
mainForm.Text = title;
|
||||
if (forceLogin && savedCredentials != null) {
|
||||
mainForm.SetLoginCredentials(savedCredentials);
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for user to enter credentials through the form
|
||||
return await WaitForCredentials(mainForm);
|
||||
}
|
||||
|
||||
private static async Task<Credentials> WaitForCredentials(MainForm mainForm) {
|
||||
var tcs = new TaskCompletionSource<Credentials>();
|
||||
|
||||
// Set up event handler to capture when login is completed
|
||||
EventHandler loginCompleted = null;
|
||||
loginCompleted = (sender, e) => {
|
||||
var form = sender as MainForm;
|
||||
if (form is { EnteredCredentials : not null }) {
|
||||
var credentials = form.EnteredCredentials;
|
||||
var saveCredentials = form.SaveCredentials;
|
||||
|
||||
if (saveCredentials) {
|
||||
try {
|
||||
CredentialManager.SaveCredentials(credentials);
|
||||
CredentialManager.SaveCredentialsForUnityClient(credentials);
|
||||
Logger.WriteLine("Credentials saved successfully.");
|
||||
} catch (Exception ex) {
|
||||
Logger.WriteError($"Failed to save credentials: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
tcs.SetResult(credentials);
|
||||
} else if (form != null && form.LoginCancelled) {
|
||||
tcs.SetResult(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Monitor for form changes (this is a simplified approach)
|
||||
var monitorTask = Task.Run(async () => {
|
||||
while (!tcs.Task.IsCompleted) {
|
||||
await Task.Delay(100);
|
||||
if (mainForm.EnteredCredentials != null || mainForm.LoginCancelled) {
|
||||
loginCompleted(mainForm, EventArgs.Empty);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
private static void SafeInvoke(Control control, Action action) {
|
||||
if (control.IsHandleCreated && control.InvokeRequired) {
|
||||
control.Invoke(action);
|
||||
} else if (control.IsHandleCreated) {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShowHelp() {
|
||||
Console.WriteLine("Eagle0 Installer");
|
||||
Console.WriteLine();
|
||||
@@ -243,11 +380,19 @@ namespace EagleInstaller {
|
||||
Console.WriteLine(" EagleInstaller.exe [OPTIONS]");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("OPTIONS:");
|
||||
Console.WriteLine(" --help, -h Show this help message");
|
||||
Console.WriteLine(" --help, -h Show this help message");
|
||||
Console.WriteLine(" --login, --change-credentials Force credential input dialog");
|
||||
Console.WriteLine(" --clear-credentials Clear saved credentials and exit");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("NORMAL OPERATION:");
|
||||
Console.WriteLine(" Run without arguments to start the normal update process.");
|
||||
Console.WriteLine(" Downloads game files from assets.eagle0.net CDN.");
|
||||
Console.WriteLine(" If credentials are saved, automatic login will occur.");
|
||||
Console.WriteLine(" Hold Shift during launch to force credential input dialog.");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("EXAMPLES:");
|
||||
Console.WriteLine(" EagleInstaller.exe # Normal operation");
|
||||
Console.WriteLine(" EagleInstaller.exe --login # Force new login");
|
||||
Console.WriteLine(" EagleInstaller.exe --clear-credentials # Clear saved login");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
manifest_name = eagle0_manifest.txt
|
||||
remote_manifest_prefix = installer/
|
||||
remote_asset_prefix = unity3d/win/
|
||||
remote_manifest_prefix = assets/installer/
|
||||
remote_asset_prefix = assets/unity3d/win/
|
||||
|
||||
@@ -55,8 +55,8 @@ func main() {
|
||||
log.Fatalf("Failed to create AWS client: %v", err)
|
||||
}
|
||||
|
||||
// Upload installer to S3 with public ACL
|
||||
err = bucketBasics.UploadFilePublic(bucketName, installerKey, installerPath)
|
||||
// Upload installer to S3
|
||||
err = bucketBasics.UploadFile(bucketName, installerKey, installerPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to upload installer: %v", err)
|
||||
}
|
||||
@@ -73,8 +73,8 @@ func main() {
|
||||
log.Fatalf("Failed to create version file: %v", err)
|
||||
}
|
||||
|
||||
// Upload version file to S3 with public ACL
|
||||
err = bucketBasics.UploadFilePublic(bucketName, versionKey, tempVersionFile)
|
||||
// Upload version file to S3
|
||||
err = bucketBasics.UploadFile(bucketName, versionKey, tempVersionFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to upload version file: %v", err)
|
||||
}
|
||||
|
||||
@@ -112,8 +112,8 @@ func (mm *ManifestManager) UpdateSection(sectionName, sectionContent string) err
|
||||
// Build new manifest
|
||||
newManifest := mm.buildManifest(sections)
|
||||
|
||||
// Upload updated manifest with public ACL
|
||||
err = mm.bucketBasics.UploadBytesPublic(bucketName, manifestPath, []byte(newManifest))
|
||||
// Upload updated manifest
|
||||
err = mm.bucketBasics.UploadBytes(bucketName, manifestPath, []byte(newManifest))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upload manifest: %v", err)
|
||||
}
|
||||
|
||||
+2
-2
@@ -118,7 +118,7 @@ func syncToS3(bb aws.BucketBasics, localDir string, s3Root string, localShas map
|
||||
}
|
||||
|
||||
if !objectMatches {
|
||||
err = bb.UploadFilePublic(bucketName, destinationPath, filePath)
|
||||
err = bb.UploadFile(bucketName, destinationPath, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func handleDir(tempDir string, outputManifestPath string) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = bb.UploadBytesPublic(bucketName, manifestPath, []byte(localManifest))
|
||||
err = bb.UploadBytes(bucketName, manifestPath, []byte(localManifest))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -130,30 +130,16 @@ func (basics BucketBasics) FetchString(bucketName string, objectKey string) (str
|
||||
}
|
||||
|
||||
func (basics BucketBasics) UploadFile(bucketName string, objectKey string, fileName string) error {
|
||||
return basics.UploadFileWithACL(bucketName, objectKey, fileName, "")
|
||||
}
|
||||
|
||||
func (basics BucketBasics) UploadFilePublic(bucketName string, objectKey string, fileName string) error {
|
||||
return basics.UploadFileWithACL(bucketName, objectKey, fileName, "public-read")
|
||||
}
|
||||
|
||||
func (basics BucketBasics) UploadFileWithACL(bucketName string, objectKey string, fileName string, acl string) error {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
input := &s3.PutObjectInput{
|
||||
_, err = basics.S3Client.PutObject(basics.ctx, &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: file,
|
||||
}
|
||||
if acl != "" {
|
||||
input.ACL = types.ObjectCannedACL(acl)
|
||||
}
|
||||
|
||||
_, err = basics.S3Client.PutObject(basics.ctx, input)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -167,24 +153,11 @@ func (basics BucketBasics) UploadFileWithACL(bucketName string, objectKey string
|
||||
}
|
||||
|
||||
func (basics BucketBasics) UploadBytes(bucketName string, objectKey string, data []byte) error {
|
||||
return basics.UploadBytesWithACL(bucketName, objectKey, data, "")
|
||||
}
|
||||
|
||||
func (basics BucketBasics) UploadBytesPublic(bucketName string, objectKey string, data []byte) error {
|
||||
return basics.UploadBytesWithACL(bucketName, objectKey, data, "public-read")
|
||||
}
|
||||
|
||||
func (basics BucketBasics) UploadBytesWithACL(bucketName string, objectKey string, data []byte, acl string) error {
|
||||
input := &s3.PutObjectInput{
|
||||
_, err := basics.S3Client.PutObject(basics.ctx, &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: bytes.NewReader(data),
|
||||
}
|
||||
if acl != "" {
|
||||
input.ACL = types.ObjectCannedACL(acl)
|
||||
}
|
||||
|
||||
_, err := basics.S3Client.PutObject(basics.ctx, input)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -91,15 +91,6 @@ object Main {
|
||||
SimpleTimedLogger.printLogger.logLine("Sentry error tracking enabled")
|
||||
}
|
||||
|
||||
// Set up global uncaught exception handler to log and report to Sentry
|
||||
Thread.setDefaultUncaughtExceptionHandler { (thread, throwable) =>
|
||||
SimpleTimedLogger.printLogger.logLine(
|
||||
s"Uncaught exception in thread ${thread.getName}: $throwable"
|
||||
)
|
||||
throwable.printStackTrace()
|
||||
Sentry.captureException(throwable)
|
||||
}
|
||||
|
||||
SimpleTimedLogger.printLogger.logLine("Starting!")
|
||||
|
||||
// Register shutdown hook to flush pending S3 saves on graceful shutdown
|
||||
|
||||
@@ -204,11 +204,11 @@ case class EngineImpl(
|
||||
def getAvailablePlayerCommands(
|
||||
fid: FactionId
|
||||
): Option[AvailableCommands] = {
|
||||
val currentStateProto = GameStateConverter.toProto(currentState)
|
||||
val commandsByProvince =
|
||||
availableCommandsFactory.availablePlayerCommands(currentState, fid)
|
||||
availableCommandsFactory.availablePlayerCommands(currentStateProto, fid)
|
||||
|
||||
Option.when(commandsByProvince.nonEmpty) {
|
||||
val currentStateProto = GameStateConverter.toProto(currentState)
|
||||
AvailableCommands(
|
||||
token = tokenForFaction(fid),
|
||||
commandsByProvince = commandsByProvince,
|
||||
|
||||
+7
-9
@@ -1,34 +1,32 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.AlmsAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.settings.{MaxAlmsFood, MinVigorForAlms}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.AlmsAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.Profession
|
||||
|
||||
object AvailableAlmsCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableAlmsCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
def availableHeroIds(gs: GameState, pid: ProvinceId): Vector[HeroId] =
|
||||
gs.provinces(pid)
|
||||
.rulingFactionHeroIds
|
||||
.toVector
|
||||
.filter(hid => gs.heroes(hid).vigor >= MinVigorForAlms.doubleValue)
|
||||
.sortBy(hid => (gs.heroes(hid).profession != Profession.Paladin, -gs.heroes(hid).vigor))
|
||||
.sortBy(hid => (!gs.heroes(hid).profession.isPaladin, -gs.heroes(hid).vigor))
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[AlmsAvailableCommand] = {
|
||||
val hids = availableHeroIds(gameState, provinceId)
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
Option.when(hids.nonEmpty && province.food > 0) {
|
||||
AlmsAvailable(
|
||||
AlmsAvailableCommand(
|
||||
availableHeroIds = hids,
|
||||
actingProvinceId = provinceId,
|
||||
foodAvailable = Math.min(MaxAlmsFood.intValue, province.food)
|
||||
foodAvailable = Math.min(MaxAlmsFood.intValue, gameState.provinces(provinceId).food)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+23
-15
@@ -1,24 +1,26 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.{ApprehendOutlawAvailableCommand, ResidentOutlaw}
|
||||
import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.UNAFFILIATED_HERO_OUTLAW
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
|
||||
import net.eagle0.eagle.library.settings.MinVigorForApprehendOutlaw
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{ApprehendOutlawAvailable, ResidentOutlaw}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.{UnaffiliatedHeroT, UnaffiliatedHeroType}
|
||||
import net.eagle0.eagle.library.util.view_filters.HeroViewFilter
|
||||
|
||||
object AvailableApprehendOutlawCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableApprehendOutlawCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private def availableToCapture(province: ProvinceT): Vector[UnaffiliatedHeroT] =
|
||||
private def availableToCapture(province: Province): Vector[UnaffiliatedHero] =
|
||||
province.unaffiliatedHeroes
|
||||
.filter(_.unaffiliatedHeroType == UnaffiliatedHeroType.Outlaw)
|
||||
.filter(_.`type` == UNAFFILIATED_HERO_OUTLAW)
|
||||
.toVector
|
||||
|
||||
private def availableActors(
|
||||
gameState: GameState,
|
||||
province: ProvinceT
|
||||
): Vector[HeroT] =
|
||||
province: Province
|
||||
): Vector[Hero] =
|
||||
province.rulingFactionHeroIds
|
||||
.map(gameState.heroes)
|
||||
.filter(_.vigor >= MinVigorForApprehendOutlaw.doubleValue)
|
||||
@@ -28,19 +30,25 @@ object AvailableApprehendOutlawCommandFactory extends ScalaAvailableCommandsFact
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[ApprehendOutlawAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
val outlaws = availableToCapture(province)
|
||||
val actors = availableActors(gameState, province)
|
||||
|
||||
Option.when(outlaws.nonEmpty && actors.nonEmpty) {
|
||||
ApprehendOutlawAvailable(
|
||||
ApprehendOutlawAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
availableHeroIds = actors.map(_.id),
|
||||
outlaws = outlaws.map(outlaw =>
|
||||
ResidentOutlaw(
|
||||
heroId = outlaw.heroId,
|
||||
lastFactionId = outlaw.lastFactionId
|
||||
hero = Some(
|
||||
HeroViewFilter.filteredHeroView(
|
||||
hero = gameState.heroes(outlaw.heroId),
|
||||
gs = gameState,
|
||||
factionId = Some(factionId)
|
||||
)
|
||||
),
|
||||
lastFactionId = outlaw.lastFaction
|
||||
)
|
||||
),
|
||||
availableBattalionIds = province.battalionIds
|
||||
|
||||
+17
-15
@@ -1,18 +1,17 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.util.battalion_type_finder.BattalionTypeFinder
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{ArmTroopsAvailable, ArmamentCost}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.api.available_command.{ArmTroopsAvailableCommand, ArmamentCost}
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
import net.eagle0.eagle.library.util.LegacyBattalionUtils
|
||||
|
||||
object AvailableArmTroopsCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableArmTroopsCommandFactory extends AvailableCommandsFactoryForType {
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[ArmTroopsAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
if !province.rulerIsTraveling then return None
|
||||
@@ -22,26 +21,29 @@ object AvailableArmTroopsCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
val battalions = province.battalionIds
|
||||
.map(gameState.battalions)
|
||||
.filter(
|
||||
_.armament < ProvinceUtils.effectiveInfrastructure(province).floor
|
||||
_.armament < LegacyProvinceUtils.effectiveInfrastructure(province).floor
|
||||
)
|
||||
.filter(_.size > 0)
|
||||
|
||||
val armamentCosts =
|
||||
battalions
|
||||
.map(_.typeId)
|
||||
.map(_.`type`)
|
||||
.distinct
|
||||
.map(bt => BattalionTypeFinder.battalionType(bt, gameState.battalionTypes))
|
||||
.map(bt =>
|
||||
LegacyBattalionUtils
|
||||
.battalionType(bt, gameState.battalionTypes.toVector)
|
||||
)
|
||||
.map(tp =>
|
||||
ArmamentCost(
|
||||
cost = tp.baseArmamentCostPerTroop * province.priceIndex,
|
||||
battalionTypeId = tp.typeId.value
|
||||
`type` = tp.typeId
|
||||
)
|
||||
)
|
||||
|
||||
val affordableBattalions = battalions
|
||||
.filter(b =>
|
||||
b.size * armamentCosts
|
||||
.find(_.battalionTypeId == b.typeId.value)
|
||||
.find(_.`type` == b.`type`)
|
||||
.get
|
||||
.cost < province.gold
|
||||
)
|
||||
@@ -49,12 +51,12 @@ object AvailableArmTroopsCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
if affordableBattalions.isEmpty then return None
|
||||
|
||||
Some(
|
||||
ArmTroopsAvailable(
|
||||
ArmTroopsAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
availableBattalions = affordableBattalions.map(_.id),
|
||||
armamentCosts = armamentCosts,
|
||||
maxArmament = ProvinceUtils.effectiveInfrastructure(province).floor.toInt,
|
||||
availableBattalionTypeIds = gameState.battalionTypes.map(_.typeId.value)
|
||||
maxArmament = LegacyProvinceUtils.effectiveInfrastructure(province).floor.toInt,
|
||||
availableBattalionTypeIds = gameState.battalionTypes.map(_.typeId)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+50
-67
@@ -2,21 +2,24 @@ package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.common.MoreOption
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.util.{IncomingArmyUtils, ProvinceDistances}
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
import net.eagle0.eagle.model.state.{Army, HostileArmyGroup, HostileArmyGroupStatus, MovingArmy}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{
|
||||
ArmyStats,
|
||||
AttackDecisionAvailable,
|
||||
ExpandedCombatUnit
|
||||
import net.eagle0.eagle.api.available_command.AttackDecisionAvailableCommand
|
||||
import net.eagle0.eagle.api.command.util.army_stats.ArmyStats
|
||||
import net.eagle0.eagle.api.command.util.attack_decision_type.{
|
||||
AdvanceDecision,
|
||||
AttackDecisionType,
|
||||
DemandTributeDecision,
|
||||
SafePassageDecision,
|
||||
WithdrawDecision
|
||||
}
|
||||
import net.eagle0.eagle.model.state.command.common.AttackDecisionType
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.common.tribute_amount.TributeAmount
|
||||
import net.eagle0.eagle.internal.army.{HostileArmyGroup, MovingArmy}
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.util.{IncomingArmyUtils, ProvinceDistances}
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
|
||||
object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableAttackDecisionCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
// We need to include the armies that have already withdrawn or advanced, in a consistent sort order,
|
||||
// so that the player doesn't learn what others have done by the shifts.
|
||||
@@ -27,7 +30,8 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
gs.provinces(pid)
|
||||
.hostileArmies
|
||||
.flatMap(_.armies)
|
||||
.sortBy(ma => (ma.army.factionId, ma.army.units.head.heroId))
|
||||
.sortBy(ma => (ma.getArmy.factionId, ma.getArmy.units.head.heroId))
|
||||
.toVector
|
||||
|
||||
private def myDecidingHostileArmies(
|
||||
pid: ProvinceId,
|
||||
@@ -37,7 +41,7 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
gs.provinces(pid)
|
||||
.hostileArmies
|
||||
.find(_.factionId == fid)
|
||||
.filter(_.status == HostileArmyGroupStatus.AwaitingDecision)
|
||||
.filter(_.status.asMessage.sealedValue.isAwaitingDecision)
|
||||
|
||||
private def defendingArmyStats(
|
||||
attackerFid: FactionId,
|
||||
@@ -62,11 +66,13 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
.takeRight(heroCount)
|
||||
.sum
|
||||
|
||||
province.rulingFactionId.map { _ =>
|
||||
province.rulingFactionId.map { defenderFid =>
|
||||
ArmyStats(
|
||||
factionId = province.rulingFactionId.get,
|
||||
factionId = defenderFid,
|
||||
heroCount = heroCount,
|
||||
troopCount = troopCount
|
||||
troopCount = troopCount,
|
||||
hostility = LegacyFactionUtils.hostilityStatus(defenderFid, attackerFid, gs),
|
||||
originProvinceId = pid
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -76,26 +82,15 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
pid: ProvinceId,
|
||||
gs: GameState
|
||||
): Vector[ArmyStats] =
|
||||
relevantArmies(pid, gs).map(ma => stats(ma.army, gs)) ++ defendingArmyStats(
|
||||
relevantArmies(pid, gs).map(ia =>
|
||||
IncomingArmyUtils.stats(toFid, ia.getArmy, gs, ia.originProvince)
|
||||
) ++ defendingArmyStats(
|
||||
toFid,
|
||||
pid,
|
||||
gs
|
||||
)
|
||||
|
||||
private def stats(
|
||||
army: Army,
|
||||
gs: GameState
|
||||
): ArmyStats = {
|
||||
val battalions = army.units.flatMap(_.battalionId).map(gs.battalions)
|
||||
val troopCount = battalions.map(_.size).sum
|
||||
ArmyStats(
|
||||
factionId = army.factionId,
|
||||
heroCount = army.units.size,
|
||||
troopCount = troopCount
|
||||
)
|
||||
}
|
||||
|
||||
private def hasResourcesToDemand(province: ProvinceT): Boolean =
|
||||
private def hasResourcesToDemand(province: Province): Boolean =
|
||||
province.gold > 0 || province.food > 0
|
||||
|
||||
private def allArmiesCanFlee(
|
||||
@@ -106,7 +101,7 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
myDecidingHostileArmies(pid, fid, gs)
|
||||
.map(_.armies)
|
||||
.getOrElse(Vector())
|
||||
.map(_.army)
|
||||
.flatMap(_.army)
|
||||
.forall(_.fleeProvinceId.isDefined)
|
||||
|
||||
private def maybeAdvanceDecision(
|
||||
@@ -121,28 +116,30 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
fid,
|
||||
gs
|
||||
)
|
||||
)(AttackDecisionType.Advance)
|
||||
)(AdvanceDecision())
|
||||
|
||||
private def maybeDemandTributeDecision(
|
||||
gs: GameState,
|
||||
pid: ProvinceId,
|
||||
fid: FactionId
|
||||
): Option[AttackDecisionType] = {
|
||||
val province = gs.provinces(pid)
|
||||
): Option[AttackDecisionType] =
|
||||
Option.when(
|
||||
hasResourcesToDemand(province)
|
||||
hasResourcesToDemand(gs.provinces(pid))
|
||||
&& IncomingArmyUtils.isHostileProvince(
|
||||
province,
|
||||
gs.provinces(pid),
|
||||
fid,
|
||||
gs
|
||||
) && allArmiesCanFlee(gs, pid, fid)
|
||||
)(
|
||||
AttackDecisionType.DemandTribute(
|
||||
gold = province.gold,
|
||||
food = province.food
|
||||
DemandTributeDecision(
|
||||
Some(
|
||||
TributeAmount(
|
||||
food = gs.provinces(pid).food,
|
||||
gold = gs.provinces(pid).gold
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private def maybeWithdrawDecision(
|
||||
gs: GameState,
|
||||
@@ -151,7 +148,7 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
): Option[AttackDecisionType] =
|
||||
Option.when(
|
||||
allArmiesCanFlee(gs, pid, fid)
|
||||
)(AttackDecisionType.Withdraw)
|
||||
)(WithdrawDecision())
|
||||
|
||||
private def maybeSafePassageDecision(
|
||||
gs: GameState,
|
||||
@@ -166,8 +163,8 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
gs
|
||||
)
|
||||
) {
|
||||
FactionUtils
|
||||
.provinces(fid, gs.provinces.values.toVector)
|
||||
LegacyFactionUtils
|
||||
.provinces(fid, gs)
|
||||
.maxByOption(_.rulingFactionHeroIds.size)
|
||||
.orElse {
|
||||
gs.provinces.values
|
||||
@@ -180,13 +177,13 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
.closestProvinceOption(
|
||||
pid,
|
||||
emptyProvinces,
|
||||
p => gs.provinces(p).neighbors.map(_.provinceId)
|
||||
p => gs.provinces(p).neighbors.map(_.provinceId).toVector
|
||||
)
|
||||
.map(pwd => gs.provinces(pwd.provinceId))
|
||||
}
|
||||
}
|
||||
.map { returnProvince =>
|
||||
AttackDecisionType.SafePassage(returnProvince.id)
|
||||
SafePassageDecision(returnProvince.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,27 +204,11 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
fid
|
||||
).toVector ++ maybeSafePassageDecision(gs, pid, fid).toVector
|
||||
|
||||
private def expandedCombatUnits(
|
||||
armies: Vector[MovingArmy],
|
||||
gs: GameState
|
||||
): Vector[ExpandedCombatUnit] =
|
||||
armies
|
||||
.flatMap(_.army.units)
|
||||
.map { cu =>
|
||||
val battalionOpt = cu.battalionId.map(gs.battalions)
|
||||
ExpandedCombatUnit(
|
||||
heroId = cu.heroId,
|
||||
battalionId = cu.battalionId,
|
||||
heroName = gs.heroes(cu.heroId).nameTextId,
|
||||
troopCount = battalionOpt.map(_.size).getOrElse(0)
|
||||
)
|
||||
}
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[AttackDecisionAvailableCommand] = {
|
||||
internalRequire(
|
||||
IncomingArmyUtils.incomingArmiesAreMutuallyAllied(
|
||||
gameState.provinces(provinceId),
|
||||
@@ -241,10 +222,12 @@ object AvailableAttackDecisionCommandFactory extends ScalaAvailableCommandsFacto
|
||||
case items if items.isEmpty => None
|
||||
case nonemptyDecisionOptions =>
|
||||
Some(
|
||||
AttackDecisionAvailable(
|
||||
AttackDecisionAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
armies = armyStats(factionId, provinceId, gameState),
|
||||
actingUnits = expandedCombatUnits(hostileArmyGroup.armies, gameState),
|
||||
actingUnits = hostileArmyGroup.armies
|
||||
.flatMap(_.getArmy.units)
|
||||
.map(cu => ExpandedCombatUnitUtils.expandedCombatUnit(cu, gameState)),
|
||||
availableDecisions = nonemptyDecisionOptions
|
||||
)
|
||||
)
|
||||
|
||||
+150
-225
@@ -18,15 +18,13 @@ import net.eagle0.eagle.api.selected_command.{
|
||||
TradeSelectedCommand
|
||||
}
|
||||
import net.eagle0.eagle.common.round_phase.RoundPhase.*
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.internal.province.Province as ProvinceProto
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.actions.impl.command.AvailableCommandTypeMap
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
import net.eagle0.eagle.library.util.IncomingArmyUtils
|
||||
import net.eagle0.eagle.model.proto_converters.command.available.AvailableCommandConverter
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
object AvailableCommandsFactory {
|
||||
def shouldFollow(
|
||||
@@ -47,7 +45,7 @@ object AvailableCommandsFactory {
|
||||
def suggestedProvinceId(
|
||||
fid: FactionId,
|
||||
commandsByProvince: Map[ProvinceId, OneProvinceAvailableCommands],
|
||||
gs: GameStateProto
|
||||
gs: GameState
|
||||
): ProvinceId = {
|
||||
val lastActedProvinceId =
|
||||
gs.factions(fid).lastActedProvinceIdThisRound
|
||||
@@ -77,60 +75,63 @@ object AvailableCommandsFactory {
|
||||
}
|
||||
|
||||
class AvailableCommandsFactory(
|
||||
private val travelingFactories: Vector[UnifiedCommandFactory] = Vector(
|
||||
ScalaFactoryAdapter(AvailableRecruitHeroesCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableDeclineQuestCommandsFactory),
|
||||
ScalaFactoryAdapter(AvailableDivineCommandsFactory),
|
||||
ScalaFactoryAdapter(AvailableArmTroopsCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableTradeCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableManagePrisonersCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableReturnCommandsFactory)
|
||||
private val travelingFactories: Vector[AvailableCommandsFactoryForType] = Vector(
|
||||
AvailableRecruitHeroesCommandFactory,
|
||||
AvailableDeclineQuestCommandsFactory,
|
||||
AvailableDivineCommandsFactory,
|
||||
AvailableArmTroopsCommandFactory,
|
||||
AvailableTradeCommandFactory,
|
||||
AvailableManagePrisonersCommandFactory,
|
||||
AvailableReturnCommandsFactory
|
||||
),
|
||||
private val handleRiotFactories: Vector[UnifiedCommandFactory] = Vector(
|
||||
ScalaFactoryAdapter(AvailableHandleRiotCrackDownCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableHandleRiotDoNothingCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableHandleRiotGiveCommandFactory)
|
||||
private val handleRiotFactories: Vector[AvailableCommandsFactoryForType] = Vector(
|
||||
AvailableHandleRiotCrackDownCommandFactory,
|
||||
AvailableHandleRiotDoNothingCommandFactory,
|
||||
AvailableHandleRiotGiveCommandFactory
|
||||
),
|
||||
private val freeForAllDecisionPhaseFactories: Vector[UnifiedCommandFactory] = Vector(
|
||||
ScalaFactoryAdapter(AvailableFreeForAllDecisionCommandFactory)
|
||||
private val freeForAllDecisionPhaseFactories: Vector[
|
||||
AvailableCommandsFactoryForType
|
||||
] = Vector(
|
||||
AvailableFreeForAllDecisionCommandFactory
|
||||
),
|
||||
private val attackDecisionPhaseFactories: Vector[UnifiedCommandFactory] = Vector(
|
||||
ScalaFactoryAdapter(AvailableAttackDecisionCommandFactory)
|
||||
private val attackDecisionPhaseFactories: Vector[
|
||||
AvailableCommandsFactoryForType
|
||||
] = Vector(
|
||||
AvailableAttackDecisionCommandFactory
|
||||
),
|
||||
private val defensePhaseFactories: Vector[UnifiedCommandFactory] = Vector(
|
||||
ScalaFactoryAdapter(AvailableResolveTributeCommandsFactory),
|
||||
ScalaFactoryAdapter(AvailableDefendCommandsFactory)
|
||||
private val defensePhaseFactories: Vector[AvailableCommandsFactoryForType] = Vector(
|
||||
AvailableResolveTributeCommandsFactory,
|
||||
AvailableDefendCommandsFactory
|
||||
),
|
||||
private val commandPhaseFactories: Vector[UnifiedCommandFactory] = Vector(
|
||||
ScalaFactoryAdapter(AvailableApprehendOutlawCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableControlWeatherCommandsFactory),
|
||||
ScalaFactoryAdapter(AvailableDiplomacyCommandsFactory),
|
||||
ScalaFactoryAdapter(AvailableExileVassalCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableFeastCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableHeroGiftCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableImproveCommandsFactory),
|
||||
ScalaFactoryAdapter(AvailableMarchCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableIssueOrdersCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableOrganizeTroopsCommandsFactory),
|
||||
ScalaFactoryAdapter(AvailableAlmsCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableReconCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableRestCommandsFactory),
|
||||
ScalaFactoryAdapter(AvailableSendSuppliesCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableStartEpidemicCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableSuppressBeastsCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableSwearBrotherhoodCommandFactory),
|
||||
ScalaFactoryAdapter(AvailableTrainCommandsFactory),
|
||||
ScalaFactoryAdapter(AvailableTravelCommandsFactory)
|
||||
private val commandPhaseFactories: Vector[AvailableCommandsFactoryForType] = Vector(
|
||||
AvailableApprehendOutlawCommandFactory,
|
||||
AvailableControlWeatherCommandsFactory,
|
||||
AvailableDiplomacyCommandsFactory,
|
||||
AvailableExileVassalCommandFactory,
|
||||
AvailableFeastCommandFactory,
|
||||
AvailableHeroGiftCommandFactory,
|
||||
AvailableImproveCommandsFactory,
|
||||
AvailableMarchCommandFactory,
|
||||
AvailableIssueOrdersCommandFactory,
|
||||
AvailableOrganizeTroopsCommandsFactory,
|
||||
AvailableAlmsCommandFactory,
|
||||
AvailableReconCommandFactory,
|
||||
AvailableRestCommandsFactory,
|
||||
AvailableSendSuppliesCommandFactory,
|
||||
AvailableStartEpidemicCommandFactory,
|
||||
AvailableSuppressBeastsCommandFactory,
|
||||
AvailableSwearBrotherhoodCommandFactory,
|
||||
AvailableTrainCommandsFactory,
|
||||
AvailableTravelCommandsFactory
|
||||
)
|
||||
) {
|
||||
private def allCommands(
|
||||
factories: Vector[UnifiedCommandFactory],
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
factories: Vector[AvailableCommandsFactoryForType],
|
||||
gs: GameState,
|
||||
fid: FactionId,
|
||||
pid: ProvinceId
|
||||
): Vector[AvailableCommand] =
|
||||
factories.flatMap(_.availableCommand(scalaState, protoState, fid, pid)).sortBy {
|
||||
factories.flatMap(_.availableCommand(gs, fid, pid)).sortBy {
|
||||
// Travel commands
|
||||
case _: RecruitHeroesAvailableCommand => 1
|
||||
case _: DivineAvailableCommand => 2
|
||||
@@ -169,11 +170,11 @@ class AvailableCommandsFactory(
|
||||
case _ => 100
|
||||
}
|
||||
|
||||
def fid(gs: GameStateProto, pid: ProvinceId): Option[FactionId] =
|
||||
def fid(gs: GameState, pid: ProvinceId): Option[FactionId] =
|
||||
gs.provinces(pid).rulingFactionId
|
||||
|
||||
private def suggestedCommandIndex(
|
||||
gs: GameStateProto,
|
||||
gs: GameState,
|
||||
pid: ProvinceId,
|
||||
commands: Vector[AvailableCommand]
|
||||
): Int =
|
||||
@@ -188,7 +189,7 @@ class AvailableCommandsFactory(
|
||||
.getOrElse(0)
|
||||
|
||||
private def optionalOneProvinceAvailableCommands(
|
||||
gs: GameStateProto,
|
||||
gs: GameState,
|
||||
pid: ProvinceId,
|
||||
commands: Vector[AvailableCommand]
|
||||
): Option[OneProvinceAvailableCommands] =
|
||||
@@ -204,155 +205,115 @@ class AvailableCommandsFactory(
|
||||
)
|
||||
}
|
||||
|
||||
def hasAvailablePleaseRecruitMePhaseCommands(gs: GameStateProto): Boolean = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
def hasAvailablePleaseRecruitMePhaseCommands(gs: GameState): Boolean =
|
||||
gs.factions.keys
|
||||
.exists(fid => pleaseRecruitMePhasePlayerCommands(scalaState, gs, fid).nonEmpty)
|
||||
}
|
||||
.exists(fid => pleaseRecruitMePhasePlayerCommands(gs, fid).nonEmpty)
|
||||
|
||||
def commandPhaseCommandsForProvince(
|
||||
gs: GameStateProto,
|
||||
pid: ProvinceId
|
||||
): Option[OneProvinceAvailableCommands] = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
commandPhaseCommandsForProvinceInternal(scalaState, gs, pid)
|
||||
}
|
||||
|
||||
private def commandPhaseCommandsForProvinceInternal(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
pid: ProvinceId
|
||||
): Option[OneProvinceAvailableCommands] =
|
||||
optionalOneProvinceAvailableCommands(
|
||||
gs = protoState,
|
||||
gs = gs,
|
||||
pid = pid,
|
||||
commands =
|
||||
if protoState.provinces(pid).rulerIsTraveling then
|
||||
allCommands(travelingFactories, scalaState, protoState, fid(protoState, pid).get, pid)
|
||||
if gs.provinces(pid).rulerIsTraveling then allCommands(travelingFactories, gs, fid(gs, pid).get, pid)
|
||||
else
|
||||
allCommands(
|
||||
commandPhaseFactories,
|
||||
scalaState,
|
||||
protoState,
|
||||
fid(protoState, pid).get,
|
||||
gs,
|
||||
fid(gs, pid).get,
|
||||
pid
|
||||
)
|
||||
)
|
||||
|
||||
private def provinceHasCommandPhaseCommands(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
pid: ProvinceId
|
||||
): Boolean =
|
||||
if protoState.provinces(pid).rulerIsTraveling then
|
||||
hasCommand(travelingFactories, scalaState, protoState, fid(protoState, pid).get, pid)
|
||||
if gs.provinces(pid).rulerIsTraveling then hasCommand(travelingFactories, gs, fid(gs, pid).get, pid)
|
||||
else
|
||||
hasCommand(
|
||||
commandPhaseFactories,
|
||||
scalaState,
|
||||
protoState,
|
||||
fid(protoState, pid).get,
|
||||
gs,
|
||||
fid(gs, pid).get,
|
||||
pid
|
||||
)
|
||||
|
||||
def defensePhaseCommandsForProvince(
|
||||
gs: GameStateProto,
|
||||
pid: ProvinceId
|
||||
): Option[OneProvinceAvailableCommands] = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
defensePhaseCommandsForProvinceInternal(scalaState, gs, pid)
|
||||
}
|
||||
|
||||
private def defensePhaseCommandsForProvinceInternal(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
pid: ProvinceId
|
||||
): Option[OneProvinceAvailableCommands] =
|
||||
optionalOneProvinceAvailableCommands(
|
||||
gs = protoState,
|
||||
gs = gs,
|
||||
pid = pid,
|
||||
commands = allCommands(
|
||||
defensePhaseFactories,
|
||||
scalaState,
|
||||
protoState,
|
||||
protoState.provinces(pid).rulingFactionId.get,
|
||||
gs,
|
||||
gs.provinces(pid).rulingFactionId.get,
|
||||
pid
|
||||
)
|
||||
)
|
||||
|
||||
private def hasCommand(
|
||||
factories: Vector[UnifiedCommandFactory],
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
factories: Vector[AvailableCommandsFactoryForType],
|
||||
gs: GameState,
|
||||
fid: FactionId,
|
||||
pid: ProvinceId
|
||||
): Boolean =
|
||||
factories.exists(_.availableCommand(scalaState, protoState, fid, pid).nonEmpty)
|
||||
factories.exists(_.availableCommand(gs, fid, pid).nonEmpty)
|
||||
|
||||
private def availableAftermathCommandsForProvince(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
pid: ProvinceId
|
||||
): Option[OneProvinceAvailableCommands] =
|
||||
optionalOneProvinceAvailableCommands(
|
||||
gs = protoState,
|
||||
gs = gs,
|
||||
pid = pid,
|
||||
commands = scalaState
|
||||
commands = gs
|
||||
.provinces(pid)
|
||||
.rulingFactionId
|
||||
.flatMap(fid =>
|
||||
AvailableHandleCapturedHeroCommandFactory
|
||||
.availableCommand(
|
||||
gameState = scalaState,
|
||||
gameState = gs,
|
||||
factionId = fid,
|
||||
provinceId = pid
|
||||
)
|
||||
.map(cmd => AvailableCommandConverter.toProto(cmd, protoState, fid))
|
||||
)
|
||||
.toVector
|
||||
)
|
||||
|
||||
private def factionLeaderProvinces(
|
||||
gs: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): Iterable[ProvinceProto] =
|
||||
): Iterable[Province] =
|
||||
gs.provinces.values
|
||||
.filter(_.rulingFactionId.contains(fid))
|
||||
.filter(p => LegacyProvinceUtils.ruledByFactionLeader(p, gs))
|
||||
|
||||
private def defensePhasePlayerCommands(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
SortedMap(
|
||||
factionLeaderProvinces(protoState, fid)
|
||||
factionLeaderProvinces(gs, fid)
|
||||
.map(_.id)
|
||||
.flatMap(pid => defensePhaseCommandsForProvinceInternal(scalaState, protoState, pid).map(pid -> _))
|
||||
.flatMap(pid => defensePhaseCommandsForProvince(gs, pid).map(pid -> _))
|
||||
.toVector*
|
||||
)
|
||||
|
||||
def handleRiotPhaseCommandsForOneProvince(
|
||||
gs: GameStateProto,
|
||||
p: ProvinceProto
|
||||
): Option[OneProvinceAvailableCommands] = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
handleRiotPhaseCommandsForOneProvinceInternal(scalaState, gs, p)
|
||||
}
|
||||
|
||||
private def handleRiotPhaseCommandsForOneProvinceInternal(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
p: ProvinceProto
|
||||
gs: GameState,
|
||||
p: Province
|
||||
): Option[OneProvinceAvailableCommands] =
|
||||
p.rulingHeroId.flatMap { fid =>
|
||||
optionalOneProvinceAvailableCommands(
|
||||
gs = protoState,
|
||||
gs = gs,
|
||||
pid = p.id,
|
||||
commands = allCommands(
|
||||
factories = handleRiotFactories,
|
||||
scalaState = scalaState,
|
||||
protoState = protoState,
|
||||
gs = gs,
|
||||
fid = fid,
|
||||
pid = p.id
|
||||
)
|
||||
@@ -360,19 +321,17 @@ class AvailableCommandsFactory(
|
||||
}
|
||||
|
||||
private def attackDecisionPhaseCommandsForOneProvince(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId,
|
||||
p: ProvinceProto
|
||||
p: Province
|
||||
): Option[OneProvinceAvailableCommands] =
|
||||
MoreOption.flatWhen(p.hostileArmies.exists(_.factionId == fid))(
|
||||
optionalOneProvinceAvailableCommands(
|
||||
gs = protoState,
|
||||
gs = gs,
|
||||
pid = p.id,
|
||||
commands = allCommands(
|
||||
factories = attackDecisionPhaseFactories,
|
||||
scalaState = scalaState,
|
||||
protoState = protoState,
|
||||
gs = gs,
|
||||
fid = fid,
|
||||
pid = p.id
|
||||
)
|
||||
@@ -380,173 +339,146 @@ class AvailableCommandsFactory(
|
||||
)
|
||||
|
||||
private def freeForAllDecisionPhaseCommandsForOneProvince(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId,
|
||||
p: ProvinceProto
|
||||
p: Province
|
||||
): Option[OneProvinceAvailableCommands] =
|
||||
optionalOneProvinceAvailableCommands(
|
||||
gs = protoState,
|
||||
gs = gs,
|
||||
pid = p.id,
|
||||
commands = allCommands(
|
||||
factories = freeForAllDecisionPhaseFactories,
|
||||
scalaState = scalaState,
|
||||
protoState = protoState,
|
||||
gs = gs,
|
||||
fid = fid,
|
||||
pid = p.id
|
||||
)
|
||||
)
|
||||
|
||||
def attackDecisionPhasePlayerCommands(
|
||||
gs: GameStateProto,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
attackDecisionPhasePlayerCommandsInternal(scalaState, gs, fid)
|
||||
}
|
||||
|
||||
private def attackDecisionPhasePlayerCommandsInternal(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
SortedMap(
|
||||
protoState.provinces.values
|
||||
gs.provinces.values
|
||||
.filter(
|
||||
_.hostileArmies.exists(ha => ha.factionId == fid)
|
||||
)
|
||||
.flatMap(p =>
|
||||
attackDecisionPhaseCommandsForOneProvince(scalaState, protoState, fid, p)
|
||||
attackDecisionPhaseCommandsForOneProvince(gs, fid, p)
|
||||
.map(opac => p.id -> opac)
|
||||
)
|
||||
.toVector*
|
||||
)
|
||||
|
||||
private def handleRiotPhaseCommandsForFaction(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
SortedMap(
|
||||
protoState.provinces.values
|
||||
gs.provinces.values
|
||||
.filter(_.rulingFactionId.contains(fid))
|
||||
.filter(LegacyProvinceUtils.hasImminentRiot)
|
||||
.filterNot(_.hasActed)
|
||||
.flatMap(p =>
|
||||
handleRiotPhaseCommandsForOneProvinceInternal(scalaState, protoState, p)
|
||||
handleRiotPhaseCommandsForOneProvince(gs, p)
|
||||
.map(opac => p.id -> opac)
|
||||
)
|
||||
.toVector*
|
||||
)
|
||||
|
||||
private def handleRiotPhaseCommandsForFactionLeader(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
handleRiotPhaseCommandsForFaction(scalaState, protoState, fid).filter {
|
||||
handleRiotPhaseCommandsForFaction(gs, fid).filter {
|
||||
case (pid, _: OneProvinceAvailableCommands) =>
|
||||
LegacyProvinceUtils.ruledByFactionLeader(protoState.provinces(pid), protoState)
|
||||
LegacyProvinceUtils.ruledByFactionLeader(gs.provinces(pid), gs)
|
||||
}
|
||||
|
||||
private def freeForAllDecisionPhasePlayerCommands(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
SortedMap(
|
||||
protoState.provinces.values
|
||||
gs.provinces.values
|
||||
.filter(
|
||||
_.hostileArmies.exists(ha => ha.factionId == fid)
|
||||
)
|
||||
.flatMap(p =>
|
||||
freeForAllDecisionPhaseCommandsForOneProvince(scalaState, protoState, fid, p)
|
||||
freeForAllDecisionPhaseCommandsForOneProvince(gs, fid, p)
|
||||
.map(opac => p.id -> opac)
|
||||
)
|
||||
.toVector*
|
||||
)
|
||||
|
||||
private def commandsPhasePlayerCommands(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
SortedMap(
|
||||
factionLeaderProvinces(protoState, fid)
|
||||
factionLeaderProvinces(gs, fid)
|
||||
.filterNot(_.hasActed)
|
||||
.map(_.id)
|
||||
.flatMap(pid => commandPhaseCommandsForProvinceInternal(scalaState, protoState, pid).map(pid -> _))
|
||||
.flatMap(pid => commandPhaseCommandsForProvince(gs, pid).map(pid -> _))
|
||||
.toVector*
|
||||
)
|
||||
|
||||
private def hasCommandsPhaseCommandsForPlayer(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): Boolean =
|
||||
factionLeaderProvinces(protoState, fid)
|
||||
factionLeaderProvinces(gs, fid)
|
||||
.filterNot(_.hasActed)
|
||||
.exists(p => provinceHasCommandPhaseCommands(scalaState, protoState, p.id))
|
||||
.exists(p => provinceHasCommandPhaseCommands(gs, p.id))
|
||||
|
||||
private def hasPlayerDefenseCommandsForPlayer(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): Boolean =
|
||||
factionLeaderProvinces(protoState, fid).exists(p =>
|
||||
defensePhaseCommandsForProvinceInternal(scalaState, protoState, p.id).isDefined
|
||||
)
|
||||
factionLeaderProvinces(gs, fid).exists(p => defensePhaseCommandsForProvince(gs, p.id).isDefined)
|
||||
|
||||
def hasAvailableHandleRiotPhaseCommands(gs: GameStateProto): Boolean = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
gs.factions.keys.exists(fid => handleRiotPhaseCommandsForFactionLeader(scalaState, gs, fid).nonEmpty)
|
||||
}
|
||||
def hasAvailableHandleRiotPhaseCommands(gs: GameState): Boolean =
|
||||
gs.factions.keys.exists(fid => handleRiotPhaseCommandsForFactionLeader(gs, fid).nonEmpty)
|
||||
|
||||
def hasAvailablePlayerCommandsPhaseCommands(gs: GameStateProto): Boolean = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
gs.factions.keys.exists(fid => hasCommandsPhaseCommandsForPlayer(scalaState, gs, fid))
|
||||
}
|
||||
def hasAvailablePlayerCommandsPhaseCommands(gs: GameState): Boolean =
|
||||
gs.factions.keys.exists(fid => hasCommandsPhaseCommandsForPlayer(gs, fid))
|
||||
|
||||
def hasAvailableFreeForAllDecisionPhaseCommands(gs: GameStateProto): Boolean = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
gs.factions.keys.exists(fid => freeForAllDecisionPhasePlayerCommands(scalaState, gs, fid).nonEmpty)
|
||||
}
|
||||
def hasAvailableFreeForAllDecisionPhaseCommands(gs: GameState): Boolean =
|
||||
gs.factions.keys.exists(fid => freeForAllDecisionPhasePlayerCommands(gs, fid).nonEmpty)
|
||||
|
||||
def hasAvailableAttackDecisionPhaseCommands(gs: GameStateProto): Boolean =
|
||||
def hasAvailableAttackDecisionPhaseCommands(gs: GameState): Boolean =
|
||||
gs.factions.keys.exists(fid => attackDecisionPhasePlayerCommands(gs, fid).nonEmpty)
|
||||
|
||||
def hasAvailablePlayerDefenseCommands(gs: GameStateProto): Boolean = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
gs.factions.keys.exists(fid => hasPlayerDefenseCommandsForPlayer(scalaState, gs, fid))
|
||||
}
|
||||
def hasAvailablePlayerDefenseCommands(gs: GameState): Boolean =
|
||||
gs.factions.keys.exists(fid => hasPlayerDefenseCommandsForPlayer(gs, fid))
|
||||
|
||||
private def diplomacyResolutionPhasePlayerCommands(
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
(
|
||||
for {
|
||||
faction <- protoState.factions.get(fid)
|
||||
faction <- gs.factions.get(fid)
|
||||
diplomacyCommand <- AvailableResolveInvitationCommandFactory
|
||||
.availableCommand(protoState, faction.id)
|
||||
.availableCommand(gs, faction.id)
|
||||
.orElse(
|
||||
AvailableResolveTruceOfferCommandFactory
|
||||
.availableCommand(protoState, faction.id)
|
||||
.availableCommand(gs, faction.id)
|
||||
)
|
||||
.orElse(
|
||||
AvailableResolveRansomOfferCommandFactory
|
||||
.availableCommand(protoState, faction.id)
|
||||
.availableCommand(gs, faction.id)
|
||||
)
|
||||
.orElse(
|
||||
AvailableResolveAllianceOfferCommandFactory
|
||||
.availableCommand(protoState, faction.id)
|
||||
.availableCommand(gs, faction.id)
|
||||
)
|
||||
.orElse(
|
||||
AvailableResolveBreakAllianceCommandFactory
|
||||
.availableCommand(protoState, faction.id)
|
||||
.availableCommand(gs, faction.id)
|
||||
)
|
||||
oneProvinceAvailableCommand <- optionalOneProvinceAvailableCommands(
|
||||
gs = protoState,
|
||||
gs = gs,
|
||||
0,
|
||||
Vector(diplomacyCommand)
|
||||
)
|
||||
@@ -554,62 +486,56 @@ class AvailableCommandsFactory(
|
||||
).getOrElse(SortedMap.empty)
|
||||
|
||||
private def pleaseRecruitMePhasePlayerCommands(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
(
|
||||
for {
|
||||
faction <- protoState.factions.get(fid)
|
||||
faction <- gs.factions.get(fid)
|
||||
pleaseRecruitMeCommand <- AvailablePleaseRecruitMeCommandFactory
|
||||
.availableCommand(scalaState, faction.id)
|
||||
.availableCommand(GameStateConverter.fromProto(gs), faction.id)
|
||||
oneProvinceAvailableCommand <- optionalOneProvinceAvailableCommands(
|
||||
gs = protoState,
|
||||
gs = gs,
|
||||
0,
|
||||
Vector(pleaseRecruitMeCommand)
|
||||
)
|
||||
} yield SortedMap(0 -> oneProvinceAvailableCommand)
|
||||
).getOrElse(SortedMap.empty)
|
||||
|
||||
def hasAvailableDiplomacyResolutionPhaseCommands(gs: GameStateProto): Boolean =
|
||||
def hasAvailableDiplomacyResolutionPhaseCommands(gs: GameState): Boolean =
|
||||
gs.factions.keys
|
||||
.exists(fid => diplomacyResolutionPhasePlayerCommands(gs, fid).nonEmpty)
|
||||
|
||||
private def availableAftermathPhasePlayerCommands(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
SortedMap(
|
||||
LegacyFactionUtils
|
||||
.provinces(fid, protoState)
|
||||
.provinces(fid, gs)
|
||||
.map(_.id)
|
||||
.flatMap(pid => availableAftermathCommandsForProvince(scalaState, protoState, pid).map(pid -> _))*
|
||||
.flatMap(pid => availableAftermathCommandsForProvince(gs, pid).map(pid -> _))*
|
||||
)
|
||||
|
||||
/** Main entry point - takes Scala GameState. Converts to proto once internally. */
|
||||
def availablePlayerCommands(
|
||||
scalaState: GameState,
|
||||
gs: GameState,
|
||||
fid: FactionId
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] = {
|
||||
val protoState = GameStateConverter.toProto(scalaState)
|
||||
protoState.currentPhase match {
|
||||
case HANDLE_RIOT => handleRiotPhaseCommandsForFactionLeader(scalaState, protoState, fid)
|
||||
case PLEASE_RECRUIT_ME => pleaseRecruitMePhasePlayerCommands(scalaState, protoState, fid)
|
||||
case PLAYER_COMMANDS => commandsPhasePlayerCommands(scalaState, protoState, fid)
|
||||
): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
|
||||
gs.currentPhase match {
|
||||
case HANDLE_RIOT => handleRiotPhaseCommandsForFactionLeader(gs, fid)
|
||||
case PLEASE_RECRUIT_ME => pleaseRecruitMePhasePlayerCommands(gs, fid)
|
||||
case PLAYER_COMMANDS => commandsPhasePlayerCommands(gs, fid)
|
||||
case FREE_FOR_ALL_DECISION =>
|
||||
freeForAllDecisionPhasePlayerCommands(scalaState, protoState, fid)
|
||||
case ATTACK_DECISION => attackDecisionPhasePlayerCommandsInternal(scalaState, protoState, fid)
|
||||
case DEFENSE_DECISION => defensePhasePlayerCommands(scalaState, protoState, fid)
|
||||
case BATTLE_AFTERMATH => availableAftermathPhasePlayerCommands(scalaState, protoState, fid)
|
||||
freeForAllDecisionPhasePlayerCommands(gs, fid)
|
||||
case ATTACK_DECISION => attackDecisionPhasePlayerCommands(gs, fid)
|
||||
case DEFENSE_DECISION => defensePhasePlayerCommands(gs, fid)
|
||||
case BATTLE_AFTERMATH => availableAftermathPhasePlayerCommands(gs, fid)
|
||||
case DIPLOMACY_RESOLUTION =>
|
||||
diplomacyResolutionPhasePlayerCommands(protoState, fid)
|
||||
diplomacyResolutionPhasePlayerCommands(gs, fid)
|
||||
case _ => SortedMap.empty[ProvinceId, OneProvinceAvailableCommands]
|
||||
}
|
||||
}
|
||||
|
||||
def hasAvailablePlayerCommands(gs: GameStateProto): Boolean = {
|
||||
val scalaState = GameStateConverter.fromProto(gs)
|
||||
def hasAvailablePlayerCommands(gs: GameState): Boolean =
|
||||
gs.currentPhase match {
|
||||
case DIPLOMACY_RESOLUTION =>
|
||||
hasAvailableDiplomacyResolutionPhaseCommands(gs)
|
||||
@@ -625,13 +551,12 @@ class AvailableCommandsFactory(
|
||||
hasAvailableFreeForAllDecisionPhaseCommands(gs)
|
||||
case PLAYER_COMMANDS =>
|
||||
gs.factions.keys.exists { fid =>
|
||||
hasCommandsPhaseCommandsForPlayer(scalaState, gs, fid)
|
||||
hasCommandsPhaseCommandsForPlayer(gs, fid)
|
||||
}
|
||||
case BATTLE_AFTERMATH =>
|
||||
gs.factions.keys.exists { fid =>
|
||||
availableAftermathPhasePlayerCommands(scalaState, gs, fid).nonEmpty
|
||||
availableAftermathPhasePlayerCommands(gs, fid).nonEmpty
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
-19
@@ -1,44 +1,49 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.ControlWeatherAvailableCommand
|
||||
import net.eagle0.eagle.api.available_command.ControlWeatherAvailableCommand.TargetProvinceOptions
|
||||
import net.eagle0.eagle.api.command.util.control_weather_type.ControlWeatherType.{
|
||||
CONTROL_WEATHER_END_BLIZZARD,
|
||||
CONTROL_WEATHER_END_DROUGHT,
|
||||
CONTROL_WEATHER_START_BLIZZARD,
|
||||
CONTROL_WEATHER_START_DROUGHT
|
||||
}
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.settings.MinVigorForControlWeather
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{ControlWeatherAvailable, TargetProvinceOptions}
|
||||
import net.eagle0.eagle.model.state.command.common.ControlWeatherType
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.Profession
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableControlWeatherCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableControlWeatherCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private def makeControlWeatherCommand(
|
||||
gameState: GameState,
|
||||
province: ProvinceT
|
||||
): Option[AvailableCommand] = {
|
||||
province: Province
|
||||
): Option[ControlWeatherAvailableCommand] = {
|
||||
val magesWithSufficientVigor = province.rulingFactionHeroIds
|
||||
.map(gameState.heroes)
|
||||
.filter(_.profession == Profession.Mage)
|
||||
.filter(_.profession.isMage)
|
||||
.filter(_.vigor >= MinVigorForControlWeather.doubleValue)
|
||||
.toVector
|
||||
|
||||
val pidPlusNeighborPids = province.id +: province.neighbors.map(_.provinceId)
|
||||
val pidPlusNeighborPids = province.id +: province.neighbors.map(
|
||||
_.provinceId
|
||||
)
|
||||
|
||||
val options = pidPlusNeighborPids.map { pid =>
|
||||
TargetProvinceOptions(
|
||||
provinceId = pid,
|
||||
controlWeatherTypes = (
|
||||
if ProvinceUtils.hasBlizzard(gameState.provinces(pid)) then Vector(ControlWeatherType.EndBlizzard)
|
||||
else Vector(ControlWeatherType.StartBlizzard)
|
||||
if LegacyProvinceUtils.hasBlizzard(gameState.provinces(pid)) then Vector(CONTROL_WEATHER_END_BLIZZARD)
|
||||
else Vector(CONTROL_WEATHER_START_BLIZZARD)
|
||||
) ++ (
|
||||
if ProvinceUtils.hasDrought(gameState.provinces(pid)) then Vector(ControlWeatherType.EndDrought)
|
||||
else Vector(ControlWeatherType.StartDrought)
|
||||
if LegacyProvinceUtils.hasDrought(gameState.provinces(pid)) then Vector(CONTROL_WEATHER_END_DROUGHT)
|
||||
else Vector(CONTROL_WEATHER_START_DROUGHT)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Option.when(magesWithSufficientVigor.nonEmpty) {
|
||||
ControlWeatherAvailable(
|
||||
ControlWeatherAvailableCommand(
|
||||
availableHeroIds = magesWithSufficientVigor
|
||||
.sortBy(-_.vigor)
|
||||
.map(_.id),
|
||||
@@ -52,7 +57,7 @@ object AvailableControlWeatherCommandsFactory extends ScalaAvailableCommandsFact
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[ControlWeatherAvailableCommand] =
|
||||
makeControlWeatherCommand(
|
||||
gameState,
|
||||
gameState.provinces(provinceId)
|
||||
|
||||
+11
-20
@@ -2,40 +2,31 @@ package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.common.MoreOption
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{DeclineQuestAvailable, ExpandedUnaffiliatedHero}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.RecruitmentInfo
|
||||
import net.eagle0.eagle.api.available_command.DeclineQuestAvailableCommand
|
||||
import net.eagle0.eagle.common.recruitment_info.RecruitmentStatus.RECRUITMENT_STATUS_HAS_QUEST
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.actions.availability.ExpandedUnaffiliatedHeroUtils.expandedUnaffiliatedHero
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableDeclineQuestCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableDeclineQuestCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[DeclineQuestAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
MoreOption.flatWhen(
|
||||
province.rulerIsTraveling
|
||||
&& ProvinceUtils.ruledByFactionLeader(province, gameState.factions.values.toVector)
|
||||
&& LegacyProvinceUtils.ruledByFactionLeader(province, gameState)
|
||||
) {
|
||||
val withQuests = province.unaffiliatedHeroes
|
||||
.filter(_.recruitmentInfo match {
|
||||
case _: RecruitmentInfo.HasQuest => true
|
||||
case _ => false
|
||||
})
|
||||
.filter(_.recruitmentInfo.get.status == RECRUITMENT_STATUS_HAS_QUEST)
|
||||
|
||||
Option.when(withQuests.nonEmpty) {
|
||||
DeclineQuestAvailable(
|
||||
DeclineQuestAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
declinableHeroes = withQuests.map(uh =>
|
||||
ExpandedUnaffiliatedHero(
|
||||
heroId = uh.heroId,
|
||||
heroName = gameState.heroes(uh.heroId).nameTextId,
|
||||
goldCost = 0
|
||||
)
|
||||
)
|
||||
declinableHeroes = withQuests.map(uh => expandedUnaffiliatedHero(gs = gameState, uh = uh))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+48
-71
@@ -1,66 +1,57 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.DefendAvailableCommand
|
||||
import net.eagle0.eagle.api.command.util.battalion_with_food_cost.BattalionWithFoodCost
|
||||
import net.eagle0.eagle.internal.army.Attacking
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.settings.{MaxCombatUnitCountPerSide, MinVigorForDefend}
|
||||
import net.eagle0.eagle.library.util.battalion_suitability.BattalionSuitability
|
||||
import net.eagle0.eagle.library.util.command_choice_helpers.CombatUnitSelector
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.BattalionUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{
|
||||
BattalionWithFoodCost,
|
||||
DefendAvailable,
|
||||
RecommendedCombatUnit,
|
||||
SuitableBattalions
|
||||
}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.{HeroT, Profession}
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.HostileArmyGroupStatus.Attacking
|
||||
|
||||
object AvailableDefendCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
|
||||
private val scalaProfessionOrdering: Map[Profession, Int] = Map(
|
||||
Profession.Paladin -> 1,
|
||||
Profession.Engineer -> 2,
|
||||
Profession.Ranger -> 3,
|
||||
Profession.Mage -> 4,
|
||||
Profession.Necromancer -> 5,
|
||||
Profession.Champion -> 6,
|
||||
Profession.NoProfession -> 7
|
||||
)
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.library.util.LegacyBattalionUtils
|
||||
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionConverter
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionTypeConverter
|
||||
import net.eagle0.eagle.model.proto_converters.SuitableBattalionsConverter
|
||||
|
||||
object AvailableDefendCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
private def sortedFleeProvinces(
|
||||
from: ProvinceT,
|
||||
from: Province,
|
||||
gs: GameState
|
||||
): Vector[ProvinceId] =
|
||||
if FactionUtils
|
||||
if LegacyFactionUtils
|
||||
.provinces(
|
||||
factionId = from.rulingFactionId.get,
|
||||
provinces = gs.provinces.values.toVector
|
||||
gameState = gs
|
||||
)
|
||||
.map(_.id)
|
||||
.toVector
|
||||
.forall(_ == from.id)
|
||||
then Vector()
|
||||
else
|
||||
from.neighbors
|
||||
.map(_.provinceId)
|
||||
.map(gs.provinces)
|
||||
// Prioritize provinces this player owns, then empty provinces
|
||||
.sortBy(p =>
|
||||
if p.rulingFactionId == from.rulingFactionId then 0
|
||||
else if p.rulingFactionId.isEmpty then 1
|
||||
else 2
|
||||
)
|
||||
.toVector
|
||||
.map(_.id)
|
||||
|
||||
private def makeDefendCommand(
|
||||
gameState: GameState,
|
||||
province: ProvinceT
|
||||
): Option[AvailableCommand] = {
|
||||
province: Province
|
||||
): Option[DefendAvailableCommand] = {
|
||||
val hostileArmies = province.hostileArmies.filter { army =>
|
||||
army.status match {
|
||||
case Attacking => true
|
||||
case _ => false
|
||||
case Attacking(_) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,54 +67,40 @@ object AvailableDefendCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
val availableBattalionsWithCosts = availableBattalions.map(batt =>
|
||||
BattalionWithFoodCost(
|
||||
battalionId = batt.id,
|
||||
foodCost = BattalionUtils
|
||||
.monthlyConsumedFood(
|
||||
batt,
|
||||
gameState.battalionTypes
|
||||
)
|
||||
.ceil
|
||||
.toInt,
|
||||
troopCount = batt.size
|
||||
LegacyBattalionUtils.monthlyConsumedFood(
|
||||
batt,
|
||||
gameState.battalionTypes.toVector
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val recommendedUnits = CombatUnitSelector
|
||||
.selectedCombatBattalions(
|
||||
desiredCount = availableHeroes.size.min(MaxCombatUnitCountPerSide.intValue),
|
||||
heroes = availableHeroes.toVector,
|
||||
battalions = availableBattalions.toVector,
|
||||
battalionTypes = gameState.battalionTypes
|
||||
)
|
||||
.map(cu => RecommendedCombatUnit(heroId = cu.heroId, battalionId = cu.battalionId))
|
||||
|
||||
val hostileUnits = hostileArmies
|
||||
val recommendedUnits = CombatUnitSelector.selectedCombatBattalions(
|
||||
desiredCount = availableHeroes.size.min(MaxCombatUnitCountPerSide.intValue),
|
||||
heroes = availableHeroes.map(HeroConverter.fromProto).toVector,
|
||||
battalions = availableBattalions.map(BattalionConverter.fromProto).toVector,
|
||||
battalionTypes = gameState.battalionTypes.map(BattalionTypeConverter.fromProto).toVector
|
||||
)
|
||||
val hostileUnits = hostileArmies
|
||||
.flatMap(_.armies)
|
||||
.map(_.army)
|
||||
.flatMap(_.army)
|
||||
.flatMap(_.units)
|
||||
|
||||
// Convert from util.SuitableBattalions to AvailableCommand.SuitableBattalions
|
||||
val suitableBattalionsMap = BattalionSuitability
|
||||
.suitableBattalionsForHeroes(
|
||||
hs = availableHeroes.toVector,
|
||||
bs = availableBattalions.toVector,
|
||||
bts = gameState.battalionTypes
|
||||
)
|
||||
.map {
|
||||
case (heroId, sb) =>
|
||||
heroId -> SuitableBattalions(sb.battalionIdsWithSuitability.map(_.battalionId))
|
||||
}
|
||||
|
||||
Some(
|
||||
DefendAvailable(
|
||||
suitableBattalionsForHeroes = suitableBattalionsMap,
|
||||
availableFleeProvinceIds = sortedFleeProvinces(from = province, gs = gameState),
|
||||
availableBattalions = availableBattalionsWithCosts,
|
||||
DefendAvailableCommand(
|
||||
availableHeroIds = availableHeroes
|
||||
.sortBy(h => scalaProfessionOrdering.getOrElse(h.profession, 10))
|
||||
.sortBy(h => professionOrdering(h.profession))
|
||||
.toVector
|
||||
.map(_.id),
|
||||
recommendedUnits = recommendedUnits,
|
||||
actingProvinceId = province.id,
|
||||
suitableBattalionsForHeroes = SuitableBattalionsConverter.toProtoMap(
|
||||
BattalionSuitability.suitableBattalionsForHeroes(
|
||||
hs = availableHeroes.map(HeroConverter.fromProto).toVector,
|
||||
bs = availableBattalions.map(BattalionConverter.fromProto).toVector,
|
||||
bts = gameState.battalionTypes.map(BattalionTypeConverter.fromProto).toVector
|
||||
)
|
||||
),
|
||||
availableBattalions = availableBattalionsWithCosts,
|
||||
availableFleeProvinceIds = sortedFleeProvinces(from = province, gs = gameState),
|
||||
recommendedUnits = recommendedUnits,
|
||||
hostileFactionIds = hostileArmies.toVector.map(_.factionId).distinct,
|
||||
hostileHeroCount = hostileUnits.size,
|
||||
hostileTroopCount = hostileUnits
|
||||
@@ -141,7 +118,7 @@ object AvailableDefendCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[DefendAvailableCommand] =
|
||||
makeDefendCommand(
|
||||
gameState,
|
||||
gameState.provinces(provinceId)
|
||||
|
||||
+139
-91
@@ -1,6 +1,23 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.DiplomacyAvailableCommand
|
||||
import net.eagle0.eagle.api.command.util.diplomacy_option.{
|
||||
AllianceOption,
|
||||
BreakAllianceOption,
|
||||
DiplomacyOption,
|
||||
InvitationOption,
|
||||
RansomOfferOption,
|
||||
TruceOption
|
||||
}
|
||||
import net.eagle0.eagle.common.diplomacy_offer.{
|
||||
HostageOfferedInExchange,
|
||||
PrisonerOfferedInExchange,
|
||||
PrisonerToBeRansomed,
|
||||
RansomOfferDetails
|
||||
}
|
||||
import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.UNAFFILIATED_HERO_PRISONER
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
AllianceGoldCost,
|
||||
BreakAllianceGoldCost,
|
||||
@@ -9,39 +26,28 @@ import net.eagle0.eagle.library.settings.{
|
||||
MinVigorForDiplomacy,
|
||||
TruceGoldCost
|
||||
}
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.hero.HeroUtils
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
import net.eagle0.eagle.library.util.ProvinceDistances
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.DiplomacyOption
|
||||
import net.eagle0.eagle.model.state.command.common.{
|
||||
DiplomacyOptionType,
|
||||
HostageOfferedInExchange,
|
||||
PrisonerOfferedInExchange,
|
||||
PrisonerToBeRansomed,
|
||||
RansomOfferDetails
|
||||
}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.Prisoner
|
||||
|
||||
object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableDiplomacyCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
def availableHeroIds(gs: GameState, pid: ProvinceId): Vector[HeroId] =
|
||||
gs.provinces(pid)
|
||||
.rulingFactionHeroIds
|
||||
.filter(hid => gs.heroes(hid).vigor >= MinVigorForDiplomacy.doubleValue)
|
||||
.toVector
|
||||
|
||||
def recommendedHeroId(gs: GameState, availableHids: Seq[HeroId]): HeroId =
|
||||
availableHids
|
||||
.sortBy(hid => (HeroUtils.fatigue(gs.heroes(hid)), -gs.heroes(hid).vigor)) match {
|
||||
.sortBy(hid => (LegacyHeroUtils.fatigue(gs.heroes(hid)), -gs.heroes(hid).vigor)) match {
|
||||
case sortedHeroes =>
|
||||
sortedHeroes
|
||||
.find(hid => !FactionUtils.isFactionHead(hid, gs.factions.values))
|
||||
.find(hid => !LegacyFactionUtils.isFactionHead(hid, gs))
|
||||
.orElse(
|
||||
sortedHeroes
|
||||
.find(hid => !FactionUtils.isFactionLeader(hid, gs.factions.values.toVector))
|
||||
.find(hid => !LegacyFactionUtils.isFactionLeader(hid, gs))
|
||||
)
|
||||
.getOrElse(sortedHeroes.head)
|
||||
}
|
||||
@@ -50,49 +56,64 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
gs: GameState,
|
||||
pid: ProvinceId,
|
||||
targetFid: FactionId
|
||||
): Option[DiplomacyOptionType] =
|
||||
): Option[DiplomacyOption] =
|
||||
Option.when(
|
||||
gs.provinces(pid).gold >= TruceGoldCost.intValue
|
||||
&& ProvinceUtils.ruledByFactionLeader(
|
||||
&& LegacyProvinceUtils.ruledByFactionLeader(
|
||||
province = gs.provinces(pid),
|
||||
factions = gs.factions.values.toVector
|
||||
gameState = gs
|
||||
)
|
||||
&& !FactionUtils.hasAlliance(
|
||||
&& !LegacyFactionUtils.hasAlliance(
|
||||
fid1 = gs.provinces(pid).rulingFactionId.get,
|
||||
fid2 = targetFid,
|
||||
factions = gs.factions.values.toVector
|
||||
gs = gs
|
||||
)
|
||||
)(DiplomacyOptionType.Truce)
|
||||
)(
|
||||
TruceOption(
|
||||
targetFactionId = targetFid,
|
||||
goldCost = TruceGoldCost.intValue
|
||||
)
|
||||
)
|
||||
|
||||
private def allianceOptionForTargetFaction(
|
||||
gs: GameState,
|
||||
pid: ProvinceId,
|
||||
actingFid: FactionId,
|
||||
targetFid: FactionId
|
||||
): Option[DiplomacyOptionType] =
|
||||
): Option[DiplomacyOption] =
|
||||
Option.when(
|
||||
gs.provinces(pid).gold >= AllianceGoldCost.intValue
|
||||
&& ProvinceUtils.ruledByFactionLeader(
|
||||
&& LegacyProvinceUtils.ruledByFactionLeader(
|
||||
province = gs.provinces(pid),
|
||||
factions = gs.factions.values.toVector
|
||||
) && !FactionUtils
|
||||
.hasAlliance(fid1 = actingFid, fid2 = targetFid, factions = gs.factions.values.toVector)
|
||||
)(DiplomacyOptionType.Alliance)
|
||||
gameState = gs
|
||||
) && !LegacyFactionUtils
|
||||
.hasAlliance(fid1 = actingFid, fid2 = targetFid, gs = gs)
|
||||
)(
|
||||
AllianceOption(
|
||||
targetFactionId = targetFid,
|
||||
goldCost = AllianceGoldCost.intValue
|
||||
)
|
||||
)
|
||||
|
||||
private def breakAllianceOptionForTargetFaction(
|
||||
gs: GameState,
|
||||
pid: ProvinceId,
|
||||
actingFid: FactionId,
|
||||
targetFid: FactionId
|
||||
): Option[DiplomacyOptionType] =
|
||||
): Option[DiplomacyOption] =
|
||||
Option.when(
|
||||
gs.provinces(pid).gold >= BreakAllianceGoldCost.intValue
|
||||
&& ProvinceUtils.ruledByFactionLeader(
|
||||
&& LegacyProvinceUtils.ruledByFactionLeader(
|
||||
province = gs.provinces(pid),
|
||||
factions = gs.factions.values.toVector
|
||||
) && FactionUtils
|
||||
.hasAlliance(fid1 = actingFid, fid2 = targetFid, factions = gs.factions.values.toVector)
|
||||
)(DiplomacyOptionType.BreakAlliance)
|
||||
gameState = gs
|
||||
) && LegacyFactionUtils
|
||||
.hasAlliance(fid1 = actingFid, fid2 = targetFid, gs = gs)
|
||||
)(
|
||||
BreakAllianceOption(
|
||||
targetFactionId = targetFid,
|
||||
goldCost = BreakAllianceGoldCost.intValue
|
||||
)
|
||||
)
|
||||
|
||||
private def hasClearPath(
|
||||
gs: GameState,
|
||||
@@ -100,7 +121,7 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
actingFid: FactionId,
|
||||
targetFid: FactionId
|
||||
): Boolean =
|
||||
FactionUtils.provinces(targetFid, gs.provinces.values.toVector).headOption.exists { targetProvince =>
|
||||
LegacyFactionUtils.provinces(targetFid, gs).headOption.exists { targetProvince =>
|
||||
val eligibleNeighbors: ProvinceId => Vector[ProvinceId] =
|
||||
p =>
|
||||
gs.provinces(p)
|
||||
@@ -115,14 +136,15 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
// inviting across the target's province
|
||||
neighborProvince.rulingFactionId.contains(targetFid) ||
|
||||
// inviting across your ally's province
|
||||
FactionUtils
|
||||
LegacyFactionUtils
|
||||
.hasAlliance(
|
||||
neighborProvince.rulingFactionId.get,
|
||||
actingFid,
|
||||
gs.factions.values.toVector
|
||||
gs
|
||||
)
|
||||
)
|
||||
.map(_.id)
|
||||
.toVector
|
||||
|
||||
ProvinceDistances
|
||||
.distance(pid, targetProvince.id, eligibleNeighbors)
|
||||
@@ -134,30 +156,35 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
pid: ProvinceId,
|
||||
actingFid: FactionId,
|
||||
targetFid: FactionId
|
||||
): Option[DiplomacyOptionType] =
|
||||
): Option[DiplomacyOption] =
|
||||
Option.when(
|
||||
gs.currentRoundId >= gs.factions(actingFid).earliestRoundForInvitation &&
|
||||
gs.provinces(pid).gold >= InviteGoldCost.intValue
|
||||
&& FactionUtils
|
||||
&& LegacyFactionUtils
|
||||
.provinceCount(
|
||||
targetFid,
|
||||
gs.provinces.values
|
||||
gs
|
||||
) <= MaxProvincesOnTargetForInvite.intValue
|
||||
&& FactionUtils.provinceCount(actingFid, gs.provinces.values) > FactionUtils
|
||||
.provinceCount(targetFid, gs.provinces.values)
|
||||
&& ProvinceUtils.ruledByFactionLeader(
|
||||
&& LegacyFactionUtils.provinceCount(actingFid, gs) > LegacyFactionUtils
|
||||
.provinceCount(targetFid, gs)
|
||||
&& LegacyProvinceUtils.ruledByFactionLeader(
|
||||
province = gs.provinces(pid),
|
||||
factions = gs.factions.values.toVector
|
||||
gameState = gs
|
||||
)
|
||||
&& hasClearPath(gs, pid, actingFid, targetFid)
|
||||
)(DiplomacyOptionType.Invitation)
|
||||
)(
|
||||
InvitationOption(
|
||||
targetFactionId = targetFid,
|
||||
goldCost = InviteGoldCost.intValue
|
||||
)
|
||||
)
|
||||
|
||||
private def ransomOptionsForTargetFaction(
|
||||
gs: GameState,
|
||||
pid: ProvinceId,
|
||||
actingFid: FactionId,
|
||||
targetFid: FactionId
|
||||
): Vector[DiplomacyOptionType] = {
|
||||
): Vector[DiplomacyOption] = {
|
||||
val prisonersAvailableToOffer = availablePrisoners(gs.provinces(pid))
|
||||
val hostagesAvailableToOffer = availableHostages(gs, pid)
|
||||
val goldAvailableToOffer = gs.provinces(pid).gold
|
||||
@@ -169,12 +196,15 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
factionId = actingFid,
|
||||
targetFactionId = targetFid
|
||||
).map { prisonerToBeRansomed =>
|
||||
DiplomacyOptionType.Ransom(
|
||||
RansomOfferDetails(
|
||||
prisonerToBeRansomed = prisonerToBeRansomed,
|
||||
prisonersOffered = prisonersAvailableToOffer,
|
||||
hostagesOffered = hostagesAvailableToOffer,
|
||||
goldOffered = goldAvailableToOffer
|
||||
RansomOfferOption(
|
||||
targetFactionId = targetFid,
|
||||
ransomOffer = Some(
|
||||
RansomOfferDetails(
|
||||
prisonerToBeRansomed = Some(prisonerToBeRansomed),
|
||||
prisonersOffered = prisonersAvailableToOffer,
|
||||
hostagesOffered = hostagesAvailableToOffer,
|
||||
goldOffered = goldAvailableToOffer
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -187,7 +217,7 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
pid: ProvinceId,
|
||||
actingFid: FactionId,
|
||||
targetFid: FactionId
|
||||
): Vector[DiplomacyOptionType] =
|
||||
): Vector[DiplomacyOption] =
|
||||
ransomOptionsForTargetFaction(
|
||||
gs,
|
||||
pid,
|
||||
@@ -228,11 +258,11 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[DiplomacyAvailableCommand] =
|
||||
if availableHeroIds(gameState, provinceId).isEmpty then None
|
||||
else if gameState.provinces(provinceId).rulingFactionHeroIds.size < 2 then None
|
||||
else {
|
||||
val optionsByFaction: Map[FactionId, Vector[DiplomacyOptionType]] = gameState.factions.keys
|
||||
gameState.factions.keys
|
||||
.filterNot(_ == factionId)
|
||||
.filterNot(fid =>
|
||||
hasIncomingDiplomacyOffer(
|
||||
@@ -241,53 +271,70 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
gameState = gameState
|
||||
)
|
||||
)
|
||||
.map { targetFid =>
|
||||
targetFid -> diplomacyOptionsForTargetFaction(
|
||||
.flatMap(targetFid =>
|
||||
diplomacyOptionsForTargetFaction(
|
||||
gs = gameState,
|
||||
pid = provinceId,
|
||||
actingFid = factionId,
|
||||
targetFid = targetFid
|
||||
)
|
||||
}
|
||||
.filter(_._2.nonEmpty)
|
||||
.toMap
|
||||
|
||||
if optionsByFaction.isEmpty then None
|
||||
else {
|
||||
val heroIds = availableHeroIds(gameState, provinceId)
|
||||
val options = optionsByFaction.map {
|
||||
case (targetFid, optionTypes) =>
|
||||
DiplomacyOption(
|
||||
targetFactionId = targetFid,
|
||||
optionTypes = optionTypes
|
||||
)
|
||||
}.toVector
|
||||
.sortBy(opt => FactionUtils.sortKey(gameState.factions(opt.targetFactionId)))
|
||||
|
||||
Some(
|
||||
AvailableCommand.DiplomacyAvailable(
|
||||
availableHeroIds = heroIds,
|
||||
options = options,
|
||||
actingProvinceId = provinceId,
|
||||
recommendedHeroId = recommendedHeroId(gameState, heroIds)
|
||||
)
|
||||
)
|
||||
.toVector
|
||||
.sortBy(opt =>
|
||||
LegacyFactionUtils.sortKey(gameState.factions(opt match {
|
||||
case TruceOption(targetFactionId, _, _ /* unknownFieldSet */ ) =>
|
||||
targetFactionId
|
||||
case InvitationOption(
|
||||
targetFactionId,
|
||||
_,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
targetFactionId
|
||||
case RansomOfferOption(
|
||||
targetFactionId,
|
||||
_,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
targetFactionId
|
||||
case AllianceOption(targetFactionId, _, _ /* unknownFieldSet */ ) =>
|
||||
targetFactionId
|
||||
case BreakAllianceOption(
|
||||
targetFactionId,
|
||||
_,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
targetFactionId
|
||||
case DiplomacyOption.Empty => ???
|
||||
}))
|
||||
) match {
|
||||
case items if items.isEmpty => None
|
||||
case options =>
|
||||
val heroIds = availableHeroIds(gameState, provinceId)
|
||||
|
||||
Some(
|
||||
DiplomacyAvailableCommand(
|
||||
availableHeroIds = heroIds,
|
||||
options = options,
|
||||
actingProvinceId = provinceId,
|
||||
recommendedHeroId = recommendedHeroId(gameState, heroIds)
|
||||
)
|
||||
)
|
||||
}
|
||||
end if
|
||||
}
|
||||
|
||||
private def availablePrisoners(
|
||||
province: ProvinceT
|
||||
province: Province
|
||||
): Vector[PrisonerOfferedInExchange] =
|
||||
province.unaffiliatedHeroes
|
||||
.filter(_.unaffiliatedHeroType == Prisoner)
|
||||
.filter(_.`type` == UNAFFILIATED_HERO_PRISONER)
|
||||
.map { prisoner =>
|
||||
PrisonerOfferedInExchange(
|
||||
heroId = prisoner.heroId,
|
||||
factionIdForPrisoner = prisoner.lastFactionId,
|
||||
factionIdForPrisoner = prisoner.lastFaction.getOrElse(-1),
|
||||
provinceIdWithHero = province.id
|
||||
)
|
||||
}
|
||||
.toVector
|
||||
|
||||
private def availableHostages(
|
||||
gameState: GameState,
|
||||
@@ -304,6 +351,7 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
provinceIdWithHero = provinceId
|
||||
)
|
||||
)
|
||||
.toVector
|
||||
|
||||
private def availablePrisonersToRansom(
|
||||
gameState: GameState,
|
||||
@@ -315,10 +363,10 @@ object AvailableDiplomacyCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
.filter(_.rulingFactionId.contains(targetFactionId))
|
||||
.toVector
|
||||
prisoner <- province.unaffiliatedHeroes.filter(
|
||||
_.unaffiliatedHeroType == Prisoner
|
||||
_.`type` == UNAFFILIATED_HERO_PRISONER
|
||||
)
|
||||
if prisoner.lastFactionId.contains(factionId)
|
||||
if FactionUtils.isFactionLeader(prisoner.heroId, gameState.factions.values.toVector)
|
||||
if prisoner.lastFaction.contains(factionId)
|
||||
if LegacyFactionUtils.isFactionLeader(prisoner.heroId, gameState)
|
||||
} yield PrisonerToBeRansomed(
|
||||
prisonerHeroId = prisoner.heroId,
|
||||
provinceIdForPrisoner = province.id
|
||||
|
||||
+16
-23
@@ -1,48 +1,41 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.common.MoreOption
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.DivineAvailableCommand
|
||||
import net.eagle0.eagle.common.recruitment_info.RecruitmentStatus.RECRUITMENT_STATUS_NOT_DIVINED
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.actions.availability.ExpandedUnaffiliatedHeroUtils.expandedUnaffiliatedHero
|
||||
import net.eagle0.eagle.library.settings.GoldCostForDivine
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{DivineAvailable, ExpandedUnaffiliatedHero}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.RecruitmentInfo
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableDivineCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
private def effectiveCost(province: ProvinceT): Int =
|
||||
object AvailableDivineCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
private def effectiveCost(province: Province): Int =
|
||||
Math.floor(GoldCostForDivine.intValue * province.priceIndex + 0.5).toInt
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[DivineAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
MoreOption.flatWhen(
|
||||
province.rulerIsTraveling
|
||||
&& ProvinceUtils.ruledByFactionLeader(province, gameState.factions.values.toVector)
|
||||
&& LegacyProvinceUtils.ruledByFactionLeader(province, gameState)
|
||||
&& province.gold >= effectiveCost(province)
|
||||
) {
|
||||
val undivined = province.unaffiliatedHeroes
|
||||
.filter(_.recruitmentInfo == RecruitmentInfo.NotDivined)
|
||||
.filter(_.recruitmentInfo.get.status == RECRUITMENT_STATUS_NOT_DIVINED)
|
||||
|
||||
val expandedHeroes = undivined.map(uh =>
|
||||
ExpandedUnaffiliatedHero(
|
||||
heroId = uh.heroId,
|
||||
heroName = gameState.heroes(uh.heroId).nameTextId,
|
||||
goldCost = effectiveCost(province)
|
||||
)
|
||||
)
|
||||
val expandedWithQuestAvailable = undivined
|
||||
.map(uh => expandedUnaffiliatedHero(gs = gameState, uh = uh))
|
||||
|
||||
Option.when(expandedHeroes.nonEmpty) {
|
||||
DivineAvailable(
|
||||
Option.when(expandedWithQuestAvailable.nonEmpty) {
|
||||
DivineAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
costPerHero = effectiveCost(province),
|
||||
divinableHeroes = expandedHeroes,
|
||||
unavailableHeroes = Vector()
|
||||
divinableHeroes = expandedWithQuestAvailable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+16
-16
@@ -1,39 +1,39 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.ExileVassalAvailable
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.api.available_command.ExileVassalAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableExileVassalCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableExileVassalCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
def exilableHeroIds(
|
||||
province: ProvinceT,
|
||||
factions: Vector[FactionT]
|
||||
gameState: GameState,
|
||||
province: Province
|
||||
): Vector[HeroId] =
|
||||
province.rulingFactionHeroIds
|
||||
.filterNot(hid => FactionUtils.isFactionLeader(hid, factions))
|
||||
.filterNot(hid => LegacyFactionUtils.isFactionLeader(hid, gameState))
|
||||
.toVector
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[ExileVassalAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
val factions = gameState.factions.values.toVector
|
||||
val exilable = exilableHeroIds(province, factions)
|
||||
val exilable = exilableHeroIds(gameState, province)
|
||||
|
||||
Option.when(
|
||||
ProvinceUtils.ruledByFactionLeader(province, factions) && exilable.nonEmpty
|
||||
LegacyProvinceUtils
|
||||
.ruledByFactionLeader(province, gameState) && exilable.nonEmpty
|
||||
) {
|
||||
ExileVassalAvailable(
|
||||
ExileVassalAvailableCommand(
|
||||
exilableHeroIds = exilable,
|
||||
actingProvinceId = provinceId
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-15
@@ -1,40 +1,40 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.FeastAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.settings.FeastGoldCostPerHero
|
||||
import net.eagle0.eagle.library.util.hero.HeroUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.FeastAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
|
||||
|
||||
object AvailableFeastCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableFeastCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private def hasHeroThatBenefits(
|
||||
gameState: GameState,
|
||||
province: ProvinceT
|
||||
): Boolean = {
|
||||
val factions = gameState.factions.values.toVector
|
||||
province: Province
|
||||
): Boolean =
|
||||
province.rulingFactionHeroIds
|
||||
.flatMap(hid => gameState.heroes.get(hid))
|
||||
.exists(h => h.vigor < h.constitution || HeroUtils.effectiveLoyalty(h, factions) < 100.0)
|
||||
}
|
||||
.map(gameState.heroes)
|
||||
.exists(h =>
|
||||
h.vigor < h.constitution || LegacyHeroUtils
|
||||
.effectiveLoyalty(h.id, gameState) < 100.0
|
||||
)
|
||||
|
||||
def feastGoldCost(province: ProvinceT): Int =
|
||||
def feastGoldCost(province: Province): Int =
|
||||
(province.rulingFactionHeroIds.length.toDouble * province.priceIndex * FeastGoldCostPerHero.intValue).ceil.toInt
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[FeastAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
val goldCost = feastGoldCost(province)
|
||||
|
||||
Option.when(
|
||||
province.gold >= goldCost && hasHeroThatBenefits(gameState, province)
|
||||
) {
|
||||
FeastAvailable(
|
||||
FeastAvailableCommand(
|
||||
goldCost = goldCost,
|
||||
actingProvinceId = provinceId
|
||||
)
|
||||
|
||||
+26
-58
@@ -1,29 +1,25 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.FreeForAllDecisionAvailableCommand
|
||||
import net.eagle0.eagle.api.command.util.army_stats.ArmyStats
|
||||
import net.eagle0.eagle.api.command.util.attack_decision_type.{AdvanceDecision, AttackDecisionType, WithdrawDecision}
|
||||
import net.eagle0.eagle.common.round_phase.RoundPhase
|
||||
import net.eagle0.eagle.internal.army.{AwaitingDecision, HostileArmyGroup, MovingArmy}
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.IncomingArmyUtils
|
||||
import net.eagle0.eagle.model.state.{Army, HostileArmyGroupStatus, MovingArmy}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{
|
||||
ArmyStats,
|
||||
ExpandedCombatUnit,
|
||||
FreeForAllDecisionAvailable
|
||||
}
|
||||
import net.eagle0.eagle.model.state.command.common.AttackDecisionType
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
|
||||
object AvailableFreeForAllDecisionCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableFreeForAllDecisionCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private def decisionOptions(
|
||||
armies: Vector[MovingArmy]
|
||||
armies: Seq[MovingArmy]
|
||||
): Vector[AttackDecisionType] =
|
||||
if armies.forall(_.army.fleeProvinceId.isDefined) then
|
||||
if armies.forall(_.getArmy.fleeProvinceId.isDefined) then
|
||||
Vector(
|
||||
AttackDecisionType.Advance,
|
||||
AttackDecisionType.Withdraw
|
||||
AdvanceDecision(),
|
||||
WithdrawDecision()
|
||||
)
|
||||
else Vector(AttackDecisionType.Advance)
|
||||
else Vector(AdvanceDecision())
|
||||
|
||||
// We need to include the armies that have already withdrawn or advanced, in a consistent sort order,
|
||||
// so that the player doesn't learn what others have done by the shifts.
|
||||
@@ -33,76 +29,48 @@ object AvailableFreeForAllDecisionCommandFactory extends ScalaAvailableCommandsF
|
||||
): Vector[MovingArmy] =
|
||||
gs.provinces(pid)
|
||||
.hostileArmies
|
||||
.filter(_.status == HostileArmyGroupStatus.AwaitingDecision)
|
||||
.filter(_.status == AwaitingDecision())
|
||||
.flatMap(_.armies)
|
||||
.sortBy(ma => (ma.army.factionId, ma.army.units.head.heroId))
|
||||
.sortBy(ma => (ma.getArmy.factionId, ma.getArmy.units.head.heroId))
|
||||
.toVector
|
||||
|
||||
private def myHostileArmies(
|
||||
pid: ProvinceId,
|
||||
fid: FactionId,
|
||||
gs: GameState
|
||||
): Option[Vector[MovingArmy]] =
|
||||
): Option[HostileArmyGroup] =
|
||||
gs.provinces(pid)
|
||||
.hostileArmies
|
||||
.filter(_.status == HostileArmyGroupStatus.AwaitingDecision)
|
||||
.filter(_.status == AwaitingDecision())
|
||||
.find(_.factionId == fid)
|
||||
.map(_.armies)
|
||||
|
||||
private def armyStats(
|
||||
toFid: FactionId,
|
||||
pid: ProvinceId,
|
||||
gs: GameState
|
||||
): Vector[ArmyStats] =
|
||||
relevantArmies(pid, gs).map(ma => stats(toFid, ma.army, gs))
|
||||
|
||||
private def stats(
|
||||
toFid: FactionId,
|
||||
army: Army,
|
||||
gs: GameState
|
||||
): ArmyStats = {
|
||||
val battalions = army.units.flatMap(_.battalionId).map(gs.battalions)
|
||||
val troopCount = battalions.map(_.size).sum
|
||||
ArmyStats(
|
||||
factionId = army.factionId,
|
||||
heroCount = army.units.size,
|
||||
troopCount = troopCount
|
||||
)
|
||||
}
|
||||
|
||||
private def expandedCombatUnits(
|
||||
armies: Vector[MovingArmy],
|
||||
gs: GameState
|
||||
): Vector[ExpandedCombatUnit] =
|
||||
armies
|
||||
.flatMap(_.army.units)
|
||||
.map { cu =>
|
||||
val battalionOpt = cu.battalionId.map(gs.battalions)
|
||||
ExpandedCombatUnit(
|
||||
heroId = cu.heroId,
|
||||
battalionId = cu.battalionId,
|
||||
heroName = gs.heroes(cu.heroId).nameTextId,
|
||||
troopCount = battalionOpt.map(_.size).getOrElse(0)
|
||||
)
|
||||
}
|
||||
relevantArmies(pid, gs).map(ia => IncomingArmyUtils.stats(toFid, ia.getArmy, gs, ia.originProvince))
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
myHostileArmies(provinceId, factionId, gameState).flatMap { armies =>
|
||||
): Option[FreeForAllDecisionAvailableCommand] =
|
||||
myHostileArmies(provinceId, factionId, gameState).flatMap { hostileArmyGroup =>
|
||||
Option.when(
|
||||
gameState.currentPhase == RoundPhase.FreeForAllDecision
|
||||
gameState.currentPhase == RoundPhase.FREE_FOR_ALL_DECISION
|
||||
&& !IncomingArmyUtils.incomingArmiesAreMutuallyAllied(
|
||||
gameState.provinces(provinceId),
|
||||
gameState
|
||||
)
|
||||
)(
|
||||
FreeForAllDecisionAvailable(
|
||||
FreeForAllDecisionAvailableCommand(
|
||||
provinceId = provinceId,
|
||||
armies = armyStats(factionId, provinceId, gameState),
|
||||
actingUnits = expandedCombatUnits(armies, gameState),
|
||||
availableDecisions = decisionOptions(armies)
|
||||
actingUnits = hostileArmyGroup.armies
|
||||
.flatMap(_.getArmy.units)
|
||||
.map(cu => ExpandedCombatUnitUtils.expandedCombatUnit(cu, gameState)),
|
||||
availableDecisions = decisionOptions(hostileArmyGroup.armies)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+34
-25
@@ -1,42 +1,51 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{
|
||||
ExpandedCapturedHero,
|
||||
HandleCapturedHeroAvailable
|
||||
import net.eagle0.eagle.api.available_command.{ExpandedCapturedHero, HandleCapturedHeroAvailableCommand}
|
||||
import net.eagle0.eagle.api.command.util.captured_hero_option.CapturedHeroOption.{
|
||||
EXECUTE_CAPTURED_HERO_OPTION,
|
||||
EXILE_CAPTURED_HERO_OPTION,
|
||||
IMPRISON_CAPTURED_HERO_OPTION,
|
||||
RECRUIT_CAPTURED_HERO_OPTION,
|
||||
RETURN_CAPTURED_HERO_OPTION
|
||||
}
|
||||
import net.eagle0.eagle.model.state.command.common.CapturedHeroOption
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.CapturedHero
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.unaffiliated_hero.CapturedHero
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.library.util.view_filters.HeroViewFilter
|
||||
|
||||
object AvailableHandleCapturedHeroCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableHandleCapturedHeroCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private val optionsWithoutRecruit =
|
||||
Vector(
|
||||
CapturedHeroOption.Imprison,
|
||||
CapturedHeroOption.Exile,
|
||||
CapturedHeroOption.Execute
|
||||
IMPRISON_CAPTURED_HERO_OPTION,
|
||||
EXILE_CAPTURED_HERO_OPTION,
|
||||
EXECUTE_CAPTURED_HERO_OPTION
|
||||
)
|
||||
private val optionsWithRecruit =
|
||||
CapturedHeroOption.Recruit +: optionsWithoutRecruit
|
||||
RECRUIT_CAPTURED_HERO_OPTION +: optionsWithoutRecruit
|
||||
private val optionsForFactionLeader = Vector(
|
||||
CapturedHeroOption.Imprison,
|
||||
CapturedHeroOption.Return,
|
||||
CapturedHeroOption.Execute
|
||||
IMPRISON_CAPTURED_HERO_OPTION,
|
||||
RETURN_CAPTURED_HERO_OPTION,
|
||||
EXECUTE_CAPTURED_HERO_OPTION
|
||||
)
|
||||
|
||||
private def expandedCapturedHero(
|
||||
gs: GameState,
|
||||
ch: CapturedHero
|
||||
): ExpandedCapturedHero = {
|
||||
val isFactionLeader = FactionUtils
|
||||
.isFactionLeader(ch.heroId, gs.factions.values.toVector)
|
||||
val isFactionLeader = LegacyFactionUtils
|
||||
.isFactionLeader(ch.heroId, gs)
|
||||
|
||||
ExpandedCapturedHero(
|
||||
heroId = ch.heroId,
|
||||
heroName = gs.heroes(ch.heroId).nameTextId,
|
||||
hero = Some(
|
||||
HeroViewFilter
|
||||
.filteredHeroView(
|
||||
hero = gs.heroes(ch.heroId),
|
||||
gs = gs,
|
||||
factionId = None // can see unaffiliated heroes
|
||||
)
|
||||
),
|
||||
options =
|
||||
if ch.recruitmentAttempted then optionsWithoutRecruit
|
||||
else if isFactionLeader
|
||||
@@ -56,11 +65,11 @@ object AvailableHandleCapturedHeroCommandFactory extends ScalaAvailableCommandsF
|
||||
gameState: GameState,
|
||||
capturedHeroes: Vector[CapturedHero],
|
||||
provinceId: ProvinceId
|
||||
): Option[HandleCapturedHeroAvailable] =
|
||||
): Option[HandleCapturedHeroAvailableCommand] =
|
||||
capturedHeroes
|
||||
.find(ch => ch.recruitmentRefusedMessageId.nonEmpty || ch.messageId.nonEmpty)
|
||||
.map { capturedHero =>
|
||||
HandleCapturedHeroAvailable(
|
||||
HandleCapturedHeroAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
availableHeroes = Vector(expandedCapturedHero(gameState, capturedHero))
|
||||
)
|
||||
@@ -70,7 +79,7 @@ object AvailableHandleCapturedHeroCommandFactory extends ScalaAvailableCommandsF
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[HandleCapturedHeroAvailableCommand] =
|
||||
// If we've already attempted to recruit one of the heroes, only show available commands for that one
|
||||
gameState
|
||||
.provinces(provinceId)
|
||||
@@ -81,13 +90,13 @@ object AvailableHandleCapturedHeroCommandFactory extends ScalaAvailableCommandsF
|
||||
case (alreadyRecruited, notRecruitedNonEmpty) if alreadyRecruited.isEmpty =>
|
||||
firstOption(
|
||||
gameState = gameState,
|
||||
capturedHeroes = notRecruitedNonEmpty,
|
||||
capturedHeroes = notRecruitedNonEmpty.toVector,
|
||||
provinceId = provinceId
|
||||
)
|
||||
case (alreadyRecruitedNonEmpty, _) =>
|
||||
firstOption(
|
||||
gameState = gameState,
|
||||
capturedHeroes = alreadyRecruitedNonEmpty,
|
||||
capturedHeroes = alreadyRecruitedNonEmpty.toVector,
|
||||
provinceId = provinceId
|
||||
)
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,17 +1,16 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.HandleRiotCrackDownAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.settings.MinVigorForCrackDown
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.HandleRiotCrackDownAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableHandleRiotCrackDownCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableHandleRiotCrackDownCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
def crackDownHeroIds(
|
||||
province: ProvinceT,
|
||||
province: Province,
|
||||
gameState: GameState
|
||||
): Vector[HeroId] =
|
||||
province.rulingFactionHeroIds
|
||||
@@ -22,14 +21,15 @@ object AvailableHandleRiotCrackDownCommandFactory extends ScalaAvailableCommands
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[HandleRiotCrackDownAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
val heroIds = crackDownHeroIds(province, gameState)
|
||||
|
||||
Option.when(
|
||||
ProvinceUtils.hasImminentRiot(province) && !province.hasActed && heroIds.nonEmpty
|
||||
LegacyProvinceUtils
|
||||
.hasImminentRiot(province) && !province.hasActed && heroIds.nonEmpty
|
||||
) {
|
||||
HandleRiotCrackDownAvailable(
|
||||
HandleRiotCrackDownAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
availableHeroIds = heroIds,
|
||||
battalionIdsAvailable = province.battalionIds
|
||||
|
||||
+10
-10
@@ -1,22 +1,22 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.HandleRiotDoNothingAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.api.available_command.HandleRiotDoNothingAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableHandleRiotDoNothingCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableHandleRiotDoNothingCommandFactory extends AvailableCommandsFactoryForType {
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
): Option[HandleRiotDoNothingAvailableCommand] =
|
||||
Option.when(
|
||||
ProvinceUtils.hasImminentRiot(province) && !province.hasActed
|
||||
LegacyProvinceUtils.hasImminentRiot(
|
||||
gameState
|
||||
.provinces(provinceId)
|
||||
) && !gameState.provinces(provinceId).hasActed
|
||||
) {
|
||||
HandleRiotDoNothingAvailable(actingProvinceId = provinceId)
|
||||
HandleRiotDoNothingAvailableCommand(actingProvinceId = provinceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-8
@@ -1,28 +1,30 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.HandleRiotGiveAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.settings.{RiotMaxFood, RiotMaxGold}
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.HandleRiotGiveAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableHandleRiotGiveCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableHandleRiotGiveCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[HandleRiotGiveAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
val food = Math.min(province.food, RiotMaxFood.intValue)
|
||||
val gold = Math.min(province.gold, RiotMaxGold.intValue)
|
||||
|
||||
Option.when(
|
||||
ProvinceUtils.hasImminentRiot(province) && !province.hasActed && (food > 0 || gold > 0)
|
||||
LegacyProvinceUtils
|
||||
.hasImminentRiot(
|
||||
province
|
||||
) && !province.hasActed && (food > 0 || gold > 0)
|
||||
) {
|
||||
HandleRiotGiveAvailable(
|
||||
HandleRiotGiveAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
foodAvailable = food,
|
||||
goldAvailable = gold
|
||||
|
||||
+14
-19
@@ -1,23 +1,21 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.{EligibleGift, HeroGiftAvailableCommand}
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.settings.MaxGiftGold
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.hero.HeroUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{EligibleGift, HeroGiftAvailable}
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
|
||||
object AvailableHeroGiftCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableHeroGiftCommandFactory extends AvailableCommandsFactoryForType {
|
||||
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils.*
|
||||
|
||||
private def eligibleRecipients(
|
||||
province: ProvinceT,
|
||||
factions: Vector[FactionT]
|
||||
): Vector[EligibleGift] =
|
||||
province: Province,
|
||||
gameState: GameState
|
||||
): Seq[EligibleGift] =
|
||||
province.rulingFactionHeroIds
|
||||
.filterNot(hid => FactionUtils.isFactionLeader(hid, factions))
|
||||
.filterNot(hid => LegacyFactionUtils.isFactionLeader(hid, gameState))
|
||||
.map(hid =>
|
||||
EligibleGift(
|
||||
recipientHeroId = hid,
|
||||
@@ -30,20 +28,17 @@ object AvailableHeroGiftCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
val factions = gameState.factions.values.toVector
|
||||
HeroUtils
|
||||
.consideredProvinces(gameState, factionId, provinceId)
|
||||
): Option[HeroGiftAvailableCommand] =
|
||||
consideredProvinces(gameState, factionId, provinceId)
|
||||
.filterNot(_.gold == 0)
|
||||
.flatMap(p => eligibleRecipients(p, factions)) match {
|
||||
.flatMap(p => eligibleRecipients(p, gameState)) match {
|
||||
case items if items.isEmpty => None
|
||||
case eg =>
|
||||
Some(
|
||||
HeroGiftAvailable(
|
||||
HeroGiftAvailableCommand(
|
||||
eligibleGifts = eg,
|
||||
actingProvinceId = provinceId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-34
@@ -1,49 +1,30 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.ImproveAvailableCommand
|
||||
import net.eagle0.eagle.common.improvement_type.ImprovementType.{AGRICULTURE, DEVASTATION, ECONOMY, INFRASTRUCTURE}
|
||||
import net.eagle0.eagle.common.profession.Profession.ENGINEER
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.library.settings.MinVigorForImprove
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.ImproveAvailable
|
||||
import net.eagle0.eagle.model.state.command.common.ImprovementType
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.{HeroT, Profession}
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.ImprovementType as StateImprovementType
|
||||
|
||||
object AvailableImproveCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableImproveCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
val IMPROVEMENT_MAX = 100.0
|
||||
|
||||
private val heroSorting: HeroT => (Double, Boolean, Double) = { (hero: HeroT) =>
|
||||
private val heroSorting: Hero => (Double, Boolean, Double) = { (hero: Hero) =>
|
||||
(
|
||||
hero.vigor / hero.constitution.toDouble,
|
||||
hero.profession == Profession.Engineer,
|
||||
hero.profession == ENGINEER,
|
||||
hero.strength + hero.agility + hero.constitution
|
||||
)
|
||||
}
|
||||
|
||||
private def availableTypes(province: ProvinceT): Vector[ImprovementType] =
|
||||
(Option.when(province.economy < IMPROVEMENT_MAX)(ImprovementType.Economy) ++
|
||||
Option.when(province.agriculture < IMPROVEMENT_MAX)(ImprovementType.Agriculture) ++
|
||||
Option.when(province.infrastructure < IMPROVEMENT_MAX)(ImprovementType.Infrastructure) ++
|
||||
Option.when(
|
||||
province.economyDevastation > 0 || province.agricultureDevastation > 0 || province.infrastructureDevastation > 0
|
||||
)(ImprovementType.Devastation)).toVector
|
||||
|
||||
private def convertImprovementType(
|
||||
stateType: StateImprovementType
|
||||
): ImprovementType = stateType match {
|
||||
case StateImprovementType.Agriculture => ImprovementType.Agriculture
|
||||
case StateImprovementType.Economy => ImprovementType.Economy
|
||||
case StateImprovementType.Infrastructure => ImprovementType.Infrastructure
|
||||
case StateImprovementType.Devastation => ImprovementType.Devastation
|
||||
}
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[ImproveAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
internalRequire(
|
||||
@@ -52,18 +33,26 @@ object AvailableImproveCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
)
|
||||
|
||||
val availableHeroes = province.rulingFactionHeroIds
|
||||
.flatMap(hid => gameState.heroes.get(hid))
|
||||
.map(gameState.heroes(_))
|
||||
.filterNot(_.vigor < MinVigorForImprove.doubleValue)
|
||||
|
||||
val types = availableTypes(province)
|
||||
val availableTypes =
|
||||
Option.when(province.economy < IMPROVEMENT_MAX)(ECONOMY) ++
|
||||
Option.when(province.agriculture < IMPROVEMENT_MAX)(AGRICULTURE) ++
|
||||
Option.when(province.infrastructure < IMPROVEMENT_MAX) {
|
||||
INFRASTRUCTURE
|
||||
} ++
|
||||
Option.when(
|
||||
province.economyDevastation > 0 || province.agricultureDevastation > 0 || province.infrastructureDevastation > 0
|
||||
)(DEVASTATION)
|
||||
|
||||
Option.when(availableHeroes.nonEmpty && types.nonEmpty) {
|
||||
ImproveAvailable(
|
||||
Option.when(availableHeroes.nonEmpty && availableTypes.nonEmpty) {
|
||||
ImproveAvailableCommand(
|
||||
availableHeroIds = availableHeroes.map(_.id),
|
||||
availableTypes = types,
|
||||
availableTypes = availableTypes.toVector,
|
||||
actingProvinceId = provinceId,
|
||||
recommendedHeroId = availableHeroes.maxBy(heroSorting).id,
|
||||
lockedType = province.lockedImprovementType.map(convertImprovementType)
|
||||
lockedType = province.lockedImprovementType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-30
@@ -1,47 +1,34 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{IssueOrdersAvailable, ProvinceOrders}
|
||||
import net.eagle0.eagle.model.state.command.common.ProvinceOrderType.{Develop, Entrust, Expand, Mobilize}
|
||||
import net.eagle0.eagle.model.state.command.common.ProvinceOrderType as CommandProvinceOrderType
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceOrderType as StateProvinceOrderType
|
||||
import net.eagle0.eagle.api.available_command.IssueOrdersAvailableCommand
|
||||
import net.eagle0.eagle.api.command.util.province_orders.ProvinceOrders
|
||||
import net.eagle0.eagle.common.province_order_type.ProvinceOrderType.*
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
|
||||
object AvailableIssueOrdersCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
|
||||
private def toCommandOrderType(stateOrderType: StateProvinceOrderType): CommandProvinceOrderType =
|
||||
stateOrderType match {
|
||||
case StateProvinceOrderType.Develop => Develop
|
||||
case StateProvinceOrderType.Mobilize => Mobilize
|
||||
case StateProvinceOrderType.Expand => Expand
|
||||
case StateProvinceOrderType.Entrust => Entrust
|
||||
case StateProvinceOrderType.UnknownOrderType => Develop // Default fallback
|
||||
}
|
||||
object AvailableIssueOrdersCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
val faction = gameState.factions(factionId)
|
||||
val provinces = gameState.provinces.values.toVector
|
||||
|
||||
): Option[IssueOrdersAvailableCommand] =
|
||||
Option.when(
|
||||
province.rulingHeroId.exists(faction.leaderIds.contains) &&
|
||||
FactionUtils.provinceCount(factionId, provinces) > 1
|
||||
gameState
|
||||
.provinces(provinceId)
|
||||
.rulingHeroId
|
||||
.exists(gameState.factions(factionId).leaders.contains) &&
|
||||
LegacyFactionUtils.provinceCount(factionId, gameState) > 1
|
||||
) {
|
||||
IssueOrdersAvailable(
|
||||
IssueOrdersAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
currentOrders = provinces
|
||||
currentOrders = gameState.provinces.values
|
||||
.filter(_.rulingFactionId.contains(factionId))
|
||||
.toVector
|
||||
.sortBy(_.name)
|
||||
.map(p => ProvinceOrders(provinceId = p.id, orderType = toCommandOrderType(p.provinceOrders))),
|
||||
availableOrders = Vector(Develop, Mobilize, Expand, Entrust),
|
||||
availableFocusProvinces = Vector()
|
||||
.map(p => ProvinceOrders(provinceId = p.id, orders = p.provinceOrders)),
|
||||
availableOrders = Vector(DEVELOP, MOBILIZE, EXPAND, ENTRUST)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+51
-46
@@ -1,18 +1,20 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.common.MoreOption
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{
|
||||
ExpandedUnaffiliatedHero,
|
||||
ManagePrisonersAvailable,
|
||||
PrisonerToManage
|
||||
import net.eagle0.eagle.api.available_command.{ManagePrisonersAvailableCommand, PrisonerToManage}
|
||||
import net.eagle0.eagle.api.command.util.prisoner_management_type.{
|
||||
PrisonerManagementOption,
|
||||
PrisonerManagementOptionExecute,
|
||||
PrisonerManagementOptionExile,
|
||||
PrisonerManagementOptionMove,
|
||||
PrisonerManagementOptionRelease,
|
||||
PrisonerManagementOptionReturn
|
||||
}
|
||||
import net.eagle0.eagle.model.state.command.common.PrisonerManagementOption
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.{UnaffiliatedHeroT, UnaffiliatedHeroType}
|
||||
import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.UNAFFILIATED_HERO_PRISONER
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
|
||||
|
||||
object AvailableManagePrisonersCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableManagePrisonersCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private def availableMoveToProvinceIds(
|
||||
fid: FactionId,
|
||||
@@ -27,34 +29,34 @@ object AvailableManagePrisonersCommandFactory extends ScalaAvailableCommandsFact
|
||||
.toVector
|
||||
|
||||
private def optionsForPrisoner(
|
||||
prisoner: UnaffiliatedHeroT,
|
||||
prisoner: UnaffiliatedHero,
|
||||
moveToPids: Vector[ProvinceId],
|
||||
gameState: GameState
|
||||
): Vector[PrisonerManagementOption] = {
|
||||
val factionWithThisPrisonerAsLeader = gameState.factions.values
|
||||
.find(_.leaderIds.contains(prisoner.heroId))
|
||||
.find(_.leaders.contains(prisoner.heroId))
|
||||
|
||||
if factionWithThisPrisonerAsLeader.isDefined then {
|
||||
Vector(
|
||||
PrisonerManagementOption.Execute
|
||||
PrisonerManagementOptionExecute()
|
||||
) ++ moveToPids
|
||||
.map(pid => PrisonerManagementOption.Move(toProvinceId = pid))
|
||||
.map(pid => PrisonerManagementOptionMove(toProvinceId = pid))
|
||||
.toVector ++ Option
|
||||
.when(
|
||||
gameState.provinces.values.exists(
|
||||
_.rulingFactionId.contains(factionWithThisPrisonerAsLeader.get.id)
|
||||
)
|
||||
)(
|
||||
PrisonerManagementOption.Return(toFactionId = factionWithThisPrisonerAsLeader.get.id)
|
||||
PrisonerManagementOptionReturn(toFactionId = factionWithThisPrisonerAsLeader.get.id)
|
||||
)
|
||||
.toVector
|
||||
} else {
|
||||
Vector(
|
||||
PrisonerManagementOption.Execute,
|
||||
PrisonerManagementOption.Exile,
|
||||
PrisonerManagementOption.Release
|
||||
PrisonerManagementOptionExecute(),
|
||||
PrisonerManagementOptionExile(),
|
||||
PrisonerManagementOptionRelease()
|
||||
) ++ moveToPids
|
||||
.map(pid => PrisonerManagementOption.Move(toProvinceId = pid))
|
||||
.map(pid => PrisonerManagementOptionMove(toProvinceId = pid))
|
||||
.toVector
|
||||
}
|
||||
}
|
||||
@@ -63,37 +65,40 @@ object AvailableManagePrisonersCommandFactory extends ScalaAvailableCommandsFact
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[ManagePrisonersAvailableCommand] =
|
||||
MoreOption.flatWhen(
|
||||
gameState.provinces(provinceId).rulerIsTraveling
|
||||
) {
|
||||
val prisoners = gameState
|
||||
)(
|
||||
gameState
|
||||
.provinces(provinceId)
|
||||
.unaffiliatedHeroes
|
||||
.filter(_.unaffiliatedHeroType == UnaffiliatedHeroType.Prisoner)
|
||||
|
||||
Option.when(prisoners.nonEmpty) {
|
||||
ManagePrisonersAvailable(
|
||||
actingProvinceId = provinceId,
|
||||
prisoners = prisoners.map { prisoner =>
|
||||
PrisonerToManage(
|
||||
prisoner = ExpandedUnaffiliatedHero(
|
||||
heroId = prisoner.heroId,
|
||||
heroName = gameState.heroes(prisoner.heroId).nameTextId,
|
||||
goldCost = 0
|
||||
),
|
||||
availableOptions = optionsForPrisoner(
|
||||
prisoner,
|
||||
moveToPids = availableMoveToProvinceIds(
|
||||
factionId,
|
||||
provinceId,
|
||||
gameState
|
||||
),
|
||||
gameState
|
||||
)
|
||||
.filter(_.`type` == UNAFFILIATED_HERO_PRISONER) match {
|
||||
case items if items.isEmpty => None
|
||||
case prisoners: Seq[UnaffiliatedHero] =>
|
||||
Some(
|
||||
ManagePrisonersAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
prisoners = prisoners.map { prisoner =>
|
||||
PrisonerToManage(
|
||||
prisoner = Some(
|
||||
ExpandedUnaffiliatedHeroUtils.expandedUnaffiliatedHero(
|
||||
gs = gameState,
|
||||
uh = prisoner
|
||||
)
|
||||
),
|
||||
availableOptions = optionsForPrisoner(
|
||||
prisoner,
|
||||
moveToPids = availableMoveToProvinceIds(
|
||||
factionId,
|
||||
provinceId,
|
||||
gameState
|
||||
),
|
||||
gameState
|
||||
)
|
||||
)
|
||||
}.toVector
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
+47
-47
@@ -1,35 +1,45 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.{
|
||||
AvailableDestinationProvince,
|
||||
MarchAvailableCommand,
|
||||
MarchCommandFromOneProvince,
|
||||
RecommendedBattleConfig
|
||||
}
|
||||
import net.eagle0.eagle.api.command.util.battalion_with_food_cost.BattalionWithFoodCost
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.settings.{MaxCombatUnitCountPerSide, MinVigorForMarch}
|
||||
import net.eagle0.eagle.library.util.{BattalionUtils, ShardokMapInfo}
|
||||
import net.eagle0.eagle.library.util.{LegacyBattalionUtils, ShardokMapInfo}
|
||||
import net.eagle0.eagle.library.util.battalion_suitability.BattalionSuitability
|
||||
import net.eagle0.eagle.library.util.command_choice_helpers.{CombatUnitSelector, MarchSuppliesHelpers}
|
||||
import net.eagle0.eagle.library.util.hero.HeroUtils
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.*
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionConverter
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionTypeConverter
|
||||
import net.eagle0.eagle.model.proto_converters.SuitableBattalionsConverter
|
||||
|
||||
object AvailableMarchCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private def oneOriginProvinceCommand(
|
||||
gameState: GameState,
|
||||
originProvince: ProvinceT
|
||||
originProvince: Province
|
||||
): Option[MarchCommandFromOneProvince] = {
|
||||
val availableDestinations = originProvince.neighbors
|
||||
.map(_.provinceId)
|
||||
.flatMap(gameState.provinces.get)
|
||||
.filterNot(ProvinceUtils.hasBlizzard)
|
||||
.filterNot(LegacyProvinceUtils.hasBlizzard)
|
||||
.map { destinationProvince =>
|
||||
val startingPositionIndex = destinationProvince.neighbors
|
||||
.find(n => n.provinceId == originProvince.id)
|
||||
.get
|
||||
.startingPositionIndex
|
||||
|
||||
val mapInfo = ShardokMapInfo.shardokMapInfos(destinationProvince.id)
|
||||
val requiresRiverCrossing = mapInfo.waterCrossingRequirementsByPosition(
|
||||
val mapInfo = ShardokMapInfo.shardokMapInfos(destinationProvince.id)
|
||||
val destinationCastleCount = mapInfo.castleCount
|
||||
val requiresRiverCrossing = mapInfo.waterCrossingRequirementsByPosition(
|
||||
startingPositionIndex
|
||||
) >= 10
|
||||
|
||||
@@ -38,19 +48,20 @@ object AvailableMarchCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
requiresRiverCrossing = requiresRiverCrossing
|
||||
)
|
||||
}
|
||||
.toVector
|
||||
|
||||
val availableHeroes = originProvince.rulingFactionHeroIds
|
||||
.map(heroId => gameState.heroes(heroId))
|
||||
.filter(_.vigor >= MinVigorForMarch.doubleValue)
|
||||
|
||||
if ProvinceUtils.hasBlizzard(originProvince) then return None
|
||||
if LegacyProvinceUtils.hasBlizzard(originProvince) then return None
|
||||
|
||||
if availableDestinations.isEmpty then return None
|
||||
if availableHeroes.isEmpty then return None
|
||||
|
||||
val availableBattalions = originProvince.battalionIds
|
||||
.map(battId => gameState.battalions(battId))
|
||||
.sortBy(_.typeId.value)
|
||||
.sortBy(_.`type`.index)
|
||||
|
||||
val recommendedSendCount =
|
||||
availableHeroes.size
|
||||
@@ -61,14 +72,14 @@ object AvailableMarchCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
Option.when(recommendedSendCount > 0) {
|
||||
val units = CombatUnitSelector.selectedCombatBattalions(
|
||||
desiredCount = recommendedSendCount,
|
||||
heroes = availableHeroes,
|
||||
battalions = availableBattalions,
|
||||
battalionTypes = gameState.battalionTypes
|
||||
heroes = availableHeroes.map(HeroConverter.fromProto).toVector,
|
||||
battalions = availableBattalions.map(BattalionConverter.fromProto).toVector,
|
||||
battalionTypes = gameState.battalionTypes.map(BattalionTypeConverter.fromProto).toVector
|
||||
)
|
||||
RecommendedBattleConfig(
|
||||
units = units.map(cu => RecommendedCombatUnit(heroId = cu.heroId, battalionId = cu.battalionId)),
|
||||
units = units,
|
||||
food = MarchSuppliesHelpers.foodToTakeOnAttack(
|
||||
units.flatMap(_.battalionId).map(gameState.battalions),
|
||||
units.flatMap(_.battalionId).map(gameState.battalions).map(BattalionConverter.fromProto),
|
||||
originProvince.food
|
||||
),
|
||||
gold = MarchSuppliesHelpers.goldToTakeOnAttack(
|
||||
@@ -78,33 +89,27 @@ object AvailableMarchCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
)
|
||||
}
|
||||
|
||||
val suitableBattalionsMap = BattalionSuitability
|
||||
.suitableBattalionsForHeroes(
|
||||
hs = availableHeroes,
|
||||
bs = availableBattalions,
|
||||
bts = gameState.battalionTypes
|
||||
)
|
||||
.map {
|
||||
case (heroId, suitableBattalions) =>
|
||||
heroId -> SuitableBattalions(
|
||||
battalionIds = suitableBattalions.battalionIdsWithSuitability.map(_.battalionId)
|
||||
)
|
||||
}
|
||||
|
||||
Some(
|
||||
MarchCommandFromOneProvince(
|
||||
availableHeroIds = availableHeroes.map(_.id),
|
||||
availableHeroIds = availableHeroes.map(_.id).toVector,
|
||||
availableBattalions = availableBattalions.map { batt =>
|
||||
BattalionWithFoodCost(
|
||||
battalionId = batt.id,
|
||||
foodCost = BattalionUtils.monthlyConsumedFood(batt, gameState.battalionTypes).toInt,
|
||||
troopCount = batt.size
|
||||
monthlyFood = LegacyBattalionUtils.monthlyConsumedFood(
|
||||
battalion = batt,
|
||||
bts = gameState.battalionTypes.toVector
|
||||
)
|
||||
)
|
||||
},
|
||||
}.toVector,
|
||||
availableDestinationProvinces = availableDestinations,
|
||||
suitableBattalionsForHeroes = suitableBattalionsMap,
|
||||
suitableBattalionsForHeroes = SuitableBattalionsConverter.toProtoMap(
|
||||
BattalionSuitability.suitableBattalionsForHeroes(
|
||||
hs = availableHeroes.map(HeroConverter.fromProto).toVector,
|
||||
bs = availableBattalions.map(BattalionConverter.fromProto).toVector,
|
||||
bts = gameState.battalionTypes.map(BattalionTypeConverter.fromProto).toVector
|
||||
)
|
||||
),
|
||||
originProvinceId = originProvince.id,
|
||||
goldCost = 0, // March doesn't cost gold
|
||||
goldAvailable = originProvince.gold,
|
||||
foodAvailable = originProvince.food,
|
||||
recommendedBattleConfig = recommendedConfig
|
||||
@@ -116,22 +121,17 @@ object AvailableMarchCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
HeroUtils
|
||||
): Option[MarchAvailableCommand] =
|
||||
LegacyHeroUtils
|
||||
.consideredProvinces(gameState, factionId, provinceId)
|
||||
.flatMap(p => oneOriginProvinceCommand(gameState = gameState, originProvince = p)) match {
|
||||
case items if items.isEmpty => None
|
||||
case opcs =>
|
||||
Some(
|
||||
AvailableCommand.MarchAvailable(
|
||||
MarchAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
oneProvinceCommands = opcs,
|
||||
availableTypes = gameState.battalionTypes.map { bt =>
|
||||
BattalionType(
|
||||
typeId = bt.typeId.value,
|
||||
name = bt.name
|
||||
)
|
||||
}
|
||||
availableTypes = gameState.battalionTypes
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+21
-24
@@ -1,45 +1,39 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{
|
||||
BattalionTypeStatus,
|
||||
OrganizeTroopsAvailable,
|
||||
TroopCost
|
||||
}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.api.available_command.{OrganizeTroopsAvailableCommand, TroopCost}
|
||||
import net.eagle0.eagle.api.available_command.OrganizeTroopsAvailableCommand.BattalionTypeStatus
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableOrganizeTroopsCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableOrganizeTroopsCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
val maxBattalions = 10 // FIXME
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[OrganizeTroopsAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
val availableTypeIds = gameState.battalionTypes
|
||||
.filter(bt =>
|
||||
ProvinceUtils.effectiveAgriculture(province) >= bt.minimumAgriculture &&
|
||||
ProvinceUtils.effectiveEconomy(province) >= bt.minimumEconomy
|
||||
)
|
||||
.map(_.typeId)
|
||||
|
||||
val availableTypeStatuses = gameState.battalionTypes.map { bt =>
|
||||
val availableTypeStatuses = gameState.battalionTypes.toVector.map { bt =>
|
||||
BattalionTypeStatus(
|
||||
typeId = bt.typeId.value,
|
||||
typeId = bt.typeId,
|
||||
minimumAgriculture = bt.minimumAgriculture,
|
||||
minimumEconomy = bt.minimumEconomy,
|
||||
meetsRequirements = availableTypeIds.contains(bt.typeId)
|
||||
meetsRequirements = LegacyProvinceUtils
|
||||
.availableBattalionTypeIds(
|
||||
province,
|
||||
gameState.battalionTypes.toVector
|
||||
)
|
||||
.contains(bt.typeId)
|
||||
)
|
||||
}
|
||||
.sortBy(_.typeId)
|
||||
.sortBy(_.typeId.value)
|
||||
|
||||
val troopCosts = gameState.battalionTypes.map(t =>
|
||||
val troopCosts = gameState.battalionTypes.toVector.map(t =>
|
||||
TroopCost(
|
||||
battalionTypeId = t.typeId.value,
|
||||
`type` = t.typeId,
|
||||
costPerTroop = t.baseCost * province.priceIndex
|
||||
)
|
||||
)
|
||||
@@ -50,14 +44,17 @@ object AvailableOrganizeTroopsCommandsFactory extends ScalaAvailableCommandsFact
|
||||
val existingEligibleBattalions = province.battalionIds
|
||||
.map(bid => gameState.battalions(bid))
|
||||
.map(_.id)
|
||||
.toVector
|
||||
|
||||
Option.when(
|
||||
existingEligibleBattalions.nonEmpty || province.battalionIds.length < maxBattalions
|
||||
) {
|
||||
OrganizeTroopsAvailable(
|
||||
OrganizeTroopsAvailableCommand(
|
||||
actingProvinceId = provinceId,
|
||||
// FIXME: adjust for province price index
|
||||
troopCosts = troopCosts,
|
||||
existingBattalions = existingEligibleBattalions,
|
||||
// FIXME: max battalions
|
||||
maxBattalions = maxBattalions,
|
||||
availableBattalionTypes = availableTypeStatuses
|
||||
)
|
||||
|
||||
+10
-14
@@ -1,43 +1,39 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.ReconAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.settings.MinVigorForRecon
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.ReconAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.Profession
|
||||
import net.eagle0.eagle.model.state.province.IncomingRecon
|
||||
|
||||
object AvailableReconCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableReconCommandFactory extends AvailableCommandsFactoryForType {
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[ReconAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
if province.rulingFactionHeroIds.size < 2 then None
|
||||
else {
|
||||
val availableHeroes = province.rulingFactionHeroIds
|
||||
.map(heroId => gameState.heroes(heroId))
|
||||
.filter(_.profession == Profession.Ranger)
|
||||
.filter(_.profession.isRanger)
|
||||
.filter(_.vigor >= MinVigorForRecon.doubleValue)
|
||||
.map(_.id)
|
||||
|
||||
val availableTargets = gameState.provinces.values
|
||||
.filterNot(_.rulingFactionId.contains(factionId))
|
||||
.filterNot(p =>
|
||||
p.incomingEndTurnActions.exists(eta =>
|
||||
eta.fromFactionId == factionId && eta.details.isInstanceOf[IncomingRecon]
|
||||
)
|
||||
.filterNot(
|
||||
_.incomingEndTurnActions
|
||||
.exists(eta => eta.action.isRecon && eta.fromFactionId == factionId)
|
||||
)
|
||||
.toVector
|
||||
.sortBy(_.name)
|
||||
.map(_.id)
|
||||
|
||||
Option.when(availableHeroes.nonEmpty && availableTargets.nonEmpty) {
|
||||
ReconAvailable(
|
||||
availableHeroIds = availableHeroes.toVector,
|
||||
ReconAvailableCommand(
|
||||
availableHeroIds = availableHeroes,
|
||||
availableTargetProvinces = availableTargets,
|
||||
actingProvinceId = provinceId
|
||||
)
|
||||
|
||||
+14
-21
@@ -1,52 +1,45 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.RecruitHeroesAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.settings.MinOddsForRecruitment
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.library.util.recruitment_odds.RecruitmentOdds
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{
|
||||
ExpandedUnaffiliatedHero,
|
||||
RecruitHeroesAvailable
|
||||
}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
import net.eagle0.eagle.library.util.recruitment_odds.LegacyRecruitmentOdds
|
||||
|
||||
object AvailableRecruitHeroesCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableRecruitHeroesCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[RecruitHeroesAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
if !province.rulerIsTraveling then return None
|
||||
|
||||
val faction = gameState.factions(factionId)
|
||||
if !ProvinceUtils.ruledByFactionLeader(province, gameState.factions.values.toVector) then return None
|
||||
if !LegacyProvinceUtils.ruledByFactionLeader(province, gameState) then return None
|
||||
|
||||
val expandedRecruitable = province.unaffiliatedHeroes
|
||||
.filterNot(uh => FactionUtils.isFactionLeader(uh.heroId, gameState.factions.values.toVector))
|
||||
.filterNot(uh => LegacyFactionUtils.isFactionLeader(uh.heroId, gameState))
|
||||
.filterNot(h =>
|
||||
RecruitmentOdds.scoreForUnaffiliatedHero(
|
||||
LegacyRecruitmentOdds.scoreForUnaffiliatedHero(
|
||||
faction = faction,
|
||||
factionLeader = gameState.heroes.get(faction.factionHeadId),
|
||||
prestige = FactionUtils.prestige(faction, gameState.provinces.values.toVector),
|
||||
prestige = LegacyFactionUtils.prestige(factionId, gameState),
|
||||
targetHero = gameState.heroes(h.heroId),
|
||||
targetUH = h
|
||||
) < MinOddsForRecruitment.doubleValue
|
||||
)
|
||||
.map(uh =>
|
||||
ExpandedUnaffiliatedHero(
|
||||
heroId = uh.heroId,
|
||||
heroName = gameState.heroes(uh.heroId).nameTextId,
|
||||
goldCost = 0 // Recruitment is free
|
||||
)
|
||||
ExpandedUnaffiliatedHeroUtils
|
||||
.expandedUnaffiliatedHero(gs = gameState, uh = uh)
|
||||
)
|
||||
|
||||
Option.when(expandedRecruitable.nonEmpty) {
|
||||
RecruitHeroesAvailable(
|
||||
RecruitHeroesAvailableCommand(
|
||||
availableHeroes = expandedRecruitable,
|
||||
actingProvinceId = provinceId
|
||||
)
|
||||
|
||||
+26
-41
@@ -1,54 +1,39 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.{HostileArmyGroup, MovingArmy}
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{ResolveTributeAvailable, TributeAndFaction}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.HostileArmyGroupStatus.TributeDemanded
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.{ResolveTributeAvailableCommand, TributeAndFaction}
|
||||
import net.eagle0.eagle.internal.army.{HostileArmyGroup, HostileArmyGroupStatus}
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.util.ArmyUtils
|
||||
|
||||
object AvailableResolveTributeCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
|
||||
private def heroCount(ag: HostileArmyGroup): Int =
|
||||
ag.armies.map(ma => ma.army.units.size).sum
|
||||
|
||||
private def troopCount(ag: HostileArmyGroup, battalions: Map[BattalionId, BattalionT]): Int =
|
||||
ag.armies
|
||||
.flatMap(_.army.units)
|
||||
.flatMap(_.battalionId)
|
||||
.flatMap(battalions.get)
|
||||
.map(_.size)
|
||||
.sum
|
||||
object AvailableResolveTributeCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private def makeResolveTributeCommand(
|
||||
province: ProvinceT,
|
||||
province: Province,
|
||||
gs: GameState
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[ResolveTributeAvailableCommand] = {
|
||||
val incomingTributeDemands =
|
||||
province.hostileArmies.filter(ag =>
|
||||
ag.status match {
|
||||
case _: TributeDemanded => true
|
||||
case _ => false
|
||||
}
|
||||
province.hostileArmies.filter(
|
||||
_.status.asMessage.sealedValue.isTributeDemanded
|
||||
)
|
||||
|
||||
Option.when(incomingTributeDemands.nonEmpty) {
|
||||
ResolveTributeAvailable(
|
||||
ResolveTributeAvailableCommand(
|
||||
actingProvinceId = province.id,
|
||||
demands = incomingTributeDemands.map { ag =>
|
||||
val tributeAmount = ag.status match {
|
||||
case TributeDemanded(amount) => amount
|
||||
case _ => throw new IllegalStateException("Expected TributeDemanded status")
|
||||
}
|
||||
TributeAndFaction(
|
||||
demandingFactionId = ag.factionId,
|
||||
goldDemanded = tributeAmount.gold,
|
||||
foodDemanded = tributeAmount.food,
|
||||
heroCount = heroCount(ag),
|
||||
troopCount = troopCount(ag, gs.battalions)
|
||||
)
|
||||
demands = incomingTributeDemands.map {
|
||||
case ag @ HostileArmyGroup(
|
||||
fid: FactionId,
|
||||
_,
|
||||
status: HostileArmyGroupStatus,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
TributeAndFaction(
|
||||
demandingFactionId = fid,
|
||||
tributeDemanded = status.asMessage.getTributeDemanded.tributeAmount,
|
||||
heroCount = ArmyUtils.heroCount(ag),
|
||||
troopCount = ArmyUtils.troopCount(ag, gs)
|
||||
)
|
||||
},
|
||||
availableGold = province.gold,
|
||||
availableFood = province.food
|
||||
@@ -60,6 +45,6 @@ object AvailableResolveTributeCommandsFactory extends ScalaAvailableCommandsFact
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[ResolveTributeAvailableCommand] =
|
||||
makeResolveTributeCommand(gameState.provinces(provinceId), gameState)
|
||||
}
|
||||
|
||||
+5
-6
@@ -1,18 +1,17 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.RestAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.api.available_command.RestAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
|
||||
object AvailableRestCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableRestCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[RestAvailableCommand] =
|
||||
Some(
|
||||
RestAvailable(
|
||||
RestAvailableCommand(
|
||||
actingProvinceId = provinceId
|
||||
)
|
||||
)
|
||||
|
||||
+5
-6
@@ -1,16 +1,15 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.ReturnAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.api.available_command.ReturnAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
|
||||
object AvailableReturnCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableReturnCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[ReturnAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
require(
|
||||
@@ -19,7 +18,7 @@ object AvailableReturnCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
)
|
||||
|
||||
Option.when(province.rulerIsTraveling)(
|
||||
ReturnAvailable(actingProvinceId = provinceId)
|
||||
ReturnAvailableCommand(actingProvinceId = provinceId)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+22
-16
@@ -1,41 +1,47 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.SendSuppliesAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.settings.MinVigorForSendSupplies
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.SendSuppliesAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableSendSuppliesCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableSendSuppliesCommandFactory extends AvailableCommandsFactoryForType {
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[SendSuppliesAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
val availableHeroIds = province.rulingFactionHeroIds
|
||||
.map(heroId => gameState.heroes(heroId))
|
||||
.filter(_.vigor >= MinVigorForSendSupplies.doubleValue)
|
||||
.filter(
|
||||
_.vigor >= MinVigorForSendSupplies.doubleValue
|
||||
)
|
||||
.map(_.id)
|
||||
|
||||
val destinationProvinceIds = province.neighbors
|
||||
.map(_.provinceId)
|
||||
.filterNot(pid => ProvinceUtils.hasBlizzard(gameState.provinces(pid)))
|
||||
.filterNot(pid =>
|
||||
LegacyProvinceUtils.hasBlizzard(
|
||||
gameState
|
||||
.provinces(pid)
|
||||
)
|
||||
)
|
||||
|
||||
val hasSupplies = province.gold > 0 || province.food > 0
|
||||
|
||||
Option.when(
|
||||
!ProvinceUtils.hasBlizzard(province) &&
|
||||
hasSupplies &&
|
||||
availableHeroIds.nonEmpty &&
|
||||
destinationProvinceIds.nonEmpty &&
|
||||
FactionUtils.provinceCount(factionId, gameState.provinces.values) > 1
|
||||
!LegacyProvinceUtils
|
||||
.hasBlizzard(
|
||||
province
|
||||
) && hasSupplies && availableHeroIds.nonEmpty && destinationProvinceIds.nonEmpty && LegacyFactionUtils
|
||||
.provinceCount(factionId, gameState) > 1
|
||||
) {
|
||||
SendSuppliesAvailable(
|
||||
availableHeroIds = availableHeroIds.toVector,
|
||||
SendSuppliesAvailableCommand(
|
||||
availableHeroIds = availableHeroIds,
|
||||
actingProvinceId = provinceId,
|
||||
foodAvailable = province.food,
|
||||
goldAvailable = province.gold,
|
||||
|
||||
+21
-19
@@ -1,37 +1,39 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.StartEpidemicAvailableCommand
|
||||
import net.eagle0.eagle.api.available_command.StartEpidemicAvailableCommand.StartEpidemicOptions
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.settings.MinVigorForStartEpidemic
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{StartEpidemicAvailable, StartEpidemicOptions}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.Profession
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableStartEpidemicCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableStartEpidemicCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private def makeStartEpidemicCommand(
|
||||
gameState: GameState,
|
||||
province: ProvinceT
|
||||
): Option[StartEpidemicAvailable] = {
|
||||
province: Province
|
||||
): Option[StartEpidemicAvailableCommand] = {
|
||||
val necromancersWithSufficientVigor = province.rulingFactionHeroIds
|
||||
.flatMap(gameState.heroes.get)
|
||||
.filter(_.profession == Profession.Necromancer)
|
||||
.map(gameState.heroes)
|
||||
.filter(_.profession.isNecromancer)
|
||||
.filter(_.vigor >= MinVigorForStartEpidemic.doubleValue)
|
||||
|
||||
val pidPlusNeighborPids = province.id +: province.neighbors.map(_.provinceId)
|
||||
val pidPlusNeighborPids = province.id +: province.neighbors.map(
|
||||
_.provinceId
|
||||
)
|
||||
|
||||
val targetProvinceIds = pidPlusNeighborPids.filterNot { pid =>
|
||||
gameState.provinces.get(pid).exists { p =>
|
||||
p.rulingFactionId == province.rulingFactionId && ProvinceUtils.hasEpidemic(p)
|
||||
}
|
||||
}
|
||||
val targetProvinceIds = pidPlusNeighborPids.filterNot(pid =>
|
||||
gameState
|
||||
.provinces(pid)
|
||||
.rulingFactionId == province.rulingFactionId && LegacyProvinceUtils
|
||||
.hasEpidemic(gameState.provinces(pid))
|
||||
)
|
||||
|
||||
Option.when(
|
||||
necromancersWithSufficientVigor.nonEmpty && targetProvinceIds.nonEmpty
|
||||
) {
|
||||
StartEpidemicAvailable(
|
||||
StartEpidemicAvailableCommand(
|
||||
availableHeroIds = necromancersWithSufficientVigor
|
||||
.sortBy(-_.vigor)
|
||||
.map(_.id),
|
||||
@@ -45,7 +47,7 @@ object AvailableStartEpidemicCommandFactory extends ScalaAvailableCommandsFactor
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[StartEpidemicAvailableCommand] =
|
||||
makeStartEpidemicCommand(
|
||||
gameState,
|
||||
gameState.provinces(provinceId)
|
||||
|
||||
+10
-11
@@ -1,26 +1,25 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.SuppressBeastsAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.settings.MinVigorForSuppressBeasts
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.SuppressBeastsAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableSuppressBeastsCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableSuppressBeastsCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
private def makeSuppressBeastsCommand(
|
||||
gameState: GameState,
|
||||
province: ProvinceT
|
||||
): Option[SuppressBeastsAvailable] = {
|
||||
province: Province
|
||||
): Option[SuppressBeastsAvailableCommand] = {
|
||||
val availableHeroIds = province.rulingFactionHeroIds
|
||||
.filter(hid => gameState.heroes(hid).vigor >= MinVigorForSuppressBeasts.doubleValue)
|
||||
|
||||
Option.when(
|
||||
ProvinceUtils.hasBeasts(province) && availableHeroIds.nonEmpty
|
||||
LegacyProvinceUtils.hasBeasts(province) && availableHeroIds.nonEmpty
|
||||
) {
|
||||
SuppressBeastsAvailable(
|
||||
SuppressBeastsAvailableCommand(
|
||||
availableHeroIds = availableHeroIds,
|
||||
availableBattalionIds = province.battalionIds,
|
||||
actingProvinceId = province.id
|
||||
@@ -32,7 +31,7 @@ object AvailableSuppressBeastsCommandFactory extends ScalaAvailableCommandsFacto
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[SuppressBeastsAvailableCommand] =
|
||||
makeSuppressBeastsCommand(
|
||||
gameState,
|
||||
gameState.provinces(provinceId)
|
||||
|
||||
+9
-9
@@ -1,17 +1,17 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.SwearBrotherhoodAvailableCommand
|
||||
import net.eagle0.eagle.api.available_command.SwearBrotherhoodAvailableCommand.HeroAndBackstory
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.settings.{MaximumFactionLeaders, MinimumLoyaltyForSwearBrotherhood}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{HeroAndBackstory, SwearBrotherhoodAvailable}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
object AvailableSwearBrotherhoodCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableSwearBrotherhoodCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
def canHaveMoreLeaders(gameState: GameState, factionId: FactionId): Boolean =
|
||||
gameState
|
||||
.factions(factionId)
|
||||
.leaderIds
|
||||
.leaders
|
||||
.length < MaximumFactionLeaders.intValue
|
||||
|
||||
def containsFactionLeader(
|
||||
@@ -32,9 +32,9 @@ object AvailableSwearBrotherhoodCommandFactory extends ScalaAvailableCommandsFac
|
||||
gameState
|
||||
.provinces(provinceId)
|
||||
.rulingFactionHeroIds
|
||||
.flatMap(gameState.heroes.get)
|
||||
.map(gameState.heroes)
|
||||
.filter(_.loyalty >= MinimumLoyaltyForSwearBrotherhood.doubleValue)
|
||||
.filterNot(h => gameState.factions(factionId).leaderIds.contains(h.id))
|
||||
.filterNot(h => gameState.factions(factionId).leaders.contains(h.id))
|
||||
.map(_.id)
|
||||
.toVector
|
||||
|
||||
@@ -42,7 +42,7 @@ object AvailableSwearBrotherhoodCommandFactory extends ScalaAvailableCommandsFac
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] =
|
||||
): Option[SwearBrotherhoodAvailableCommand] =
|
||||
if canHaveMoreLeaders(gameState, factionId) && containsFactionLeader(
|
||||
gameState,
|
||||
factionId,
|
||||
@@ -53,7 +53,7 @@ object AvailableSwearBrotherhoodCommandFactory extends ScalaAvailableCommandsFac
|
||||
case items if items.isEmpty => None
|
||||
case hs =>
|
||||
Some(
|
||||
SwearBrotherhoodAvailable(
|
||||
SwearBrotherhoodAvailableCommand(
|
||||
availableHeroes = hs.map { hid =>
|
||||
HeroAndBackstory(
|
||||
heroId = hid,
|
||||
|
||||
+5
-6
@@ -1,24 +1,23 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.TradeAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.settings.{BaseFoodBuyPrice, BaseFoodSellPrice}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.TradeAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
object AvailableTradeCommandFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableTradeCommandFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[TradeAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
Option.when(
|
||||
province.rulerIsTraveling && (province.gold > 0 || province.food > 0)
|
||||
) {
|
||||
TradeAvailable(
|
||||
TradeAvailableCommand(
|
||||
foodAvailable = province.food,
|
||||
goldAvailable = province.gold,
|
||||
foodSellPrice = BaseFoodSellPrice.doubleValue * province.priceIndex,
|
||||
|
||||
+10
-10
@@ -1,19 +1,19 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.TrainAvailableCommand
|
||||
import net.eagle0.eagle.common.profession.Profession.CHAMPION
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.library.settings.MinVigorForTrain
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.TrainAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.{HeroT, Profession}
|
||||
|
||||
object AvailableTrainCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableTrainCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
private val TRAINING_MAX = 100.0
|
||||
|
||||
private val heroSorting: HeroT => (Double, Boolean, Double) = { (hero: HeroT) =>
|
||||
private val heroSorting: Hero => (Double, Boolean, Double) = { (hero: Hero) =>
|
||||
(
|
||||
hero.vigor / hero.constitution.toDouble,
|
||||
hero.profession == Profession.Champion,
|
||||
hero.profession == CHAMPION,
|
||||
hero.strength + hero.charisma + hero.constitution
|
||||
)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ object AvailableTrainCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[TrainAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
val availableHeroes = province.rulingFactionHeroIds
|
||||
.map(heroId => gameState.heroes(heroId))
|
||||
@@ -33,8 +33,8 @@ object AvailableTrainCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
.exists(_.training < TRAINING_MAX)
|
||||
|
||||
Option.when(availableHeroes.nonEmpty && hasTrainableBattalion) {
|
||||
TrainAvailable(
|
||||
availableHeroIds = availableHeroes.map(_.id).toVector,
|
||||
TrainAvailableCommand(
|
||||
availableHeroIds = availableHeroes.map(_.id),
|
||||
actingProvinceId = provinceId,
|
||||
recommendedHeroId = availableHeroes.maxBy(heroSorting).id
|
||||
)
|
||||
|
||||
+12
-9
@@ -1,19 +1,18 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.TravelAvailableCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.settings.MinVigorForTravel
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.TravelAvailable
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
|
||||
object AvailableTravelCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
object AvailableTravelCommandsFactory extends AvailableCommandsFactoryForType {
|
||||
|
||||
override def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand] = {
|
||||
): Option[TravelAvailableCommand] = {
|
||||
val province = gameState.provinces(provinceId)
|
||||
|
||||
require(
|
||||
@@ -22,12 +21,16 @@ object AvailableTravelCommandsFactory extends ScalaAvailableCommandsFactory {
|
||||
)
|
||||
|
||||
Option.when(
|
||||
!province.rulerIsTraveling && !ProvinceUtils.hasBlizzard(province) && province.rulingHeroId
|
||||
.flatMap(gameState.heroes.get)
|
||||
!province.rulerIsTraveling && !LegacyProvinceUtils
|
||||
.hasBlizzard(province) && province.rulingHeroId
|
||||
.map(gameState.heroes(_))
|
||||
.map(_.vigor)
|
||||
.exists(_ >= MinVigorForTravel.doubleValue)
|
||||
) {
|
||||
TravelAvailable(actingProvinceId = provinceId)
|
||||
TravelAvailableCommand(
|
||||
actingProvinceId = provinceId
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,5 @@
|
||||
load("@rules_scala//scala:scala.bzl", "scala_library")
|
||||
|
||||
scala_library(
|
||||
name = "scala_available_commands_factory",
|
||||
srcs = ["ScalaAvailableCommandsFactory.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "unified_command_factory",
|
||||
srcs = ["UnifiedCommandFactory.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "factory_adapters",
|
||||
srcs = ["FactoryAdapters.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
":unified_command_factory",
|
||||
],
|
||||
deps = [
|
||||
":available_commands_factory_for_type",
|
||||
":scala_available_commands_factory",
|
||||
":unified_command_factory",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/model/proto_converters/command/available:available_command_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "availability",
|
||||
srcs = ["AvailableCommandsFactory.scala"],
|
||||
@@ -105,22 +53,17 @@ scala_library(
|
||||
":available_trade_command_factory",
|
||||
":available_train_command_factory",
|
||||
":available_travel_commands_factory",
|
||||
":factory_adapters",
|
||||
":pkg",
|
||||
":unified_command_factory",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:province_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:more_option",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:available_command_type_map",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:incoming_army_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/command/available:available_command_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
@@ -131,18 +74,14 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:min_vigor_for_apprehend_outlaw",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:hero_view_filter",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -152,16 +91,13 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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_type_finder",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:legacy_battalion_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -172,21 +108,20 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":expanded_combat_unit_utils",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:more_option",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:incoming_army_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:province_distances",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//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/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -209,17 +144,14 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [":scala_available_commands_factory"],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:min_vigor_for_control_weather",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//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/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -229,17 +161,15 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:more_option",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/availability:expanded_unaffiliated_hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -249,22 +179,24 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [":scala_available_commands_factory"],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:max_combat_unit_count_per_side",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:min_vigor_for_defend",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:battalion_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:legacy_battalion_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/battalion_suitability",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:combat_unit_selector",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
|
||||
"//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:suitable_battalions_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -274,11 +206,11 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:alliance_gold_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:break_alliance_gold_cost",
|
||||
@@ -287,14 +219,9 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:min_vigor_for_diplomacy",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:truce_gold_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:province_distances",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//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/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -304,19 +231,17 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":expanded_unaffiliated_hero_utils",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:expanded_unaffiliated_hero_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:more_option",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:gold_cost_for_divine",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//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/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -326,18 +251,14 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//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/library/util/faction_utils:legacy_faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -347,17 +268,14 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:feast_gold_cost_per_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//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/library/util/hero:legacy_hero_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -368,17 +286,17 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":expanded_combat_unit_utils",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:expanded_combat_unit_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:incoming_army_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//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/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -388,17 +306,15 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:captured_hero",
|
||||
"//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/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:hero_view_filter",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -410,16 +326,17 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/availability:available_commands_factory_for_type",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:min_vigor_for_crack_down",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//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/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -431,14 +348,16 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/availability:available_commands_factory_for_type",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -450,16 +369,18 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/availability:available_commands_factory_for_type",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:riot_max_food",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:riot_max_gold",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -469,19 +390,15 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:max_gift_gold",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//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/library/util/faction_utils:legacy_faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -491,20 +408,15 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:prisoner_management_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/settings:min_vigor_for_improve",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:improvement_type",
|
||||
"//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/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -514,17 +426,15 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":expanded_unaffiliated_hero_utils",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:prisoner_management_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:more_option",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//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/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -534,24 +444,27 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:max_combat_unit_count_per_side",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:min_vigor_for_march",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:battalion_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:legacy_battalion_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:shardok_map_info",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/battalion_suitability",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:combat_unit_selector",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:march_supplies_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//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/library/util/hero:legacy_hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
"//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:suitable_battalions_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -561,15 +474,13 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [":scala_available_commands_factory"],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/faction_utils",
|
||||
"//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/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -579,13 +490,13 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [":scala_available_commands_factory"],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -616,17 +527,15 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:max_alms_food",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:min_vigor_for_alms",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -636,17 +545,13 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:min_vigor_for_recon",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:incoming_end_turn_action",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -656,19 +561,19 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":expanded_unaffiliated_hero_utils",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:expanded_unaffiliated_hero_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/settings:min_odds_for_recruitment",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/recruitment_odds",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/recruitment_odds:legacy_recruitment_odds",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -802,17 +707,13 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/model/state:army",
|
||||
"//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/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:army_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -822,14 +723,12 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -839,14 +738,12 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -856,17 +753,15 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:min_vigor_for_send_supplies",
|
||||
"//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/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -876,18 +771,14 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:min_vigor_for_start_epidemic",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -897,17 +788,14 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:min_vigor_for_suppress_beasts",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//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/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -917,17 +805,14 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:maximum_faction_leaders",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:minimum_loyalty_for_swear_brotherhood",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero/backstory_version",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -937,16 +822,14 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:base_food_buy_price",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:base_food_sell_price",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -956,17 +839,13 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:min_vigor_for_train",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -976,16 +855,14 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
],
|
||||
exports = [
|
||||
":scala_available_commands_factory",
|
||||
],
|
||||
deps = [
|
||||
":scala_available_commands_factory",
|
||||
":available_commands_factory_for_type",
|
||||
":pkg",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_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/settings:min_vigor_for_travel",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.AvailableCommand as AvailableCommandProto
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.model.proto_converters.command.available.AvailableCommandConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
/** Wraps a NEW Scala factory - converts Scala output to Proto. */
|
||||
class ScalaFactoryAdapter(factory: ScalaAvailableCommandsFactory) extends UnifiedCommandFactory {
|
||||
override def availableCommand(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommandProto] =
|
||||
factory
|
||||
.availableCommand(scalaState, factionId, provinceId)
|
||||
.map(cmd => AvailableCommandConverter.toProto(cmd, protoState, factionId))
|
||||
}
|
||||
|
||||
/** Wraps a LEGACY proto factory - passes proto GameState to it. */
|
||||
class LegacyFactoryAdapter(factory: AvailableCommandsFactoryForType) extends UnifiedCommandFactory {
|
||||
override def availableCommand(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommandProto] =
|
||||
factory.availableCommand(protoState, factionId, provinceId)
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
/**
|
||||
* Trait for factories that produce Scala AvailableCommand from Scala GameState.
|
||||
*
|
||||
* New factories should implement this trait to avoid proto dependencies. The output is converted to proto in the
|
||||
* orchestrator via ScalaFactoryAdapter.
|
||||
*/
|
||||
trait ScalaAvailableCommandsFactory {
|
||||
def availableCommand(
|
||||
gameState: GameState,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommand]
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package net.eagle0.eagle.library.actions.availability
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command.AvailableCommand as AvailableCommandProto
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
/**
|
||||
* Unified trait for command factories used by the orchestrator.
|
||||
*
|
||||
* Takes both Scala and Proto GameState to support both new Scala factories (which use Scala state) and legacy Proto
|
||||
* factories (which use Proto state). Returns Proto AvailableCommand for backward compatibility.
|
||||
*/
|
||||
trait UnifiedCommandFactory {
|
||||
def availableCommand(
|
||||
scalaState: GameState,
|
||||
protoState: GameStateProto,
|
||||
factionId: FactionId,
|
||||
provinceId: ProvinceId
|
||||
): Option[AvailableCommandProto]
|
||||
}
|
||||
@@ -495,7 +495,6 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/client_text:client_text_store",
|
||||
"//src/main/scala/net/eagle0/eagle/client_text:text_generation_result",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_id_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
+4
-6
@@ -2,7 +2,7 @@ package net.eagle0.eagle.library.actions.llm_prompt_generators
|
||||
|
||||
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.client_text.{ClientTextStore, TextGenerationResult}
|
||||
import net.eagle0.eagle.common.battalion_type.BattalionTypeId as BattalionTypeIdProto
|
||||
import net.eagle0.eagle.common.battalion_type.BattalionTypeId
|
||||
import net.eagle0.eagle.common.unaffiliated_hero_quest.{
|
||||
AllianceQuest,
|
||||
AlmsAcrossRealmQuest,
|
||||
@@ -32,7 +32,6 @@ import net.eagle0.eagle.library.actions.llm_prompt_generators.HeroDescriptionGen
|
||||
descriptionWithoutFaction
|
||||
}
|
||||
import net.eagle0.eagle.library.EagleInternalException
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionTypeIdConverter
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
@@ -190,15 +189,14 @@ object QuestEndedGeneratorUtilities {
|
||||
|
||||
case UpgradeBattalionQuest(
|
||||
provinceId: FactionId,
|
||||
battalionTypeIdProto: BattalionTypeIdProto,
|
||||
battalionTypeId: BattalionTypeId,
|
||||
minimumArmament: Double,
|
||||
minimumTraining: Double,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
val battalionTypeId = BattalionTypeIdConverter.fromProto(battalionTypeIdProto)
|
||||
val battType =
|
||||
val battType =
|
||||
gameState.battalionTypes.find(_.typeId == battalionTypeId).get
|
||||
val targetProvince = gameState.provinces(provinceId)
|
||||
val targetProvince = gameState.provinces(provinceId)
|
||||
for {
|
||||
unaffiliatedHeroName <- unaffiliatedHeroNameResult
|
||||
} yield s"$unaffiliatedHeroName wanted to see a mighty ${battType.name}, with an armament of ${minimumArmament.ceil.toInt} and training of ${minimumTraining.ceil.toInt}, in ${targetProvince.name}."
|
||||
|
||||
@@ -157,7 +157,6 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/ai:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
@@ -193,7 +192,6 @@ scala_library(
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/ai:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
|
||||
-864
@@ -1,864 +0,0 @@
|
||||
package net.eagle0.eagle.model.proto_converters.command.available
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.api.available_command as proto
|
||||
import net.eagle0.eagle.api.command.util.attack_decision_type.{
|
||||
AdvanceDecision,
|
||||
AttackDecisionType as AttackDecisionTypeProto,
|
||||
DemandTributeDecision,
|
||||
SafePassageDecision,
|
||||
WithdrawDecision
|
||||
}
|
||||
import net.eagle0.eagle.api.command.util.captured_hero_option.CapturedHeroOption as CapturedHeroOptionProto
|
||||
import net.eagle0.eagle.api.command.util.control_weather_type.ControlWeatherType as ControlWeatherTypeProto
|
||||
import net.eagle0.eagle.api.command.util.diplomacy_option.{
|
||||
AllianceOption,
|
||||
BreakAllianceOption,
|
||||
DiplomacyOption as DiplomacyOptionProto,
|
||||
InvitationOption,
|
||||
RansomOfferOption,
|
||||
TruceOption
|
||||
}
|
||||
import net.eagle0.eagle.api.command.util.prisoner_management_type.{
|
||||
PrisonerManagementOption as PrisonerManagementOptionProto,
|
||||
PrisonerManagementOptionExecute,
|
||||
PrisonerManagementOptionExile,
|
||||
PrisonerManagementOptionMove,
|
||||
PrisonerManagementOptionRelease,
|
||||
PrisonerManagementOptionReturn
|
||||
}
|
||||
import net.eagle0.eagle.common.battalion_type.{
|
||||
BattalionType as BattalionTypeProto,
|
||||
BattalionTypeId as BattalionTypeIdProto
|
||||
}
|
||||
import net.eagle0.eagle.common.diplomacy_offer.{
|
||||
HostageOfferedInExchange as HostageOfferedInExchangeProto,
|
||||
PrisonerOfferedInExchange as PrisonerOfferedInExchangeProto,
|
||||
PrisonerToBeRansomed as PrisonerToBeRansomedProto,
|
||||
RansomOfferDetails as RansomOfferDetailsProto
|
||||
}
|
||||
import net.eagle0.eagle.common.diplomacy_offer.DiplomacyOffer as DiplomacyOfferProto
|
||||
import net.eagle0.eagle.common.improvement_type.ImprovementType as ImprovementTypeProto
|
||||
import net.eagle0.eagle.common.province_order_type.ProvinceOrderType as ProvinceOrderTypeProto
|
||||
import net.eagle0.eagle.common.tribute_amount.TributeAmount as TributeAmountProto
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.settings.{AllianceGoldCost, BreakAllianceGoldCost, InviteGoldCost, TruceGoldCost}
|
||||
import net.eagle0.eagle.library.util.view_filters.{HeroViewFilter, LegacyBattalionViewFilter}
|
||||
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.common.*
|
||||
|
||||
/**
|
||||
* Converts Scala AvailableCommand to proto AvailableCommand.
|
||||
*
|
||||
* Requires GameState and viewing FactionId as context to construct full view objects (like HeroView) from IDs stored in
|
||||
* the Scala types.
|
||||
*/
|
||||
object AvailableCommandConverter {
|
||||
|
||||
def toProto(
|
||||
command: AvailableCommand,
|
||||
gameState: GameState,
|
||||
viewingFactionId: FactionId
|
||||
): proto.AvailableCommand = command match {
|
||||
case AlmsAvailable(
|
||||
foodAvailable: Int,
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
) =>
|
||||
proto.AlmsAvailableCommand(
|
||||
foodAvailable = foodAvailable,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableHeroIds = availableHeroIds
|
||||
)
|
||||
|
||||
case ApprehendOutlawAvailable(
|
||||
outlaws: Vector[ResidentOutlaw],
|
||||
availableBattalionIds: Vector[BattalionId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
) =>
|
||||
proto.ApprehendOutlawAvailableCommand(
|
||||
outlaws = outlaws.map(o => residentOutlawToProto(o, gameState, viewingFactionId)),
|
||||
availableBattalionIds = availableBattalionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableHeroIds = availableHeroIds
|
||||
)
|
||||
|
||||
case ArmTroopsAvailable(
|
||||
armamentCosts: Vector[ArmamentCost],
|
||||
availableBattalions: Vector[BattalionId],
|
||||
maxArmament: Int,
|
||||
actingProvinceId: ProvinceId,
|
||||
availableBattalionTypeIds: Vector[Int]
|
||||
) =>
|
||||
proto.ArmTroopsAvailableCommand(
|
||||
armamentCosts = armamentCosts.map(armamentCostToProto),
|
||||
availableBattalions = availableBattalions,
|
||||
maxArmament = maxArmament,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableBattalionTypeIds = availableBattalionTypeIds.map(intToBattalionTypeId)
|
||||
)
|
||||
|
||||
case AttackDecisionAvailable(
|
||||
armies: Vector[ArmyStats],
|
||||
actingUnits: Vector[ExpandedCombatUnit],
|
||||
availableDecisions: Vector[AttackDecisionType],
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.AttackDecisionAvailableCommand(
|
||||
armies = armies.map(armyStatsToProto),
|
||||
actingUnits = actingUnits.map(u => expandedCombatUnitToProto(u, gameState)),
|
||||
availableDecisions = availableDecisions.map(attackDecisionTypeToProto),
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case ControlWeatherAvailable(
|
||||
options: Vector[TargetProvinceOptions],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
) =>
|
||||
proto.ControlWeatherAvailableCommand(
|
||||
options = options.map(targetProvinceOptionsToProto),
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableHeroIds = availableHeroIds
|
||||
)
|
||||
|
||||
case DeclineQuestAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
declinableHeroes: Vector[ExpandedUnaffiliatedHero]
|
||||
) =>
|
||||
proto.DeclineQuestAvailableCommand(
|
||||
actingProvinceId = actingProvinceId,
|
||||
declinableHeroes = declinableHeroes.map(h => expandedUnaffiliatedHeroToProto(h, gameState, actingProvinceId))
|
||||
)
|
||||
|
||||
case DefendAvailable(
|
||||
suitableBattalionsForHeroes: Map[HeroId, SuitableBattalions],
|
||||
availableFleeProvinceIds: Vector[ProvinceId],
|
||||
availableBattalions: Vector[BattalionWithFoodCost],
|
||||
availableHeroIds: Vector[HeroId],
|
||||
recommendedUnits: Vector[RecommendedCombatUnit],
|
||||
actingProvinceId: ProvinceId,
|
||||
hostileFactionIds: Vector[FactionId],
|
||||
hostileHeroCount: Int,
|
||||
hostileTroopCount: Int
|
||||
) =>
|
||||
proto.DefendAvailableCommand(
|
||||
suitableBattalionsForHeroes = suitableBattalionsForHeroesToProto(suitableBattalionsForHeroes),
|
||||
availableFleeProvinceIds = availableFleeProvinceIds,
|
||||
availableBattalions = availableBattalions.map(battalionWithFoodCostToProto),
|
||||
availableHeroIds = availableHeroIds,
|
||||
recommendedUnits = recommendedUnits.map(recommendedCombatUnitToProto),
|
||||
actingProvinceId = actingProvinceId,
|
||||
hostileFactionIds = hostileFactionIds,
|
||||
hostileHeroCount = hostileHeroCount,
|
||||
hostileTroopCount = hostileTroopCount
|
||||
)
|
||||
|
||||
case DiplomacyAvailable(
|
||||
options: Vector[DiplomacyOption],
|
||||
availableHeroIds: Vector[HeroId],
|
||||
actingProvinceId: ProvinceId,
|
||||
recommendedHeroId: HeroId
|
||||
) =>
|
||||
proto.DiplomacyAvailableCommand(
|
||||
options = options.flatMap(diplomacyOptionToProtos),
|
||||
availableHeroIds = availableHeroIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
recommendedHeroId = recommendedHeroId
|
||||
)
|
||||
|
||||
case DivineAvailable(
|
||||
costPerHero: Int,
|
||||
divinableHeroes: Vector[ExpandedUnaffiliatedHero],
|
||||
unavailableHeroes: Vector[ExpandedUnaffiliatedHero],
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.DivineAvailableCommand(
|
||||
costPerHero = costPerHero,
|
||||
divinableHeroes = divinableHeroes.map(h => expandedUnaffiliatedHeroToProto(h, gameState, actingProvinceId)),
|
||||
unavailableHeroes = unavailableHeroes.map(h => expandedUnaffiliatedHeroToProto(h, gameState, actingProvinceId)),
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case ExileVassalAvailable(actingProvinceId: ProvinceId, exilableHeroIds: Vector[HeroId]) =>
|
||||
proto.ExileVassalAvailableCommand(
|
||||
actingProvinceId = actingProvinceId,
|
||||
exilableHeroIds = exilableHeroIds
|
||||
)
|
||||
|
||||
case FeastAvailable(goldCost: Int, actingProvinceId: ProvinceId) =>
|
||||
proto.FeastAvailableCommand(
|
||||
goldCost = goldCost,
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case FreeForAllDecisionAvailable(
|
||||
provinceId: ProvinceId,
|
||||
armies: Vector[ArmyStats],
|
||||
actingUnits: Vector[ExpandedCombatUnit],
|
||||
availableDecisions: Vector[AttackDecisionType]
|
||||
) =>
|
||||
proto.FreeForAllDecisionAvailableCommand(
|
||||
provinceId = provinceId,
|
||||
armies = armies.map(armyStatsToProto),
|
||||
actingUnits = actingUnits.map(u => expandedCombatUnitToProto(u, gameState)),
|
||||
availableDecisions = availableDecisions.map(attackDecisionTypeToProto)
|
||||
)
|
||||
|
||||
case HandleCapturedHeroAvailable(
|
||||
availableHeroes: Vector[ExpandedCapturedHero],
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.HandleCapturedHeroAvailableCommand(
|
||||
availableHeroes = availableHeroes.map(h => expandedCapturedHeroToProto(h, gameState, viewingFactionId)),
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case HandleRiotCrackDownAvailable(
|
||||
availableHeroIds: Vector[HeroId],
|
||||
battalionIdsAvailable: Vector[BattalionId],
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.HandleRiotCrackDownAvailableCommand(
|
||||
availableHeroIds = availableHeroIds,
|
||||
battalionIdsAvailable = battalionIdsAvailable,
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case HandleRiotDoNothingAvailable(actingProvinceId: ProvinceId) =>
|
||||
proto.HandleRiotDoNothingAvailableCommand(
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case HandleRiotGiveAvailable(
|
||||
foodAvailable: Int,
|
||||
goldAvailable: Int,
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.HandleRiotGiveAvailableCommand(
|
||||
foodAvailable = foodAvailable,
|
||||
goldAvailable = goldAvailable,
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case HeroGiftAvailable(eligibleGifts: Vector[EligibleGift], actingProvinceId: ProvinceId) =>
|
||||
proto.HeroGiftAvailableCommand(
|
||||
eligibleGifts = eligibleGifts.map(eligibleGiftToProto),
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case ImproveAvailable(
|
||||
availableTypes: Vector[ImprovementType],
|
||||
availableHeroIds: Vector[HeroId],
|
||||
recommendedHeroId: HeroId,
|
||||
actingProvinceId: ProvinceId,
|
||||
lockedType: Option[ImprovementType]
|
||||
) =>
|
||||
proto.ImproveAvailableCommand(
|
||||
availableTypes = availableTypes.map(improvementTypeToProto),
|
||||
availableHeroIds = availableHeroIds,
|
||||
recommendedHeroId = recommendedHeroId,
|
||||
actingProvinceId = actingProvinceId,
|
||||
lockedType = lockedType.map(improvementTypeToProto).getOrElse(ImprovementTypeProto.NONE)
|
||||
)
|
||||
|
||||
case IssueOrdersAvailable(
|
||||
currentOrders: Vector[ProvinceOrders],
|
||||
availableOrders: Vector[ProvinceOrderType],
|
||||
availableFocusProvinces: Vector[ProvinceId],
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.IssueOrdersAvailableCommand(
|
||||
currentOrders = currentOrders.map(provinceOrdersToProto),
|
||||
availableOrders = availableOrders.map(provinceOrderTypeToProto),
|
||||
availableFocusProvinces = availableFocusProvinces,
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case ManagePrisonersAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
prisoners: Vector[PrisonerToManage]
|
||||
) =>
|
||||
proto.ManagePrisonersAvailableCommand(
|
||||
actingProvinceId = actingProvinceId,
|
||||
prisoners = prisoners.map(p => prisonerToManageToProto(p, gameState, actingProvinceId))
|
||||
)
|
||||
|
||||
case MarchAvailable(
|
||||
oneProvinceCommands: Vector[MarchCommandFromOneProvince],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableTypes: Vector[BattalionType]
|
||||
) =>
|
||||
proto.MarchAvailableCommand(
|
||||
oneProvinceCommands = oneProvinceCommands.map(cmd => marchCommandFromOneProvinceToProto(cmd, gameState)),
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableTypes = availableTypes.map(battalionTypeToProto)
|
||||
)
|
||||
|
||||
case OrganizeTroopsAvailable(
|
||||
troopCosts: Vector[TroopCost],
|
||||
existingBattalions: Vector[BattalionId],
|
||||
maxBattalions: Int,
|
||||
actingProvinceId: ProvinceId,
|
||||
availableBattalionTypes: Vector[BattalionTypeStatus]
|
||||
) =>
|
||||
proto.OrganizeTroopsAvailableCommand(
|
||||
troopCosts = troopCosts.map(troopCostToProto),
|
||||
existingBattalions = existingBattalions,
|
||||
maxBattalions = maxBattalions,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableBattalionTypes = availableBattalionTypes.map(battalionTypeStatusToProto)
|
||||
)
|
||||
|
||||
case PleaseRecruitMeAvailable(
|
||||
availableProvinces: Vector[OneProvincePleaseRecruitMe],
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.PleaseRecruitMeAvailableCommand(
|
||||
availableProvinces = availableProvinces.map(p => oneProvincePleaseRecruitMeToProto(p, gameState)),
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case ReconAvailable(
|
||||
availableTargetProvinces: Vector[ProvinceId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
) =>
|
||||
proto.ReconAvailableCommand(
|
||||
availableTargetProvinces = availableTargetProvinces,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableHeroIds = availableHeroIds
|
||||
)
|
||||
|
||||
case RecruitHeroesAvailable(
|
||||
availableHeroes: Vector[ExpandedUnaffiliatedHero],
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.RecruitHeroesAvailableCommand(
|
||||
availableHeroes = availableHeroes.map(h => expandedUnaffiliatedHeroToProto(h, gameState, actingProvinceId)),
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case ResolveAllianceOfferAvailable(offers: Vector[DiplomacyOfferInfo]) =>
|
||||
proto.ResolveAllianceOfferAvailableCommand(
|
||||
offers = offers.map(o => diplomacyOfferInfoToProto(o, gameState, viewingFactionId))
|
||||
)
|
||||
|
||||
case ResolveBreakAllianceAvailable(offers: Vector[DiplomacyOfferInfo]) =>
|
||||
proto.ResolveBreakAllianceAvailableCommand(
|
||||
offers = offers.map(o => diplomacyOfferInfoToProto(o, gameState, viewingFactionId))
|
||||
)
|
||||
|
||||
case ResolveInvitationAvailable(invitations: Vector[DiplomacyOfferInfo]) =>
|
||||
proto.ResolveInvitationAvailableCommand(
|
||||
invitations = invitations.map(o => diplomacyOfferInfoToProto(o, gameState, viewingFactionId))
|
||||
)
|
||||
|
||||
case ResolveRansomOfferAvailable(offers: Vector[DiplomacyOfferInfo]) =>
|
||||
proto.ResolveRansomOfferAvailableCommand(
|
||||
offers = offers.map(o => diplomacyOfferInfoToProto(o, gameState, viewingFactionId))
|
||||
)
|
||||
|
||||
case ResolveTruceOfferAvailable(offers: Vector[DiplomacyOfferInfo]) =>
|
||||
proto.ResolveTruceOfferAvailableCommand(
|
||||
offers = offers.map(o => diplomacyOfferInfoToProto(o, gameState, viewingFactionId))
|
||||
)
|
||||
|
||||
case ResolveTributeAvailable(
|
||||
demands: Vector[TributeAndFaction],
|
||||
availableGold: Int,
|
||||
availableFood: Int,
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.ResolveTributeAvailableCommand(
|
||||
demands = demands.map(tributeAndFactionToProto),
|
||||
availableGold = availableGold,
|
||||
availableFood = availableFood,
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case RestAvailable(actingProvinceId: ProvinceId) =>
|
||||
proto.RestAvailableCommand(
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case ReturnAvailable(actingProvinceId: ProvinceId) =>
|
||||
proto.ReturnAvailableCommand(
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case SendSuppliesAvailable(
|
||||
goldAvailable: Int,
|
||||
foodAvailable: Int,
|
||||
availableDestinationProvinceIds: Vector[ProvinceId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
) =>
|
||||
proto.SendSuppliesAvailableCommand(
|
||||
goldAvailable = goldAvailable,
|
||||
foodAvailable = foodAvailable,
|
||||
availableDestinationProvinceIds = availableDestinationProvinceIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableHeroIds = availableHeroIds
|
||||
)
|
||||
|
||||
case StartEpidemicAvailable(
|
||||
options: Vector[StartEpidemicOptions],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
) =>
|
||||
proto.StartEpidemicAvailableCommand(
|
||||
options = options.map(startEpidemicOptionsToProto),
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableHeroIds = availableHeroIds
|
||||
)
|
||||
|
||||
case SuppressBeastsAvailable(
|
||||
availableHeroIds: Vector[HeroId],
|
||||
availableBattalionIds: Vector[BattalionId],
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.SuppressBeastsAvailableCommand(
|
||||
availableHeroIds = availableHeroIds,
|
||||
availableBattalionIds = availableBattalionIds,
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case SwearBrotherhoodAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroes: Vector[HeroAndBackstory]
|
||||
) =>
|
||||
proto.SwearBrotherhoodAvailableCommand(
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableHeroes = availableHeroes.map(heroAndBackstoryToProto)
|
||||
)
|
||||
|
||||
case TradeAvailable(
|
||||
foodAvailable: Int,
|
||||
goldAvailable: Int,
|
||||
foodBuyPrice: Double,
|
||||
foodSellPrice: Double,
|
||||
actingProvinceId: ProvinceId
|
||||
) =>
|
||||
proto.TradeAvailableCommand(
|
||||
foodAvailable = foodAvailable,
|
||||
goldAvailable = goldAvailable,
|
||||
foodBuyPrice = foodBuyPrice,
|
||||
foodSellPrice = foodSellPrice,
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
|
||||
case TrainAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId],
|
||||
recommendedHeroId: HeroId
|
||||
) =>
|
||||
proto.TrainAvailableCommand(
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableHeroIds = availableHeroIds,
|
||||
recommendedHeroId = recommendedHeroId
|
||||
)
|
||||
|
||||
case TravelAvailable(actingProvinceId: ProvinceId) =>
|
||||
proto.TravelAvailableCommand(
|
||||
actingProvinceId = actingProvinceId
|
||||
)
|
||||
}
|
||||
|
||||
// Helper functions for nested types
|
||||
|
||||
private def residentOutlawToProto(
|
||||
outlaw: ResidentOutlaw,
|
||||
gameState: GameState,
|
||||
viewingFactionId: FactionId
|
||||
): proto.ResidentOutlaw =
|
||||
proto.ResidentOutlaw(
|
||||
hero = Some(
|
||||
HeroViewFilter.filteredHeroView(
|
||||
hero = gameState.heroes(outlaw.heroId),
|
||||
gs = gameState,
|
||||
factionId = Some(viewingFactionId)
|
||||
)
|
||||
),
|
||||
lastFactionId = outlaw.lastFactionId
|
||||
)
|
||||
|
||||
private def armamentCostToProto(cost: ArmamentCost): proto.ArmamentCost =
|
||||
proto.ArmamentCost(
|
||||
`type` = intToBattalionTypeId(cost.battalionTypeId),
|
||||
cost = cost.cost
|
||||
)
|
||||
|
||||
private def intToBattalionTypeId(id: Int): BattalionTypeIdProto =
|
||||
BattalionTypeIdProto.fromValue(id)
|
||||
|
||||
private def armyStatsToProto(stats: ArmyStats): net.eagle0.eagle.api.command.util.army_stats.ArmyStats =
|
||||
net.eagle0.eagle.api.command.util.army_stats.ArmyStats(
|
||||
factionId = stats.factionId,
|
||||
heroCount = stats.heroCount,
|
||||
troopCount = stats.troopCount
|
||||
)
|
||||
|
||||
private def expandedCombatUnitToProto(
|
||||
unit: ExpandedCombatUnit,
|
||||
gameState: GameState
|
||||
): net.eagle0.eagle.api.command.util.expanded_combat_unit.ExpandedCombatUnit =
|
||||
net.eagle0.eagle.api.command.util.expanded_combat_unit.ExpandedCombatUnit(
|
||||
hero = Some(
|
||||
HeroViewFilter.filteredHeroView(
|
||||
hero = gameState.heroes(unit.heroId),
|
||||
gs = gameState,
|
||||
factionId = None
|
||||
)
|
||||
),
|
||||
battalion = unit.battalionId
|
||||
.map(gameState.battalions)
|
||||
.map(LegacyBattalionViewFilter.filteredBattalionViewBelongingToPlayer)
|
||||
)
|
||||
|
||||
private def targetProvinceOptionsToProto(
|
||||
options: TargetProvinceOptions
|
||||
): proto.ControlWeatherAvailableCommand.TargetProvinceOptions =
|
||||
proto.ControlWeatherAvailableCommand.TargetProvinceOptions(
|
||||
provinceId = options.provinceId,
|
||||
controlWeatherTypes = options.controlWeatherTypes.map(controlWeatherTypeToProto)
|
||||
)
|
||||
|
||||
private def expandedUnaffiliatedHeroToProto(
|
||||
hero: ExpandedUnaffiliatedHero,
|
||||
gameState: GameState,
|
||||
provinceId: ProvinceId
|
||||
): net.eagle0.eagle.api.command.util.expanded_unaffiliated_hero.ExpandedUnaffiliatedHero = {
|
||||
// Look up the full unaffiliated hero data from the province
|
||||
val unaffiliatedHero = gameState
|
||||
.provinces(provinceId)
|
||||
.unaffiliatedHeroes
|
||||
.find(_.heroId == hero.heroId)
|
||||
.getOrElse(
|
||||
throw new IllegalStateException(s"UnaffiliatedHero ${hero.heroId} not found in province $provinceId")
|
||||
)
|
||||
|
||||
net.eagle0.eagle.api.command.util.expanded_unaffiliated_hero.ExpandedUnaffiliatedHero(
|
||||
hero = Some(
|
||||
HeroViewFilter.filteredHeroView(
|
||||
hero = gameState.heroes(hero.heroId),
|
||||
gs = gameState,
|
||||
factionId = None
|
||||
)
|
||||
),
|
||||
`type` = unaffiliatedHero.`type`,
|
||||
quest = unaffiliatedHero.recruitmentInfo.flatMap(_.quest),
|
||||
pleaseRecruitMeTextId = unaffiliatedHero.pleaseRecruitMeTextId,
|
||||
divinationTextId = unaffiliatedHero.divinedTextId
|
||||
)
|
||||
}
|
||||
|
||||
private def suitableBattalionsForHeroesToProto(
|
||||
sbMap: Map[HeroId, SuitableBattalions]
|
||||
): Map[HeroId, net.eagle0.eagle.api.command.util.appropriate_battalions.SuitableBattalions] =
|
||||
// The Scala SuitableBattalions only has battalion IDs, but proto expects suitability levels.
|
||||
// Convert with default OPTIMAL suitability since we don't have suitability info.
|
||||
sbMap.map {
|
||||
case (heroId, sb) =>
|
||||
heroId -> net.eagle0.eagle.api.command.util.appropriate_battalions.SuitableBattalions(
|
||||
battalionIdsWithSuitability = sb.battalionIds.map { battalionId =>
|
||||
net.eagle0.eagle.api.command.util.appropriate_battalions.SuitableBattalions.BattalionIdWithSuitability(
|
||||
battalionId = battalionId,
|
||||
level =
|
||||
net.eagle0.eagle.api.command.util.appropriate_battalions.SuitableBattalions.SuitabilityLevel.OPTIMAL
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private def battalionWithFoodCostToProto(
|
||||
b: BattalionWithFoodCost
|
||||
): net.eagle0.eagle.api.command.util.battalion_with_food_cost.BattalionWithFoodCost =
|
||||
net.eagle0.eagle.api.command.util.battalion_with_food_cost.BattalionWithFoodCost(
|
||||
battalionId = b.battalionId,
|
||||
monthlyFood = b.foodCost.toDouble // Scala has Int foodCost, proto has Double monthlyFood
|
||||
)
|
||||
|
||||
private def recommendedCombatUnitToProto(
|
||||
unit: RecommendedCombatUnit
|
||||
): net.eagle0.eagle.common.combat_unit.CombatUnit =
|
||||
net.eagle0.eagle.common.combat_unit.CombatUnit(
|
||||
heroId = unit.heroId,
|
||||
battalionId = unit.battalionId
|
||||
)
|
||||
|
||||
// Scala DiplomacyOption groups multiple option types per target faction.
|
||||
// Proto DiplomacyOption is a oneof with individual option types.
|
||||
// So one Scala option can produce multiple proto options.
|
||||
private def diplomacyOptionToProtos(option: DiplomacyOption): Vector[DiplomacyOptionProto] =
|
||||
option.optionTypes.map { optionType =>
|
||||
optionType match {
|
||||
case DiplomacyOptionType.Alliance =>
|
||||
AllianceOption(
|
||||
targetFactionId = option.targetFactionId,
|
||||
goldCost = AllianceGoldCost.intValue
|
||||
)
|
||||
case DiplomacyOptionType.Truce =>
|
||||
TruceOption(
|
||||
targetFactionId = option.targetFactionId,
|
||||
goldCost = TruceGoldCost.intValue
|
||||
)
|
||||
case DiplomacyOptionType.Invitation =>
|
||||
InvitationOption(
|
||||
targetFactionId = option.targetFactionId,
|
||||
goldCost = InviteGoldCost.intValue
|
||||
)
|
||||
case DiplomacyOptionType.BreakAlliance =>
|
||||
BreakAllianceOption(
|
||||
targetFactionId = option.targetFactionId,
|
||||
goldCost = BreakAllianceGoldCost.intValue
|
||||
)
|
||||
case DiplomacyOptionType.Ransom(details: RansomOfferDetails) =>
|
||||
RansomOfferOption(
|
||||
targetFactionId = option.targetFactionId,
|
||||
ransomOffer = Some(ransomOfferDetailsToProto(details))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def ransomOfferDetailsToProto(details: RansomOfferDetails): RansomOfferDetailsProto =
|
||||
RansomOfferDetailsProto(
|
||||
prisonerToBeRansomed = Some(prisonerToBeRansomedToProto(details.prisonerToBeRansomed)),
|
||||
prisonersOffered = details.prisonersOffered.map(prisonerOfferedInExchangeToProto),
|
||||
hostagesOffered = details.hostagesOffered.map(hostageOfferedInExchangeToProto),
|
||||
goldOffered = details.goldOffered
|
||||
)
|
||||
|
||||
private def prisonerToBeRansomedToProto(prisoner: PrisonerToBeRansomed): PrisonerToBeRansomedProto =
|
||||
PrisonerToBeRansomedProto(
|
||||
prisonerHeroId = prisoner.prisonerHeroId,
|
||||
provinceIdForPrisoner = prisoner.provinceIdForPrisoner
|
||||
)
|
||||
|
||||
private def prisonerOfferedInExchangeToProto(
|
||||
prisoner: PrisonerOfferedInExchange
|
||||
): PrisonerOfferedInExchangeProto =
|
||||
PrisonerOfferedInExchangeProto(
|
||||
heroId = prisoner.heroId,
|
||||
factionIdForPrisoner = prisoner.factionIdForPrisoner.getOrElse(-1),
|
||||
provinceIdWithHero = prisoner.provinceIdWithHero
|
||||
)
|
||||
|
||||
private def hostageOfferedInExchangeToProto(hostage: HostageOfferedInExchange): HostageOfferedInExchangeProto =
|
||||
HostageOfferedInExchangeProto(
|
||||
heroId = hostage.heroId,
|
||||
provinceIdWithHero = hostage.provinceIdWithHero
|
||||
)
|
||||
|
||||
private def expandedCapturedHeroToProto(
|
||||
hero: ExpandedCapturedHero,
|
||||
gameState: GameState,
|
||||
viewingFactionId: FactionId
|
||||
): proto.ExpandedCapturedHero =
|
||||
proto.ExpandedCapturedHero(
|
||||
hero = Some(
|
||||
HeroViewFilter.filteredHeroView(
|
||||
hero = gameState.heroes(hero.heroId),
|
||||
gs = gameState,
|
||||
factionId = Some(viewingFactionId)
|
||||
)
|
||||
),
|
||||
options = hero.options.map(capturedHeroOptionToProto),
|
||||
messageId = hero.messageId,
|
||||
previousFactionId = hero.previousFactionId
|
||||
)
|
||||
|
||||
private def eligibleGiftToProto(gift: EligibleGift): proto.EligibleGift =
|
||||
proto.EligibleGift(
|
||||
recipientHeroId = gift.recipientHeroId,
|
||||
goldAvailable = gift.goldAvailable,
|
||||
recipientProvinceId = gift.recipientProvinceId
|
||||
)
|
||||
|
||||
private def provinceOrdersToProto(
|
||||
orders: ProvinceOrders
|
||||
): net.eagle0.eagle.api.command.util.province_orders.ProvinceOrders =
|
||||
net.eagle0.eagle.api.command.util.province_orders.ProvinceOrders(
|
||||
provinceId = orders.provinceId,
|
||||
orders = provinceOrderTypeToProto(orders.orderType)
|
||||
)
|
||||
|
||||
private def prisonerToManageToProto(
|
||||
prisoner: PrisonerToManage,
|
||||
gameState: GameState,
|
||||
provinceId: ProvinceId
|
||||
): proto.PrisonerToManage =
|
||||
proto.PrisonerToManage(
|
||||
prisoner = Some(expandedUnaffiliatedHeroToProto(prisoner.prisoner, gameState, provinceId)),
|
||||
availableOptions = prisoner.availableOptions.map(prisonerManagementOptionToProto)
|
||||
)
|
||||
|
||||
private def marchCommandFromOneProvinceToProto(
|
||||
cmd: MarchCommandFromOneProvince,
|
||||
gameState: GameState
|
||||
): proto.MarchCommandFromOneProvince =
|
||||
proto.MarchCommandFromOneProvince(
|
||||
suitableBattalionsForHeroes = suitableBattalionsForHeroesToProto(cmd.suitableBattalionsForHeroes),
|
||||
originProvinceId = cmd.originProvinceId,
|
||||
goldCost = cmd.goldCost,
|
||||
goldAvailable = cmd.goldAvailable,
|
||||
foodAvailable = cmd.foodAvailable,
|
||||
availableDestinationProvinces = cmd.availableDestinationProvinces.map(availableDestinationProvinceToProto),
|
||||
availableHeroIds = cmd.availableHeroIds,
|
||||
availableBattalions = cmd.availableBattalions.map(battalionWithFoodCostToProto),
|
||||
recommendedBattleConfig = cmd.recommendedBattleConfig.map(recommendedBattleConfigToProto)
|
||||
)
|
||||
|
||||
private def availableDestinationProvinceToProto(
|
||||
dest: AvailableDestinationProvince
|
||||
): proto.AvailableDestinationProvince =
|
||||
proto.AvailableDestinationProvince(
|
||||
provinceId = dest.provinceId,
|
||||
requiresRiverCrossing = dest.requiresRiverCrossing
|
||||
)
|
||||
|
||||
private def recommendedBattleConfigToProto(config: RecommendedBattleConfig): proto.RecommendedBattleConfig =
|
||||
proto.RecommendedBattleConfig(
|
||||
units = config.units.map(recommendedCombatUnitToProto),
|
||||
food = config.food,
|
||||
gold = config.gold
|
||||
)
|
||||
|
||||
private def battalionTypeToProto(bt: BattalionType): BattalionTypeProto =
|
||||
BattalionTypeProto(
|
||||
typeId = intToBattalionTypeId(bt.typeId),
|
||||
name = bt.name
|
||||
)
|
||||
|
||||
private def troopCostToProto(cost: TroopCost): proto.TroopCost =
|
||||
proto.TroopCost(
|
||||
`type` = intToBattalionTypeId(cost.battalionTypeId),
|
||||
costPerTroop = cost.costPerTroop
|
||||
)
|
||||
|
||||
private def battalionTypeStatusToProto(
|
||||
status: BattalionTypeStatus
|
||||
): proto.OrganizeTroopsAvailableCommand.BattalionTypeStatus =
|
||||
proto.OrganizeTroopsAvailableCommand.BattalionTypeStatus(
|
||||
typeId = intToBattalionTypeId(status.typeId),
|
||||
meetsRequirements = status.meetsRequirements,
|
||||
minimumAgriculture = status.minimumAgriculture,
|
||||
minimumEconomy = status.minimumEconomy
|
||||
)
|
||||
|
||||
private def oneProvincePleaseRecruitMeToProto(
|
||||
prov: OneProvincePleaseRecruitMe,
|
||||
gameState: GameState
|
||||
): proto.OneProvincePleaseRecruitMe =
|
||||
proto.OneProvincePleaseRecruitMe(
|
||||
provinceId = prov.provinceId,
|
||||
availableHeroes = prov.availableHeroes.map(h => expandedUnaffiliatedHeroToProto(h, gameState, prov.provinceId))
|
||||
)
|
||||
|
||||
// DiplomacyOfferInfo is too simplified - look up the full offer from GameState
|
||||
private def diplomacyOfferInfoToProto(
|
||||
info: DiplomacyOfferInfo,
|
||||
gameState: GameState,
|
||||
viewingFactionId: FactionId
|
||||
): DiplomacyOfferProto =
|
||||
// The offers exist in the faction's incoming diplomacy offers
|
||||
gameState
|
||||
.factions(viewingFactionId)
|
||||
.incomingDiplomacyOffers
|
||||
.find(offer =>
|
||||
offer.originatingFactionId == info.offeringFactionId &&
|
||||
offer.targetFactionId == info.targetFactionId
|
||||
)
|
||||
.getOrElse(
|
||||
throw new IllegalStateException(
|
||||
s"DiplomacyOffer from ${info.offeringFactionId} to ${info.targetFactionId} not found"
|
||||
)
|
||||
)
|
||||
|
||||
private def tributeAndFactionToProto(taf: TributeAndFaction): proto.TributeAndFaction =
|
||||
proto.TributeAndFaction(
|
||||
demandingFactionId = taf.demandingFactionId,
|
||||
tributeDemanded = Some(
|
||||
TributeAmountProto(
|
||||
gold = taf.goldDemanded,
|
||||
food = taf.foodDemanded
|
||||
)
|
||||
),
|
||||
heroCount = taf.heroCount,
|
||||
troopCount = taf.troopCount
|
||||
)
|
||||
|
||||
private def startEpidemicOptionsToProto(
|
||||
options: StartEpidemicOptions
|
||||
): proto.StartEpidemicAvailableCommand.StartEpidemicOptions =
|
||||
proto.StartEpidemicAvailableCommand.StartEpidemicOptions(
|
||||
targetProvinceId = options.targetProvinceId
|
||||
)
|
||||
|
||||
private def heroAndBackstoryToProto(hab: HeroAndBackstory): proto.SwearBrotherhoodAvailableCommand.HeroAndBackstory =
|
||||
proto.SwearBrotherhoodAvailableCommand.HeroAndBackstory(
|
||||
heroId = hab.heroId,
|
||||
backstoryTextId = hab.backstoryTextId
|
||||
)
|
||||
|
||||
// Enum converters
|
||||
|
||||
// AttackDecisionType proto is a oneof - each type is a case class
|
||||
private def attackDecisionTypeToProto(t: AttackDecisionType): AttackDecisionTypeProto =
|
||||
t match {
|
||||
case AttackDecisionType.Advance => AdvanceDecision()
|
||||
case AttackDecisionType.Withdraw => WithdrawDecision()
|
||||
case AttackDecisionType.DemandTribute(gold: Int, food: Int) =>
|
||||
DemandTributeDecision(
|
||||
tribute = Some(TributeAmountProto(gold = gold, food = food))
|
||||
)
|
||||
case AttackDecisionType.SafePassage(destinationProvinceId: ProvinceId) =>
|
||||
SafePassageDecision(toProvinceId = destinationProvinceId)
|
||||
}
|
||||
|
||||
private def controlWeatherTypeToProto(t: ControlWeatherType): ControlWeatherTypeProto =
|
||||
t match {
|
||||
case ControlWeatherType.StartBlizzard => ControlWeatherTypeProto.CONTROL_WEATHER_START_BLIZZARD
|
||||
case ControlWeatherType.EndBlizzard => ControlWeatherTypeProto.CONTROL_WEATHER_END_BLIZZARD
|
||||
case ControlWeatherType.StartDrought => ControlWeatherTypeProto.CONTROL_WEATHER_START_DROUGHT
|
||||
case ControlWeatherType.EndDrought => ControlWeatherTypeProto.CONTROL_WEATHER_END_DROUGHT
|
||||
}
|
||||
|
||||
private def capturedHeroOptionToProto(opt: CapturedHeroOption): CapturedHeroOptionProto =
|
||||
opt match {
|
||||
case CapturedHeroOption.Imprison => CapturedHeroOptionProto.IMPRISON_CAPTURED_HERO_OPTION
|
||||
case CapturedHeroOption.Execute => CapturedHeroOptionProto.EXECUTE_CAPTURED_HERO_OPTION
|
||||
case CapturedHeroOption.Exile => CapturedHeroOptionProto.EXILE_CAPTURED_HERO_OPTION
|
||||
case CapturedHeroOption.Return => CapturedHeroOptionProto.RETURN_CAPTURED_HERO_OPTION
|
||||
case CapturedHeroOption.Recruit => CapturedHeroOptionProto.RECRUIT_CAPTURED_HERO_OPTION
|
||||
}
|
||||
|
||||
private def improvementTypeToProto(t: ImprovementType): ImprovementTypeProto =
|
||||
t match {
|
||||
case ImprovementType.Agriculture => ImprovementTypeProto.AGRICULTURE
|
||||
case ImprovementType.Economy => ImprovementTypeProto.ECONOMY
|
||||
case ImprovementType.Infrastructure => ImprovementTypeProto.INFRASTRUCTURE
|
||||
case ImprovementType.Devastation => ImprovementTypeProto.DEVASTATION
|
||||
}
|
||||
|
||||
// ScalaPB generates oneofs as sealed traits where each option is a case class
|
||||
// that extends the trait. So PrisonerManagementOptionRelease() is a PrisonerManagementOption.
|
||||
private def prisonerManagementOptionToProto(opt: PrisonerManagementOption): PrisonerManagementOptionProto =
|
||||
opt match {
|
||||
case PrisonerManagementOption.Release => PrisonerManagementOptionRelease()
|
||||
case PrisonerManagementOption.Execute => PrisonerManagementOptionExecute()
|
||||
case PrisonerManagementOption.Exile => PrisonerManagementOptionExile()
|
||||
case PrisonerManagementOption.Move(toProvinceId) => PrisonerManagementOptionMove(toProvinceId = toProvinceId)
|
||||
case PrisonerManagementOption.Return(toFactionId) => PrisonerManagementOptionReturn(toFactionId = toFactionId)
|
||||
}
|
||||
|
||||
private def provinceOrderTypeToProto(t: ProvinceOrderType): ProvinceOrderTypeProto =
|
||||
t match {
|
||||
case ProvinceOrderType.Develop => ProvinceOrderTypeProto.DEVELOP
|
||||
case ProvinceOrderType.Mobilize => ProvinceOrderTypeProto.MOBILIZE
|
||||
case ProvinceOrderType.Expand => ProvinceOrderTypeProto.EXPAND
|
||||
case ProvinceOrderType.Entrust => ProvinceOrderTypeProto.ENTRUST
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
load("@rules_scala//scala:scala.bzl", "scala_library")
|
||||
|
||||
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/service:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:appropriate_battalions_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:army_stats_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:attack_decision_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:battalion_with_food_cost_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:captured_hero_option_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:diplomacy_option_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:expanded_combat_unit_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:expanded_unaffiliated_hero_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:prisoner_management_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:province_orders_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:battalion_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:combat_unit_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:improvement_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:tribute_amount_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/settings:alliance_gold_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:break_alliance_gold_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:invite_gold_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:truce_gold_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:hero_view_filter",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:legacy_battalion_view_filter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/common",
|
||||
"@com_google_protobuf//:protobuf_java",
|
||||
],
|
||||
)
|
||||
@@ -1,30 +0,0 @@
|
||||
load("@rules_scala//scala:scala.bzl", "scala_library")
|
||||
|
||||
scala_library(
|
||||
name = "selected_command_converter",
|
||||
srcs = ["SelectedCommandConverter.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/service:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
],
|
||||
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",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:attack_decision_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:captured_hero_option_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:diplomacy_option_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:prisoner_management_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api/command/util:province_orders_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:combat_unit_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_status_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:improvement_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//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/diplomacy_offer/status",
|
||||
],
|
||||
)
|
||||
-465
@@ -1,465 +0,0 @@
|
||||
package net.eagle0.eagle.model.proto_converters.command.selected
|
||||
|
||||
import net.eagle0.eagle.api.command.util.attack_decision_type.{
|
||||
AdvanceDecision,
|
||||
AttackDecisionType as AttackDecisionTypeProto,
|
||||
DemandTributeDecision,
|
||||
SafePassageDecision,
|
||||
WithdrawDecision
|
||||
}
|
||||
import net.eagle0.eagle.api.command.util.captured_hero_option.CapturedHeroOption as CapturedHeroOptionProto
|
||||
import net.eagle0.eagle.api.command.util.control_weather_type.ControlWeatherType as ControlWeatherTypeProto
|
||||
import net.eagle0.eagle.api.command.util.diplomacy_option.{
|
||||
AllianceOption,
|
||||
BreakAllianceOption,
|
||||
DiplomacyOption as DiplomacyOptionProto,
|
||||
InvitationOption,
|
||||
RansomOfferOption,
|
||||
TruceOption
|
||||
}
|
||||
import net.eagle0.eagle.api.command.util.prisoner_management_type.{
|
||||
PrisonerManagementOption as PrisonerManagementOptionProto,
|
||||
PrisonerManagementOptionExecute,
|
||||
PrisonerManagementOptionExile,
|
||||
PrisonerManagementOptionMove,
|
||||
PrisonerManagementOptionRelease,
|
||||
PrisonerManagementOptionReturn
|
||||
}
|
||||
import net.eagle0.eagle.api.selected_command as proto
|
||||
import net.eagle0.eagle.common.diplomacy_offer.{
|
||||
HostageOfferedInExchange as HostageOfferedInExchangeProto,
|
||||
PrisonerOfferedInExchange as PrisonerOfferedInExchangeProto,
|
||||
PrisonerToBeRansomed as PrisonerToBeRansomedProto,
|
||||
RansomOfferDetails as RansomOfferDetailsProto
|
||||
}
|
||||
import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus as DiplomacyOfferStatusProto
|
||||
import net.eagle0.eagle.common.improvement_type.ImprovementType as ImprovementTypeProto
|
||||
import net.eagle0.eagle.common.province_order_type.ProvinceOrderType as ProvinceOrderTypeProto
|
||||
import net.eagle0.eagle.model.state.command.common.*
|
||||
import net.eagle0.eagle.model.state.command.selected.*
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.*
|
||||
import net.eagle0.eagle.model.state.diplomacy_offer.status.{Accepted, Rejected, Status}
|
||||
|
||||
/**
|
||||
* Converts proto SelectedCommand to Scala SelectedCommand.
|
||||
*/
|
||||
object SelectedCommandConverter {
|
||||
|
||||
def fromProto(command: proto.SelectedCommand): SelectedCommand =
|
||||
command match {
|
||||
case cmd: proto.AlmsSelectedCommand =>
|
||||
AlmsSelected(
|
||||
amount = cmd.amount,
|
||||
actingHeroId = cmd.actingHeroId
|
||||
)
|
||||
|
||||
case cmd: proto.ApprehendOutlawSelectedCommand =>
|
||||
ApprehendOutlawSelected(
|
||||
heroIdToApprehend = cmd.heroIdToApprehend,
|
||||
battalionId = cmd.battalionId,
|
||||
actingHeroId = cmd.actingHeroId
|
||||
)
|
||||
|
||||
case cmd: proto.ArmTroopsSelectedCommand =>
|
||||
ArmTroopsSelected(
|
||||
armedBattalions = cmd.armedBattalions.map(armedBattalionFromProto).toVector
|
||||
)
|
||||
|
||||
case cmd: proto.AttackDecisionSelectedCommand =>
|
||||
AttackDecisionSelected(
|
||||
decision = attackDecisionTypeFromProto(cmd.decision)
|
||||
)
|
||||
|
||||
case cmd: proto.ControlWeatherSelectedCommand =>
|
||||
ControlWeatherSelected(
|
||||
selectedType = controlWeatherTypeFromProto(cmd.selectedType),
|
||||
selectedProvinceId = cmd.selectedProvinceId,
|
||||
actingHeroId = cmd.actingHeroId
|
||||
)
|
||||
|
||||
case cmd: proto.DeclineQuestSelectedCommand =>
|
||||
DeclineQuestSelected(heroId = cmd.heroId)
|
||||
|
||||
case cmd: proto.DefendSelectedCommand =>
|
||||
DefendSelected(
|
||||
fleeProvinceId = cmd.fleeProvinceId,
|
||||
defendingUnits = cmd.defendingUnits.map(combatUnitFromProto).toVector
|
||||
)
|
||||
|
||||
case cmd: proto.DiplomacySelectedCommand =>
|
||||
DiplomacySelected(
|
||||
selectedOption = diplomacyOptionFromProto(cmd.selectedOption),
|
||||
targetFactionId = cmd.targetFactionId,
|
||||
sentHeroId = cmd.sentHeroId
|
||||
)
|
||||
|
||||
case cmd: proto.DivineSelectedCommand =>
|
||||
DivineSelected(heroIds = cmd.heroIds.toVector)
|
||||
|
||||
case cmd: proto.ExileVassalSelectedCommand =>
|
||||
ExileVassalSelected(exiledHeroId = cmd.exiledHeroId)
|
||||
|
||||
case _: proto.FeastSelectedCommand =>
|
||||
FeastSelected
|
||||
|
||||
case cmd: proto.FreeForAllDecisionSelectedCommand =>
|
||||
FreeForAllDecisionSelected(
|
||||
provinceId = cmd.provinceId,
|
||||
decision = attackDecisionTypeFromProto(cmd.decision)
|
||||
)
|
||||
|
||||
case cmd: proto.HandleCapturedHeroSelectedCommand =>
|
||||
HandleCapturedHeroSelected(
|
||||
heroId = cmd.heroId,
|
||||
selectedOption = capturedHeroOptionFromProto(cmd.selectedOption)
|
||||
)
|
||||
|
||||
case cmd: proto.HandleRiotCrackDownSelectedCommand =>
|
||||
HandleRiotCrackDownSelected(
|
||||
heroId = cmd.heroId,
|
||||
battalionId = cmd.battalionId,
|
||||
actingHeroId = cmd.actingHeroId
|
||||
)
|
||||
|
||||
case _: proto.HandleRiotDoNothingSelectedCommand =>
|
||||
HandleRiotDoNothingSelected
|
||||
|
||||
case cmd: proto.HandleRiotGiveSelectedCommand =>
|
||||
HandleRiotGiveSelected(
|
||||
foodAmount = cmd.foodAmount,
|
||||
goldAmount = cmd.goldAmount
|
||||
)
|
||||
|
||||
case cmd: proto.HeroGiftSelectedCommand =>
|
||||
HeroGiftSelected(
|
||||
recipientHeroId = cmd.recipientHeroId,
|
||||
amount = cmd.amount
|
||||
)
|
||||
|
||||
case cmd: proto.ImproveSelectedCommand =>
|
||||
ImproveSelected(
|
||||
improvementType = improvementTypeFromProto(cmd.improvementType),
|
||||
actingHeroId = cmd.actingHeroId,
|
||||
lockType = cmd.lockType
|
||||
)
|
||||
|
||||
case cmd: proto.IssueOrdersSelectedCommand =>
|
||||
IssueOrdersSelected(
|
||||
newOrders = cmd.newOrders.map(provinceOrderFromProto).toVector,
|
||||
newFocusProvince = cmd.newFocusProvince
|
||||
)
|
||||
|
||||
case cmd: proto.ManagePrisonersSelectedCommand =>
|
||||
ManagePrisonersSelected(
|
||||
prisonerHeroId = cmd.prisonerHeroId,
|
||||
chosenOption = prisonerManagementOptionFromProto(cmd.chosenOption)
|
||||
)
|
||||
|
||||
case cmd: proto.MarchSelectedCommand =>
|
||||
MarchSelected(
|
||||
gold = cmd.gold,
|
||||
food = cmd.food,
|
||||
originProvince = cmd.originProvince,
|
||||
destinationProvinceId = cmd.destinationProvinceId,
|
||||
marchingUnits = cmd.marchingUnits.map(combatUnitFromProto).toVector
|
||||
)
|
||||
|
||||
case cmd: proto.OrganizeTroopsSelectedCommand =>
|
||||
OrganizeTroopsSelected(
|
||||
changedBattalions = cmd.changedBattalions.map(changedBattalionFromProto).toVector,
|
||||
newBattalions = cmd.newBattalions.map(newBattalionFromProto).toVector
|
||||
)
|
||||
|
||||
case cmd: proto.PleaseRecruitMeSelectedCommand =>
|
||||
PleaseRecruitMeSelected(
|
||||
provinceId = cmd.provinceId,
|
||||
heroId = cmd.heroId,
|
||||
accept = cmd.accept
|
||||
)
|
||||
|
||||
case cmd: proto.ReconSelectedCommand =>
|
||||
ReconSelected(
|
||||
actingHeroId = cmd.actingHeroId,
|
||||
targetProvinceId = cmd.targetProvinceId
|
||||
)
|
||||
|
||||
case cmd: proto.RecruitHeroesSelectedCommand =>
|
||||
RecruitHeroesSelected(heroIds = cmd.heroIds.toVector)
|
||||
|
||||
case cmd: proto.ResolveAllianceOfferSelectedCommand =>
|
||||
ResolveAllianceOfferSelected(
|
||||
originatingFactionId = cmd.originatingFactionId,
|
||||
resolution = statusFromProto(cmd.resolution)
|
||||
)
|
||||
|
||||
case cmd: proto.ResolveBreakAllianceSelectedCommand =>
|
||||
ResolveBreakAllianceSelected(
|
||||
originatingFactionId = cmd.originatingFactionId,
|
||||
resolution = statusFromProto(cmd.resolution)
|
||||
)
|
||||
|
||||
case cmd: proto.ResolveInvitationSelectedCommand =>
|
||||
ResolveInvitationSelected(
|
||||
originatingFactionId = cmd.originatingFactionId,
|
||||
resolution = statusFromProto(cmd.resolution)
|
||||
)
|
||||
|
||||
case cmd: proto.ResolveRansomOfferSelectedCommand =>
|
||||
val prisonerHeroId = cmd.ransomOffer.flatMap { offer =>
|
||||
offer.offerDetails match {
|
||||
case net.eagle0.eagle.common.diplomacy_offer.RansomOfferDetails(
|
||||
prisonerToBeRansomed,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
_
|
||||
) =>
|
||||
prisonerToBeRansomed.map(_.prisonerHeroId)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
ResolveRansomOfferSelected(
|
||||
offeringFactionId = cmd.offeringFactionId,
|
||||
prisonerHeroId = prisonerHeroId.getOrElse(-1),
|
||||
resolution = statusFromProto(cmd.resolution)
|
||||
)
|
||||
|
||||
case cmd: proto.ResolveTruceOfferSelectedCommand =>
|
||||
ResolveTruceOfferSelected(
|
||||
originatingFactionId = cmd.originatingFactionId,
|
||||
resolution = statusFromProto(cmd.resolution)
|
||||
)
|
||||
|
||||
case cmd: proto.ResolveTributeSelectedCommand =>
|
||||
ResolveTributeSelected(
|
||||
demandingFactionId = cmd.demandingFactionId,
|
||||
paid = cmd.paid
|
||||
)
|
||||
|
||||
case _: proto.RestSelectedCommand =>
|
||||
RestSelected
|
||||
|
||||
case _: proto.ReturnSelectedCommand =>
|
||||
ReturnSelected
|
||||
|
||||
case cmd: proto.SendSuppliesSelectedCommand =>
|
||||
SendSuppliesSelected(
|
||||
gold = cmd.gold,
|
||||
food = cmd.food,
|
||||
actingHeroId = cmd.actingHeroId,
|
||||
destinationProvinceId = cmd.destinationProvinceId
|
||||
)
|
||||
|
||||
case cmd: proto.StartEpidemicSelectedCommand =>
|
||||
StartEpidemicSelected(
|
||||
selectedProvinceId = cmd.selectedProvinceId,
|
||||
actingHeroId = cmd.actingHeroId
|
||||
)
|
||||
|
||||
case cmd: proto.SuppressBeastsSelectedCommand =>
|
||||
SuppressBeastsSelected(
|
||||
heroId = cmd.heroId,
|
||||
battalionId = cmd.battalionId
|
||||
)
|
||||
|
||||
case cmd: proto.SwearBrotherhoodSelectedCommand =>
|
||||
SwearBrotherhoodSelected(newBrotherHeroId = cmd.newBrotherHeroId)
|
||||
|
||||
case cmd: proto.TradeSelectedCommand =>
|
||||
TradeSelected(
|
||||
tradeType = tradeTypeFromProto(cmd.tradeType),
|
||||
amount = cmd.amount
|
||||
)
|
||||
|
||||
case cmd: proto.TrainSelectedCommand =>
|
||||
TrainSelected(actingHeroId = cmd.actingHeroId)
|
||||
|
||||
case _: proto.TravelSelectedCommand =>
|
||||
TravelSelected
|
||||
|
||||
case proto.SelectedCommand.Empty =>
|
||||
throw new IllegalArgumentException("SelectedCommand is empty")
|
||||
}
|
||||
|
||||
// Helper functions for nested types
|
||||
|
||||
private def armedBattalionFromProto(
|
||||
ab: net.eagle0.eagle.api.command.util.armed_battalion.ArmedBattalion
|
||||
): ArmedBattalion =
|
||||
ArmedBattalion(
|
||||
battalionId = ab.id,
|
||||
battalionTypeId = ab.newArmament
|
||||
)
|
||||
|
||||
private def combatUnitFromProto(cu: net.eagle0.eagle.common.combat_unit.CombatUnit): CombatUnit =
|
||||
CombatUnit(
|
||||
heroId = cu.heroId,
|
||||
battalionId = cu.battalionId
|
||||
)
|
||||
|
||||
private def provinceOrderFromProto(
|
||||
po: net.eagle0.eagle.api.command.util.province_orders.ProvinceOrders
|
||||
): ProvinceOrder =
|
||||
ProvinceOrder(
|
||||
provinceId = po.provinceId,
|
||||
orderType = provinceOrderTypeFromProto(po.orders)
|
||||
)
|
||||
|
||||
private def changedBattalionFromProto(
|
||||
cb: proto.OrganizeTroopsSelectedCommand.ChangedBattalion
|
||||
): ChangedBattalion =
|
||||
ChangedBattalion(
|
||||
id = cb.id,
|
||||
troopsFromOtherBattalion = cb.troopsFromOtherBattalion.map(troopsFromOtherBattalionFromProto).toVector,
|
||||
newTroops = cb.newTroops,
|
||||
dismissedTroops = cb.dismissedTroops
|
||||
)
|
||||
|
||||
private def newBattalionFromProto(nb: proto.OrganizeTroopsSelectedCommand.NewBattalion): NewBattalion =
|
||||
NewBattalion(
|
||||
typeId = nb.`type`.value,
|
||||
troopsFromOtherBattalion = nb.troopsFromOtherBattalion.map(troopsFromOtherBattalionFromProto).toVector,
|
||||
newTroops = nb.newTroops
|
||||
)
|
||||
|
||||
private def troopsFromOtherBattalionFromProto(
|
||||
t: proto.OrganizeTroopsSelectedCommand.TroopsFromOtherBattalion
|
||||
): TroopsFromOtherBattalion =
|
||||
TroopsFromOtherBattalion(
|
||||
fromBattalionId = t.fromBattalionId,
|
||||
countMoved = t.countMoved
|
||||
)
|
||||
|
||||
// Enum converters
|
||||
|
||||
private def attackDecisionTypeFromProto(decision: AttackDecisionTypeProto): AttackDecisionType =
|
||||
decision match {
|
||||
case _: AdvanceDecision => AttackDecisionType.Advance
|
||||
case _: WithdrawDecision => AttackDecisionType.Withdraw
|
||||
case d: DemandTributeDecision =>
|
||||
val tribute = d.tribute.getOrElse(
|
||||
throw new IllegalArgumentException("DemandTributeDecision missing tribute")
|
||||
)
|
||||
AttackDecisionType.DemandTribute(gold = tribute.gold, food = tribute.food)
|
||||
case d: SafePassageDecision =>
|
||||
AttackDecisionType.SafePassage(destinationProvinceId = d.toProvinceId)
|
||||
case AttackDecisionTypeProto.Empty =>
|
||||
throw new IllegalArgumentException("AttackDecisionType has no value set")
|
||||
}
|
||||
|
||||
private def controlWeatherTypeFromProto(t: ControlWeatherTypeProto): ControlWeatherType =
|
||||
t match {
|
||||
case ControlWeatherTypeProto.CONTROL_WEATHER_START_BLIZZARD => ControlWeatherType.StartBlizzard
|
||||
case ControlWeatherTypeProto.CONTROL_WEATHER_END_BLIZZARD => ControlWeatherType.EndBlizzard
|
||||
case ControlWeatherTypeProto.CONTROL_WEATHER_START_DROUGHT => ControlWeatherType.StartDrought
|
||||
case ControlWeatherTypeProto.CONTROL_WEATHER_END_DROUGHT => ControlWeatherType.EndDrought
|
||||
case ControlWeatherTypeProto.CONTROL_WEATHER_UNKNOWN | ControlWeatherTypeProto.Unrecognized(_) =>
|
||||
throw new IllegalArgumentException(s"Unknown ControlWeatherType: $t")
|
||||
}
|
||||
|
||||
private def capturedHeroOptionFromProto(opt: CapturedHeroOptionProto): CapturedHeroOption =
|
||||
opt match {
|
||||
case CapturedHeroOptionProto.IMPRISON_CAPTURED_HERO_OPTION => CapturedHeroOption.Imprison
|
||||
case CapturedHeroOptionProto.EXECUTE_CAPTURED_HERO_OPTION => CapturedHeroOption.Execute
|
||||
case CapturedHeroOptionProto.EXILE_CAPTURED_HERO_OPTION => CapturedHeroOption.Exile
|
||||
case CapturedHeroOptionProto.RETURN_CAPTURED_HERO_OPTION => CapturedHeroOption.Return
|
||||
case CapturedHeroOptionProto.RECRUIT_CAPTURED_HERO_OPTION => CapturedHeroOption.Recruit
|
||||
case CapturedHeroOptionProto.UNKNOWN_CAPTURED_HERO_OPTION | CapturedHeroOptionProto.Unrecognized(_) =>
|
||||
throw new IllegalArgumentException(s"Unknown CapturedHeroOption: $opt")
|
||||
}
|
||||
|
||||
private def diplomacyOptionFromProto(option: DiplomacyOptionProto): DiplomacyOptionType =
|
||||
option match {
|
||||
case _: AllianceOption => DiplomacyOptionType.Alliance
|
||||
case _: TruceOption => DiplomacyOptionType.Truce
|
||||
case _: InvitationOption => DiplomacyOptionType.Invitation
|
||||
case _: BreakAllianceOption => DiplomacyOptionType.BreakAlliance
|
||||
case r: RansomOfferOption =>
|
||||
val details = r.ransomOffer.getOrElse(
|
||||
throw new IllegalArgumentException("RansomOfferOption missing ransomOffer")
|
||||
)
|
||||
DiplomacyOptionType.Ransom(ransomOfferDetailsFromProto(details))
|
||||
case DiplomacyOptionProto.Empty =>
|
||||
throw new IllegalArgumentException("DiplomacyOption has no value set")
|
||||
}
|
||||
|
||||
private def ransomOfferDetailsFromProto(details: RansomOfferDetailsProto): RansomOfferDetails =
|
||||
RansomOfferDetails(
|
||||
prisonerToBeRansomed = prisonerToBeRansomedFromProto(
|
||||
details.prisonerToBeRansomed.getOrElse(
|
||||
throw new IllegalArgumentException("RansomOfferDetails missing prisonerToBeRansomed")
|
||||
)
|
||||
),
|
||||
prisonersOffered = details.prisonersOffered.map(prisonerOfferedInExchangeFromProto).toVector,
|
||||
hostagesOffered = details.hostagesOffered.map(hostageOfferedInExchangeFromProto).toVector,
|
||||
goldOffered = details.goldOffered
|
||||
)
|
||||
|
||||
private def prisonerToBeRansomedFromProto(p: PrisonerToBeRansomedProto): PrisonerToBeRansomed =
|
||||
PrisonerToBeRansomed(
|
||||
prisonerHeroId = p.prisonerHeroId,
|
||||
provinceIdForPrisoner = p.provinceIdForPrisoner
|
||||
)
|
||||
|
||||
private def prisonerOfferedInExchangeFromProto(p: PrisonerOfferedInExchangeProto): PrisonerOfferedInExchange =
|
||||
PrisonerOfferedInExchange(
|
||||
heroId = p.heroId,
|
||||
factionIdForPrisoner = if p.factionIdForPrisoner >= 0 then Some(p.factionIdForPrisoner) else None,
|
||||
provinceIdWithHero = p.provinceIdWithHero
|
||||
)
|
||||
|
||||
private def hostageOfferedInExchangeFromProto(h: HostageOfferedInExchangeProto): HostageOfferedInExchange =
|
||||
HostageOfferedInExchange(
|
||||
heroId = h.heroId,
|
||||
provinceIdWithHero = h.provinceIdWithHero
|
||||
)
|
||||
|
||||
private def improvementTypeFromProto(t: ImprovementTypeProto): ImprovementType =
|
||||
t match {
|
||||
case ImprovementTypeProto.AGRICULTURE => ImprovementType.Agriculture
|
||||
case ImprovementTypeProto.ECONOMY => ImprovementType.Economy
|
||||
case ImprovementTypeProto.INFRASTRUCTURE => ImprovementType.Infrastructure
|
||||
case ImprovementTypeProto.NONE | ImprovementTypeProto.Unrecognized(_) =>
|
||||
throw new IllegalArgumentException(s"Unknown ImprovementType: $t")
|
||||
}
|
||||
|
||||
private def prisonerManagementOptionFromProto(opt: PrisonerManagementOptionProto): PrisonerManagementOption =
|
||||
opt match {
|
||||
case _: PrisonerManagementOptionRelease => PrisonerManagementOption.Release
|
||||
case _: PrisonerManagementOptionExecute => PrisonerManagementOption.Execute
|
||||
case _: PrisonerManagementOptionExile => PrisonerManagementOption.Exile
|
||||
case move: PrisonerManagementOptionMove => PrisonerManagementOption.Move(toProvinceId = move.toProvinceId)
|
||||
case ret: PrisonerManagementOptionReturn => PrisonerManagementOption.Return(toFactionId = ret.toFactionId)
|
||||
case PrisonerManagementOptionProto.Empty =>
|
||||
throw new IllegalArgumentException("PrisonerManagementOption has no value set")
|
||||
}
|
||||
|
||||
private def provinceOrderTypeFromProto(t: ProvinceOrderTypeProto): ProvinceOrderType =
|
||||
t match {
|
||||
case ProvinceOrderTypeProto.DEVELOP => ProvinceOrderType.Develop
|
||||
case ProvinceOrderTypeProto.MOBILIZE => ProvinceOrderType.Mobilize
|
||||
case ProvinceOrderTypeProto.EXPAND => ProvinceOrderType.Expand
|
||||
case ProvinceOrderTypeProto.ENTRUST => ProvinceOrderType.Entrust
|
||||
case ProvinceOrderTypeProto.UNKNOWN_ORDERS | ProvinceOrderTypeProto.Unrecognized(_) =>
|
||||
throw new IllegalArgumentException(s"Unknown ProvinceOrderType: $t")
|
||||
}
|
||||
|
||||
private def tradeTypeFromProto(t: proto.TradeSelectedCommand.TradeType): TradeType =
|
||||
t match {
|
||||
case proto.TradeSelectedCommand.TradeType.BUY_FOOD => TradeType.BuyFood
|
||||
case proto.TradeSelectedCommand.TradeType.SELL_FOOD => TradeType.SellFood
|
||||
case proto.TradeSelectedCommand.TradeType.UNKNOWN | proto.TradeSelectedCommand.TradeType.Unrecognized(_) =>
|
||||
throw new IllegalArgumentException(s"Unknown TradeType: $t")
|
||||
}
|
||||
|
||||
private def statusFromProto(status: DiplomacyOfferStatusProto): Status =
|
||||
status match {
|
||||
case DiplomacyOfferStatusProto.DIPLOMACY_OFFER_STATUS_ACCEPTED => Accepted
|
||||
case DiplomacyOfferStatusProto.DIPLOMACY_OFFER_STATUS_REJECTED => Rejected
|
||||
case DiplomacyOfferStatusProto.DIPLOMACY_OFFER_STATUS_UNKNOWN |
|
||||
DiplomacyOfferStatusProto.DIPLOMACY_OFFER_STATUS_UNRESOLVED |
|
||||
DiplomacyOfferStatusProto.DIPLOMACY_OFFER_STATUS_IMPRISONED |
|
||||
DiplomacyOfferStatusProto.DIPLOMACY_OFFER_STATUS_INVALIDATED | DiplomacyOfferStatusProto.Unrecognized(_) =>
|
||||
throw new IllegalArgumentException(s"Invalid resolution status: $status")
|
||||
}
|
||||
}
|
||||
-1
@@ -6,7 +6,6 @@ scala_library(
|
||||
visibility = [
|
||||
"//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/command:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/diplomacy_offer:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero:__pkg__",
|
||||
|
||||
@@ -136,7 +136,7 @@ scala_library(
|
||||
name = "improvement_type",
|
||||
srcs = ["ImprovementType.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/util:__subpackages__",
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
package net.eagle0.eagle.model.state
|
||||
|
||||
enum BattalionTypeId(val value: Int):
|
||||
case LightInfantry extends BattalionTypeId(0)
|
||||
case HeavyInfantry extends BattalionTypeId(1)
|
||||
case LightCavalry extends BattalionTypeId(2)
|
||||
case HeavyCavalry extends BattalionTypeId(3)
|
||||
case Longbowmen extends BattalionTypeId(4)
|
||||
case Undead extends BattalionTypeId(5)
|
||||
sealed abstract class BattalionTypeId(val value: Int)
|
||||
object BattalionTypeId {
|
||||
case object LightInfantry extends BattalionTypeId(0)
|
||||
case object HeavyInfantry extends BattalionTypeId(1)
|
||||
case object LightCavalry extends BattalionTypeId(2)
|
||||
case object HeavyCavalry extends BattalionTypeId(3)
|
||||
case object Longbowmen extends BattalionTypeId(4)
|
||||
case object Undead extends BattalionTypeId(5)
|
||||
|
||||
object BattalionTypeId:
|
||||
def fromInt(value: Int): BattalionTypeId =
|
||||
BattalionTypeId.fromOrdinal(value)
|
||||
|
||||
// Enable type-safe equality for this type
|
||||
given CanEqual[BattalionTypeId, BattalionTypeId] = CanEqual.derived
|
||||
def fromInt(value: Int): BattalionTypeId = value match {
|
||||
case 0 => LightInfantry
|
||||
case 1 => HeavyInfantry
|
||||
case 2 => LightCavalry
|
||||
case 3 => HeavyCavalry
|
||||
case 4 => Longbowmen
|
||||
case 5 => Undead
|
||||
case _ =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Invalid BattalionTypeId value: $value"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
package net.eagle0.eagle.model.state.command.available
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.command.common.*
|
||||
import AvailableCommand.*
|
||||
|
||||
/** Scala 3 enum representing available command options for a player. */
|
||||
enum AvailableCommand:
|
||||
case AlmsAvailable(
|
||||
foodAvailable: Int,
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
case ApprehendOutlawAvailable(
|
||||
outlaws: Vector[ResidentOutlaw],
|
||||
availableBattalionIds: Vector[BattalionId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
case ArmTroopsAvailable(
|
||||
armamentCosts: Vector[ArmamentCost],
|
||||
availableBattalions: Vector[BattalionId],
|
||||
maxArmament: Int,
|
||||
actingProvinceId: ProvinceId,
|
||||
availableBattalionTypeIds: Vector[Int]
|
||||
)
|
||||
case AttackDecisionAvailable(
|
||||
armies: Vector[ArmyStats],
|
||||
actingUnits: Vector[ExpandedCombatUnit],
|
||||
availableDecisions: Vector[AttackDecisionType],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case ControlWeatherAvailable(
|
||||
options: Vector[TargetProvinceOptions],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
case DeclineQuestAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
declinableHeroes: Vector[ExpandedUnaffiliatedHero]
|
||||
)
|
||||
case DefendAvailable(
|
||||
suitableBattalionsForHeroes: Map[HeroId, SuitableBattalions],
|
||||
availableFleeProvinceIds: Vector[ProvinceId],
|
||||
availableBattalions: Vector[BattalionWithFoodCost],
|
||||
availableHeroIds: Vector[HeroId],
|
||||
recommendedUnits: Vector[RecommendedCombatUnit],
|
||||
actingProvinceId: ProvinceId,
|
||||
hostileFactionIds: Vector[FactionId],
|
||||
hostileHeroCount: Int,
|
||||
hostileTroopCount: Int
|
||||
)
|
||||
case DiplomacyAvailable(
|
||||
options: Vector[DiplomacyOption],
|
||||
availableHeroIds: Vector[HeroId],
|
||||
actingProvinceId: ProvinceId,
|
||||
recommendedHeroId: HeroId
|
||||
)
|
||||
case DivineAvailable(
|
||||
costPerHero: Int,
|
||||
divinableHeroes: Vector[ExpandedUnaffiliatedHero],
|
||||
unavailableHeroes: Vector[ExpandedUnaffiliatedHero],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case ExileVassalAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
exilableHeroIds: Vector[HeroId]
|
||||
)
|
||||
case FeastAvailable(goldCost: Int, actingProvinceId: ProvinceId)
|
||||
case FreeForAllDecisionAvailable(
|
||||
provinceId: ProvinceId,
|
||||
armies: Vector[ArmyStats],
|
||||
actingUnits: Vector[ExpandedCombatUnit],
|
||||
availableDecisions: Vector[AttackDecisionType]
|
||||
)
|
||||
case HandleCapturedHeroAvailable(
|
||||
availableHeroes: Vector[ExpandedCapturedHero],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case HandleRiotCrackDownAvailable(
|
||||
availableHeroIds: Vector[HeroId],
|
||||
battalionIdsAvailable: Vector[BattalionId],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case HandleRiotDoNothingAvailable(actingProvinceId: ProvinceId)
|
||||
case HandleRiotGiveAvailable(
|
||||
foodAvailable: Int,
|
||||
goldAvailable: Int,
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case HeroGiftAvailable(
|
||||
eligibleGifts: Vector[EligibleGift],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case ImproveAvailable(
|
||||
availableTypes: Vector[ImprovementType],
|
||||
availableHeroIds: Vector[HeroId],
|
||||
recommendedHeroId: HeroId,
|
||||
actingProvinceId: ProvinceId,
|
||||
lockedType: Option[ImprovementType]
|
||||
)
|
||||
case IssueOrdersAvailable(
|
||||
currentOrders: Vector[ProvinceOrders],
|
||||
availableOrders: Vector[ProvinceOrderType],
|
||||
availableFocusProvinces: Vector[ProvinceId],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case ManagePrisonersAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
prisoners: Vector[PrisonerToManage]
|
||||
)
|
||||
case MarchAvailable(
|
||||
oneProvinceCommands: Vector[MarchCommandFromOneProvince],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableTypes: Vector[BattalionType]
|
||||
)
|
||||
case OrganizeTroopsAvailable(
|
||||
troopCosts: Vector[TroopCost],
|
||||
existingBattalions: Vector[BattalionId],
|
||||
maxBattalions: Int,
|
||||
actingProvinceId: ProvinceId,
|
||||
availableBattalionTypes: Vector[BattalionTypeStatus]
|
||||
)
|
||||
case PleaseRecruitMeAvailable(
|
||||
availableProvinces: Vector[OneProvincePleaseRecruitMe],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case ReconAvailable(
|
||||
availableTargetProvinces: Vector[ProvinceId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
case RecruitHeroesAvailable(
|
||||
availableHeroes: Vector[ExpandedUnaffiliatedHero],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case ResolveAllianceOfferAvailable(offers: Vector[DiplomacyOfferInfo])
|
||||
case ResolveBreakAllianceAvailable(offers: Vector[DiplomacyOfferInfo])
|
||||
case ResolveInvitationAvailable(invitations: Vector[DiplomacyOfferInfo])
|
||||
case ResolveRansomOfferAvailable(offers: Vector[DiplomacyOfferInfo])
|
||||
case ResolveTruceOfferAvailable(offers: Vector[DiplomacyOfferInfo])
|
||||
case ResolveTributeAvailable(
|
||||
demands: Vector[TributeAndFaction],
|
||||
availableGold: Int,
|
||||
availableFood: Int,
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case RestAvailable(actingProvinceId: ProvinceId)
|
||||
case ReturnAvailable(actingProvinceId: ProvinceId)
|
||||
case SendSuppliesAvailable(
|
||||
goldAvailable: Int,
|
||||
foodAvailable: Int,
|
||||
availableDestinationProvinceIds: Vector[ProvinceId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
case StartEpidemicAvailable(
|
||||
options: Vector[StartEpidemicOptions],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId]
|
||||
)
|
||||
case SuppressBeastsAvailable(
|
||||
availableHeroIds: Vector[HeroId],
|
||||
availableBattalionIds: Vector[BattalionId],
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case SwearBrotherhoodAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroes: Vector[HeroAndBackstory]
|
||||
)
|
||||
case TradeAvailable(
|
||||
foodAvailable: Int,
|
||||
goldAvailable: Int,
|
||||
foodBuyPrice: Double,
|
||||
foodSellPrice: Double,
|
||||
actingProvinceId: ProvinceId
|
||||
)
|
||||
case TrainAvailable(
|
||||
actingProvinceId: ProvinceId,
|
||||
availableHeroIds: Vector[HeroId],
|
||||
recommendedHeroId: HeroId
|
||||
)
|
||||
case TravelAvailable(actingProvinceId: ProvinceId)
|
||||
end AvailableCommand
|
||||
|
||||
/** Supporting types for AvailableCommand. */
|
||||
object AvailableCommand:
|
||||
case class ResidentOutlaw(
|
||||
heroId: HeroId,
|
||||
lastFactionId: Option[FactionId]
|
||||
)
|
||||
|
||||
case class ArmamentCost(
|
||||
battalionTypeId: Int,
|
||||
cost: Double
|
||||
)
|
||||
|
||||
case class ArmyStats(
|
||||
factionId: FactionId,
|
||||
heroCount: Int,
|
||||
troopCount: Int
|
||||
)
|
||||
|
||||
case class ExpandedCombatUnit(
|
||||
heroId: HeroId,
|
||||
battalionId: Option[BattalionId],
|
||||
heroName: String,
|
||||
troopCount: Int
|
||||
)
|
||||
|
||||
case class TargetProvinceOptions(
|
||||
provinceId: ProvinceId,
|
||||
controlWeatherTypes: Vector[ControlWeatherType]
|
||||
)
|
||||
|
||||
case class ExpandedUnaffiliatedHero(
|
||||
heroId: HeroId,
|
||||
heroName: String,
|
||||
goldCost: Int
|
||||
)
|
||||
|
||||
case class SuitableBattalions(
|
||||
battalionIds: Vector[BattalionId]
|
||||
)
|
||||
|
||||
case class BattalionWithFoodCost(
|
||||
battalionId: BattalionId,
|
||||
foodCost: Int,
|
||||
troopCount: Int
|
||||
)
|
||||
|
||||
case class RecommendedCombatUnit(
|
||||
heroId: HeroId,
|
||||
battalionId: Option[BattalionId]
|
||||
)
|
||||
|
||||
case class DiplomacyOption(
|
||||
targetFactionId: FactionId,
|
||||
optionTypes: Vector[DiplomacyOptionType]
|
||||
)
|
||||
|
||||
case class ExpandedCapturedHero(
|
||||
heroId: HeroId,
|
||||
heroName: String,
|
||||
options: Vector[CapturedHeroOption],
|
||||
messageId: String,
|
||||
previousFactionId: FactionId
|
||||
)
|
||||
|
||||
case class EligibleGift(
|
||||
recipientHeroId: HeroId,
|
||||
goldAvailable: Int,
|
||||
recipientProvinceId: ProvinceId
|
||||
)
|
||||
|
||||
case class ProvinceOrders(
|
||||
provinceId: ProvinceId,
|
||||
orderType: ProvinceOrderType
|
||||
)
|
||||
|
||||
case class PrisonerToManage(
|
||||
prisoner: ExpandedUnaffiliatedHero,
|
||||
availableOptions: Vector[PrisonerManagementOption]
|
||||
)
|
||||
|
||||
case class MarchCommandFromOneProvince(
|
||||
suitableBattalionsForHeroes: Map[HeroId, SuitableBattalions],
|
||||
originProvinceId: ProvinceId,
|
||||
goldCost: Int,
|
||||
goldAvailable: Int,
|
||||
foodAvailable: Int,
|
||||
availableDestinationProvinces: Vector[AvailableDestinationProvince],
|
||||
availableHeroIds: Vector[HeroId],
|
||||
availableBattalions: Vector[BattalionWithFoodCost],
|
||||
recommendedBattleConfig: Option[RecommendedBattleConfig]
|
||||
)
|
||||
|
||||
case class AvailableDestinationProvince(
|
||||
provinceId: ProvinceId,
|
||||
requiresRiverCrossing: Boolean
|
||||
)
|
||||
|
||||
case class RecommendedBattleConfig(
|
||||
units: Vector[RecommendedCombatUnit],
|
||||
food: Int,
|
||||
gold: Int
|
||||
)
|
||||
|
||||
case class BattalionType(
|
||||
typeId: Int,
|
||||
name: String
|
||||
)
|
||||
|
||||
case class TroopCost(
|
||||
battalionTypeId: Int,
|
||||
costPerTroop: Double
|
||||
)
|
||||
|
||||
case class BattalionTypeStatus(
|
||||
typeId: Int,
|
||||
meetsRequirements: Boolean,
|
||||
minimumAgriculture: Int,
|
||||
minimumEconomy: Int
|
||||
)
|
||||
|
||||
case class OneProvincePleaseRecruitMe(
|
||||
provinceId: ProvinceId,
|
||||
availableHeroes: Vector[ExpandedUnaffiliatedHero]
|
||||
)
|
||||
|
||||
case class DiplomacyOfferInfo(
|
||||
offeringFactionId: FactionId,
|
||||
targetFactionId: FactionId,
|
||||
heroId: Option[HeroId]
|
||||
)
|
||||
|
||||
case class TributeAndFaction(
|
||||
demandingFactionId: FactionId,
|
||||
goldDemanded: Int,
|
||||
foodDemanded: Int,
|
||||
heroCount: Int,
|
||||
troopCount: Int
|
||||
)
|
||||
|
||||
case class StartEpidemicOptions(
|
||||
targetProvinceId: ProvinceId
|
||||
)
|
||||
|
||||
case class HeroAndBackstory(
|
||||
heroId: HeroId,
|
||||
backstoryTextId: String
|
||||
)
|
||||
end AvailableCommand
|
||||
@@ -1,17 +0,0 @@
|
||||
load("@rules_scala//scala:scala.bzl", "scala_library")
|
||||
|
||||
scala_library(
|
||||
name = "available",
|
||||
srcs = ["AvailableCommand.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/command:__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__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/common",
|
||||
],
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user