mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 06:15:41 +00:00
Compare commits
166
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8e59565ac | ||
|
|
913d0d5e4b | ||
|
|
1f5bfe9e4d | ||
|
|
1d0b1dafed | ||
|
|
2571f65ac4 | ||
|
|
f52fd34879 | ||
|
|
3610c34b4a | ||
|
|
30390816df | ||
|
|
9c77f2f843 | ||
|
|
c7fa4c5180 | ||
|
|
66d544fb0a | ||
|
|
7f62f07d0b | ||
|
|
2829ec1aae | ||
|
|
97ffb0633d | ||
|
|
f378429786 | ||
|
|
f755d89b2a | ||
|
|
a8e200ab03 | ||
|
|
68bec95772 | ||
|
|
fde9529a31 | ||
|
|
0d044a7468 | ||
|
|
ee4b9c1d8f | ||
|
|
a7bd3deb76 | ||
|
|
9e69b213b0 | ||
|
|
f75ac01959 | ||
|
|
3eafc2b31c | ||
|
|
b39f48e8d6 | ||
|
|
4346e2dc6f | ||
|
|
85122128bf | ||
|
|
5a58ea3ffb | ||
|
|
e1237da088 | ||
|
|
b4eebf5ead | ||
|
|
922a825648 | ||
|
|
3602d439a5 | ||
|
|
1718d8e885 | ||
|
|
f58f3ac31a | ||
|
|
6e65b556b4 | ||
|
|
d86597b92e | ||
|
|
ce667c5784 | ||
|
|
13a939f88e | ||
|
|
52ae7cad7f | ||
|
|
eacad46c9b | ||
|
|
1bd8fc7154 | ||
|
|
50ccb1b7d8 | ||
|
|
c693553365 | ||
|
|
6c3e4bc202 | ||
|
|
92d9a2dbd8 | ||
|
|
5df24d23c0 | ||
|
|
1215b5044c | ||
|
|
b1263ec4df | ||
|
|
19e85bde8b | ||
|
|
cbe71f4fd4 | ||
|
|
52afd836ac | ||
|
|
095a29a4a0 | ||
|
|
49ff9f8b09 | ||
|
|
457c1e933a | ||
|
|
f5e56f0da9 | ||
|
|
0d8df5e97c | ||
|
|
92af09660c | ||
|
|
435e7e08f3 | ||
|
|
2516c42c08 | ||
|
|
19dc4fd1b1 | ||
|
|
0305cc7506 | ||
|
|
5854929085 | ||
|
|
232e560724 | ||
|
|
994de5cdc2 | ||
|
|
dcc303ad37 | ||
|
|
84ecc4d13d | ||
|
|
eb338b1d0b | ||
|
|
1b1261f4f6 | ||
|
|
3fff156344 | ||
|
|
3d39238045 | ||
|
|
705b176992 | ||
|
|
1c2e150899 | ||
|
|
8af820c58e | ||
|
|
940a4836a7 | ||
|
|
1ef2fa0f84 | ||
|
|
7efdf90100 | ||
|
|
be930740cf | ||
|
|
241b49ac23 | ||
|
|
77499dbdad | ||
|
|
a4c688f126 | ||
|
|
5ee753e989 | ||
|
|
77c6c16096 | ||
|
|
2105ccc791 | ||
|
|
e03af03acb | ||
|
|
041a43be97 | ||
|
|
53180baddd | ||
|
|
b88613a6f1 | ||
|
|
dbc5455de0 | ||
|
|
17357eba33 | ||
|
|
ff9ab1907b | ||
|
|
0b9ebb04c8 | ||
|
|
10a3b3a046 | ||
|
|
7cf852ab52 | ||
|
|
2341df8998 | ||
|
|
190e71d3c4 | ||
|
|
c0a9b90715 | ||
|
|
6d13774fd0 | ||
|
|
41252ccaa6 | ||
|
|
ff05a35991 | ||
|
|
8b7e9eaeb6 | ||
|
|
7f80eb0ef2 | ||
|
|
ef151c2ef4 | ||
|
|
e7cad8dd36 | ||
|
|
c4edb2f6a0 | ||
|
|
11709e6d48 | ||
|
|
0b0fcc411d | ||
|
|
1aadcc863f | ||
|
|
9ac167e66c | ||
|
|
1530873df5 | ||
|
|
75a5b4fa21 | ||
|
|
b6c45da7bc | ||
|
|
5e30673281 | ||
|
|
d38c611a18 | ||
|
|
570bc557f5 | ||
|
|
c1f4152cae | ||
|
|
0472362abc | ||
|
|
b732b60520 | ||
|
|
fc1f988654 | ||
|
|
7ef93c4a11 | ||
|
|
247aa3e685 | ||
|
|
aa0292bad6 | ||
|
|
08cd608ac2 | ||
|
|
f85a32dc08 | ||
|
|
b5deb69ef7 | ||
|
|
872883f710 | ||
|
|
3d402e7bdd | ||
|
|
862480c470 | ||
|
|
98b5d7f034 | ||
|
|
e5c5a929ec | ||
|
|
d39ee392b9 | ||
|
|
b574c6c950 | ||
|
|
074c984f3b | ||
|
|
2817ca529d | ||
|
|
4f932d0a56 | ||
|
|
2dd931dc8f | ||
|
|
c7ab2ecb44 | ||
|
|
8466ab6ed9 | ||
|
|
7b74905973 | ||
|
|
ecfb5a0a94 | ||
|
|
967cb74aa5 | ||
|
|
34daf8f38d | ||
|
|
e12359821c | ||
|
|
eea41e6c05 | ||
|
|
36d1e75719 | ||
|
|
35e93c8ce1 | ||
|
|
d603b01733 | ||
|
|
6c66b5df82 | ||
|
|
3b59e21407 | ||
|
|
d0e74ac4cb | ||
|
|
c04aa3d753 | ||
|
|
f3fade7ed2 | ||
|
|
79d8e837e8 | ||
|
|
c39795fa9f | ||
|
|
4a7c55a382 | ||
|
|
7e4396552e | ||
|
|
40219f2a67 | ||
|
|
ce35c176ee | ||
|
|
a852f03214 | ||
|
|
7c8b43d91b | ||
|
|
ecf763b24b | ||
|
|
8ad4fc52e3 | ||
|
|
7e48f6fd2b | ||
|
|
5c81832cde | ||
|
|
569458d691 | ||
|
|
c825bf4606 |
@@ -14,6 +14,7 @@ common --worker_sandboxing
|
||||
common --local_test_jobs=64
|
||||
common --jobs=64
|
||||
|
||||
# Keep these C++ standard flags in sync with tools/copts.bzl and scripts/shardok_cpp_config.sh.
|
||||
common --cxxopt="--std=c++23"
|
||||
common --cxxopt="-Wno-deprecated-non-prototype"
|
||||
common --per_file_copt=src/test/cpp/.*@-Wno-character-conversion
|
||||
@@ -37,6 +38,8 @@ build:ubsan --strip=never
|
||||
build:ubsan --copt=-fno-omit-frame-pointer
|
||||
build:ubsan --copt=-Wno-macro-redefined
|
||||
build:ubsan --copt=-fsanitize=undefined
|
||||
# Protobuf's arena implementation trips UBSan alignment checks in external code on macOS.
|
||||
build:ubsan --copt=-fno-sanitize=alignment
|
||||
build:ubsan --linkopt=-fsanitize=undefined
|
||||
test:ubsan --test_env=UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1
|
||||
|
||||
|
||||
+10
-18
@@ -2,25 +2,17 @@ Checks: >
|
||||
-*,
|
||||
bugprone-*,
|
||||
performance-*,
|
||||
readability-*,
|
||||
modernize-*,
|
||||
cppcoreguidelines-*,
|
||||
modernize-use-nullptr,
|
||||
modernize-use-override,
|
||||
modernize-use-auto,
|
||||
modernize-use-nodiscard,
|
||||
readability-braces-around-statements,
|
||||
readability-qualified-auto,
|
||||
readability-redundant-member-init,
|
||||
cppcoreguidelines-pro-type-cstyle-cast,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
-cppcoreguidelines-pro-type-const-cast,
|
||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
-cppcoreguidelines-pro-type-union-access,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-modernize-use-trailing-return-type,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-identifier-length,
|
||||
-readability-magic-numbers
|
||||
-bugprone-branch-clone,
|
||||
-bugprone-signed-char-misuse
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: 'src/(main|test)/cpp/net/eagle0/(shardok|common)/.*'
|
||||
FormatStyle: file
|
||||
|
||||
@@ -12,6 +12,9 @@ on:
|
||||
- '.bazelrc'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- 'ci/github_actions/summarize_bazel_bep.py'
|
||||
- '.clang-tidy'
|
||||
- 'scripts/run-clang-tidy.sh'
|
||||
- 'scripts/check_shardok_clang_tidy.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/bazel_test.yml'
|
||||
- '!src/main/csharp/**'
|
||||
@@ -26,6 +29,9 @@ on:
|
||||
- '.bazelrc'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- 'ci/github_actions/summarize_bazel_bep.py'
|
||||
- '.clang-tidy'
|
||||
- 'scripts/run-clang-tidy.sh'
|
||||
- 'scripts/check_shardok_clang_tidy.sh'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/bazel_test.yml'
|
||||
- '!src/main/csharp/**'
|
||||
@@ -78,6 +84,8 @@ jobs:
|
||||
node-version: 'lts/*'
|
||||
- name: Check JavaScript syntax
|
||||
run: node --check src/main/go/net/eagle0/admin_server/static/map_editor.js
|
||||
- name: Check Shardok clang-tidy gate
|
||||
run: ./scripts/check_shardok_clang_tidy.sh
|
||||
|
||||
test:
|
||||
runs-on: [self-hosted, bazel]
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
name: Shardok Sanitizer Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- "src/main/cpp/net/eagle0/shardok/**"
|
||||
- "src/test/cpp/net/eagle0/shardok/**"
|
||||
- "src/main/protobuf/net/eagle0/shardok/**"
|
||||
- "src/main/protobuf/net/eagle0/common/**"
|
||||
- "WORKSPACE"
|
||||
- "MODULE.bazel"
|
||||
- "MODULE.bazel.lock"
|
||||
- "BUILD.bazel"
|
||||
- ".bazelrc"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- ".github/workflows/shardok_sanitizer_test.yml"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- "ci/github_actions/summarize_bazel_bep.py"
|
||||
pull_request:
|
||||
paths:
|
||||
- "src/main/cpp/net/eagle0/shardok/**"
|
||||
- "src/test/cpp/net/eagle0/shardok/**"
|
||||
- "src/main/protobuf/net/eagle0/shardok/**"
|
||||
- "src/main/protobuf/net/eagle0/common/**"
|
||||
- "WORKSPACE"
|
||||
- "MODULE.bazel"
|
||||
- "MODULE.bazel.lock"
|
||||
- "BUILD.bazel"
|
||||
- ".bazelrc"
|
||||
- ".github/actions/setup-bazel/**"
|
||||
- ".github/workflows/shardok_sanitizer_test.yml"
|
||||
- "ci/github_actions/ensure_bazel_installed.sh"
|
||||
- "ci/github_actions/summarize_bazel_bep.py"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
shardok-sanitizer-test:
|
||||
name: Shardok ${{ matrix.name }}
|
||||
runs-on: [self-hosted, bazel]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: ASan
|
||||
config: asan
|
||||
- name: UBSan
|
||||
config: ubsan
|
||||
|
||||
steps:
|
||||
- name: Ensure Git LFS available for checkout
|
||||
run: |
|
||||
COMMON_PATHS=(/opt/homebrew/bin /usr/local/bin)
|
||||
for path in "${COMMON_PATHS[@]}"; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path" >> "$GITHUB_PATH"
|
||||
export PATH="$path:$PATH"
|
||||
fi
|
||||
done
|
||||
|
||||
if command -v git-lfs >/dev/null 2>&1; then
|
||||
git-lfs version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
brew install git-lfs
|
||||
git-lfs version
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
lfs: false
|
||||
- name: Setup Bazel
|
||||
uses: ./.github/actions/setup-bazel
|
||||
- name: Run Shardok sanitizer tests
|
||||
run: bazel test --config=${{ matrix.config }} --build_event_json_file=${{ matrix.config }}.json //src/test/cpp/net/eagle0/shardok/...
|
||||
- name: Summarize Bazel build metrics
|
||||
if: always()
|
||||
run: python3 ci/github_actions/summarize_bazel_bep.py ${{ matrix.config }}.json
|
||||
- name: Archive sanitizer test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: shardok-${{ matrix.config }}.json
|
||||
path: ${{ matrix.config }}.json
|
||||
retention-days: 3
|
||||
@@ -11,6 +11,11 @@
|
||||
**NEVER kill Unity without asking the user first.** The user might be actively using the Unity Editor. Do not kill,
|
||||
force-quit, terminate, or otherwise stop Unity processes unless the user explicitly approves that specific action.
|
||||
|
||||
**NEVER change Unity's default/open scene as part of running checks.** Unity batchmode or editor validation may write
|
||||
scene-selection churn such as `ProjectSettings/EditorBuildSettings.asset`, `ProjectSettings/SceneTemplateSettings.json`,
|
||||
or scene files simply because a different scene was open or loaded. Treat those as unintended local environment changes:
|
||||
do not stage them, and restore them before committing unless the user explicitly asked to change scenes/build settings.
|
||||
|
||||
## CRITICAL GIT RULES (NEVER VIOLATE)
|
||||
|
||||
**NEVER use `git -C`.** Just run `git` directly from the cwd — it finds the repo via `.git` discovery. Do not `cd` to the repo root either (see the no-`cd` bash rule above).
|
||||
|
||||
@@ -71,7 +71,7 @@ LLM settings can be changed at runtime via the admin console:
|
||||
1. Navigate to Admin Console → Settings
|
||||
2. Change `LlmProvider` to select vendor (gemini, openai, claude)
|
||||
3. Change the corresponding model name setting:
|
||||
- `GeminiModelName` (default: gemini-3.1-flash-lite-preview)
|
||||
- `GeminiModelName` (default: gemini-3.1-flash-lite)
|
||||
- `OpenAiModelName` (default: gpt-4.1-mini)
|
||||
- `ClaudeModelName` (default: claude-3-5-haiku-20241022)
|
||||
|
||||
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# Runs the curated Shardok clang-tidy gate used by CI.
|
||||
#
|
||||
# This intentionally checks representative files instead of the whole C++ tree.
|
||||
# Keep this list low-noise; expand it as cleanup PRs make more areas tidy-clean.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(git rev-parse --show-toplevel)"
|
||||
source "$repo_root/scripts/shardok_cpp_config.sh"
|
||||
|
||||
files=(
|
||||
"src/main/cpp/net/eagle0/shardok/library/ActionCost.hpp"
|
||||
"src/main/cpp/net/eagle0/shardok/library/CombatDamage.hpp"
|
||||
)
|
||||
|
||||
compiler_args=(
|
||||
"-I$repo_root"
|
||||
"$SHARDOK_CXX_STANDARD_ARG"
|
||||
)
|
||||
|
||||
if command -v xcrun >/dev/null 2>&1; then
|
||||
sdk_path="$(xcrun --show-sdk-path)"
|
||||
compiler_args+=("-isysroot" "$sdk_path")
|
||||
fi
|
||||
|
||||
"$repo_root/scripts/run-clang-tidy.sh" --verify-config
|
||||
|
||||
"$repo_root/scripts/run-clang-tidy.sh" --quiet --warnings-as-errors="*" "${files[@]}" -- "${compiler_args[@]}"
|
||||
@@ -10,13 +10,14 @@ set -e
|
||||
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
||||
|
||||
repo_root="$(git rev-parse --show-toplevel)"
|
||||
source "$repo_root/scripts/shardok_cpp_config.sh"
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "Usage: scripts/run-clang-tidy.sh [clang-tidy args] <files> [-- <compiler args>]"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " scripts/run-clang-tidy.sh --verify-config"
|
||||
echo " scripts/run-clang-tidy.sh src/main/cpp/net/eagle0/shardok/library/CombatDamage.hpp -- -I$repo_root -std=c++23"
|
||||
echo " scripts/run-clang-tidy.sh src/main/cpp/net/eagle0/shardok/library/CombatDamage.hpp -- -I$repo_root $SHARDOK_CXX_STANDARD_ARG"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Shared C++ settings for shell scripts that invoke Shardok tooling outside Bazel.
|
||||
|
||||
SHARDOK_CXX_STANDARD="c++23"
|
||||
SHARDOK_CXX_STANDARD_ARG="-std=${SHARDOK_CXX_STANDARD}"
|
||||
@@ -28,7 +28,7 @@ static inline auto HashBuffer(const uint8_t* data, size_t size) -> uint64_t {
|
||||
|
||||
// Process 8 bytes at a time
|
||||
while (data + 8 <= end) {
|
||||
uint64_t word;
|
||||
uint64_t word = 0;
|
||||
// Use memcpy to avoid alignment issues and let compiler optimize
|
||||
__builtin_memcpy(&word, data, sizeof(word));
|
||||
hash ^= word;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#define ITERABLE_BITSET_INDEX_CHECKS false
|
||||
|
||||
@@ -104,9 +105,11 @@ public:
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = size_t;
|
||||
using pointer = size_t*;
|
||||
using reference = size_t&;
|
||||
using pointer = void;
|
||||
using reference = size_t;
|
||||
|
||||
private:
|
||||
size_t storeIndex;
|
||||
@@ -142,13 +145,20 @@ public:
|
||||
auto operator!=(const Iter& rhs) const -> bool {
|
||||
return storeIndex != rhs.storeIndex || bitIndex != rhs.bitIndex;
|
||||
}
|
||||
void operator++() {
|
||||
auto operator++() -> Iter& {
|
||||
bitIndex++;
|
||||
if (bitIndex >= STORE_BITS) {
|
||||
storeIndex++;
|
||||
bitIndex = 0;
|
||||
}
|
||||
ToNext();
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator++(int) -> Iter {
|
||||
Iter previous = *this;
|
||||
++(*this);
|
||||
return previous;
|
||||
}
|
||||
|
||||
auto operator*() const -> size_t { return (storeIndex << BIT_SHIFT) + bitIndex; }
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
#include "RandomGenerator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <stdexcept>
|
||||
|
||||
constexpr double kOpenEndedHighThreshold = 95.0;
|
||||
constexpr double kOpenEndedLowThreshold = 5.0;
|
||||
@@ -67,7 +69,8 @@ auto RandomGenerator::ReverseOpenEndedPercentile() -> double {
|
||||
}
|
||||
|
||||
auto RandomGenerator::IntBetween(const int min, const int max) -> int {
|
||||
return int(DoubleZeroToOne() * (max - min)) + min;
|
||||
if (min >= max) { throw std::invalid_argument("random integer range must be non-empty"); }
|
||||
return std::min(int(DoubleZeroToOne() * (max - min)) + min, max - 1);
|
||||
}
|
||||
|
||||
auto RandomGenerator::IntBelow(const int max) -> int { return IntBetween(0, max); }
|
||||
@@ -94,6 +97,7 @@ StdLibraryGenerator::StdLibraryGenerator(const std::mt19937_64::result_type seed
|
||||
}
|
||||
|
||||
auto StdLibraryGenerator::IntBetween(const int min, const int max) -> int {
|
||||
if (min >= max) { throw std::invalid_argument("random integer range must be non-empty"); }
|
||||
std::uniform_int_distribution<int> unifInt(min, max - 1);
|
||||
return unifInt(engine);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
class RandomGenerator;
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
|
||||
template<class T>
|
||||
auto RandomElement(const std::vector<T>& vec) -> T {
|
||||
if (vec.empty()) { throw std::invalid_argument("cannot choose from an empty vector"); }
|
||||
return vec.at(static_cast<std::size_t>(IntBelow(static_cast<int>(vec.size()))));
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#ifndef EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
|
||||
#define EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -29,14 +31,24 @@ private:
|
||||
size_t position = 0;
|
||||
|
||||
auto DoubleZeroToOne() -> double override {
|
||||
if (position >= sequence.size()) { position = 0; }
|
||||
const double nextVal = sequence.at(position);
|
||||
position++;
|
||||
if (position >= sequence.size()) { position = 0; }
|
||||
return nextVal;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit SequenceRandomGenerator(std::vector<double> s) : sequence(std::move(s)) {}
|
||||
explicit SequenceRandomGenerator(std::vector<double> s) : sequence(std::move(s)) {
|
||||
if (sequence.empty()) {
|
||||
throw std::invalid_argument("sequence random generator requires at least one value");
|
||||
}
|
||||
for (const double value : sequence) {
|
||||
if (!std::isfinite(value) || value < 0.0 || value > 1.0) {
|
||||
throw std::invalid_argument(
|
||||
"sequence random generator values must be between 0 and 1");
|
||||
}
|
||||
}
|
||||
}
|
||||
static auto WithSequence(const std::vector<double>& s)
|
||||
-> std::shared_ptr<SequenceRandomGenerator> {
|
||||
return std::make_shared<SequenceRandomGenerator>(s);
|
||||
|
||||
@@ -9,18 +9,24 @@
|
||||
#ifndef EAGLE0_COMMON_BYTE_VECTOR_HPP
|
||||
#define EAGLE0_COMMON_BYTE_VECTOR_HPP
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <initializer_list>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
class byte_vector : public vector<uint8_t> {
|
||||
class byte_vector {
|
||||
private:
|
||||
std::vector<uint8_t> bytes_;
|
||||
|
||||
public:
|
||||
class HexException : public std::exception {
|
||||
const std::string description;
|
||||
@@ -35,60 +41,78 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
static auto HexNibbleToValue(const char nibble) -> uint8_t {
|
||||
if (nibble >= '0' && nibble <= '9') {
|
||||
return uint8_t(nibble - '0');
|
||||
} else if (nibble >= 'A' && nibble <= 'F') {
|
||||
return uint8_t(nibble - 'A' + 10);
|
||||
} else if (nibble >= 'a' && nibble <= 'f') {
|
||||
return uint8_t(nibble - 'a' + 10);
|
||||
static auto HexCharsToByte(const std::string_view digits) -> uint8_t {
|
||||
unsigned int parsedByte = 0;
|
||||
const auto [ptr, ec] =
|
||||
std::from_chars(digits.data(), digits.data() + digits.size(), parsedByte, 16);
|
||||
if (ec != std::errc() || ptr != digits.data() + digits.size()) {
|
||||
throw HexException(ptr == digits.data() + digits.size() ? digits.back() : *ptr);
|
||||
}
|
||||
throw HexException(nibble);
|
||||
}
|
||||
|
||||
static auto HexCharsToByte(std::istringstream& str) -> uint8_t {
|
||||
uint8_t b = 0;
|
||||
char c = 0;
|
||||
str >> c;
|
||||
b |= (HexNibbleToValue(c) << 4u);
|
||||
str >> c;
|
||||
b |= HexNibbleToValue(c);
|
||||
return b;
|
||||
return static_cast<uint8_t>(parsedByte);
|
||||
}
|
||||
|
||||
public:
|
||||
byte_vector() = default;
|
||||
byte_vector(const std::initializer_list<uint8_t> list) : vector<uint8_t>(list) {}
|
||||
byte_vector(const std::initializer_list<uint8_t> list) : bytes_(list) {}
|
||||
template<class InputIt>
|
||||
byte_vector(InputIt begin, InputIt end) : vector<uint8_t>(begin, end) {}
|
||||
byte_vector(InputIt begin, InputIt end) : bytes_(begin, end) {}
|
||||
byte_vector(const byte_vector& rhs) = default;
|
||||
explicit byte_vector(const std::string& s) : byte_vector(s.begin(), s.end()) {}
|
||||
explicit byte_vector(const size_t size) : vector<uint8_t>(size) {}
|
||||
explicit byte_vector(void* data, const size_t size) : vector<uint8_t>(size) {
|
||||
this->assign((uint8_t*)data, (uint8_t*)data + size);
|
||||
byte_vector(byte_vector&& rhs) noexcept = default;
|
||||
explicit byte_vector(const std::string& s) : bytes_(s.begin(), s.end()) {}
|
||||
explicit byte_vector(std::vector<uint8_t> bytes) : bytes_(std::move(bytes)) {}
|
||||
explicit byte_vector(const size_t size) : bytes_(size) {}
|
||||
explicit byte_vector(const void* data, const size_t size) {
|
||||
auto* bytes = static_cast<const uint8_t*>(data);
|
||||
bytes_.assign(bytes, bytes + size);
|
||||
}
|
||||
|
||||
auto operator==(const vector<uint8_t>& vec) -> bool {
|
||||
if (this->size() != vec.size()) return false;
|
||||
for (size_t i = 0; i < size(); i++) {
|
||||
if ((*this)[i] != vec[i]) return false;
|
||||
}
|
||||
return true;
|
||||
auto operator=(const byte_vector& rhs) -> byte_vector& = default;
|
||||
auto operator=(byte_vector&& rhs) noexcept -> byte_vector& = default;
|
||||
|
||||
[[nodiscard]] auto operator==(const std::vector<uint8_t>& vec) const -> bool {
|
||||
return bytes_ == vec;
|
||||
}
|
||||
[[nodiscard]] auto operator==(const byte_vector& vec) const -> bool {
|
||||
return bytes_ == vec.bytes_;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto data() -> uint8_t* { return bytes_.data(); }
|
||||
[[nodiscard]] auto data() const -> const uint8_t* { return bytes_.data(); }
|
||||
[[nodiscard]] auto size() const -> size_t { return bytes_.size(); }
|
||||
[[nodiscard]] auto empty() const -> bool { return bytes_.empty(); }
|
||||
void reserve(const size_t count) { bytes_.reserve(count); }
|
||||
void resize(const size_t count) { bytes_.resize(count); }
|
||||
void swap(byte_vector& other) noexcept { bytes_.swap(other.bytes_); }
|
||||
|
||||
[[nodiscard]] auto begin() -> std::vector<uint8_t>::iterator { return bytes_.begin(); }
|
||||
[[nodiscard]] auto begin() const -> std::vector<uint8_t>::const_iterator {
|
||||
return bytes_.begin();
|
||||
}
|
||||
[[nodiscard]] auto end() -> std::vector<uint8_t>::iterator { return bytes_.end(); }
|
||||
[[nodiscard]] auto end() const -> std::vector<uint8_t>::const_iterator { return bytes_.end(); }
|
||||
[[nodiscard]] auto cbegin() const -> std::vector<uint8_t>::const_iterator {
|
||||
return bytes_.cbegin();
|
||||
}
|
||||
[[nodiscard]] auto cend() const -> std::vector<uint8_t>::const_iterator {
|
||||
return bytes_.cend();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator[](const size_t index) -> uint8_t& { return bytes_[index]; }
|
||||
[[nodiscard]] auto operator[](const size_t index) const -> uint8_t { return bytes_[index]; }
|
||||
|
||||
auto append(const char* bytes, const size_t length) {
|
||||
const auto previousSize = this->size();
|
||||
this->resize(previousSize + length);
|
||||
std::memcpy(this->data() + previousSize, bytes, length);
|
||||
const auto previousSize = size();
|
||||
resize(previousSize + length);
|
||||
std::memcpy(data() + previousSize, bytes, length);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
auto append(const T* elts, const size_t count) {
|
||||
append((char*)elts, count * sizeof(T));
|
||||
append(reinterpret_cast<const char*>(elts), count * sizeof(T));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
auto append(const vector<T> elts) {
|
||||
auto append(const vector<T>& elts) {
|
||||
append(elts.data(), elts.size());
|
||||
}
|
||||
|
||||
@@ -98,7 +122,7 @@ public:
|
||||
}
|
||||
|
||||
auto operator+=(const uint8_t c) -> byte_vector& {
|
||||
this->insert(end(), c);
|
||||
bytes_.push_back(c);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -111,7 +135,7 @@ public:
|
||||
|
||||
template<class Iterable>
|
||||
auto operator+=(const Iterable& rhs) -> byte_vector& {
|
||||
this->insert(end(), rhs.cbegin(), rhs.cend());
|
||||
bytes_.insert(end(), rhs.cbegin(), rhs.cend());
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -119,7 +143,7 @@ public:
|
||||
if (size() < 8) {
|
||||
uint64_t total = 0;
|
||||
for (size_t i = 0; i < size(); i++) {
|
||||
total <<= 8u;
|
||||
total <<= 8U;
|
||||
total += (*this)[i];
|
||||
}
|
||||
return total;
|
||||
@@ -128,9 +152,9 @@ public:
|
||||
uint64_t firstBytes = 0;
|
||||
uint64_t lastBytes = 0;
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
firstBytes <<= 8u;
|
||||
firstBytes <<= 8U;
|
||||
firstBytes += (*this)[i];
|
||||
lastBytes <<= 8u;
|
||||
lastBytes <<= 8U;
|
||||
lastBytes += (*this)[i];
|
||||
}
|
||||
|
||||
@@ -141,8 +165,8 @@ public:
|
||||
std::ifstream inputFileStream(path, std::ios::binary | std::ios::ate);
|
||||
const std::streamsize size = inputFileStream.tellg();
|
||||
inputFileStream.seekg(0, std::ios::beg);
|
||||
this->resize((size_t(size)));
|
||||
inputFileStream.read((char*)this->data(), size);
|
||||
this->resize(static_cast<size_t>(size));
|
||||
inputFileStream.read(reinterpret_cast<char*>(this->data()), size);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -153,34 +177,41 @@ public:
|
||||
}
|
||||
const std::streamsize size = inputFileStream.tellg();
|
||||
inputFileStream.seekg(0, std::ios::beg);
|
||||
byte_vector serializedGame((size_t(size)));
|
||||
inputFileStream.read((char*)serializedGame.data(), size);
|
||||
byte_vector serializedGame(static_cast<size_t>(size));
|
||||
inputFileStream.read(reinterpret_cast<char*>(serializedGame.data()), size);
|
||||
return serializedGame;
|
||||
}
|
||||
|
||||
static auto FromHex(const char* hexString) -> byte_vector { return FromHex(string(hexString)); }
|
||||
|
||||
static auto FromHex(string hexString) -> byte_vector {
|
||||
if (hexString[0] == '0' && (hexString[1] == 'x' || hexString[1] == 'X')) {
|
||||
hexString = string(hexString.begin() + 1, hexString.end());
|
||||
static auto FromHex(const string& hexString) -> byte_vector {
|
||||
std::string_view digits(hexString);
|
||||
if (digits.starts_with("0x") || digits.starts_with("0X")) {
|
||||
digits.remove_prefix(2);
|
||||
} else if (digits.starts_with("x") || digits.starts_with("X")) {
|
||||
digits.remove_prefix(1);
|
||||
}
|
||||
if (hexString[0] == 'x' || hexString[0] == 'X') {
|
||||
hexString = string(hexString.begin() + 1, hexString.end());
|
||||
}
|
||||
byte_vector vec;
|
||||
const size_t len = hexString.size();
|
||||
vec.reserve(len / 2);
|
||||
std::istringstream hexStream(hexString);
|
||||
|
||||
while (size_t(hexStream.tellg()) < len) { vec += HexCharsToByte(hexStream); }
|
||||
if (digits.size() % 2 != 0) { throw HexException(digits.empty() ? '\0' : digits.back()); }
|
||||
|
||||
byte_vector vec;
|
||||
vec.reserve(digits.size() / 2);
|
||||
|
||||
for (size_t index = 0; index < digits.size(); index += 2) {
|
||||
vec += HexCharsToByte(digits.substr(index, 2));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
explicit operator std::string() const { return std::string(begin(), end()); }
|
||||
};
|
||||
|
||||
inline auto operator<<(std::ostream& ostr, byte_vector vec) -> std::ostream& {
|
||||
ostr.write((char*)vec.data(), (std::streamsize)vec.size());
|
||||
inline auto operator==(const std::vector<uint8_t>& lhs, const byte_vector& rhs) -> bool {
|
||||
return rhs == lhs;
|
||||
}
|
||||
|
||||
inline auto operator<<(std::ostream& ostr, const byte_vector& vec) -> std::ostream& {
|
||||
ostr.write(reinterpret_cast<const char*>(vec.data()), static_cast<std::streamsize>(vec.size()));
|
||||
return ostr;
|
||||
}
|
||||
|
||||
|
||||
@@ -209,8 +209,8 @@ auto AbstractMCTSAI::BuildMCTSTree(
|
||||
}));
|
||||
}
|
||||
|
||||
// Wait for all threads to complete
|
||||
for (auto& future : futures) { future.wait(); }
|
||||
// Wait for all threads to complete and propagate worker exceptions.
|
||||
for (auto& future : futures) { future.get(); }
|
||||
} else {
|
||||
// Single-threaded MCTS
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
|
||||
@@ -18,7 +18,7 @@ struct alignas(64) OneMapInfo {
|
||||
std::map<int, int> positionsRequiringCrossing;
|
||||
};
|
||||
|
||||
auto CalculateMap(
|
||||
[[nodiscard]] auto CalculateMap(
|
||||
const std::string& mapName,
|
||||
const std::shared_ptr<shardok::ActionPointDistancesCache>& apdCache,
|
||||
const shardok::BattalionTypeSPtr& battalionType) -> OneMapInfo;
|
||||
|
||||
@@ -25,25 +25,44 @@ DIST_T NormalizedCostWhenBraving(const DIST_T cost) {
|
||||
|
||||
struct TargetAndDistance {
|
||||
Coords target;
|
||||
size_t targetIndex;
|
||||
CoordsSet attackLocations;
|
||||
double targetPower;
|
||||
DIST_T distance;
|
||||
|
||||
TargetAndDistance(const Coords& t, const CoordsSet& al, const double tp, const DIST_T d)
|
||||
TargetAndDistance(
|
||||
const Coords& t,
|
||||
const size_t ti,
|
||||
const CoordsSet& al,
|
||||
const double tp,
|
||||
const DIST_T d)
|
||||
: target(t),
|
||||
targetIndex(ti),
|
||||
attackLocations(al),
|
||||
targetPower(tp),
|
||||
distance(d) {}
|
||||
};
|
||||
|
||||
auto CoordsIndex(const Coords& coords, const int columnCount) {
|
||||
return coords.row() * columnCount + coords.column();
|
||||
}
|
||||
|
||||
auto CoordsFromIndex(const size_t index, const int columnCount) -> Coords {
|
||||
return Coords(
|
||||
static_cast<int8_t>(index / static_cast<size_t>(columnCount)),
|
||||
static_cast<int8_t>(index % static_cast<size_t>(columnCount)));
|
||||
}
|
||||
|
||||
auto MinDistance(
|
||||
const Coords& start,
|
||||
const CoordsSet& destinations,
|
||||
const ActionPointDistances* apd) -> DIST_T {
|
||||
DIST_T minDistance = ActionPointDistances::IMPOSSIBLE;
|
||||
const int startIndex = CoordsIndex(start, destinations.ColumnCount());
|
||||
|
||||
for (const Coords& dest : destinations) {
|
||||
if (const DIST_T distance = apd->Distance(start, dest); distance < minDistance) {
|
||||
for (const auto destIndex : destinations.indexIterator()) {
|
||||
if (const DIST_T distance = apd->Distance(startIndex, static_cast<int>(destIndex));
|
||||
distance < minDistance) {
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
@@ -113,10 +132,6 @@ auto EffectiveDistance(
|
||||
|
||||
auto Power(const Unit* unit) -> double { return unit->battalion().size(); }
|
||||
|
||||
auto CoordsIndex(const Coords& coords, const int columnCount) {
|
||||
return coords.row() * columnCount + coords.column();
|
||||
}
|
||||
|
||||
// Chooses a list of targets in priority order for each unit.
|
||||
auto GenerateTargetPriorities(
|
||||
const vector<const Unit*>& occupants,
|
||||
@@ -163,10 +178,10 @@ auto GenerateTargetPriorities(
|
||||
bravingApd = apdCache->GetRaw(map, mapId, battType, true, braveWaterCost);
|
||||
}
|
||||
|
||||
for (const Coords& targetLocation : targets) {
|
||||
const auto coordsIndex = CoordsIndex(targetLocation, cc);
|
||||
const auto& occupant = occupants[coordsIndex];
|
||||
for (const auto targetIndex : targets.indexIterator()) {
|
||||
const auto& occupant = occupants[targetIndex];
|
||||
|
||||
const Coords targetLocation = CoordsFromIndex(targetIndex, cc);
|
||||
const auto& attackLocations = alCache->CachedLocations(targetLocation, isLateGame);
|
||||
|
||||
if (occupant && occupant->player_id() != attackerId) {
|
||||
@@ -179,12 +194,14 @@ auto GenerateTargetPriorities(
|
||||
|
||||
targetsWithDistance.emplace_back(
|
||||
targetLocation,
|
||||
targetIndex,
|
||||
attackLocsForUnit,
|
||||
occupantPower,
|
||||
distance);
|
||||
} else {
|
||||
targetsWithDistance.emplace_back(
|
||||
targetLocation,
|
||||
targetIndex,
|
||||
attackLocations.LocationsWithEnemyInRange(unit),
|
||||
occupantPower,
|
||||
ActionPointDistances::IMPOSSIBLE);
|
||||
@@ -195,13 +212,11 @@ auto GenerateTargetPriorities(
|
||||
// Sort by distance
|
||||
std::ranges::sort(
|
||||
targetsWithDistance,
|
||||
[&powerAttackingEachTarget,
|
||||
cc](const TargetAndDistance& left, const TargetAndDistance& right) {
|
||||
const auto leftIndex = CoordsIndex(left.target, cc);
|
||||
const auto rightIndex = CoordsIndex(right.target, cc);
|
||||
|
||||
const double powerAttackingLeft = powerAttackingEachTarget[leftIndex];
|
||||
const double powerAttackingRight = powerAttackingEachTarget[rightIndex];
|
||||
[&powerAttackingEachTarget](
|
||||
const TargetAndDistance& left,
|
||||
const TargetAndDistance& right) {
|
||||
const double powerAttackingLeft = powerAttackingEachTarget[left.targetIndex];
|
||||
const double powerAttackingRight = powerAttackingEachTarget[right.targetIndex];
|
||||
|
||||
const bool leftAlreadyOverwhelmed =
|
||||
powerAttackingLeft >= kOverpowerRatio * left.targetPower;
|
||||
@@ -215,8 +230,7 @@ auto GenerateTargetPriorities(
|
||||
});
|
||||
|
||||
if (!targetsWithDistance.empty()) {
|
||||
powerAttackingEachTarget[CoordsIndex(targetsWithDistance.front().target, cc)] +=
|
||||
Power(unit);
|
||||
powerAttackingEachTarget[targetsWithDistance.front().targetIndex] += Power(unit);
|
||||
}
|
||||
|
||||
tpl.priorityOrder.reserve(targetsWithDistance.size());
|
||||
|
||||
@@ -114,8 +114,7 @@ auto AttackLocationsCache::CachedLocations(const CoordsSet &targets, const bool
|
||||
-> AttackLocations {
|
||||
AttackLocations al(rowCount, columnCount);
|
||||
|
||||
for (const auto &target : targets) {
|
||||
const int index = target.row() * columnCount + target.column();
|
||||
for (const auto index : targets.indexIterator()) {
|
||||
if (lateGame) {
|
||||
al += lateGameLocations[index];
|
||||
} else {
|
||||
@@ -137,4 +136,4 @@ auto AttackLocationsCache::CachedLocations(const vector<const Unit *> &enemies,
|
||||
return CachedLocations(targets, lateGame);
|
||||
}
|
||||
|
||||
} // namespace shardok
|
||||
} // namespace shardok
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "AICommandFilter.hpp"
|
||||
#include "TranspositionTable.hpp"
|
||||
@@ -21,12 +23,49 @@
|
||||
|
||||
namespace shardok {
|
||||
|
||||
static const std::vector<double> kAverageSequence = {0.5};
|
||||
static const auto kAverageGenerator = std::make_shared<SequenceRandomGenerator>(kAverageSequence);
|
||||
|
||||
constexpr bool kMultithread = true;
|
||||
constexpr bool kLogging = false;
|
||||
|
||||
[[nodiscard]] static auto AverageGenerator() -> std::shared_ptr<SequenceRandomGenerator> {
|
||||
return std::make_shared<SequenceRandomGenerator>(std::vector{0.5});
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto DescribeCommandForLog(const CommandSPtr& command) -> std::string {
|
||||
if (!command) { return "<null command>"; }
|
||||
|
||||
std::ostringstream out;
|
||||
out << "type=" << command->GetCommandType()
|
||||
<< " player=" << static_cast<int>(command->GetPlayerId())
|
||||
<< " actor=" << command->GetActorUnitId() << " target=("
|
||||
<< static_cast<int>(command->GetTargetRow()) << ','
|
||||
<< static_cast<int>(command->GetTargetColumn()) << ')';
|
||||
|
||||
if (command->HasOdds()) { out << " odds_percentile=" << command->GetOddsPercentile(); }
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto DescribeCommandAt(
|
||||
const CommandListSPtr& commands,
|
||||
const size_t commandIndex) -> std::string {
|
||||
if (!commands) { return "command=<null command list>"; }
|
||||
if (commandIndex >= commands->size()) {
|
||||
std::ostringstream out;
|
||||
out << "command=<index " << commandIndex << " out of " << commands->size() << ">";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::ostringstream out;
|
||||
out << "command={" << DescribeCommandForLog(commands->at(commandIndex)) << '}';
|
||||
return out.str();
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto WithExceptionContext(
|
||||
const std::string& context,
|
||||
const std::exception& exception) -> std::runtime_error {
|
||||
return std::runtime_error(context + ": " + exception.what());
|
||||
}
|
||||
|
||||
static auto IsDeterministic(const CommandType type) -> bool {
|
||||
switch (type) {
|
||||
case net::eagle0::shardok::common::MOVE_COMMAND:
|
||||
@@ -39,13 +78,11 @@ static auto IsDeterministic(const CommandType type) -> bool {
|
||||
case net::eagle0::shardok::common::PLACE_HIDDEN_UNIT_COMMAND:
|
||||
case net::eagle0::shardok::common::UNIT_STOP_COMMAND:
|
||||
case net::eagle0::shardok::common::UNIT_REST_COMMAND:
|
||||
case net::eagle0::shardok::common::FLEE_COMMAND:
|
||||
case net::eagle0::shardok::common::REINFORCE_COMMAND:
|
||||
case net::eagle0::shardok::common::RETREAT_COMMAND:
|
||||
case net::eagle0::shardok::common::END_PLAYER_SETUP_COMMAND:
|
||||
case net::eagle0::shardok::common::HIDE_COMMAND:
|
||||
case net::eagle0::shardok::common::FORTIFY_COMMAND:
|
||||
case net::eagle0::shardok::common::BECOME_OUTLAW_COMMAND:
|
||||
case net::eagle0::shardok::common::HOLY_WAVE_COMMAND:
|
||||
case net::eagle0::shardok::common::EVACUATE_PRISONERS_COMMAND:
|
||||
case net::eagle0::shardok::common::REPAIR_COMMAND: return true;
|
||||
@@ -59,6 +96,14 @@ static auto RandomnessSampleForRepeat(const int repeatIteration, const int maxRe
|
||||
return static_cast<double>(repeatIteration) / static_cast<double>(maxRepeatCount - 1);
|
||||
}
|
||||
|
||||
static auto SuccessSampleForOdds(const double successChance) -> double {
|
||||
return successChance / 2.0;
|
||||
}
|
||||
|
||||
static auto FailureSampleForOdds(const double successChance) -> double {
|
||||
return successChance + ((1.0 - successChance) / 2.0);
|
||||
}
|
||||
|
||||
static auto CommandSorter(
|
||||
const AICommandEvaluator::IndexAndScore& l,
|
||||
const AICommandEvaluator::IndexAndScore& r) -> bool {
|
||||
@@ -133,17 +178,34 @@ auto AICommandEvaluator::PerformLookahead(
|
||||
std::launch::deferred,
|
||||
[bestCommandFuture = std::move(bestCommandFuture),
|
||||
innerEngine,
|
||||
nextCommands,
|
||||
pid,
|
||||
nextUtility,
|
||||
remainingLookahead]() mutable -> EvaluationResult {
|
||||
const auto bestCommand = bestCommandFuture.get();
|
||||
IndexAndScore bestCommand;
|
||||
try {
|
||||
bestCommand = bestCommandFuture.get();
|
||||
} catch (const std::exception& e) {
|
||||
std::ostringstream context;
|
||||
context << "AI PerformLookahead failed while resolving best command"
|
||||
<< " player=" << static_cast<int>(pid)
|
||||
<< " remaining_lookahead=" << remainingLookahead
|
||||
<< " next_command_count=" << nextCommands->size();
|
||||
throw WithExceptionContext(context.str(), e);
|
||||
}
|
||||
if (!bestCommand.completed) {
|
||||
return EvaluationResult{.score = nextUtility, .completed = false};
|
||||
}
|
||||
if (bestCommand.index >= nextCommands->size()) {
|
||||
std::cerr << "AI lookahead produced command index " << bestCommand.index
|
||||
<< " with only " << nextCommands->size()
|
||||
<< " available commands for player " << static_cast<int>(pid)
|
||||
<< " at remaining lookahead " << remainingLookahead << "\n";
|
||||
return EvaluationResult{.score = nextUtility, .completed = false};
|
||||
}
|
||||
|
||||
ScoreValue resultScore;
|
||||
if (auto& nextCommand = innerEngine->GetAvailableCommandsForAIPlayer(pid)->at(
|
||||
bestCommand.index);
|
||||
if (const auto& nextCommand = nextCommands->at(bestCommand.index);
|
||||
nextCommand->GetCommandType() !=
|
||||
net::eagle0::shardok::common::END_TURN_COMMAND) {
|
||||
resultScore = bestCommand.immediateScore;
|
||||
@@ -194,14 +256,36 @@ auto AICommandEvaluator::EvaluateWithRandomness(
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
const auto guessedCommands = guessedEngine.GetAvailableCommandsForAIPlayer(pid);
|
||||
auto innerEngine = std::make_shared<ShardokEngine>(guessedEngine, false);
|
||||
innerEngine->PostCommand(pid, commandIndex, randomGenerator);
|
||||
try {
|
||||
innerEngine->PostCommand(pid, commandIndex, randomGenerator);
|
||||
} catch (const std::exception& e) {
|
||||
std::ostringstream context;
|
||||
context << "AI EvaluateWithRandomness failed while posting simulated command"
|
||||
<< " player=" << static_cast<int>(pid) << " command_index=" << commandIndex
|
||||
<< " guessed_command_count=" << guessedCommands->size()
|
||||
<< " remaining_lookahead=" << remainingLookahead << ' '
|
||||
<< DescribeCommandAt(guessedCommands, commandIndex);
|
||||
throw WithExceptionContext(context.str(), e);
|
||||
}
|
||||
|
||||
auto innerUtility = scorer_.GuessedStateScore(
|
||||
isDefender,
|
||||
innerEngine->GetCurrentGameState(),
|
||||
attackerStrategy,
|
||||
allCastleCoords);
|
||||
ScoreValue innerUtility;
|
||||
try {
|
||||
innerUtility = scorer_.GuessedStateScore(
|
||||
isDefender,
|
||||
innerEngine->GetCurrentGameState(),
|
||||
attackerStrategy,
|
||||
allCastleCoords);
|
||||
} catch (const std::exception& e) {
|
||||
std::ostringstream context;
|
||||
context << "AI EvaluateWithRandomness failed while scoring simulated state"
|
||||
<< " player=" << static_cast<int>(pid) << " command_index=" << commandIndex
|
||||
<< " guessed_command_count=" << guessedCommands->size()
|
||||
<< " remaining_lookahead=" << remainingLookahead << ' '
|
||||
<< DescribeCommandAt(guessedCommands, commandIndex);
|
||||
throw WithExceptionContext(context.str(), e);
|
||||
}
|
||||
|
||||
returnValue.immediateScore = innerUtility;
|
||||
returnValue.completed = true;
|
||||
@@ -216,22 +300,33 @@ auto AICommandEvaluator::EvaluateWithRandomness(
|
||||
isDefender,
|
||||
remainingLookahead,
|
||||
maxRepeatCount,
|
||||
commandIndex,
|
||||
innerEngine,
|
||||
attackerStrategy,
|
||||
innerUtility,
|
||||
&allCastleCoords,
|
||||
guessedCommands,
|
||||
allCastleCoords,
|
||||
deadline]() -> EvaluationResult {
|
||||
auto lookaheadFuture = PerformLookahead(
|
||||
pid,
|
||||
isDefender,
|
||||
remainingLookahead,
|
||||
maxRepeatCount,
|
||||
innerEngine,
|
||||
innerUtility,
|
||||
attackerStrategy,
|
||||
allCastleCoords,
|
||||
deadline);
|
||||
return lookaheadFuture.get();
|
||||
try {
|
||||
auto lookaheadFuture = PerformLookahead(
|
||||
pid,
|
||||
isDefender,
|
||||
remainingLookahead,
|
||||
maxRepeatCount,
|
||||
innerEngine,
|
||||
innerUtility,
|
||||
attackerStrategy,
|
||||
allCastleCoords,
|
||||
deadline);
|
||||
return lookaheadFuture.get();
|
||||
} catch (const std::exception& e) {
|
||||
std::ostringstream context;
|
||||
context << "AI EvaluateWithRandomness failed during recursive lookahead"
|
||||
<< " player=" << static_cast<int>(pid) << " command_index=" << commandIndex
|
||||
<< " remaining_lookahead=" << remainingLookahead << ' '
|
||||
<< DescribeCommandAt(guessedCommands, commandIndex);
|
||||
throw WithExceptionContext(context.str(), e);
|
||||
}
|
||||
};
|
||||
|
||||
if constexpr (kMultithread) {
|
||||
@@ -333,7 +428,19 @@ auto AICommandEvaluator::FindBestCommand(
|
||||
|
||||
for (uint32_t index = 0; index < commandCount; ++index) {
|
||||
const auto originalIndex = filteredIndices[index];
|
||||
const auto& guessedDescriptor = guessedDescriptors->at(originalIndex);
|
||||
const CommandSPtr guessedDescriptor = [&] {
|
||||
try {
|
||||
return guessedDescriptors->at(originalIndex);
|
||||
} catch (const std::exception& e) {
|
||||
std::ostringstream context;
|
||||
context << "AI FindBestCommand failed while reading filtered command"
|
||||
<< " player=" << static_cast<int>(pid) << " filtered_index=" << index
|
||||
<< " original_index=" << originalIndex
|
||||
<< " guessed_command_count=" << guessedDescriptors->size()
|
||||
<< " filtered_command_count=" << filteredIndices.size();
|
||||
throw WithExceptionContext(context.str(), e);
|
||||
}
|
||||
}();
|
||||
const auto guessedCommandType = guessedDescriptor->GetCommandType();
|
||||
|
||||
commandEvaluations[index].index = originalIndex;
|
||||
@@ -351,7 +458,7 @@ auto AICommandEvaluator::FindBestCommand(
|
||||
originalIndex,
|
||||
remainingLookahead,
|
||||
maxRepeatCount,
|
||||
kAverageGenerator,
|
||||
AverageGenerator(),
|
||||
guessedEngine,
|
||||
attackerStrategy,
|
||||
allCastleCoords,
|
||||
@@ -365,7 +472,7 @@ auto AICommandEvaluator::FindBestCommand(
|
||||
const auto successChancePercentile = guessedDescriptor->GetOddsPercentile();
|
||||
const double successChance = static_cast<double>(successChancePercentile) / 100.0;
|
||||
|
||||
// Success attempt uses 1.0 - (successChance / 2) as the roll
|
||||
// Success attempt uses the midpoint of the successful percentile range.
|
||||
auto successEvaluation = EvaluateWithRandomness(
|
||||
pid,
|
||||
isDefender,
|
||||
@@ -373,13 +480,13 @@ auto AICommandEvaluator::FindBestCommand(
|
||||
remainingLookahead,
|
||||
maxRepeatCount,
|
||||
std::make_shared<SequenceRandomGenerator>(
|
||||
std::vector{1.0 - successChance / 2.0}),
|
||||
std::vector{SuccessSampleForOdds(successChance)}),
|
||||
guessedEngine,
|
||||
attackerStrategy,
|
||||
allCastleCoords,
|
||||
deadline);
|
||||
|
||||
// Failure attempt uses the average of (1 - successChance) and 0 as the roll
|
||||
// Failure attempt uses the midpoint of the failing percentile range.
|
||||
auto failureEvaluation = EvaluateWithRandomness(
|
||||
pid,
|
||||
isDefender,
|
||||
@@ -387,7 +494,7 @@ auto AICommandEvaluator::FindBestCommand(
|
||||
remainingLookahead,
|
||||
maxRepeatCount,
|
||||
std::make_shared<SequenceRandomGenerator>(
|
||||
std::vector{(1.0 - successChance) / 2.0}),
|
||||
std::vector{FailureSampleForOdds(successChance)}),
|
||||
guessedEngine,
|
||||
attackerStrategy,
|
||||
allCastleCoords,
|
||||
@@ -455,7 +562,17 @@ auto AICommandEvaluator::FindBestCommand(
|
||||
ScoreValue totalLookaheadScore = 0.0;
|
||||
bool completed = eval.immediateCompleted;
|
||||
for (auto& future : eval.lookaheadFutures) {
|
||||
const auto lookahead = future.get();
|
||||
EvaluationResult lookahead;
|
||||
try {
|
||||
lookahead = future.get();
|
||||
} catch (const std::exception& e) {
|
||||
std::ostringstream context;
|
||||
context << "AI FindBestCommand failed while resolving lookahead future"
|
||||
<< " command_index=" << eval.index
|
||||
<< " command_type=" << eval.type
|
||||
<< " lookahead_future_count=" << eval.lookaheadFutures.size();
|
||||
throw WithExceptionContext(context.str(), e);
|
||||
}
|
||||
if (!lookahead.completed) { completed = false; }
|
||||
totalLookaheadScore += lookahead.score;
|
||||
}
|
||||
@@ -522,7 +639,7 @@ auto AICommandEvaluator::EvaluateCommand(
|
||||
commandIndex,
|
||||
remainingLookahead,
|
||||
maxRepeatCount,
|
||||
kAverageGenerator,
|
||||
AverageGenerator(),
|
||||
guessedEngine,
|
||||
attackerStrategy,
|
||||
allCastleCoords,
|
||||
@@ -541,7 +658,8 @@ auto AICommandEvaluator::EvaluateCommand(
|
||||
commandIndex,
|
||||
remainingLookahead,
|
||||
maxRepeatCount,
|
||||
std::make_shared<SequenceRandomGenerator>(std::vector{1.0 - successChance / 2.0}),
|
||||
std::make_shared<SequenceRandomGenerator>(
|
||||
std::vector{SuccessSampleForOdds(successChance)}),
|
||||
guessedEngine,
|
||||
attackerStrategy,
|
||||
allCastleCoords,
|
||||
@@ -554,7 +672,8 @@ auto AICommandEvaluator::EvaluateCommand(
|
||||
commandIndex,
|
||||
remainingLookahead,
|
||||
maxRepeatCount,
|
||||
std::make_shared<SequenceRandomGenerator>(std::vector{(1.0 - successChance) / 2.0}),
|
||||
std::make_shared<SequenceRandomGenerator>(
|
||||
std::vector{FailureSampleForOdds(successChance)}),
|
||||
guessedEngine,
|
||||
attackerStrategy,
|
||||
allCastleCoords,
|
||||
|
||||
@@ -116,8 +116,9 @@ auto DefenderDistanceBuf(
|
||||
double sum = 0.0;
|
||||
double decr = kPerUnitDebufDecay;
|
||||
for (const WithoutAndWith &pointCost : pointCosts) {
|
||||
const int effectiveCost =
|
||||
(pointCost.without == 999) ? int(1.2 * pointCost.with) : pointCost.without;
|
||||
const int effectiveCost = (pointCost.without == 999)
|
||||
? static_cast<int>(1.2 * pointCost.with)
|
||||
: pointCost.without;
|
||||
sum += decr / (effectiveCost + 1);
|
||||
decr *= kPerUnitDebufDecay;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace shardok {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr double kCertainCombatWinChance = 0.95;
|
||||
|
||||
void WritePercent(std::ostream& output, const double value) {
|
||||
const auto flags = output.flags();
|
||||
const auto precision = output.precision();
|
||||
@@ -154,8 +156,8 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
|
||||
const CommandListSPtr& availableCommands,
|
||||
const CommandList::const_iterator& fleeCommand,
|
||||
int maxRounds,
|
||||
int minimumFleeOddsThreshold,
|
||||
int desperateFleeThreshold,
|
||||
int /*minimumFleeOddsThreshold*/,
|
||||
int /*desperateFleeThreshold*/,
|
||||
bool enableDebugLogging) -> FleeDecision {
|
||||
// Get flee success odds
|
||||
const int fleeSuccessChance = (*fleeCommand)->GetOddsPercentile();
|
||||
@@ -164,67 +166,31 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
|
||||
std::cout << "AI FinalRound: Evaluating flee (odds=" << fleeSuccessChance << "%)...\n";
|
||||
}
|
||||
|
||||
// Check if flee odds are good enough to attempt
|
||||
if (fleeSuccessChance >= minimumFleeOddsThreshold) {
|
||||
if (enableDebugLogging) {
|
||||
std::cout << "AI FinalRound: Good flee odds (" << fleeSuccessChance
|
||||
<< "% >= " << minimumFleeOddsThreshold << "%), choosing flee\n";
|
||||
}
|
||||
return FleeDecision{
|
||||
true,
|
||||
GetFleeCommandIndex(fleeCommand, availableCommands),
|
||||
"Good flee odds"};
|
||||
}
|
||||
|
||||
// Low flee odds - evaluate if fighting might be better
|
||||
const double combatWinChance = EstimateCombatSuccess(playerId, guessedState, maxRounds);
|
||||
|
||||
// If combat situation is hopeless, even bad flee odds are better than certain death
|
||||
if (combatWinChance <= 0.05 && fleeSuccessChance >= desperateFleeThreshold) {
|
||||
if (combatWinChance < kCertainCombatWinChance) {
|
||||
if (enableDebugLogging) {
|
||||
std::cout << "AI FinalRound: Combat hopeless (";
|
||||
std::cout << "AI FinalRound: Combat not certain (";
|
||||
WritePercent(std::cout, combatWinChance * 100);
|
||||
std::cout << "%), desperate flee attempt (" << fleeSuccessChance << "%)\n";
|
||||
std::cout << "% < ";
|
||||
WritePercent(std::cout, kCertainCombatWinChance * 100);
|
||||
std::cout << "%), choosing flee\n";
|
||||
}
|
||||
return FleeDecision{
|
||||
true,
|
||||
GetFleeCommandIndex(fleeCommand, availableCommands),
|
||||
"Combat hopeless, desperate flee"};
|
||||
"Last round combat not certain"};
|
||||
}
|
||||
|
||||
// Detailed flee vs fight comparison
|
||||
const double fleeChance = static_cast<double>(fleeSuccessChance) / 100.0;
|
||||
|
||||
// Compare expected outcomes:
|
||||
// - Flee: fleeChance of survival (not victory, but avoiding loss)
|
||||
// - Fight: combatWinChance of victory (better than survival)
|
||||
|
||||
constexpr double FLEE_VS_COMBAT_MARGIN =
|
||||
0.8; // Require 80% of combat chance to prefer fighting
|
||||
const double adjustedCombatThreshold = combatWinChance * FLEE_VS_COMBAT_MARGIN;
|
||||
|
||||
if (enableDebugLogging) {
|
||||
std::cout << "AI FinalRound: Flee=" << fleeSuccessChance << "%, Combat=";
|
||||
std::cout << "AI FinalRound: Combat effectively certain (";
|
||||
WritePercent(std::cout, combatWinChance * 100);
|
||||
std::cout << "%, Threshold=";
|
||||
WritePercent(std::cout, adjustedCombatThreshold * 100);
|
||||
std::cout << "% -> ";
|
||||
}
|
||||
|
||||
if (fleeChance > adjustedCombatThreshold) {
|
||||
if (enableDebugLogging) { std::cout << "FLEE (better odds)\n"; }
|
||||
return FleeDecision{
|
||||
true,
|
||||
GetFleeCommandIndex(fleeCommand, availableCommands),
|
||||
"Flee has better expected outcome"};
|
||||
} else {
|
||||
if (enableDebugLogging) { std::cout << "FIGHT (better expected outcome)\n"; }
|
||||
// Return 0 to indicate we should use standard command selection
|
||||
return FleeDecision{
|
||||
false,
|
||||
0, // Will be replaced by StandardChooseCommandIndex
|
||||
"Fighting has better expected outcome"};
|
||||
std::cout << "%), fighting\n";
|
||||
}
|
||||
return FleeDecision{
|
||||
false,
|
||||
0, // Will be replaced by StandardChooseCommandIndex
|
||||
"Combat effectively certain"};
|
||||
}
|
||||
|
||||
auto AIFleeDecisionCalculator::ShouldConsiderFleeing(
|
||||
|
||||
@@ -5,15 +5,34 @@
|
||||
#include "AIMinimumDistanceAndTarget.hpp"
|
||||
|
||||
namespace shardok {
|
||||
namespace {
|
||||
|
||||
auto CoordsIndex(const Coords &coords, const int columnCount) -> int {
|
||||
return coords.row() * columnCount + coords.column();
|
||||
}
|
||||
|
||||
auto CoordsFromIndex(const size_t index, const int columnCount) -> Coords {
|
||||
return Coords(
|
||||
static_cast<int8_t>(index / static_cast<size_t>(columnCount)),
|
||||
static_cast<int8_t>(index % static_cast<size_t>(columnCount)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto MinimumDistanceAndTarget(
|
||||
const ActionPointDistances *apd,
|
||||
const Coords &origin,
|
||||
const CoordsSet &destinations) -> CoordsAndDistance {
|
||||
CoordsAndDistance min{Coords(-1, -1), ActionPointDistances::IMPOSSIBLE};
|
||||
for (const auto &dest : destinations) {
|
||||
const DIST_T dist = apd->Distance(origin, dest);
|
||||
const int columnCount = destinations.ColumnCount();
|
||||
const int originIndex = CoordsIndex(origin, columnCount);
|
||||
|
||||
for (const auto destIndex : destinations.indexIterator()) {
|
||||
const DIST_T dist = apd->Distance(originIndex, static_cast<int>(destIndex));
|
||||
if (dist == ActionPointDistances::IMPOSSIBLE) continue;
|
||||
if (dist < min.distance) min = CoordsAndDistance{dest, dist};
|
||||
if (dist < min.distance) {
|
||||
min = CoordsAndDistance{CoordsFromIndex(destIndex, columnCount), dist};
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
@@ -25,4 +44,4 @@ auto MinimumDistance(
|
||||
const CoordsSet &destinations) -> int {
|
||||
return MinimumDistanceAndTarget(apd, origin, destinations).distance;
|
||||
}
|
||||
} // namespace shardok
|
||||
} // namespace shardok
|
||||
|
||||
@@ -25,7 +25,8 @@ using Unit = net::eagle0::shardok::storage::fb::Unit;
|
||||
net::eagle0::shardok::storage::fb::Profession profession) -> bool;
|
||||
|
||||
[[nodiscard]] auto CastleClaimCapableAttackerUnitCount(const GameStateW &gameState) -> int;
|
||||
[[nodiscard]] auto PlayerInfoForPid(const GameStateW &, PlayerId pid) -> const PlayerInfo *;
|
||||
[[nodiscard]] auto PlayerInfoForPid(const GameStateW &gameState, PlayerId pid)
|
||||
-> const PlayerInfo *;
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
|
||||
@@ -88,8 +88,8 @@ auto ContextFreeUnitValue(const Unit *unit) -> ScoreValue {
|
||||
break;
|
||||
}
|
||||
|
||||
const double battalionValue = battalionTypeMultiplier * (1.0 + armament / 100.0) *
|
||||
(1.0 + training / 100.0) * (0.5 + morale / 100.0) *
|
||||
const double battalionValue = battalionTypeMultiplier * (1.0 + (armament / 100.0)) *
|
||||
(1.0 + (training / 100.0)) * (0.5 + (morale / 100.0)) *
|
||||
unit->battalion().size();
|
||||
|
||||
const double heroValue =
|
||||
@@ -120,15 +120,16 @@ auto archeryValue(const vector<const Unit *> &enemyUnits, const bool canShootNow
|
||||
|
||||
auto reduceValue(const Unit *unit, const Terrain *unitTerrain) -> double {
|
||||
// TODO: make this depend on value of reducing castle too
|
||||
if (unit->fortified() || unitTerrain->modifier().castle().present())
|
||||
if (unit->fortified() || unitTerrain->modifier().castle().present()) {
|
||||
return kReduceFortifiedPossibleValue;
|
||||
else if (AllowsFortification(unitTerrain))
|
||||
return kReduceUnfortifiedPossibleValue;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
if (AllowsFortification(unitTerrain)) { return kReduceUnfortifiedPossibleValue; }
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
auto fearValue(const vector<const Unit *> &) -> double { return kFearPossibleValue; }
|
||||
auto fearValue([[maybe_unused]] const vector<const Unit *> &enemyUnits) -> double {
|
||||
return kFearPossibleValue;
|
||||
}
|
||||
|
||||
auto lightningValue(const Unit *unit) -> double {
|
||||
// TODO: make this depend on the value of the targets
|
||||
@@ -235,9 +236,8 @@ auto meteorValue(
|
||||
multiplier = kMeteorNoneRoundMultiplier * vigorFactor;
|
||||
}
|
||||
return maxValue * multiplier;
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
case net::eagle0::shardok::storage::fb::MultiroundMagicState_TARGET:
|
||||
@@ -247,9 +247,8 @@ auto meteorValue(
|
||||
map,
|
||||
enemyOccupants,
|
||||
friendlyOccupants);
|
||||
} else {
|
||||
return bestTargetValue(unit, map, enemyOccupants, friendlyOccupants, meteorRange);
|
||||
}
|
||||
return bestTargetValue(unit, map, enemyOccupants, friendlyOccupants, meteorRange);
|
||||
case net::eagle0::shardok::storage::fb::MultiroundMagicState_CAST:
|
||||
return kMeteorCastRoundMultiplier *
|
||||
meteorDropRawValue(
|
||||
|
||||
@@ -169,6 +169,7 @@ auto WaterCrossingTiles(
|
||||
auto *terr = const_cast<net::eagle0::shardok::storage::fb::Terrain *>(
|
||||
mapCopy->mutable_terrain()->GetMutableObject(index));
|
||||
terr->mutable_modifier().mutable_bridge().mutate_present(true);
|
||||
terr->mutable_modifier().mutable_bridge().mutate_integrity(100.0);
|
||||
terr->mutable_modifier().mutable_fire().mutate_present(false);
|
||||
|
||||
auto hash = ActionPointDistancesCache::GetMapId(mapCopy);
|
||||
|
||||
@@ -19,8 +19,11 @@
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -465,8 +468,10 @@ auto ShardokAIClient::ChooseCommandIndex(
|
||||
const GameSettingsSPtr &settings,
|
||||
const GameStateView &gsv,
|
||||
const CommandListSPtr &realAvailableCommands) const -> CommandChoiceResults {
|
||||
static int typeChosenCount[net::eagle0::shardok::common::CommandType_MAX + 1];
|
||||
static int totalChoices = 0;
|
||||
static std::array<std::atomic<int>, net::eagle0::shardok::common::CommandType_MAX + 1>
|
||||
typeChosenCount;
|
||||
static std::atomic<int> totalChoices = 0;
|
||||
static std::mutex reportMutex;
|
||||
|
||||
CommandChoiceResults results{};
|
||||
|
||||
@@ -484,15 +489,18 @@ auto ShardokAIClient::ChooseCommandIndex(
|
||||
}
|
||||
|
||||
const auto chosenType = (*realAvailableCommands)[results.chosenIndex]->GetCommandType();
|
||||
typeChosenCount[static_cast<int>(chosenType)]++;
|
||||
totalChoices++;
|
||||
typeChosenCount[static_cast<size_t>(chosenType)].fetch_add(1, std::memory_order_relaxed);
|
||||
const int updatedTotal = totalChoices.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
|
||||
if (totalChoices % 1000 == 0) {
|
||||
if (updatedTotal % 1000 == 0) {
|
||||
std::lock_guard lock(reportMutex);
|
||||
std::cout << "TYPES CHOSEN:\n";
|
||||
vector<std::pair<int, CommandType>> choices{};
|
||||
for (int i = 0; i <= net::eagle0::shardok::common::CommandType_MAX; ++i) {
|
||||
if (typeChosenCount[i] != 0) {
|
||||
choices.emplace_back(typeChosenCount[i], static_cast<CommandType>(i));
|
||||
if (const int count =
|
||||
typeChosenCount[static_cast<size_t>(i)].load(std::memory_order_relaxed);
|
||||
count != 0) {
|
||||
choices.emplace_back(count, static_cast<CommandType>(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,10 @@ TranspositionTable::probe(const GameStateW& state, int depth, PlayerId player) {
|
||||
uint64_t stored_hash = entry.hash_full.load(std::memory_order_relaxed);
|
||||
uint8_t stored_depth = entry.depth.load(std::memory_order_relaxed);
|
||||
uint8_t stored_player = entry.player_id.load(std::memory_order_relaxed);
|
||||
uint16_t stored_age = entry.age.load(std::memory_order_relaxed);
|
||||
|
||||
if (stored_hash == hash && stored_depth >= depth && stored_player == player) {
|
||||
if (stored_hash == hash && stored_depth >= depth && stored_player == player &&
|
||||
stored_age == current_age.load(std::memory_order_relaxed)) {
|
||||
stats.hits++;
|
||||
float score = entry.score.load(std::memory_order_relaxed);
|
||||
return static_cast<ScoreValue>(score);
|
||||
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
[[nodiscard]] PlayerId getPlayer() const { return player_; }
|
||||
[[nodiscard]] int getActorId() const { return actorId_; }
|
||||
[[nodiscard]] std::pair<int, int> getTarget() const { return {targetRow_, targetCol_}; }
|
||||
[[nodiscard]] bool hasOdds() const { return hasOdds_; }
|
||||
|
||||
private:
|
||||
// Store only essential fields (~25 bytes, all POD, cache-friendly)
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
|
||||
#include "ShardokAction.hpp"
|
||||
@@ -32,9 +34,30 @@ auto IsDefenderPlayer(const GameStateW& gameState, const PlayerId playerId) -> b
|
||||
return false;
|
||||
}
|
||||
|
||||
auto LegalActionsCacheKey(const uint64_t stateHash, const bool rootIsDefender) -> uint64_t {
|
||||
auto MixCacheKey(uint64_t seed, const uint64_t value) -> uint64_t {
|
||||
constexpr uint64_t kHashCombineConstant = 0x9e3779b97f4a7c15ULL;
|
||||
seed ^= value + kHashCombineConstant + (seed << 6) + (seed >> 2);
|
||||
return seed;
|
||||
}
|
||||
|
||||
auto PointerIdentityHash(const void* ptr) -> uint64_t {
|
||||
return static_cast<uint64_t>(reinterpret_cast<std::uintptr_t>(ptr));
|
||||
}
|
||||
|
||||
auto LegalActionsCacheKey(
|
||||
const uint64_t stateHash,
|
||||
const bool rootIsDefender,
|
||||
const GameSettings* gameSettings,
|
||||
const APDCache* apdCache) -> uint64_t {
|
||||
constexpr uint64_t kDefenderRoleHash = 0x9e3779b97f4a7c15ULL;
|
||||
return rootIsDefender ? stateHash ^ kDefenderRoleHash : stateHash;
|
||||
uint64_t key = rootIsDefender ? stateHash ^ kDefenderRoleHash : stateHash;
|
||||
|
||||
// The cached action set is the output of AICommandFilter, not just the raw command list.
|
||||
// Include the context objects that can affect filtering so a process-wide cache entry from
|
||||
// one search cannot be reused by a different settings/APD context with the same state hash.
|
||||
key = MixCacheKey(key, PointerIdentityHash(gameSettings));
|
||||
key = MixCacheKey(key, PointerIdentityHash(apdCache != nullptr ? apdCache->get() : nullptr));
|
||||
return key;
|
||||
}
|
||||
|
||||
auto SortActionsByWeight(
|
||||
@@ -72,6 +95,10 @@ std::atomic<uint64_t> ShardokGameEngine::cacheMisses_{0};
|
||||
std::atomic<uint64_t> ShardokGameEngine::timeInHashComputation_{0};
|
||||
std::atomic<uint64_t> ShardokGameEngine::timeInLegalActionsComputation_{0};
|
||||
|
||||
namespace {
|
||||
std::once_flag legalActionsCacheReserveOnce;
|
||||
} // namespace
|
||||
|
||||
ShardokGameEngine::ShardokGameEngine(
|
||||
[[maybe_unused]] const ShardokEngine* engine,
|
||||
const AIScoreCalculator* scoreCalculator,
|
||||
@@ -92,7 +119,9 @@ ShardokGameEngine::ShardokGameEngine(
|
||||
criticalTileCoords_(criticalTileCoords) {
|
||||
// Thread-local cache is automatically initialized per thread
|
||||
// Reserve space to reduce rehashing (based on profiling: ~30-50K unique states per search)
|
||||
legalActionsCache_.reserve(100000);
|
||||
std::call_once(legalActionsCacheReserveOnce, [] {
|
||||
ShardokGameEngine::legalActionsCache_.reserve(100000);
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<MCTSGameState> ShardokGameEngine::applyAction(
|
||||
@@ -106,27 +135,15 @@ std::unique_ptr<MCTSGameState> ShardokGameEngine::applyAction(
|
||||
|
||||
const auto currentPlayer = static_cast<PlayerId>(state.currentPlayerId());
|
||||
|
||||
// Use cached engine if available (avoids recomputing GetAvailableCommands for same state)
|
||||
std::shared_ptr<ShardokEngine> engine;
|
||||
if (auto cachedEngine = shardokState->getCachedEngine()) {
|
||||
// Clone the cached engine to preserve command cache
|
||||
engine = std::make_shared<ShardokEngine>(*cachedEngine);
|
||||
} else {
|
||||
// Create fresh engine and populate command cache
|
||||
engine = std::make_shared<ShardokEngine>(
|
||||
gameSettings_,
|
||||
shardokState->getShardokState(),
|
||||
criticalTileCoords_,
|
||||
0,
|
||||
false);
|
||||
// Populate command cache (result intentionally unused, just populating cache)
|
||||
[[maybe_unused]] const auto commands =
|
||||
engine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
// Cache the engine for future use with this state
|
||||
shardokState->setCachedEngine(engine);
|
||||
// Clone it for applying the action (don't mutate the cached engine)
|
||||
engine = std::make_shared<ShardokEngine>(*engine);
|
||||
}
|
||||
auto engine = std::make_shared<ShardokEngine>(
|
||||
gameSettings_,
|
||||
shardokState->getShardokState(),
|
||||
criticalTileCoords_,
|
||||
0,
|
||||
false);
|
||||
// Populate the AI command list before posting so the MCTS action index maps to the
|
||||
// no-follow-up command ordering used by getLegalActions().
|
||||
[[maybe_unused]] const auto commands = engine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
|
||||
// Create deterministic random generator if a specific roll is requested
|
||||
// deterministicRoll of -1.0 (default) means use random generator
|
||||
@@ -187,11 +204,6 @@ std::unique_ptr<MCTSGameState> ShardokGameEngine::applyAction(
|
||||
*alCache_,
|
||||
criticalTileCoords_);
|
||||
|
||||
// Cache the engine on the new state so score() can use it for END_TURN normalization
|
||||
// The engine's command list may be stale after the action was applied, but that's OK -
|
||||
// we'll refresh it when we call GetAvailableCommandsForAIPlayer() in score()
|
||||
newState->setCachedEngine(engine);
|
||||
|
||||
// Don't pre-compute hash - let it be computed lazily on first use
|
||||
// Many states (especially in simulation) never need their hash computed
|
||||
return newState;
|
||||
@@ -211,28 +223,18 @@ void ShardokGameEngine::applyActionMutable(
|
||||
|
||||
const auto currentPlayer = static_cast<PlayerId>(state->currentPlayerId());
|
||||
|
||||
// Use cached engine if available
|
||||
std::shared_ptr<ShardokEngine> engine;
|
||||
if (auto cachedEngine = shardokState->getCachedEngine()) {
|
||||
engine = std::make_shared<ShardokEngine>(*cachedEngine);
|
||||
} else {
|
||||
engine = std::make_shared<ShardokEngine>(
|
||||
gameSettings_,
|
||||
shardokState->getShardokState(),
|
||||
criticalTileCoords_,
|
||||
0,
|
||||
false);
|
||||
// Populate command cache (result intentionally unused, just populating cache)
|
||||
[[maybe_unused]] const auto commands =
|
||||
engine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
shardokState->setCachedEngine(engine);
|
||||
engine = std::make_shared<ShardokEngine>(*engine);
|
||||
}
|
||||
auto engine = std::make_shared<ShardokEngine>(
|
||||
gameSettings_,
|
||||
shardokState->getShardokState(),
|
||||
criticalTileCoords_,
|
||||
0,
|
||||
false);
|
||||
// Populate the AI command list before posting so the MCTS action index maps to the
|
||||
// no-follow-up command ordering used by getLegalActions().
|
||||
[[maybe_unused]] const auto commands = engine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
|
||||
engine->PostCommand(currentPlayer, shardokAction->getIndex(), nullptr);
|
||||
shardokState->getMutableShardokState() = engine->GetCurrentGameState();
|
||||
// Clear the cached engine and hash since the state has been mutated
|
||||
shardokState->setCachedEngine(nullptr);
|
||||
shardokState->invalidateHashCache();
|
||||
}
|
||||
|
||||
@@ -258,7 +260,8 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
|
||||
// Time hash computation
|
||||
const auto hashStart = std::chrono::high_resolution_clock::now();
|
||||
const uint64_t stateHash = shardokState->hash();
|
||||
const uint64_t cacheKey = LegalActionsCacheKey(stateHash, isDefender_);
|
||||
const uint64_t cacheKey =
|
||||
LegalActionsCacheKey(stateHash, isDefender_, gameSettings_.get(), apdCache_);
|
||||
const auto hashEnd = std::chrono::high_resolution_clock::now();
|
||||
timeInHashComputation_.fetch_add(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(hashEnd - hashStart).count(),
|
||||
@@ -266,40 +269,49 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
|
||||
|
||||
// Check transposition table for cached legal actions
|
||||
if (auto it = legalActionsCache_.find(cacheKey); it != legalActionsCache_.end()) {
|
||||
cacheHits_.fetch_add(1, std::memory_order_relaxed);
|
||||
const auto engine = std::make_shared<ShardokEngine>(
|
||||
gameSettings_,
|
||||
shardokState->getShardokState(),
|
||||
criticalTileCoords_);
|
||||
|
||||
// Use cached engine
|
||||
shardokState->setCachedEngine(it->second.engine);
|
||||
const CommandListSPtr commands = engine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
|
||||
// Get commands from the cached engine (Engine already caches these internally)
|
||||
const CommandListSPtr commands =
|
||||
it->second.engine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
|
||||
if (!commands || commands->empty()) { return {}; }
|
||||
|
||||
// Convert to MCTSActions using stored filtered indices
|
||||
std::vector<std::unique_ptr<MCTSAction>> actions;
|
||||
actions.reserve(it->second.filteredIndices.size());
|
||||
|
||||
for (const size_t origIdx : it->second.filteredIndices) {
|
||||
if (origIdx < commands->size()) {
|
||||
const auto& cmd = commands->at(origIdx);
|
||||
|
||||
// Extract essential fields directly from command (no proto conversion!)
|
||||
actions.push_back(std::make_unique<ShardokAction>(
|
||||
origIdx,
|
||||
cmd->GetCommandType(),
|
||||
cmd->GetPlayerId(),
|
||||
cmd->GetActorUnitId(),
|
||||
cmd->GetTargetRow(),
|
||||
cmd->GetTargetColumn(),
|
||||
cmd->HasOdds()));
|
||||
bool cacheEntryMatches = commands != nullptr && commands->size() == it->second.commandCount;
|
||||
for (const auto& cachedAction : it->second.actions) {
|
||||
const size_t commandIndex = cachedAction.getIndex();
|
||||
if (!cacheEntryMatches || commandIndex >= commands->size()) {
|
||||
cacheEntryMatches = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto& command = commands->at(commandIndex);
|
||||
const auto [targetRow, targetColumn] = cachedAction.getTarget();
|
||||
cacheEntryMatches = command->GetCommandType() ==
|
||||
static_cast<net::eagle0::shardok::common::CommandType>(
|
||||
cachedAction.getType()) &&
|
||||
command->GetPlayerId() == cachedAction.getPlayer() &&
|
||||
command->GetActorUnitId() == cachedAction.getActorId() &&
|
||||
command->GetTargetRow() == targetRow &&
|
||||
command->GetTargetColumn() == targetColumn &&
|
||||
command->HasOdds() == cachedAction.hasOdds();
|
||||
if (!cacheEntryMatches) { break; }
|
||||
}
|
||||
|
||||
// Sort actions by weight (descending) to ensure MCTS explores high-value actions first
|
||||
const std::vector<double> weights = getActionWeights(actions, state);
|
||||
return SortActionsByWeight(std::move(actions), weights);
|
||||
if (cacheEntryMatches) {
|
||||
cacheHits_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// Convert immutable cached action snapshots to MCTS action instances.
|
||||
std::vector<std::unique_ptr<MCTSAction>> actions;
|
||||
actions.reserve(it->second.actions.size());
|
||||
|
||||
for (const auto& cachedAction : it->second.actions) {
|
||||
actions.push_back(cachedAction.clone());
|
||||
}
|
||||
|
||||
// Sort actions by weight (descending) to ensure MCTS explores high-value actions first
|
||||
const std::vector<double> weights = getActionWeights(actions, state);
|
||||
return SortActionsByWeight(std::move(actions), weights);
|
||||
}
|
||||
}
|
||||
|
||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -307,17 +319,10 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
|
||||
// Time legal actions computation
|
||||
const auto actionsStart = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Use cached engine if available, otherwise create and cache it
|
||||
std::shared_ptr<ShardokEngine> engine;
|
||||
if (auto cachedEngine = shardokState->getCachedEngine()) {
|
||||
engine = cachedEngine;
|
||||
} else {
|
||||
engine = std::make_shared<ShardokEngine>(
|
||||
gameSettings_,
|
||||
shardokState->getShardokState(),
|
||||
criticalTileCoords_);
|
||||
shardokState->setCachedEngine(engine);
|
||||
}
|
||||
const auto engine = std::make_shared<ShardokEngine>(
|
||||
gameSettings_,
|
||||
shardokState->getShardokState(),
|
||||
criticalTileCoords_);
|
||||
|
||||
const CommandListSPtr commands = engine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
|
||||
@@ -338,20 +343,24 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
|
||||
// Convert only filtered commands to MCTSActions
|
||||
std::vector<std::unique_ptr<MCTSAction>> actions;
|
||||
actions.reserve(filteredIndices.size());
|
||||
std::vector<ShardokAction> cachedActions;
|
||||
cachedActions.reserve(filteredIndices.size());
|
||||
|
||||
for (const size_t idx : filteredIndices) {
|
||||
if (idx < commands->size()) {
|
||||
const auto& cmd = commands->at(idx);
|
||||
|
||||
// Extract essential fields directly from command (no proto conversion!)
|
||||
actions.push_back(std::make_unique<ShardokAction>(
|
||||
ShardokAction action(
|
||||
idx,
|
||||
cmd->GetCommandType(),
|
||||
cmd->GetPlayerId(),
|
||||
cmd->GetActorUnitId(),
|
||||
cmd->GetTargetRow(),
|
||||
cmd->GetTargetColumn(),
|
||||
cmd->HasOdds()));
|
||||
cmd->HasOdds());
|
||||
actions.push_back(action.clone());
|
||||
cachedActions.push_back(action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,20 +377,20 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
|
||||
.count(),
|
||||
std::memory_order_relaxed);
|
||||
|
||||
// Store in transposition table for future lookups
|
||||
// Note: We only store filtered indices and the engine (which caches commands internally)
|
||||
// This avoids duplicating heavy protocol buffer objects
|
||||
// Store immutable action snapshots in the transposition table for future lookups.
|
||||
// Do not store ShardokEngine here: its command cache is mutable and not safe to share
|
||||
// across MCTS worker threads.
|
||||
// Use lazy_emplace_l to ensure thread-safe insertion (locks the bucket during construction)
|
||||
legalActionsCache_.lazy_emplace_l(
|
||||
cacheKey,
|
||||
[&](typename decltype(legalActionsCache_)::value_type& v) {
|
||||
// Update existing entry
|
||||
v.second.filteredIndices = filteredIndices;
|
||||
v.second.engine = engine;
|
||||
v.second.commandCount = commands->size();
|
||||
v.second.actions = cachedActions;
|
||||
},
|
||||
[&](const typename decltype(legalActionsCache_)::constructor& ctor) {
|
||||
// Create new entry
|
||||
ctor(cacheKey, LegalActionsCache{filteredIndices, engine});
|
||||
ctor(cacheKey, LegalActionsCache{commands->size(), cachedActions});
|
||||
});
|
||||
|
||||
return actions;
|
||||
@@ -414,15 +423,7 @@ std::vector<double> ShardokGameEngine::getActionWeights(
|
||||
"indicates a type mismatch in the MCTS adapter layer");
|
||||
}
|
||||
|
||||
// Get cached engine and command list for looking up command protos
|
||||
auto cachedEngine = shardokState->getCachedEngine();
|
||||
if (!cachedEngine) {
|
||||
throw MCTSInternalError(
|
||||
"ShardokGameEngine::getActionWeights called with state that has no cached engine");
|
||||
}
|
||||
|
||||
const auto currentPlayer = static_cast<PlayerId>(state.currentPlayerId());
|
||||
const CommandListSPtr commands = cachedEngine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
|
||||
// Determine if current player is defender (not root player!)
|
||||
// During simulation we need to use the correct perspective for action weighting
|
||||
@@ -441,20 +442,13 @@ std::vector<double> ShardokGameEngine::getActionWeights(
|
||||
"indicates a type mismatch in the MCTS adapter layer");
|
||||
}
|
||||
|
||||
// Look up command proto from cached engine using action's index
|
||||
const size_t cmdIndex = shardokAction->getIndex();
|
||||
if (cmdIndex >= commands->size()) {
|
||||
throw MCTSInternalError(
|
||||
"ShardokGameEngine::getActionWeights: action index out of bounds");
|
||||
}
|
||||
|
||||
const auto& cmd = commands->at(cmdIndex);
|
||||
const auto [targetRow, targetColumn] = shardokAction->getTarget();
|
||||
|
||||
weights.push_back(AIHeuristicWeighting::GetCommandWeight(
|
||||
cmd->GetCommandType(),
|
||||
cmd->GetActorUnitId(),
|
||||
cmd->GetPlayerId(),
|
||||
Coords{cmd->GetTargetRow(), cmd->GetTargetColumn()},
|
||||
static_cast<net::eagle0::shardok::common::CommandType>(shardokAction->getType()),
|
||||
shardokAction->getActorId(),
|
||||
shardokAction->getPlayer(),
|
||||
Coords{static_cast<int8_t>(targetRow), static_cast<int8_t>(targetColumn)},
|
||||
gameState,
|
||||
castleCoords_,
|
||||
apdCache_,
|
||||
@@ -488,7 +482,7 @@ bool ShardokGameEngine::shouldStopSearch(
|
||||
size_t ShardokGameEngine::mapFilteredIndexToOriginal(
|
||||
size_t filteredIndex,
|
||||
const MCTSGameState& state) const {
|
||||
// Get the filtered actions (uses cached engine)
|
||||
// Get the filtered actions.
|
||||
auto actions = getLegalActions(state, state.currentPlayerId(), 0, 0);
|
||||
|
||||
// Check bounds
|
||||
@@ -590,22 +584,12 @@ ChanceOutcomeInfo ShardokGameEngine::getBinaryOutcomeInfo(
|
||||
|
||||
const auto currentPlayer = static_cast<PlayerId>(state.currentPlayerId());
|
||||
|
||||
// Get or create the engine for this state
|
||||
std::shared_ptr<ShardokEngine> engine;
|
||||
if (auto cachedEngine = shardokState->getCachedEngine()) {
|
||||
engine = cachedEngine;
|
||||
} else {
|
||||
engine = std::make_shared<ShardokEngine>(
|
||||
gameSettings_,
|
||||
shardokState->getShardokState(),
|
||||
criticalTileCoords_,
|
||||
0,
|
||||
false);
|
||||
// Populate command cache
|
||||
[[maybe_unused]] const auto commands =
|
||||
engine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
shardokState->setCachedEngine(engine);
|
||||
}
|
||||
const auto engine = std::make_shared<ShardokEngine>(
|
||||
gameSettings_,
|
||||
shardokState->getShardokState(),
|
||||
criticalTileCoords_,
|
||||
0,
|
||||
false);
|
||||
|
||||
// Get command descriptors
|
||||
const auto descriptors = engine->GetAvailableCommandsForAIPlayer(currentPlayer);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "src/main/cpp/net/eagle0/common/mcts/abstract/MCTSGameEngine.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIStrategy.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/mcts/adapters/ShardokAction.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
|
||||
@@ -99,11 +100,12 @@ public:
|
||||
void resetCacheStatistics();
|
||||
|
||||
private:
|
||||
// Transposition table entry for caching legal actions
|
||||
// Note: We don't store command protos since the Engine already caches them
|
||||
// Transposition table entry for caching legal actions.
|
||||
// Store immutable action snapshots only; ShardokEngine owns mutable command caches and
|
||||
// must remain per-state/per-thread.
|
||||
struct LegalActionsCache {
|
||||
std::vector<size_t> filteredIndices;
|
||||
std::shared_ptr<ShardokEngine> engine; // Engine with populated command cache
|
||||
size_t commandCount;
|
||||
std::vector<ShardokAction> actions;
|
||||
};
|
||||
|
||||
const AIScoreCalculator* scoreCalculator_;
|
||||
|
||||
@@ -98,7 +98,6 @@ std::unique_ptr<MCTSGameState> ShardokGameState::clone() const {
|
||||
apdCache_,
|
||||
alCache_,
|
||||
criticalTileCoords_);
|
||||
// Don't copy the cached engine - each state needs its own
|
||||
return cloned;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "src/main/cpp/net/eagle0/common/mcts/abstract/MCTSGameState.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/ai/AIStrategy.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
|
||||
@@ -52,10 +51,6 @@ public:
|
||||
[[nodiscard]] const GameSettings* getSettings() const { return settings_; }
|
||||
[[nodiscard]] const CoordsSet& getCriticalTileCoords() const { return criticalTileCoords_; }
|
||||
|
||||
// Engine caching for performance (avoids recomputing available commands)
|
||||
void setCachedEngine(std::shared_ptr<ShardokEngine> engine) const { cachedEngine_ = engine; }
|
||||
[[nodiscard]] std::shared_ptr<ShardokEngine> getCachedEngine() const { return cachedEngine_; }
|
||||
|
||||
// Invalidate hash cache when state is mutated
|
||||
void invalidateHashCache() const {
|
||||
hashCached_ = false;
|
||||
@@ -74,7 +69,6 @@ private:
|
||||
mutable uint64_t cachedHash_ = 0;
|
||||
mutable bool hashCached_ = false;
|
||||
const CoordsSet criticalTileCoords_; // Own the data to avoid dangling references
|
||||
mutable std::shared_ptr<ShardokEngine> cachedEngine_; // Engine with cached available commands
|
||||
};
|
||||
|
||||
} // namespace mcts
|
||||
|
||||
@@ -133,8 +133,7 @@ auto DefenderHoldsCriticalTilesVictoryScore(
|
||||
const auto cc = gameState->hex_map()->column_count();
|
||||
const auto occupants = Occupants(*gameState->units(), rc, cc);
|
||||
|
||||
for (const Coords& criticalTileLocation : criticalTileLocations) {
|
||||
const auto index = criticalTileLocation.row() * cc + criticalTileLocation.column();
|
||||
for (const auto index : criticalTileLocations.indexIterator()) {
|
||||
const auto& occupant = occupants[index];
|
||||
|
||||
if (occupant && occupant->battalion().type() !=
|
||||
|
||||
+5
-5
@@ -32,7 +32,7 @@ public:
|
||||
* Initialize game settings from the default configuration files.
|
||||
* Must be called before creating game states.
|
||||
*/
|
||||
static auto InitializeGameSettings() -> GameSettingsSPtr;
|
||||
[[nodiscard]] static auto InitializeGameSettings() -> GameSettingsSPtr;
|
||||
|
||||
/**
|
||||
* Create the standard "Perf" test configuration:
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
* @param defenderToggle If true, AI is defender; if false, AI is attacker
|
||||
* @return A GameStateW with the configured battle
|
||||
*/
|
||||
static auto CreatePerfTestGameState(
|
||||
[[nodiscard]] static auto CreatePerfTestGameState(
|
||||
const GameSettingsSPtr& settings,
|
||||
bool defenderToggle = false) -> GameStateW;
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
* @param defenderToggle If true, AI is defender; if false, AI is attacker
|
||||
* @return A GameStateW with the configured battle
|
||||
*/
|
||||
static auto CreateCustomTestGameState(
|
||||
[[nodiscard]] static auto CreateCustomTestGameState(
|
||||
const GameSettingsSPtr& settings,
|
||||
const std::string& mapName,
|
||||
int aiUnitCount,
|
||||
@@ -67,11 +67,11 @@ public:
|
||||
|
||||
private:
|
||||
// Helper functions for building game state components
|
||||
static auto
|
||||
[[nodiscard]] static auto
|
||||
AddPlayerInfo(flatbuffers::FlatBufferBuilder& fbb, int playerId, bool isDefender, int food)
|
||||
-> flatbuffers::Offset<net::eagle0::shardok::storage::fb::PlayerInfo>;
|
||||
|
||||
static auto AddGenericUnit(
|
||||
[[nodiscard]] static auto AddGenericUnit(
|
||||
PlayerId playerId,
|
||||
UnitId unitId,
|
||||
const net::eagle0::shardok::storage::fb::Coords& location,
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
* @param mctsConfig MCTS settings, used only when algorithmType is MCTS
|
||||
* @return Unique pointer to created ShardokAIClient
|
||||
*/
|
||||
static auto Create(
|
||||
[[nodiscard]] static auto Create(
|
||||
PlayerId playerId,
|
||||
bool isDefender,
|
||||
const net::eagle0::shardok::storage::fb::HexMap* hexMap,
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
*
|
||||
* @return Shared pointer to initialized GameSettings
|
||||
*/
|
||||
static auto CreateDefault() -> GameSettingsSPtr;
|
||||
[[nodiscard]] static auto CreateDefault() -> GameSettingsSPtr;
|
||||
};
|
||||
|
||||
} // namespace shardok::ai_testing_common
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
@@ -27,6 +28,77 @@ using net::eagle0::common::ScopedShardokLatencyTrace;
|
||||
|
||||
using net::eagle0::shardok::common::GameStatus;
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] auto DescribeCommandForLog(const CommandSPtr &command) -> std::string {
|
||||
if (!command) { return "<null command>"; }
|
||||
|
||||
std::ostringstream out;
|
||||
out << "type=" << command->GetCommandType()
|
||||
<< " player=" << static_cast<int>(command->GetPlayerId())
|
||||
<< " actor=" << command->GetActorUnitId() << " target=("
|
||||
<< static_cast<int>(command->GetTargetRow()) << ','
|
||||
<< static_cast<int>(command->GetTargetColumn()) << ')';
|
||||
|
||||
if (command->HasOdds()) { out << " odds_percentile=" << command->GetOddsPercentile(); }
|
||||
|
||||
try {
|
||||
out << " proto={" << command->GetCommandProto().ShortDebugString() << '}';
|
||||
} catch (const std::exception &e) { out << " proto_error={" << e.what() << '}'; }
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto DescribeCommandsForLog(const CommandList &commands) -> std::string {
|
||||
std::ostringstream out;
|
||||
out << "available_command_count=" << commands.size();
|
||||
for (size_t i = 0; i < commands.size(); ++i) {
|
||||
out << "\n [" << i << "] " << DescribeCommandForLog(commands[i]);
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto DescribeCurrentStateForLog(const ShardokEngine &engine) -> std::string {
|
||||
const auto *state = engine.GetCurrentGameState().Get();
|
||||
if (state == nullptr) { return "state=<null>"; }
|
||||
|
||||
const auto vectorSize = [](const auto *vector) -> size_t {
|
||||
return vector == nullptr ? 0 : vector->size();
|
||||
};
|
||||
|
||||
std::ostringstream out;
|
||||
out << "state={round=" << static_cast<int>(state->current_round())
|
||||
<< " current_player=" << static_cast<int>(state->current_player())
|
||||
<< " history_count=" << engine.GetUnfilteredHistoryCount();
|
||||
|
||||
if (const auto *status = state->status(); status != nullptr) {
|
||||
out << " status=" << static_cast<int>(status->state()) << " end_condition=";
|
||||
if (const auto *endCondition = status->end_game_condition(); endCondition != nullptr) {
|
||||
out << "{type=" << static_cast<int>(endCondition->victory_type())
|
||||
<< " victory_details=" << static_cast<int>(endCondition->victory_details())
|
||||
<< " draw_details=" << static_cast<int>(endCondition->draw_details()) << '}';
|
||||
} else {
|
||||
out << "<null>";
|
||||
}
|
||||
} else {
|
||||
out << " status=<null>";
|
||||
}
|
||||
|
||||
out << " units=" << vectorSize(state->units())
|
||||
<< " player_infos=" << vectorSize(state->player_infos());
|
||||
|
||||
if (const auto *hexMap = state->hex_map(); hexMap != nullptr) {
|
||||
out << " map=" << hexMap->row_count() << 'x' << hexMap->column_count();
|
||||
} else {
|
||||
out << " map=<null>";
|
||||
}
|
||||
|
||||
out << '}';
|
||||
return out.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class InvalidTokenException : public std::exception {};
|
||||
|
||||
class MismatchedPlayerIdException : public std::exception {
|
||||
@@ -154,6 +226,8 @@ void ShardokGameController::DoAIThread() {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string aiPhase = "choosing command";
|
||||
int64_t chosenIndex = -1;
|
||||
try {
|
||||
// Phase 2: AI thinks (NO LOCK - this is the slow part)
|
||||
auto results = [&] {
|
||||
@@ -165,6 +239,7 @@ void ShardokGameController::DoAIThread() {
|
||||
{"available_command_count", std::to_string(availableCommands->size())}});
|
||||
return aiClient->ChooseCommandIndex(settings, gsv, availableCommands);
|
||||
}();
|
||||
chosenIndex = results.chosenIndex;
|
||||
|
||||
// Phase 3: Post the command (brief lock)
|
||||
{
|
||||
@@ -183,6 +258,7 @@ void ShardokGameController::DoAIThread() {
|
||||
}
|
||||
|
||||
{
|
||||
aiPhase = "posting chosen command";
|
||||
ScopedShardokLatencyTrace trace(
|
||||
"ai_engine_post_command",
|
||||
cachedGameId,
|
||||
@@ -193,6 +269,7 @@ void ShardokGameController::DoAIThread() {
|
||||
}
|
||||
LockedNotifyClients();
|
||||
{
|
||||
aiPhase = "posting forced follow-up commands";
|
||||
ScopedShardokLatencyTrace trace(
|
||||
"ai_engine_post_forced_commands",
|
||||
cachedGameId,
|
||||
@@ -204,16 +281,32 @@ void ShardokGameController::DoAIThread() {
|
||||
aiThreadKeepGoing = !engine->GameIsOver();
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
std::ostringstream error;
|
||||
error << "AI error in game " << cachedGameId << ", player " << playerId << ", round "
|
||||
<< gsv.current_round() << ": " << e.what();
|
||||
|
||||
std::ostringstream context;
|
||||
context << "AI failure context: phase=" << aiPhase
|
||||
<< ", expected_history_count=" << expectedHistoryCount
|
||||
<< ", current_history_count=" << engine->GetUnfilteredHistoryCount()
|
||||
<< ", chosen_index=" << chosenIndex
|
||||
<< ", current_player=" << static_cast<int>(engine->GetCurrentPlayerId()) << ", "
|
||||
<< DescribeCurrentStateForLog(*engine);
|
||||
|
||||
const std::string commandsContext = availableCommands
|
||||
? DescribeCommandsForLog(*availableCommands)
|
||||
: "available_command_count=<cleared>";
|
||||
|
||||
std::cerr << "AI thread exception in game " << cachedGameId << ", player " << playerId
|
||||
<< ", round " << gsv.current_round() << ": " << e.what() << '\n';
|
||||
std::cerr << context.str() << '\n';
|
||||
std::cerr << commandsContext << '\n';
|
||||
|
||||
void *backtraceArray[20];
|
||||
const int backtraceSize = backtrace(backtraceArray, 20);
|
||||
backtrace_symbols_fd(backtraceArray, backtraceSize, STDERR_FILENO);
|
||||
|
||||
aiThreadErrorMessage = "AI error in game " + cachedGameId + ", player " +
|
||||
std::to_string(playerId) + ", round " +
|
||||
std::to_string(gsv.current_round()) + ": " + e.what();
|
||||
aiThreadErrorMessage = error.str() + "\n" + context.str() + "\n" + commandsContext;
|
||||
aiThreadFailed.store(true);
|
||||
updateCondition.notify_all();
|
||||
break;
|
||||
|
||||
@@ -189,7 +189,8 @@ struct BattalionType {
|
||||
}
|
||||
|
||||
[[nodiscard]] auto GetCostToEnterTerrain(const TerrainProto &terrain) const -> ActionCost {
|
||||
if (HasBridge(terrain.modifier())) {
|
||||
if (HasBridge(terrain.modifier()) && terrain.modifier().bridge().has_integrity() &&
|
||||
terrain.modifier().bridge().integrity().value() > 0.0) {
|
||||
return {ActionCost::standard,
|
||||
ActionPoints(std::ceil(
|
||||
100.0 * minimumCostToEnterBridge /
|
||||
@@ -208,7 +209,7 @@ struct BattalionType {
|
||||
[[nodiscard]] auto GetCostToEnterTerrain(
|
||||
const net::eagle0::shardok::storage::fb::Terrain_::Type terrainType,
|
||||
const net::eagle0::shardok::storage::fb::TileModifier &tm) const -> ActionCost {
|
||||
if (tm.bridge().present()) {
|
||||
if (tm.bridge().present() && tm.bridge().integrity() > 0.0) {
|
||||
return {ActionCost::standard,
|
||||
ActionPoints(
|
||||
std::ceil(100.0 * minimumCostToEnterBridge / tm.bridge().integrity()))};
|
||||
|
||||
@@ -22,13 +22,13 @@ namespace shardok {
|
||||
using PercentileRollOdds = net::eagle0::shardok::storage::Odds;
|
||||
using OtherFactor = net::eagle0::shardok::storage::Odds_OtherFactor;
|
||||
|
||||
double BonusFromStat(double stat);
|
||||
[[nodiscard]] double BonusFromStat(double stat);
|
||||
|
||||
int16_t AverageBonusFromStats(const std::vector<double> &stats);
|
||||
[[nodiscard]] int16_t AverageBonusFromStats(const std::vector<double> &stats);
|
||||
|
||||
OtherFactor MakeOtherFactor(int value, const std::string &id);
|
||||
[[nodiscard]] OtherFactor MakeOtherFactor(int value, const std::string &id);
|
||||
|
||||
PercentileRollOdds MakeOdds(
|
||||
[[nodiscard]] PercentileRollOdds MakeOdds(
|
||||
int base = 0,
|
||||
int terr = 0,
|
||||
int weath = 0,
|
||||
@@ -36,7 +36,7 @@ PercentileRollOdds MakeOdds(
|
||||
int statF = 0,
|
||||
std::span<const OtherFactor> others = {});
|
||||
|
||||
PercentileRollOdds MakeOdds(
|
||||
[[nodiscard]] PercentileRollOdds MakeOdds(
|
||||
int base,
|
||||
int terr,
|
||||
int weath,
|
||||
@@ -44,11 +44,11 @@ PercentileRollOdds MakeOdds(
|
||||
const std::vector<double> &stats,
|
||||
std::span<const OtherFactor> others = {});
|
||||
|
||||
int16_t GetTotalOdds(const PercentileRollOdds &odds);
|
||||
[[nodiscard]] int16_t GetTotalOdds(const PercentileRollOdds &odds);
|
||||
|
||||
bool PercentileRollSucceeds(const PercentileRollOdds &odds, double roll);
|
||||
[[nodiscard]] bool PercentileRollSucceeds(const PercentileRollOdds &odds, double roll);
|
||||
|
||||
int16_t GetSuccessChance(const PercentileRollOdds &odds);
|
||||
[[nodiscard]] int16_t GetSuccessChance(const PercentileRollOdds &odds);
|
||||
} // namespace shardok
|
||||
|
||||
#endif // EAGLE0_SHARDOK_LIBRARY_PERCENTILE_ROLL_ODDS_HPP
|
||||
|
||||
@@ -140,7 +140,12 @@ ShardokEngine::ShardokEngine(const ShardokEngine &toCopy, const bool trackHistor
|
||||
gameState(toCopy.gameState),
|
||||
trackHistory(trackHistory),
|
||||
startingHistoryCount(toCopy.startingHistoryCount),
|
||||
startingHistoryState(trackHistory ? toCopy.startingHistoryState : GameStateW{}),
|
||||
actionHistory(
|
||||
trackHistory ? toCopy.actionHistory : std::vector<ShardokActionWithResultingState>{}),
|
||||
criticalTileCoords(toCopy.criticalTileCoords),
|
||||
tutorialController_(toCopy.tutorialController_),
|
||||
pendingReinforcementPlacement_(toCopy.pendingReinforcementPlacement_),
|
||||
cachedAvailableCommands(toCopy.cachedAvailableCommands) {}
|
||||
|
||||
auto ShardokEngine::GetGameStateAtStartOfAction(const ActionId startingActionId) const
|
||||
@@ -718,8 +723,8 @@ auto SecuredByPlayerByUnitIdFromHistory(
|
||||
continue;
|
||||
}
|
||||
if (!resolvedUnit.has_secured_by_player()) { continue; }
|
||||
const auto *unit = reinterpret_cast<const Unit *>(resolvedUnit.unit_bytes().data());
|
||||
securedByPlayerByUnitId[unit->unit_id()] = resolvedUnit.secured_by_player().value();
|
||||
const auto unit = UnitFromBytes(resolvedUnit.unit_bytes());
|
||||
securedByPlayerByUnitId[unit.unit_id()] = resolvedUnit.secured_by_player().value();
|
||||
}
|
||||
}
|
||||
return securedByPlayerByUnitId;
|
||||
|
||||
+24
-30
@@ -14,6 +14,7 @@
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/UnitHelpers.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/map/HexMapHasher.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/game_state.hpp"
|
||||
|
||||
@@ -30,10 +31,6 @@ inline auto GetMutableUnit(GameState *state, UnitId unitId) -> Unit * {
|
||||
return const_cast<Unit *>(state->mutable_units()->GetMutableObject(unitId));
|
||||
}
|
||||
|
||||
inline auto UnitFromBytes(const std::string &unitBytes) -> const Unit & {
|
||||
return *reinterpret_cast<const Unit *>(unitBytes.data());
|
||||
}
|
||||
|
||||
using net::eagle0::shardok::storage::fb::VictoryCondition;
|
||||
using net::eagle0::shardok::storage::fb::VictoryType;
|
||||
|
||||
@@ -101,7 +98,7 @@ void ApplyResolvedUnit(
|
||||
GameStateT &inoutState,
|
||||
const ResolvedUnitProto &resolvedUnit,
|
||||
const double vipCapturedMoraleAdjustment) {
|
||||
const Unit &unit = UnitFromBytes(resolvedUnit.unit_bytes());
|
||||
const Unit unit = UnitFromBytes(resolvedUnit.unit_bytes());
|
||||
UnitId unitId = unit.unit_id();
|
||||
|
||||
if (unit.has_attached_hero() &&
|
||||
@@ -169,15 +166,15 @@ void MutatingAddUnits(GameStateW &mutatingState, const ActionResultProto &result
|
||||
|
||||
// First pass: check what kind of modifications we need
|
||||
for (const auto &unitBytes : result.changed_units_fb()) {
|
||||
const auto *unit = &UnitFromBytes(unitBytes);
|
||||
maxChangedUnitId = std::max(maxChangedUnitId, unit->unit_id());
|
||||
const auto unit = UnitFromBytes(unitBytes);
|
||||
maxChangedUnitId = std::max(maxChangedUnitId, unit.unit_id());
|
||||
|
||||
if (static_cast<unsigned int>(unit->unit_id()) >= mutatingState->units()->size()) {
|
||||
if (static_cast<unsigned int>(unit.unit_id()) >= mutatingState->units()->size()) {
|
||||
// Unit ID beyond vector size - must expand
|
||||
needsVectorExpansion = true;
|
||||
break; // No point checking further
|
||||
} else if (
|
||||
mutatingState->units()->Get(unit->unit_id())->status() ==
|
||||
mutatingState->units()->Get(unit.unit_id())->status() ==
|
||||
net::eagle0::shardok::storage::fb::UnitStatus_RESERVED_SLOT) {
|
||||
// Unit wants to use a reserved slot
|
||||
needsReservedSlotConversion = true;
|
||||
@@ -199,8 +196,8 @@ void MutatingAddUnits(GameStateW &mutatingState, const ActionResultProto &result
|
||||
// Convert reserved slots to real units in place
|
||||
// We only need to process the units that are being changed
|
||||
for (const auto &unitBytes : result.changed_units_fb()) {
|
||||
const auto *unit = &UnitFromBytes(unitBytes);
|
||||
auto *mutableUnit = GetMutableUnit(mutatingState.Get(), unit->unit_id());
|
||||
const auto unit = UnitFromBytes(unitBytes);
|
||||
auto *mutableUnit = GetMutableUnit(mutatingState.Get(), unit.unit_id());
|
||||
if (mutableUnit->status() ==
|
||||
net::eagle0::shardok::storage::fb::UnitStatus_RESERVED_SLOT) {
|
||||
// Convert this reserved slot to a real unit
|
||||
@@ -299,51 +296,48 @@ void MutatingApplyResult(
|
||||
const double vipCapturedMoraleAdjustment = settings.Backing().vip_captured_morale_adjustment();
|
||||
|
||||
for (const auto &changedUnitBytes : result.changed_units_fb()) {
|
||||
const Unit *changedUnit = &UnitFromBytes(changedUnitBytes);
|
||||
const Unit changedUnit = UnitFromBytes(changedUnitBytes);
|
||||
|
||||
internalAssert(
|
||||
changedUnit->unit_id() < static_cast<int32_t>(mutatingGameState->units()->size()),
|
||||
changedUnit.unit_id() < static_cast<int32_t>(mutatingGameState->units()->size()),
|
||||
"Got a changed unit outside acceptable range");
|
||||
const auto battalionSizeAfter = changedUnit->battalion().size();
|
||||
const auto battalionSizeAfter = changedUnit.battalion().size();
|
||||
|
||||
const int battalionSizeBefore =
|
||||
(changedUnit->unit_id() < static_cast<int32_t>(initialUnitCount))
|
||||
? mutatingGameState->units()
|
||||
->Get(changedUnit->unit_id())
|
||||
->battalion()
|
||||
.size()
|
||||
: changedUnit->battalion().size();
|
||||
(changedUnit.unit_id() < static_cast<int32_t>(initialUnitCount))
|
||||
? mutatingGameState->units()->Get(changedUnit.unit_id())->battalion().size()
|
||||
: changedUnit.battalion().size();
|
||||
|
||||
auto status = changedUnit->status();
|
||||
if (IsDestroyed(*changedUnit)) {
|
||||
auto status = changedUnit.status();
|
||||
if (IsDestroyed(changedUnit)) {
|
||||
status =
|
||||
changedUnit->has_attached_hero()
|
||||
changedUnit.has_attached_hero()
|
||||
? net::eagle0::shardok::storage::fb::UnitStatus_CAPTURED_UNIT
|
||||
: net::eagle0::shardok::storage::fb::UnitStatus_DESTROYED_SUMMONED_UNIT;
|
||||
|
||||
ApplyResolvedUnit(
|
||||
mutatingGameState.Get(),
|
||||
*changedUnit,
|
||||
changedUnit,
|
||||
status,
|
||||
vipCapturedMoraleAdjustment,
|
||||
settings);
|
||||
}
|
||||
|
||||
// Capture old position before applying changes
|
||||
auto *mutableUnit = GetMutableUnit(mutatingGameState.Get(), changedUnit->unit_id());
|
||||
auto *mutableUnit = GetMutableUnit(mutatingGameState.Get(), changedUnit.unit_id());
|
||||
const auto oldLocation = mutableUnit->location();
|
||||
|
||||
fb::ApplyUnit(mutableUnit, changedUnit, status);
|
||||
fb::ApplyUnit(mutableUnit, &changedUnit, status);
|
||||
|
||||
// Update occupied tiles bitfield if position changed
|
||||
const auto &newLocation = changedUnit->location();
|
||||
const auto &newLocation = changedUnit.location();
|
||||
if (oldLocation.row() != newLocation.row() ||
|
||||
oldLocation.column() != newLocation.column()) {
|
||||
mutatingGameState.UpdateOccupiedTile(oldLocation, newLocation);
|
||||
}
|
||||
|
||||
if (battalionSizeBefore != battalionSizeAfter) {
|
||||
if (changedUnit->battalion().type() ==
|
||||
if (changedUnit.battalion().type() ==
|
||||
net::eagle0::shardok::storage::fb::BattalionTypeId_UNDEAD) {
|
||||
if (battalionSizeAfter > battalionSizeBefore) {
|
||||
// Undead consumed some bodies
|
||||
@@ -355,7 +349,7 @@ void MutatingApplyResult(
|
||||
if (battalionSizeAfter > battalionSizeBefore) {
|
||||
std::ostringstream stringStream;
|
||||
stringStream << "Battalion got larger! Was " << battalionSizeBefore << ", now "
|
||||
<< changedUnit << " result was " << result.DebugString();
|
||||
<< &changedUnit << " result was " << result.DebugString();
|
||||
throw ShardokInternalErrorException(stringStream.str());
|
||||
}
|
||||
|
||||
@@ -365,7 +359,7 @@ void MutatingApplyResult(
|
||||
if (newCount != 0) {
|
||||
newArmament =
|
||||
(mutatingGameState->dead_count() * mutatingGameState->dead_armament() +
|
||||
casualties * changedUnit->battalion().armament()) /
|
||||
casualties * changedUnit.battalion().armament()) /
|
||||
newCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ cc_library(
|
||||
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/map:hex_map_hasher",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/unit",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/util:action_result_flatbuffer_helpers",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/util:hex_map_utils",
|
||||
"//src/main/protobuf/net/eagle0/shardok/storage:action_result_cc_proto",
|
||||
],
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace shardok::fb {
|
||||
|
||||
using namespace net::eagle0::shardok::storage::fb;
|
||||
using net::eagle0::shardok::storage::fb::UnitStatus;
|
||||
|
||||
auto ApplyUnit(
|
||||
Unit* toUnit,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
|
||||
|
||||
namespace shardok {
|
||||
using namespace net::eagle0::shardok::common;
|
||||
using net::eagle0::shardok::common::WATER_DAMAGE;
|
||||
|
||||
auto EscapeChance(
|
||||
const int base,
|
||||
@@ -65,7 +65,7 @@ auto EscapeChance(
|
||||
}
|
||||
|
||||
auto ToVector(const Units &units) -> vector<const Unit *> {
|
||||
auto v = vector<const Unit *>{};
|
||||
vector<const Unit *> v{};
|
||||
v.reserve(units.size());
|
||||
for (const Unit *u : units) { v.push_back(u); }
|
||||
return v;
|
||||
|
||||
@@ -60,7 +60,8 @@ auto StartPlayerTurnAction::InternalExecute(
|
||||
->Get(startingState->month() - 1)
|
||||
->is_winter() &&
|
||||
!inCastle) {
|
||||
effectiveBase += float(settingsGetter.Backing().winter_morale_adjustment());
|
||||
effectiveBase +=
|
||||
static_cast<float>(settingsGetter.Backing().winter_morale_adjustment());
|
||||
}
|
||||
float moraleDiff = effectiveBase - updatedUnit.battalion().morale();
|
||||
MutatingMaybeAdjustMorale(
|
||||
|
||||
@@ -34,7 +34,8 @@ auto UndeadChangeAction::InternalExecute(
|
||||
int growth = newCount - decayedSize;
|
||||
|
||||
float newArmament =
|
||||
float(unit->battalion().armament() * decayedSize + armament * growth) / newCount;
|
||||
static_cast<float>(unit->battalion().armament() * decayedSize + armament * growth) /
|
||||
newCount;
|
||||
|
||||
auto newUnit = *unit;
|
||||
newUnit.mutable_battalion().mutate_size(newCount);
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
namespace shardok {
|
||||
|
||||
class EvacuatePrisonersCommandFactory : public CommandFactory {
|
||||
const SettingsGetter& settings;
|
||||
const SettingsGetter settings;
|
||||
|
||||
public:
|
||||
explicit EvacuatePrisonersCommandFactory(const SettingsGetter& getter);
|
||||
|
||||
@@ -61,9 +61,11 @@ void FleeCommandFactory::AddAvailableFleeCommands(
|
||||
const auto *occupant = Occupant(allUnits, twoAwayCoords);
|
||||
if (occupant && !occupant->hidden()) {
|
||||
if (std::ranges::contains(allyPids, occupant->player_id())) {
|
||||
adjacentFriendliesMod += int32_t(perAdjacentFriendly * adjustmentForTwoAway);
|
||||
adjacentFriendliesMod +=
|
||||
static_cast<int32_t>(perAdjacentFriendly * adjustmentForTwoAway);
|
||||
} else {
|
||||
adjacentEnemiesMod += int32_t(perAdjacentEnemy * adjustmentForTwoAway);
|
||||
adjacentEnemiesMod +=
|
||||
static_cast<int32_t>(perAdjacentEnemy * adjustmentForTwoAway);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ struct AccumulatedMoveInfo {
|
||||
Coords endLocation;
|
||||
ActionPoints pointCost = 0;
|
||||
bool willUnhide = false;
|
||||
vector<Coords> targets = {};
|
||||
vector<Coords> targets{};
|
||||
|
||||
AccumulatedMoveInfo(
|
||||
const Coords &startLocation,
|
||||
|
||||
@@ -305,6 +305,7 @@ cc_library(
|
||||
"//src/main/cpp/net/eagle0/shardok/library:percentile_roll_odds",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:shardok_command",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/util:action_result_flatbuffer_helpers",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/view_filters:odds_filter",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/PercentileRollOdds.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/OddsFilter.hpp"
|
||||
|
||||
namespace shardok {
|
||||
auto BecomeOutlawCommand::InternalExecute(
|
||||
@@ -59,7 +60,12 @@ auto BecomeOutlawCommand::GetCommandProto() const -> CommandProto {
|
||||
proto.set_player(GetPlayerId());
|
||||
proto.set_type(net::eagle0::shardok::common::BECOME_OUTLAW_COMMAND);
|
||||
proto.mutable_actor()->set_value(fleeingUnitId);
|
||||
*proto.mutable_odds() = OddsFilteredForPlayer(successOdds, GetPlayerId());
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
auto BecomeOutlawCommand::GetOddsPercentile() const -> int32_t {
|
||||
return OddsFilteredForPlayer(successOdds, GetPlayerId()).success_chance();
|
||||
}
|
||||
} // namespace shardok
|
||||
|
||||
@@ -41,6 +41,8 @@ public:
|
||||
}
|
||||
|
||||
[[nodiscard]] auto GetCommandProto() const -> CommandProto override;
|
||||
[[nodiscard]] auto HasOdds() const -> bool override { return true; }
|
||||
[[nodiscard]] auto GetOddsPercentile() const -> int32_t override;
|
||||
[[nodiscard]] auto CanBeFollowUp() const -> bool override { return false; }
|
||||
[[nodiscard]] auto CanDoWithLowMorale() const -> bool override { return true; }
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ public:
|
||||
}
|
||||
|
||||
[[nodiscard]] auto GetCommandProto() const -> CommandProto override;
|
||||
[[nodiscard]] auto HasOdds() const -> bool override { return true; }
|
||||
[[nodiscard]] auto GetOddsPercentile() const -> int32_t override;
|
||||
[[nodiscard]] auto CanBeFollowUp() const -> bool override { return false; }
|
||||
[[nodiscard]] auto CanDoWithLowMorale() const -> bool override { return true; }
|
||||
|
||||
@@ -52,7 +52,7 @@ auto FreezeWaterCommand::InternalExecute(
|
||||
if (PercentileRollSucceeds(odds, roll)) {
|
||||
freezeResult.set_type(ActionType::FREEZE_WATER);
|
||||
|
||||
TileModifierProto newTileModifier = fb::ToTileModifierProto(targetTerrain->modifier());
|
||||
TileModifierProto newTileModifier = fb::ToTileModifierProto(targetTerrain.modifier());
|
||||
IncreaseFrozen(newTileModifier, freezeWaterIntegrity);
|
||||
*freezeResult.add_changed_tile_modifiers() = MakeTmc(target, newTileModifier);
|
||||
} else {
|
||||
|
||||
@@ -29,7 +29,7 @@ protected:
|
||||
const ActionCost cost;
|
||||
const UnitId actorId;
|
||||
const Coords target;
|
||||
const Terrain* targetTerrain;
|
||||
const Terrain targetTerrain;
|
||||
const UnitId occupantId;
|
||||
const PercentileRollOdds odds;
|
||||
const int freezeWaterIntegrity;
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
cost(cost),
|
||||
actorId(actor->unit_id()),
|
||||
target(target),
|
||||
targetTerrain(targetTerrain),
|
||||
targetTerrain(*targetTerrain),
|
||||
occupantId(underwaterOccupant->unit_id()),
|
||||
odds(std::move(odds)),
|
||||
freezeWaterIntegrity(freezeWaterIntegrity),
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
cost(cost),
|
||||
actorId(actor->unit_id()),
|
||||
target(target),
|
||||
targetTerrain(targetTerrain),
|
||||
targetTerrain(*targetTerrain),
|
||||
occupantId(-1),
|
||||
odds(std::move(odds)),
|
||||
freezeWaterIntegrity(freezeWaterIntegrity),
|
||||
|
||||
@@ -59,7 +59,7 @@ auto HolyWaveDamageAction::InternalExecute(
|
||||
resultProto.set_type(net::eagle0::shardok::common::HOLY_WAVE_DAMAGE);
|
||||
|
||||
const Unit *damagedUnit = currentState->units()->Get(damagedUnitId);
|
||||
auto newSize = int32_t(damagedUnit->battalion().size() * (1.0 - damagePercentage));
|
||||
auto newSize = static_cast<int32_t>(damagedUnit->battalion().size() * (1.0 - damagePercentage));
|
||||
|
||||
auto damagedUnitAfter = *damagedUnit;
|
||||
damagedUnitAfter.mutable_battalion().mutate_size(newSize);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/common/ByteHasher.hpp"
|
||||
@@ -34,6 +35,7 @@ private:
|
||||
uint8_t* buffer;
|
||||
|
||||
static auto CopyBuffer(const uint8_t* data, const size_t size) -> uint8_t* {
|
||||
if (data == nullptr || size == 0) { return nullptr; }
|
||||
auto* b = WrapperAllocator::GetDefaultAllocator()->allocate(size);
|
||||
std::memcpy(b, data, size);
|
||||
return b;
|
||||
@@ -47,12 +49,29 @@ private:
|
||||
return CopyBuffer(copiedFrom.buffer, copiedFrom.size);
|
||||
}
|
||||
|
||||
static void ValidateBufferParams(const uint8_t* data, const size_t offset, const size_t size) {
|
||||
if (data == nullptr) {
|
||||
throw std::invalid_argument("Cannot construct wrapper from null bytes");
|
||||
}
|
||||
if (size == 0) { throw std::invalid_argument("Cannot construct wrapper from zero bytes"); }
|
||||
if (offset >= size) {
|
||||
throw std::invalid_argument("Cannot construct wrapper with offset outside buffer");
|
||||
}
|
||||
}
|
||||
|
||||
static auto CopyValidatedBuffer(const uint8_t* data, const size_t offset, const size_t size)
|
||||
-> uint8_t* {
|
||||
ValidateBufferParams(data, offset, size);
|
||||
return CopyBuffer(data, size);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit Wrapper(flatbuffers::FlatBufferBuilder& fbb) : size(0), offset(0) {
|
||||
const uint8_t* releasedBuf = fbb.ReleaseRaw(size, offset);
|
||||
|
||||
if (releasedBuf == nullptr || size == 0) {
|
||||
buffer = nullptr;
|
||||
delete[] releasedBuf;
|
||||
throw std::invalid_argument("Cannot construct wrapper from empty FlatBufferBuilder");
|
||||
} else {
|
||||
buffer = CopyBuffer(releasedBuf, size);
|
||||
}
|
||||
@@ -62,7 +81,7 @@ public:
|
||||
explicit Wrapper(uint8_t* allocatedBytes, const size_t offset, const size_t size)
|
||||
: size(size),
|
||||
offset(offset),
|
||||
buffer(CopyBuffer(allocatedBytes, size)) {}
|
||||
buffer(CopyValidatedBuffer(allocatedBytes, offset, size)) {}
|
||||
|
||||
Wrapper() : size(0), offset(0), buffer(nullptr) {}
|
||||
Wrapper(Wrapper&& movedFrom) noexcept
|
||||
@@ -76,17 +95,10 @@ public:
|
||||
|
||||
auto operator=(const Wrapper& other) -> Wrapper& {
|
||||
if (&other != this) {
|
||||
if (other.size > size) {
|
||||
if (buffer != nullptr) {
|
||||
WrapperAllocator::GetDefaultAllocator()->deallocate(buffer, size);
|
||||
}
|
||||
buffer = CopyBuffer(other.buffer, other.size);
|
||||
} else if (buffer != nullptr && other.buffer != nullptr) {
|
||||
std::memcpy(buffer, other.buffer, other.size);
|
||||
}
|
||||
|
||||
size = other.size;
|
||||
offset = other.offset;
|
||||
Wrapper copy(other);
|
||||
std::swap(size, copy.size);
|
||||
std::swap(offset, copy.offset);
|
||||
std::swap(buffer, copy.buffer);
|
||||
}
|
||||
|
||||
return *this;
|
||||
@@ -110,10 +122,22 @@ public:
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
operator const FB*() const { return Get(); } // NOLINT(*-explicit-constructor)
|
||||
|
||||
auto Get() const -> const FB* { return flatbuffers::GetRoot<FB>(buffer + offset); }
|
||||
auto Get() -> FB* { return flatbuffers::GetMutableRoot<FB>(buffer + offset); }
|
||||
auto operator->() const -> const FB* { return flatbuffers::GetRoot<FB>(buffer + offset); }
|
||||
auto operator->() -> FB* { return flatbuffers::GetMutableRoot<FB>(buffer + offset); }
|
||||
auto Get() const -> const FB* {
|
||||
if (buffer == nullptr) { return nullptr; }
|
||||
return flatbuffers::GetRoot<FB>(buffer + offset);
|
||||
}
|
||||
auto Get() -> FB* {
|
||||
if (buffer == nullptr) { return nullptr; }
|
||||
return flatbuffers::GetMutableRoot<FB>(buffer + offset);
|
||||
}
|
||||
auto operator->() const -> const FB* {
|
||||
if (const auto* root = Get(); root != nullptr) { return root; }
|
||||
throw std::logic_error("Cannot dereference an empty wrapper");
|
||||
}
|
||||
auto operator->() -> FB* {
|
||||
if (auto* root = Get(); root != nullptr) { return root; }
|
||||
throw std::logic_error("Cannot dereference an empty wrapper");
|
||||
}
|
||||
|
||||
[[nodiscard]] auto SaveTo(const std::string& path) const -> bool {
|
||||
return FilesystemUtils::AtomicallySaveToPath(path, ToByteVector());
|
||||
@@ -123,6 +147,7 @@ public:
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ToByteString() const -> string {
|
||||
if (buffer == nullptr || size == 0) { return {}; }
|
||||
string result;
|
||||
result.reserve(sizeof(size_t) + size);
|
||||
result.append(reinterpret_cast<const char*>(&offset), sizeof(size_t));
|
||||
@@ -138,25 +163,23 @@ public:
|
||||
}
|
||||
static auto FromByteString(const string& str) -> Wrapper {
|
||||
if (str.size() < sizeof(size_t)) {
|
||||
return Wrapper(); // Return empty wrapper for invalid data
|
||||
throw std::invalid_argument("Cannot construct wrapper from truncated bytes");
|
||||
}
|
||||
|
||||
size_t offset = 0;
|
||||
std::memcpy(&offset, str.data(), sizeof(size_t));
|
||||
|
||||
size_t size = str.size() - sizeof(size_t);
|
||||
auto toReturn = Wrapper(
|
||||
return Wrapper(
|
||||
const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(str.data())) + sizeof(size_t),
|
||||
offset,
|
||||
size);
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ToByteVector() const -> byte_vector { return byte_vector(ToByteString()); }
|
||||
static auto FromByteVector(const byte_vector& bv) -> Wrapper {
|
||||
if (bv.size() < sizeof(size_t)) {
|
||||
return Wrapper(); // Return empty wrapper for invalid data
|
||||
throw std::invalid_argument("Cannot construct wrapper from truncated bytes");
|
||||
}
|
||||
|
||||
size_t offset = 0;
|
||||
@@ -164,9 +187,7 @@ public:
|
||||
|
||||
size_t size = bv.size() - sizeof(size_t);
|
||||
// Note: CopyBuffer will handle const_cast safely by making a copy
|
||||
auto toReturn = Wrapper(const_cast<uint8_t*>(bv.data()) + sizeof(size_t), offset, size);
|
||||
|
||||
return toReturn;
|
||||
return Wrapper(const_cast<uint8_t*>(bv.data()) + sizeof(size_t), offset, size);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,27 @@
|
||||
|
||||
namespace shardok::fb {
|
||||
|
||||
using namespace net::eagle0::shardok::storage::fb;
|
||||
using flatbuffers::FlatBufferBuilder;
|
||||
using flatbuffers::Offset;
|
||||
using net::eagle0::shardok::storage::fb::AlliedPlayer;
|
||||
using net::eagle0::shardok::storage::fb::CreateGameStatusDirect;
|
||||
using net::eagle0::shardok::storage::fb::EndGameCondition;
|
||||
using net::eagle0::shardok::storage::fb::GameStateBuilder;
|
||||
using net::eagle0::shardok::storage::fb::HexMapDirection_NORTHEAST;
|
||||
using net::eagle0::shardok::storage::fb::PlayerInfoBuilder;
|
||||
using net::eagle0::shardok::storage::fb::Profession_NECROMANCER;
|
||||
using net::eagle0::shardok::storage::fb::UnitStatus_PENDING_REINFORCEMENT;
|
||||
using net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT;
|
||||
using net::eagle0::shardok::storage::fb::UnitStatus_RESERVED_SLOT;
|
||||
using net::eagle0::shardok::storage::fb::VictoryCondition;
|
||||
using net::eagle0::shardok::storage::fb::VictoryCondition_VICTORY_CONDITION_HOLDS_CRITICAL_TILES;
|
||||
using net::eagle0::shardok::storage::fb::VictoryCondition_VICTORY_CONDITION_LAST_ALLIANCE_STANDING;
|
||||
using net::eagle0::shardok::storage::fb::VictoryCondition_VICTORY_CONDITION_LAST_PLAYER_STANDING;
|
||||
using net::eagle0::shardok::storage::fb::VictoryCondition_VICTORY_CONDITION_MOST_TROOPS_STANDING;
|
||||
using net::eagle0::shardok::storage::fb::VictoryCondition_VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS;
|
||||
using net::eagle0::shardok::storage::fb::WeatherConditions_CLOUDS;
|
||||
using net::eagle0::shardok::storage::fb::Wind;
|
||||
namespace GameStatusNs = net::eagle0::shardok::storage::fb::GameStatus_;
|
||||
using GameStatusProto = net::eagle0::shardok::common::GameStatus;
|
||||
|
||||
const std::vector<PlayerId> kWinningPids{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
|
||||
@@ -136,7 +156,7 @@ auto SetupInitialGameState(
|
||||
EndGameCondition endGameCondition;
|
||||
auto gameStatusOffset = CreateGameStatusDirect(
|
||||
fbb,
|
||||
GameStatus_::State_SET_UP,
|
||||
GameStatusNs::State_SET_UP,
|
||||
nullptr,
|
||||
&kWinningPids,
|
||||
&endGameCondition);
|
||||
@@ -171,7 +191,8 @@ auto SetupInitialGameState(
|
||||
net::eagle0::shardok::storage::fb::MultiroundMagicState_NONE);
|
||||
if (modifiedUnit.can_archery()) {
|
||||
modifiedUnit.mutate_volleys_remaining(
|
||||
int32_t(modifiedUnit.battalion().armament() *
|
||||
static_cast<int32_t>(
|
||||
modifiedUnit.battalion().armament() *
|
||||
settings.Backing().armament_to_volleys()) +
|
||||
settings.GetBattalionType(modifiedUnit.battalion().type())->bonusVolleys);
|
||||
}
|
||||
@@ -293,9 +314,9 @@ auto ToPlayerInfoProto(const PlayerInfo* fbPI) -> PlayerInfoProto {
|
||||
auto ToWeatherProto(const Weather* fbW) -> net::eagle0::shardok::common::Weather {
|
||||
net::eagle0::shardok::common::Weather protoWeather;
|
||||
protoWeather.set_conditions(
|
||||
(net::eagle0::shardok::common::Weather_Conditions)fbW->conditions());
|
||||
static_cast<net::eagle0::shardok::common::Weather_Conditions>(fbW->conditions()));
|
||||
protoWeather.mutable_wind()->set_direction(
|
||||
(net::eagle0::shardok::common::HexMapDirection)fbW->wind().direction());
|
||||
static_cast<net::eagle0::shardok::common::HexMapDirection>(fbW->wind().direction()));
|
||||
protoWeather.mutable_wind()->set_speed_in_mph(fbW->wind().speed_in_mph());
|
||||
|
||||
return protoWeather;
|
||||
|
||||
@@ -13,20 +13,28 @@
|
||||
namespace shardok::fb {
|
||||
|
||||
using TerrainProto = net::eagle0::shardok::common::Terrain;
|
||||
using net::eagle0::shardok::storage::fb::Coords;
|
||||
using net::eagle0::shardok::storage::fb::CreateStartingPositionList;
|
||||
using net::eagle0::shardok::storage::fb::CreateStartingPositionListDirect;
|
||||
using net::eagle0::shardok::storage::fb::HexMapBuilder;
|
||||
using net::eagle0::shardok::storage::fb::MonthlyWeather;
|
||||
using net::eagle0::shardok::storage::fb::SingleModifier;
|
||||
using net::eagle0::shardok::storage::fb::StartingPositionList;
|
||||
using net::eagle0::shardok::storage::fb::Terrain;
|
||||
using net::eagle0::shardok::storage::fb::TileModifier;
|
||||
namespace TerrainNs = net::eagle0::shardok::storage::fb::Terrain_;
|
||||
|
||||
using namespace net::eagle0::shardok::storage::fb;
|
||||
|
||||
auto ConvertType(const net::eagle0::shardok::common::Terrain_Type protoType) -> Terrain_::Type {
|
||||
using namespace Terrain_;
|
||||
auto ConvertType(const net::eagle0::shardok::common::Terrain_Type protoType) -> TerrainNs::Type {
|
||||
switch (protoType) {
|
||||
case net::eagle0::shardok::common::Terrain_Type_CITY: return Type_CITY;
|
||||
case net::eagle0::shardok::common::Terrain_Type_FOREST: return Type_FOREST;
|
||||
case net::eagle0::shardok::common::Terrain_Type_HILL: return Type_HILL;
|
||||
case net::eagle0::shardok::common::Terrain_Type_MOUNTAIN: return Type_MOUNTAIN;
|
||||
case net::eagle0::shardok::common::Terrain_Type_PLAINS: return Type_PLAINS;
|
||||
case net::eagle0::shardok::common::Terrain_Type_RIVER: return Type_RIVER;
|
||||
case net::eagle0::shardok::common::Terrain_Type_STILL_WATER: return Type_STILL_WATER;
|
||||
case net::eagle0::shardok::common::Terrain_Type_SWAMP: return Type_SWAMP;
|
||||
case net::eagle0::shardok::common::Terrain_Type_CITY: return TerrainNs::Type_CITY;
|
||||
case net::eagle0::shardok::common::Terrain_Type_FOREST: return TerrainNs::Type_FOREST;
|
||||
case net::eagle0::shardok::common::Terrain_Type_HILL: return TerrainNs::Type_HILL;
|
||||
case net::eagle0::shardok::common::Terrain_Type_MOUNTAIN: return TerrainNs::Type_MOUNTAIN;
|
||||
case net::eagle0::shardok::common::Terrain_Type_PLAINS: return TerrainNs::Type_PLAINS;
|
||||
case net::eagle0::shardok::common::Terrain_Type_RIVER: return TerrainNs::Type_RIVER;
|
||||
case net::eagle0::shardok::common::Terrain_Type_STILL_WATER:
|
||||
return TerrainNs::Type_STILL_WATER;
|
||||
case net::eagle0::shardok::common::Terrain_Type_SWAMP: return TerrainNs::Type_SWAMP;
|
||||
|
||||
case net::eagle0::shardok::common::Terrain_Type_UNKNOWN:
|
||||
case net::eagle0::shardok::common::Terrain_Type_NONE:
|
||||
@@ -208,7 +216,7 @@ auto ConvertHexMapProto(
|
||||
}
|
||||
|
||||
auto CopyStartingPositionsList(FlatBufferBuilder& fbb, const StartingPositionList* spl) {
|
||||
std::vector<Coords> coordsVec = {};
|
||||
std::vector<Coords> coordsVec{};
|
||||
|
||||
if (spl && spl->positions()) {
|
||||
coordsVec.reserve(spl->positions()->size());
|
||||
@@ -232,7 +240,7 @@ auto CopyHexMap(const HexMap* hexMap) -> HexMapW {
|
||||
for (const auto* w : *hexMap->monthly_weather()) weatherVec.push_back(*w);
|
||||
const auto weatherOffset = fbb.CreateVectorOfStructs(weatherVec);
|
||||
|
||||
std::vector<Offset<StartingPositionList>> asplVec = {};
|
||||
std::vector<Offset<StartingPositionList>> asplVec{};
|
||||
asplVec.reserve(hexMap->attacker_starting_positions()->size());
|
||||
for (const auto* spl : *hexMap->attacker_starting_positions()) {
|
||||
asplVec.push_back(CopyStartingPositionsList(fbb, spl));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
|
||||
load("//tools:copts.bzl", "COPTS")
|
||||
|
||||
cc_library(
|
||||
@@ -28,6 +28,14 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "coords_set_benchmark",
|
||||
srcs = ["CoordsSetBenchmark.cpp"],
|
||||
copts = COPTS,
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":coords_set"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "hex_map_direction",
|
||||
hdrs = ["HexMapDirection.hpp"],
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace std {
|
||||
template<>
|
||||
struct hash<net::eagle0::shardok::common::Coords> {
|
||||
[[nodiscard]] auto operator()(const net::eagle0::shardok::common::Coords &c) const -> size_t {
|
||||
return size_t(c.row() << 16 | c.column());
|
||||
return static_cast<size_t>(c.row() << 16 | c.column());
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
#define EAGLE0_SHARDOK_LIBRARY_MAP_COORDS_SET_HPP
|
||||
|
||||
#include <bit>
|
||||
#include <functional>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -17,7 +20,11 @@
|
||||
|
||||
namespace shardok {
|
||||
|
||||
#ifndef NDEBUG
|
||||
constexpr bool kCoordsSetIndexChecks = true;
|
||||
#else
|
||||
constexpr bool kCoordsSetIndexChecks = false;
|
||||
#endif
|
||||
|
||||
constexpr size_t STANDARD_SIZE = size_t{12} * 14;
|
||||
|
||||
@@ -28,13 +35,28 @@ class CoordsSet {
|
||||
private:
|
||||
BackingBitset store;
|
||||
|
||||
int mapWidth;
|
||||
int mapHeight;
|
||||
uint8_t columnCount;
|
||||
uint8_t rowCount;
|
||||
|
||||
uint32_t indexCount;
|
||||
[[maybe_unused]] uint16_t indexCount;
|
||||
|
||||
void AssertCoordsInBounds([[maybe_unused]] const int row, [[maybe_unused]] const int column)
|
||||
const {
|
||||
if constexpr (kCoordsSetIndexChecks) {
|
||||
assert(row >= 0);
|
||||
assert(column >= 0);
|
||||
assert(row < rowCount);
|
||||
assert(column < columnCount);
|
||||
}
|
||||
}
|
||||
|
||||
void AssertIndexInBounds([[maybe_unused]] const uint32_t idx) const {
|
||||
if constexpr (kCoordsSetIndexChecks) { assert(idx < indexCount); }
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Index(const int row, const int column) const -> int {
|
||||
return (row * mapWidth) + column;
|
||||
AssertCoordsInBounds(row, column);
|
||||
return (row * columnCount) + column;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Index(const Coords& c) const -> int { return Index(c.row(), c.column()); }
|
||||
@@ -49,18 +71,25 @@ private:
|
||||
};
|
||||
|
||||
void Remove(uint32_t idx) {
|
||||
if (kCoordsSetIndexChecks) { assert(idx < indexCount); }
|
||||
AssertIndexInBounds(idx);
|
||||
store.Unset(idx);
|
||||
}
|
||||
[[nodiscard]] auto Contains(uint32_t idx) const -> bool {
|
||||
if (kCoordsSetIndexChecks) { assert(idx < indexCount); }
|
||||
AssertIndexInBounds(idx);
|
||||
return store.IsSet(idx);
|
||||
}
|
||||
|
||||
public:
|
||||
CoordsSet(int w, int h) : mapWidth(w), mapHeight(h), indexCount(w * h) {
|
||||
if (kCoordsSetIndexChecks) {
|
||||
assert(static_cast<size_t>(w) * static_cast<size_t>(h) < STANDARD_SIZE);
|
||||
CoordsSet(const int w, const int h)
|
||||
: columnCount(static_cast<uint8_t>(w)),
|
||||
rowCount(static_cast<uint8_t>(h)),
|
||||
indexCount(static_cast<uint16_t>(w * h)) {
|
||||
if constexpr (kCoordsSetIndexChecks) {
|
||||
assert(w >= 0);
|
||||
assert(h >= 0);
|
||||
assert(w <= std::numeric_limits<uint8_t>::max());
|
||||
assert(h <= std::numeric_limits<uint8_t>::max());
|
||||
assert(static_cast<size_t>(w) * static_cast<size_t>(h) <= STANDARD_SIZE);
|
||||
}
|
||||
clear();
|
||||
}
|
||||
@@ -74,15 +103,20 @@ public:
|
||||
auto operator=(const CoordsSet& rhs) -> CoordsSet& = default;
|
||||
auto operator=(CoordsSet&& rhs) -> CoordsSet& = default;
|
||||
|
||||
auto operator==(const CoordsSet& rhs) const -> bool {
|
||||
if (mapWidth != rhs.mapWidth) return false;
|
||||
if (mapHeight != rhs.mapHeight) return false;
|
||||
[[nodiscard]] auto operator==(const CoordsSet& rhs) const -> bool {
|
||||
if (columnCount != rhs.columnCount) return false;
|
||||
if (rowCount != rhs.rowCount) return false;
|
||||
return store == rhs.store;
|
||||
}
|
||||
|
||||
void Add(const Coords& c) { AddIndex(Index(c)); }
|
||||
void Add(const int row, const int column) { AddIndex(Index(row, column)); }
|
||||
void AddIndex(const uint32_t idx) { store.Set(idx); }
|
||||
void Add(const Coords& c) { AddMapIndexUnchecked(Index(c)); }
|
||||
void Add(const int row, const int column) { AddMapIndexUnchecked(Index(row, column)); }
|
||||
|
||||
// For callers that already have row * column_count + column.
|
||||
void AddMapIndexUnchecked(const uint32_t idx) {
|
||||
AssertIndexInBounds(idx);
|
||||
store.Set(idx);
|
||||
}
|
||||
|
||||
// Always succeeds
|
||||
void Remove(const Coords& c) { Remove(Index(c)); }
|
||||
@@ -101,13 +135,15 @@ public:
|
||||
|
||||
[[nodiscard]] auto empty() const -> bool { return store.empty(); }
|
||||
|
||||
[[nodiscard]] auto ColumnCount() const -> int { return columnCount; }
|
||||
|
||||
// Should only be used for debugging.
|
||||
[[nodiscard]] auto as_vector() const -> std::vector<Coords> {
|
||||
return std::vector<Coords>{this->begin(), this->end()};
|
||||
}
|
||||
|
||||
auto operator+=(const CoordsSet& rhs) -> CoordsSet& {
|
||||
if (mapWidth != rhs.mapWidth || mapHeight != rhs.mapHeight) {
|
||||
if (columnCount != rhs.columnCount || rowCount != rhs.rowCount) {
|
||||
throw MismatchException("mismatched sizes");
|
||||
}
|
||||
|
||||
@@ -115,14 +151,14 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator+(const CoordsSet& rhs) const -> CoordsSet {
|
||||
[[nodiscard]] auto operator+(const CoordsSet& rhs) const -> CoordsSet {
|
||||
CoordsSet cs = *this;
|
||||
cs += rhs;
|
||||
return cs;
|
||||
}
|
||||
|
||||
auto operator-=(const CoordsSet& rhs) -> CoordsSet& {
|
||||
if (mapWidth != rhs.mapWidth || mapHeight != rhs.mapHeight) {
|
||||
if (columnCount != rhs.columnCount || rowCount != rhs.rowCount) {
|
||||
throw MismatchException("mismatched sizes");
|
||||
}
|
||||
|
||||
@@ -130,15 +166,25 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator-(const CoordsSet& rhs) const -> CoordsSet {
|
||||
[[nodiscard]] auto operator-(const CoordsSet& rhs) const -> CoordsSet {
|
||||
CoordsSet cs = *this;
|
||||
cs -= rhs;
|
||||
return cs;
|
||||
}
|
||||
|
||||
void Filter(const std::function<bool(int, int)>& f) {
|
||||
for (uint32_t i = 0; i < indexCount; ++i) {
|
||||
if (Contains(i) && !f(i / mapWidth, i % mapWidth)) { Remove(i); }
|
||||
template<class Func>
|
||||
void Filter(Func&& f) {
|
||||
if (size() * 4 > indexCount) {
|
||||
for (uint32_t i = 0; i < indexCount; ++i) {
|
||||
if (Contains(i) && !f(i / columnCount, i % columnCount)) { Remove(i); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto iter = store.begin(); iter != store.end();) {
|
||||
const uint32_t i = static_cast<uint32_t>(*iter);
|
||||
++iter;
|
||||
if (!f(i / columnCount, i % columnCount)) { Remove(i); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,9 +192,9 @@ public:
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = Coords;
|
||||
using difference_type = int;
|
||||
using pointer = Coords*;
|
||||
using reference = Coords&;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = Coords;
|
||||
|
||||
private:
|
||||
BackingBitset::Iter iterator;
|
||||
@@ -158,15 +204,28 @@ public:
|
||||
public:
|
||||
Iter(const CoordsSet& cs, BackingBitset::Iter iter) : iterator(iter), cs(cs) {}
|
||||
|
||||
auto operator==(const Iter& rhs) const -> bool { return iterator == rhs.iterator; }
|
||||
auto operator!=(const Iter& rhs) const -> bool { return iterator != rhs.iterator; }
|
||||
void operator++() { ++iterator; }
|
||||
[[nodiscard]] auto operator==(const Iter& rhs) const -> bool {
|
||||
return iterator == rhs.iterator;
|
||||
}
|
||||
[[nodiscard]] auto operator!=(const Iter& rhs) const -> bool {
|
||||
return iterator != rhs.iterator;
|
||||
}
|
||||
auto operator++() -> Iter& {
|
||||
++iterator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() const -> Coords {
|
||||
auto operator++(int) -> Iter {
|
||||
Iter previous = *this;
|
||||
++(*this);
|
||||
return previous;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator*() const -> Coords {
|
||||
const size_t index = *iterator;
|
||||
|
||||
const int row = index / cs.mapWidth;
|
||||
const int column = index % cs.mapWidth;
|
||||
const int row = static_cast<int>(index / static_cast<size_t>(cs.columnCount));
|
||||
const int column = static_cast<int>(index % static_cast<size_t>(cs.columnCount));
|
||||
return Coords(row, column);
|
||||
}
|
||||
};
|
||||
@@ -185,25 +244,21 @@ public:
|
||||
[[nodiscard]] auto end() const -> BackingBitset::Iter { return cs.store.end(); }
|
||||
};
|
||||
[[nodiscard]] auto indexIterator() const -> IndexIterator { return IndexIterator(*this); }
|
||||
|
||||
[[nodiscard]] auto ToString() const -> std::string {
|
||||
return std::string(reinterpret_cast<char const*>(&store), STANDARD_SIZE);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
namespace common {
|
||||
static inline auto Contains(const shardok::CoordsSet& container, const shardok::Coords& elt)
|
||||
-> bool {
|
||||
[[nodiscard]] static inline auto Contains(
|
||||
const shardok::CoordsSet& container,
|
||||
const shardok::Coords& elt) -> bool {
|
||||
return container.Contains(elt);
|
||||
}
|
||||
|
||||
template<class Func>
|
||||
void FilterInPlace(shardok::CoordsSet& container, Func fn) {
|
||||
for (const shardok::Coords& c : container) {
|
||||
if (!fn(c)) container.Remove(c);
|
||||
}
|
||||
container.Filter(
|
||||
[&fn](const int row, const int column) { return fn(shardok::Coords(row, column)); });
|
||||
}
|
||||
} // namespace common
|
||||
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
|
||||
|
||||
namespace shardok {
|
||||
namespace {
|
||||
|
||||
constexpr int kColumnCount = 12;
|
||||
constexpr int kRowCount = 14;
|
||||
constexpr int kIndexCount = kColumnCount * kRowCount;
|
||||
|
||||
volatile uint64_t benchmarkSink = 0;
|
||||
|
||||
template<class T>
|
||||
void DoNotOptimize(const T& value) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
asm volatile("" : : "g"(&value) : "memory");
|
||||
#else
|
||||
benchmarkSink ^= reinterpret_cast<uintptr_t>(&value);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct BenchmarkResult {
|
||||
std::string_view name;
|
||||
int iterations;
|
||||
std::chrono::nanoseconds elapsed;
|
||||
uint64_t checksum;
|
||||
};
|
||||
|
||||
void Consume(uint64_t checksum) { benchmarkSink = checksum; }
|
||||
|
||||
[[nodiscard]] auto MakePatternedCoordsSet(const int step, const int offset = 0) -> CoordsSet {
|
||||
CoordsSet coords(kColumnCount, kRowCount);
|
||||
for (int index = offset; index < kIndexCount; index += step) {
|
||||
coords.AddMapIndexUnchecked(static_cast<uint32_t>(index));
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
template<class Func>
|
||||
[[nodiscard]] auto RunBenchmark(std::string_view name, const int iterations, Func func)
|
||||
-> BenchmarkResult {
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
const uint64_t checksum = func(iterations);
|
||||
const auto end = std::chrono::steady_clock::now();
|
||||
Consume(checksum);
|
||||
return BenchmarkResult{
|
||||
name,
|
||||
iterations,
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(end - start),
|
||||
checksum};
|
||||
}
|
||||
|
||||
void PrintResult(const BenchmarkResult& result) {
|
||||
const double nsPerIteration =
|
||||
static_cast<double>(result.elapsed.count()) / static_cast<double>(result.iterations);
|
||||
std::cout << std::left << std::setw(28) << result.name << " iterations=" << std::right
|
||||
<< std::setw(10) << result.iterations << " ns/op=" << std::fixed
|
||||
<< std::setprecision(2) << std::setw(10) << nsPerIteration
|
||||
<< " checksum=" << result.checksum << '\n';
|
||||
}
|
||||
|
||||
[[nodiscard]] auto BenchmarkCopies(const int iterations) -> uint64_t {
|
||||
const CoordsSet source = MakePatternedCoordsSet(3);
|
||||
uint64_t checksum = 0;
|
||||
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
CoordsSet copy = source;
|
||||
DoNotOptimize(copy);
|
||||
checksum += copy.size();
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto BenchmarkUnions(const int iterations) -> uint64_t {
|
||||
const CoordsSet left = MakePatternedCoordsSet(3);
|
||||
const CoordsSet right = MakePatternedCoordsSet(5, 1);
|
||||
uint64_t checksum = 0;
|
||||
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
CoordsSet combined = left;
|
||||
combined += right;
|
||||
DoNotOptimize(combined);
|
||||
checksum += combined.size();
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto BenchmarkSubtractions(const int iterations) -> uint64_t {
|
||||
const CoordsSet full = MakePatternedCoordsSet(1);
|
||||
const CoordsSet removed = MakePatternedCoordsSet(4);
|
||||
uint64_t checksum = 0;
|
||||
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
CoordsSet remaining = full;
|
||||
remaining -= removed;
|
||||
DoNotOptimize(remaining);
|
||||
checksum += remaining.size();
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto BenchmarkContains(const int iterations) -> uint64_t {
|
||||
const CoordsSet coords = MakePatternedCoordsSet(3);
|
||||
uint64_t checksum = 0;
|
||||
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
const int index = i % kIndexCount;
|
||||
checksum += coords.Contains(index / kColumnCount, index % kColumnCount) ? 1 : 0;
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto BenchmarkCoordinateIteration(const int iterations) -> uint64_t {
|
||||
const CoordsSet coords = MakePatternedCoordsSet(3);
|
||||
uint64_t checksum = 0;
|
||||
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
for (const auto& coord : coords) {
|
||||
checksum += static_cast<uint64_t>(coord.row() + coord.column());
|
||||
}
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto BenchmarkIndexIteration(const int iterations) -> uint64_t {
|
||||
const CoordsSet coords = MakePatternedCoordsSet(3);
|
||||
uint64_t checksum = 0;
|
||||
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
for (const auto index : coords.indexIterator()) { checksum += index; }
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto BenchmarkDenseFilter(const int iterations) -> uint64_t {
|
||||
const CoordsSet source = MakePatternedCoordsSet(1);
|
||||
uint64_t checksum = 0;
|
||||
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
CoordsSet filtered = source;
|
||||
filtered.Filter([](const int row, const int column) { return (row + column) % 3 == 0; });
|
||||
DoNotOptimize(filtered);
|
||||
checksum += filtered.size();
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto BenchmarkSparseFilter(const int iterations) -> uint64_t {
|
||||
const CoordsSet source = MakePatternedCoordsSet(7);
|
||||
uint64_t checksum = 0;
|
||||
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
CoordsSet filtered = source;
|
||||
filtered.Filter([](const int row, const int column) { return (row + column) % 3 == 0; });
|
||||
DoNotOptimize(filtered);
|
||||
checksum += filtered.size();
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace shardok
|
||||
|
||||
auto main() -> int {
|
||||
using shardok::BenchmarkContains;
|
||||
using shardok::BenchmarkCoordinateIteration;
|
||||
using shardok::BenchmarkCopies;
|
||||
using shardok::BenchmarkDenseFilter;
|
||||
using shardok::BenchmarkIndexIteration;
|
||||
using shardok::BenchmarkSparseFilter;
|
||||
using shardok::BenchmarkSubtractions;
|
||||
using shardok::BenchmarkUnions;
|
||||
using shardok::CoordsSet;
|
||||
using shardok::PrintResult;
|
||||
using shardok::RunBenchmark;
|
||||
|
||||
std::cout << "CoordsSet benchmark\n";
|
||||
std::cout << "sizeof(CoordsSet)=" << sizeof(CoordsSet)
|
||||
<< " alignof(CoordsSet)=" << alignof(CoordsSet) << '\n';
|
||||
|
||||
PrintResult(RunBenchmark("copy", 5'000'000, BenchmarkCopies));
|
||||
PrintResult(RunBenchmark("union", 3'000'000, BenchmarkUnions));
|
||||
PrintResult(RunBenchmark("subtraction", 3'000'000, BenchmarkSubtractions));
|
||||
PrintResult(RunBenchmark("contains", 20'000'000, BenchmarkContains));
|
||||
PrintResult(RunBenchmark("coord iteration", 1'000'000, BenchmarkCoordinateIteration));
|
||||
PrintResult(RunBenchmark("index iteration", 1'000'000, BenchmarkIndexIteration));
|
||||
PrintResult(RunBenchmark("dense filter", 500'000, BenchmarkDenseFilter));
|
||||
PrintResult(RunBenchmark("sparse filter", 500'000, BenchmarkSparseFilter));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ using TileModifierProto = net::eagle0::shardok::common::TileModifier;
|
||||
|
||||
SingleModifier MakeSingleModifier(const std::optional<double>& integ = std::nullopt);
|
||||
|
||||
bool operator==(const SingleModifier& lhs, const SingleModifier& rhs);
|
||||
[[nodiscard]] bool operator==(const SingleModifier& lhs, const SingleModifier& rhs);
|
||||
|
||||
TileModifierProto MakeTileModifier(bool onFire = false);
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
namespace shardok {
|
||||
using TileModifierWithCoords = net::eagle0::shardok::common::TileModifierWithCoords;
|
||||
|
||||
auto MakeTmc(const Coords &coordinates, const TileModifierProto &modifier)
|
||||
[[nodiscard]] auto MakeTmc(const Coords &coordinates, const TileModifierProto &modifier)
|
||||
-> TileModifierWithCoords;
|
||||
|
||||
auto MakeTmc(
|
||||
[[nodiscard]] auto MakeTmc(
|
||||
const Coords &coordinates,
|
||||
const net::eagle0::shardok::storage::fb::TileModifier &modifier) -> TileModifierWithCoords;
|
||||
|
||||
|
||||
@@ -92,7 +92,8 @@ public:
|
||||
net::eagle0::shardok::storage::fb::WeatherConditions conditions) const -> int;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto GetGetter() const -> Getter { return Getter(*this); }
|
||||
[[nodiscard]] auto GetGetter() const & -> Getter { return Getter(*this); }
|
||||
[[nodiscard]] auto GetGetter() const && -> Getter = delete;
|
||||
|
||||
class Setter {
|
||||
private:
|
||||
@@ -118,7 +119,8 @@ public:
|
||||
void SetFirePropensityFromWeather(Weather::Conditions wc, int propensity);
|
||||
};
|
||||
|
||||
auto GetSetter() -> Setter { return Setter(*this); }
|
||||
[[nodiscard]] auto GetSetter() & -> Setter { return Setter(*this); }
|
||||
[[nodiscard]] auto GetSetter() && -> Setter = delete;
|
||||
|
||||
virtual ~GameSettings() = default;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace shardok {
|
||||
|
||||
class SettingsLoader {
|
||||
public:
|
||||
static auto LoadSettings() -> std::shared_ptr<GameSettings>;
|
||||
[[nodiscard]] static auto LoadSettings() -> std::shared_ptr<GameSettings>;
|
||||
};
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
#ifndef EAGLE0_SHARDOK_LIBRARY_UTIL_ACTION_RESULT_FLATBUFFER_HELPERS_HPP
|
||||
#define EAGLE0_SHARDOK_LIBRARY_UTIL_ACTION_RESULT_FLATBUFFER_HELPERS_HPP
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "src/main/flatbuffer/net/eagle0/shardok/storage/unit.hpp"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/storage/action_result.pb.h"
|
||||
@@ -16,13 +19,30 @@ using ActionResult = net::eagle0::shardok::storage::ActionResult;
|
||||
using ResolvedUnit = net::eagle0::shardok::storage::ResolvedUnit;
|
||||
using Unit = net::eagle0::shardok::storage::fb::Unit;
|
||||
|
||||
static_assert(std::is_trivially_copyable_v<Unit>);
|
||||
|
||||
[[nodiscard]] static inline auto UnitToBytes(const Unit& unit) -> std::string {
|
||||
std::string bytes(sizeof(Unit), '\0');
|
||||
std::memcpy(bytes.data(), &unit, sizeof(Unit));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
[[nodiscard]] static inline auto UnitFromBytes(const std::string& unitBytes) -> Unit {
|
||||
if (unitBytes.size() != sizeof(Unit)) {
|
||||
throw std::invalid_argument("Unexpected unit byte length");
|
||||
}
|
||||
|
||||
Unit unit{};
|
||||
std::memcpy(&unit, unitBytes.data(), sizeof(Unit));
|
||||
return unit;
|
||||
}
|
||||
|
||||
static inline void AddChangedUnit(ActionResult& result, const Unit& unit) {
|
||||
result.add_changed_units_fb(&unit, sizeof(Unit));
|
||||
}
|
||||
|
||||
static inline void AddUnitToResolved(ResolvedUnit& resolvedUnit, const Unit& unit) {
|
||||
*resolvedUnit.mutable_unit_bytes() =
|
||||
std::string(reinterpret_cast<const char*>(&unit), sizeof(Unit));
|
||||
*resolvedUnit.mutable_unit_bytes() = UnitToBytes(unit);
|
||||
}
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
@@ -6,6 +6,7 @@ cc_library(
|
||||
hdrs = ["ActionResultFlatbufferHelpers.hpp"],
|
||||
copts = COPTS,
|
||||
visibility = [
|
||||
"//src/main/cpp/net/eagle0/shardok:__subpackages__",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:__subpackages__",
|
||||
"//src/test/cpp/net/eagle0/shardok/library:__subpackages__",
|
||||
],
|
||||
|
||||
@@ -306,10 +306,9 @@ auto MakeActionResultFb(const ActionType type, const MeleeResultFb &results, con
|
||||
ar.mutable_actor()->set_value(results.attackerAfter.unit_id());
|
||||
ar.mutable_target_unit()->set_value(results.defenderAfter.unit_id());
|
||||
|
||||
AddChangedUnit(ar, results.attackerAfter);
|
||||
auto *attCU = reinterpret_cast<Unit *>(
|
||||
ar.mutable_changed_units_fb(ar.changed_units_fb_size() - 1)->data());
|
||||
if (resetZoc) attCU->mutate_has_moved_in_zoc(false);
|
||||
Unit attackerAfter = results.attackerAfter;
|
||||
if (resetZoc) attackerAfter.mutate_has_moved_in_zoc(false);
|
||||
AddChangedUnit(ar, attackerAfter);
|
||||
|
||||
AddChangedUnit(ar, results.defenderAfter);
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ const std::vector<CoordsSet> HexMapUtils::standardCoords =
|
||||
|
||||
auto HexMapUtils::ComputeAllStandardAdjacentCoords() -> std::vector<CoordsSet> {
|
||||
std::vector<CoordsSet> arr;
|
||||
arr.reserve(size_t(STANDARD_MAP_ROWS * STANDARD_MAP_COLUMNS));
|
||||
arr.reserve(static_cast<size_t>(STANDARD_MAP_ROWS * STANDARD_MAP_COLUMNS));
|
||||
|
||||
for (int index = 0; index < STANDARD_MAP_ROWS * STANDARD_MAP_COLUMNS; ++index) {
|
||||
arr.push_back(ComputeAdjacentCoords(
|
||||
@@ -127,25 +127,25 @@ auto HexMapUtils::ComputeAdjacentCoords(
|
||||
|
||||
// Row above
|
||||
if (origin.row() > 0) {
|
||||
cs.AddIndex(originIndex - columnCount);
|
||||
cs.AddMapIndexUnchecked(originIndex - columnCount);
|
||||
|
||||
if (origin.row() % 2 == 0) {
|
||||
if (origin.column() > 0) { cs.AddIndex(originIndex - columnCount - 1); }
|
||||
if (origin.column() > 0) { cs.AddMapIndexUnchecked(originIndex - columnCount - 1); }
|
||||
} else if (origin.column() < columnCount - 1) {
|
||||
cs.AddIndex(originIndex - columnCount + 1);
|
||||
cs.AddMapIndexUnchecked(originIndex - columnCount + 1);
|
||||
}
|
||||
}
|
||||
// This row
|
||||
if (origin.column() > 0) { cs.AddIndex(originIndex - 1); }
|
||||
if (origin.column() < columnCount - 1) { cs.AddIndex(originIndex + 1); }
|
||||
if (origin.column() > 0) { cs.AddMapIndexUnchecked(originIndex - 1); }
|
||||
if (origin.column() < columnCount - 1) { cs.AddMapIndexUnchecked(originIndex + 1); }
|
||||
// Row below
|
||||
if (origin.row() < rowCount - 1) {
|
||||
cs.AddIndex(originIndex + columnCount);
|
||||
cs.AddMapIndexUnchecked(originIndex + columnCount);
|
||||
|
||||
if (origin.row() % 2 == 0) {
|
||||
if (origin.column() > 0) { cs.AddIndex(originIndex + columnCount - 1); }
|
||||
if (origin.column() > 0) { cs.AddMapIndexUnchecked(originIndex + columnCount - 1); }
|
||||
} else if (origin.column() < columnCount - 1) {
|
||||
cs.AddIndex(originIndex + columnCount + 1);
|
||||
cs.AddMapIndexUnchecked(originIndex + columnCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ struct alignas(16) AdjacentTile {
|
||||
|
||||
AdjacentTile(const Coords co, const HexMapDirection dir) : directionTo(dir), coords(co) {}
|
||||
|
||||
auto operator==(const AdjacentTile &rhs) const -> bool {
|
||||
[[nodiscard]] auto operator==(const AdjacentTile &rhs) const -> bool {
|
||||
return coords == rhs.coords && directionTo == rhs.directionTo;
|
||||
}
|
||||
};
|
||||
@@ -73,21 +73,21 @@ public:
|
||||
|
||||
[[nodiscard]] auto AllCastleCoords(const HexMap *hexMap) -> CoordsSet;
|
||||
|
||||
auto HasForestAccess(
|
||||
[[nodiscard]] auto HasForestAccess(
|
||||
const Coords &coords,
|
||||
const Units *units,
|
||||
const HexMap *hexMap,
|
||||
const std::vector<PlayerId> &allyPids,
|
||||
PlayerId player) -> bool;
|
||||
|
||||
auto GetTerrain(const HexMap *map, const Coords &coords) -> const Terrain *;
|
||||
auto GetMutableTerrain(HexMap *map, const Coords &coords) -> Terrain *;
|
||||
[[nodiscard]] auto GetTerrain(const HexMap *map, const Coords &coords) -> const Terrain *;
|
||||
[[nodiscard]] auto GetMutableTerrain(HexMap *map, const Coords &coords) -> Terrain *;
|
||||
// Overload for const HexMap - uses const_cast internally. Safe when the underlying buffer is
|
||||
// mutable.
|
||||
auto GetMutableTerrain(const HexMap *map, const Coords &coords) -> Terrain *;
|
||||
[[nodiscard]] auto GetMutableTerrain(const HexMap *map, const Coords &coords) -> Terrain *;
|
||||
|
||||
auto CoordsAreValid(const HexMap *map, const Coords &coords) -> bool;
|
||||
auto CoordsAreValid(int rowCount, int columnCount, const Coords &coords) -> bool;
|
||||
[[nodiscard]] auto CoordsAreValid(const HexMap *map, const Coords &coords) -> bool;
|
||||
[[nodiscard]] auto CoordsAreValid(int rowCount, int columnCount, const Coords &coords) -> bool;
|
||||
|
||||
[[nodiscard]] auto GetTilesAcrossWater(const HexMap *map, const Coords &coords) -> CoordsSet;
|
||||
|
||||
@@ -100,7 +100,8 @@ auto CoordsAreValid(int rowCount, int columnCount, const Coords &coords) -> bool
|
||||
[[nodiscard]] auto TwoAwayTilesUnblockedByMountains(const HexMap *map, const Coords &coords)
|
||||
-> CoordsSet;
|
||||
|
||||
static inline auto Occupant(const Units *units, const Coords &coords) -> const Unit * {
|
||||
[[nodiscard]] static inline auto Occupant(const Units *units, const Coords &coords)
|
||||
-> const Unit * {
|
||||
for (const auto &unit : *units) {
|
||||
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
|
||||
unit->location() == coords) {
|
||||
@@ -111,7 +112,8 @@ static inline auto Occupant(const Units *units, const Coords &coords) -> const U
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline auto Occupant(const Units &units, const Coords &coords) -> const Unit * {
|
||||
[[nodiscard]] static inline auto Occupant(const Units &units, const Coords &coords)
|
||||
-> const Unit * {
|
||||
for (const auto &unit : units) {
|
||||
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
|
||||
unit->location() == coords) {
|
||||
@@ -122,7 +124,7 @@ static inline auto Occupant(const Units &units, const Coords &coords) -> const U
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline auto Occupants(
|
||||
[[nodiscard]] static inline auto Occupants(
|
||||
const std::vector<const Unit *> &units,
|
||||
const int rowCount,
|
||||
const int columnCount) -> std::vector<const Unit *> {
|
||||
@@ -138,7 +140,7 @@ static inline auto Occupants(
|
||||
return positions;
|
||||
}
|
||||
|
||||
static inline auto Occupants(
|
||||
[[nodiscard]] static inline auto Occupants(
|
||||
const flatbuffers::Vector<const Unit *> &units,
|
||||
const int rowCount,
|
||||
const int columnCount) -> std::vector<const Unit *> {
|
||||
@@ -154,8 +156,9 @@ static inline auto Occupants(
|
||||
return positions;
|
||||
}
|
||||
|
||||
static inline auto Occupant(const std::vector<const Unit *> &units, const Coords &coords)
|
||||
-> const Unit * {
|
||||
[[nodiscard]] static inline auto Occupant(
|
||||
const std::vector<const Unit *> &units,
|
||||
const Coords &coords) -> const Unit * {
|
||||
for (const auto &unit : units) {
|
||||
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
|
||||
unit->location() == coords) {
|
||||
@@ -167,7 +170,7 @@ static inline auto Occupant(const std::vector<const Unit *> &units, const Coords
|
||||
}
|
||||
|
||||
template<class UnitContainer>
|
||||
auto Occupant(const UnitContainer &units, const Coords &coords)
|
||||
[[nodiscard]] auto Occupant(const UnitContainer &units, const Coords &coords)
|
||||
-> std::optional<typename UnitContainer::value_type> {
|
||||
for (const auto &unit : units) {
|
||||
if (unit.location() == coords) { return unit; }
|
||||
@@ -177,7 +180,8 @@ auto Occupant(const UnitContainer &units, const Coords &coords)
|
||||
}
|
||||
|
||||
template<class U>
|
||||
auto Occupant(const std::map<UnitId, U> &units, const Coords &coords) -> std::optional<U> {
|
||||
[[nodiscard]] auto Occupant(const std::map<UnitId, U> &units, const Coords &coords)
|
||||
-> std::optional<U> {
|
||||
for (const auto &[unitId, unit] : units) {
|
||||
if (unit.location() == coords) { return unit; }
|
||||
}
|
||||
@@ -186,7 +190,7 @@ auto Occupant(const std::map<UnitId, U> &units, const Coords &coords) -> std::op
|
||||
}
|
||||
|
||||
template<class U>
|
||||
auto Occupant(const ::google::protobuf::Map<UnitId, U> &units, const Coords &coords)
|
||||
[[nodiscard]] auto Occupant(const ::google::protobuf::Map<UnitId, U> &units, const Coords &coords)
|
||||
-> std::optional<U> {
|
||||
for (const auto &[unitId, unit] : units) {
|
||||
if (unit.location() == coords) { return unit; }
|
||||
@@ -196,7 +200,7 @@ auto Occupant(const ::google::protobuf::Map<UnitId, U> &units, const Coords &coo
|
||||
}
|
||||
|
||||
template<class U, class UnitContainer>
|
||||
auto Occupant(const UnitContainer &units, const Coords &coords) -> std::optional<U> {
|
||||
[[nodiscard]] auto Occupant(const UnitContainer &units, const Coords &coords) -> std::optional<U> {
|
||||
for (const auto &unit : units) {
|
||||
if (unit.location() == coords) { return unit; }
|
||||
}
|
||||
@@ -204,7 +208,7 @@ auto Occupant(const UnitContainer &units, const Coords &coords) -> std::optional
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static inline auto FriendlyOccupant(
|
||||
[[nodiscard]] static inline auto FriendlyOccupant(
|
||||
const PlayerId pid,
|
||||
const Units *units,
|
||||
const std::vector<PlayerId> &allyPids,
|
||||
@@ -221,7 +225,7 @@ static inline auto FriendlyOccupant(
|
||||
}
|
||||
|
||||
template<class U, class UnitContainer>
|
||||
auto FriendlyOccupant(
|
||||
[[nodiscard]] auto FriendlyOccupant(
|
||||
PlayerId pid,
|
||||
const UnitContainer &units,
|
||||
const std::vector<PlayerId> &allyPids,
|
||||
@@ -237,14 +241,14 @@ auto FriendlyOccupant(
|
||||
return {};
|
||||
}
|
||||
|
||||
auto KnownEnemyOccupant(
|
||||
[[nodiscard]] auto KnownEnemyOccupant(
|
||||
PlayerId playerId,
|
||||
const Units *units,
|
||||
const std::vector<PlayerId> &allyPids,
|
||||
const Coords &coords) -> const Unit *;
|
||||
|
||||
template<class UnitContainer>
|
||||
auto KnownEnemyOccupant(
|
||||
[[nodiscard]] auto KnownEnemyOccupant(
|
||||
PlayerId playerId,
|
||||
const UnitContainer &units,
|
||||
const std::vector<PlayerId> &allyPids,
|
||||
@@ -277,31 +281,35 @@ auto KnownEnemyOccupant(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto KnownAdjacentEnemies(
|
||||
[[nodiscard]] auto KnownAdjacentEnemies(
|
||||
PlayerId playerId,
|
||||
const Units *units,
|
||||
const HexMap *map,
|
||||
const std::vector<PlayerId> &allyPids,
|
||||
const Coords &coords) -> std::vector<const Unit *>;
|
||||
|
||||
auto DirectionsTo(int columnCount, const Coords &origin, const Coords &destination)
|
||||
[[nodiscard]] auto DirectionsTo(int columnCount, const Coords &origin, const Coords &destination)
|
||||
-> HexMapDirectionsTo;
|
||||
|
||||
static inline auto DirectionsTo(const HexMap *map, const Coords &origin, const Coords &destination)
|
||||
-> HexMapDirectionsTo {
|
||||
[[nodiscard]] static inline auto DirectionsTo(
|
||||
const HexMap *map,
|
||||
const Coords &origin,
|
||||
const Coords &destination) -> HexMapDirectionsTo {
|
||||
return DirectionsTo(map->column_count(), origin, destination);
|
||||
}
|
||||
|
||||
auto MaybeTileInDirection(const HexMap *map, const Coords &inCoords, HexMapDirection orientation)
|
||||
-> std::optional<Coords>;
|
||||
[[nodiscard]] auto MaybeTileInDirection(
|
||||
const HexMap *map,
|
||||
const Coords &inCoords,
|
||||
HexMapDirection orientation) -> std::optional<Coords>;
|
||||
|
||||
auto MaybeOccupantInDirection(
|
||||
[[nodiscard]] auto MaybeOccupantInDirection(
|
||||
const HexMap *map,
|
||||
const Units *units,
|
||||
const Coords &coords,
|
||||
HexMapDirection direction) -> const Unit *;
|
||||
|
||||
auto GetAllCoords(const HexMap *hexMap) -> CoordsSet;
|
||||
[[nodiscard]] auto GetAllCoords(const HexMap *hexMap) -> CoordsSet;
|
||||
|
||||
void MutatingSetTileModifier(
|
||||
HexMapT *hexMap,
|
||||
@@ -318,9 +326,10 @@ void MutatingSetTileModifier(
|
||||
const CoordsProto &coords,
|
||||
const TileModifierProto &TileModifierProto);
|
||||
|
||||
auto GetCriticalTileLocations(const HexMap *hexMap) -> CoordsSet;
|
||||
[[nodiscard]] auto GetCriticalTileLocations(const HexMap *hexMap) -> CoordsSet;
|
||||
|
||||
auto IsBelievedEmpty(PlayerId playerId, const Coords &coords, const Units *units) -> bool;
|
||||
[[nodiscard]] auto IsBelievedEmpty(PlayerId playerId, const Coords &coords, const Units *units)
|
||||
-> bool;
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ namespace shardok {
|
||||
using PlayerInfoFb = net::eagle0::shardok::storage::fb::PlayerInfo;
|
||||
using GameState = net::eagle0::shardok::storage::fb::GameState;
|
||||
|
||||
auto PlayerIsDefender(const GameState *gameState, int playerId) -> bool;
|
||||
auto NextPlayerId(const GameState *gameState, PlayerId pid, bool allowUncontrolled = true)
|
||||
-> PlayerId;
|
||||
auto AlliedPids(const GameState *gameState, int playerId) -> std::vector<PlayerId>;
|
||||
[[nodiscard]] auto PlayerIsDefender(const GameState *gameState, int playerId) -> bool;
|
||||
[[nodiscard]] auto
|
||||
NextPlayerId(const GameState *gameState, PlayerId pid, bool allowUncontrolled = true) -> PlayerId;
|
||||
[[nodiscard]] auto AlliedPids(const GameState *gameState, int playerId) -> std::vector<PlayerId>;
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <ranges>
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/GameStateFilter.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/GameStateViewDiffer.hpp"
|
||||
#include "src/main/protobuf/net/eagle0/shardok/common/action_type.pb.h"
|
||||
@@ -87,7 +88,7 @@ auto TargetIsHiddenFromOpponents(const ActionResultType type) -> bool {
|
||||
if (result.has_actor()) {
|
||||
const auto actorId = result.actor().value();
|
||||
for (const auto &unitData : result.changed_units_fb()) {
|
||||
const auto &unit = *reinterpret_cast<const Unit *>(unitData.data());
|
||||
const auto unit = UnitFromBytes(unitData);
|
||||
if (unit.unit_id() == actorId) {
|
||||
opponentKnowledgeOfActor = OpponentKnowledge(unit, askingPlayer);
|
||||
hiddenActor = unit.hidden();
|
||||
|
||||
@@ -13,6 +13,7 @@ cc_library(
|
||||
deps = [
|
||||
":unit_filter",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/settings:game_settings",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/util:action_result_flatbuffer_helpers",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/view_filters:game_state_filter",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/view_filters:game_state_view_differ",
|
||||
"//src/main/protobuf/net/eagle0/shardok/api:action_result_view_cc_proto",
|
||||
|
||||
@@ -70,7 +70,8 @@ auto MakeGameStateViewDiff(const GameStateView &before, const GameStateView &aft
|
||||
|
||||
for (int tileIndex = 0; tileIndex < afterCount; ++tileIndex) {
|
||||
Coords coords =
|
||||
Coords(int8_t(tileIndex / afterColumnCount), int8_t(tileIndex % afterColumnCount));
|
||||
Coords(static_cast<int8_t>(tileIndex / afterColumnCount),
|
||||
static_cast<int8_t>(tileIndex % afterColumnCount));
|
||||
const auto afterModifier = after.hex_map().terrain(tileIndex).modifier();
|
||||
|
||||
if (tileIndex >= before.hex_map().terrain_size()) {
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace shardok {
|
||||
using net::eagle0::shardok::api::GameStateView;
|
||||
using net::eagle0::shardok::api::GameStateViewDiff;
|
||||
|
||||
auto MakeGameStateViewDiff(const GameStateView& before, const GameStateView& after)
|
||||
[[nodiscard]] auto MakeGameStateViewDiff(const GameStateView& before, const GameStateView& after)
|
||||
-> std::optional<GameStateViewDiff>;
|
||||
|
||||
} // namespace shardok
|
||||
|
||||
@@ -57,6 +57,7 @@ cc_library(
|
||||
deps = [
|
||||
"//src/main/cpp/net/eagle0/shardok/library:shardok_c_types",
|
||||
"//src/main/cpp/net/eagle0/shardok/library:shardok_exception",
|
||||
"//src/main/cpp/net/eagle0/shardok/library/util:action_result_flatbuffer_helpers",
|
||||
"//src/main/flatbuffer/net/eagle0/shardok/storage:unit_cc_fbs",
|
||||
"//src/main/protobuf/net/eagle0/common:common_unit_cc_proto",
|
||||
"//src/main/protobuf/net/eagle0/common:shardok_internal_interface_cc_grpc",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/ShardokException.hpp"
|
||||
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
|
||||
#include "src/main/protobuf/net/eagle0/common/victory_condition.pb.h"
|
||||
|
||||
namespace shardok {
|
||||
@@ -71,13 +72,13 @@ auto PopulateGameOverResponse(
|
||||
|
||||
// copy in all the resolvedUnits that correspond to this player
|
||||
for (const auto &internalRu : resolvedUnits) {
|
||||
const auto *unit = reinterpret_cast<const Unit *>(internalRu.unit_bytes().data());
|
||||
if (!unit->has_attached_hero()) continue;
|
||||
const auto unit = UnitFromBytes(internalRu.unit_bytes());
|
||||
if (!unit.has_attached_hero()) continue;
|
||||
if (!IncludeUnitProtoInReturn(internalRu.status())) continue;
|
||||
if (unit->player_id() != shardokPid) continue;
|
||||
if (unit.player_id() != shardokPid) continue;
|
||||
|
||||
auto *ru = resolutionInfo->add_units();
|
||||
*ru->mutable_unit() = FromUnitFb(*unit);
|
||||
*ru->mutable_unit() = FromUnitFb(unit);
|
||||
ru->set_status(FromInternalStatus(internalRu.status()));
|
||||
if (internalRu.status() ==
|
||||
net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_SECURED_UNIT) {
|
||||
|
||||
@@ -22,12 +22,12 @@ auto PopulateGameOverResponse(
|
||||
const std::vector<net::eagle0::shardok::storage::ResolvedUnit> &resolvedUnits,
|
||||
net::eagle0::common::GameOverResponse *gameOverResponse) -> bool;
|
||||
|
||||
auto IncludeUnitProtoInReturn(
|
||||
[[nodiscard]] auto IncludeUnitProtoInReturn(
|
||||
const net::eagle0::shardok::storage::ResolvedUnit::UnitStatus &internalStatus) -> bool;
|
||||
|
||||
auto FromUnitFb(const Unit &unit) -> net::eagle0::common::CommonUnit;
|
||||
[[nodiscard]] auto FromUnitFb(const Unit &unit) -> net::eagle0::common::CommonUnit;
|
||||
|
||||
auto FromInternalStatus(
|
||||
[[nodiscard]] auto FromInternalStatus(
|
||||
const net::eagle0::shardok::storage::ResolvedUnit::UnitStatus internalStatus)
|
||||
-> net::eagle0::common::UnitStatus;
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ private:
|
||||
* Read auth token from a file, stripping whitespace.
|
||||
* Returns empty string if file doesn't exist or is empty.
|
||||
*/
|
||||
inline auto ReadAuthTokenFromFile(const std::string& filePath) -> std::string {
|
||||
[[nodiscard]] inline auto ReadAuthTokenFromFile(const std::string& filePath) -> std::string {
|
||||
if (filePath.empty()) { return ""; }
|
||||
|
||||
std::ifstream file(filePath);
|
||||
|
||||
@@ -26,10 +26,10 @@ public:
|
||||
[[nodiscard]] auto what() const noexcept -> const char* override { return description.c_str(); }
|
||||
};
|
||||
|
||||
auto LoadMap(const std::string& mapName) -> HexMapProto;
|
||||
[[nodiscard]] auto LoadMap(const std::string& mapName) -> HexMapProto;
|
||||
|
||||
auto LoadMapFromBytes(const std::string& mapBytes) -> HexMapProto;
|
||||
[[nodiscard]] auto LoadMapFromBytes(const std::string& mapBytes) -> HexMapProto;
|
||||
|
||||
auto GetMapNames() -> std::vector<std::string>;
|
||||
[[nodiscard]] auto GetMapNames() -> std::vector<std::string>;
|
||||
|
||||
#endif // EAGLE0_SHARDOK_UTIL_MAP_LOADER_HPP
|
||||
|
||||
+99
-15
@@ -37,7 +37,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -2, y: 0}
|
||||
m_SizeDelta: {x: -14, y: 0}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!222 &8488118418423736121
|
||||
CanvasRenderer:
|
||||
@@ -1859,12 +1859,12 @@ RectTransform:
|
||||
- {fileID: 5557391494505736092}
|
||||
- {fileID: 6313361240299796939}
|
||||
- {fileID: 5570932315451852925}
|
||||
m_Father: {fileID: 2429126807788525227}
|
||||
m_Father: {fileID: 910000000000000004}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 217.5914, y: -8}
|
||||
m_SizeDelta: {x: 173.1829, y: 202}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &5247279438940124088
|
||||
MonoBehaviour:
|
||||
@@ -2268,14 +2268,13 @@ RectTransform:
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 8329760851148613071}
|
||||
- {fileID: 760782539024697097}
|
||||
- {fileID: 910000000000000004}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: -20}
|
||||
m_SizeDelta: {x: 0, y: -40}
|
||||
m_AnchoredPosition: {x: 0, y: -26}
|
||||
m_SizeDelta: {x: 0, y: -52}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8056181741204377687
|
||||
CanvasRenderer:
|
||||
@@ -2334,6 +2333,70 @@ MonoBehaviour:
|
||||
maxLabel: {fileID: 2383775986439252}
|
||||
maxEvenLabel: {fileID: 2632144799086272744}
|
||||
maxEvenButton: {fileID: 8627715712575757530}
|
||||
--- !u!1 &910000000000000003
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 910000000000000004}
|
||||
- component: {fileID: 910000000000000005}
|
||||
m_Layer: 0
|
||||
m_Name: Content
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &910000000000000004
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000000000000003}
|
||||
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: 8329760851148613071}
|
||||
- {fileID: 760782539024697097}
|
||||
m_Father: {fileID: 2429126807788525227}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -10, y: -10}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &910000000000000005
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000000000000003}
|
||||
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: 4
|
||||
m_Spacing: 10
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 1
|
||||
m_ChildControlHeight: 1
|
||||
m_ChildScaleWidth: 0
|
||||
m_ChildScaleHeight: 0
|
||||
m_ReverseArrangement: 0
|
||||
--- !u!1 &6366334277554995035
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -2344,6 +2407,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 8329760851148613071}
|
||||
- component: {fileID: 531306430670215360}
|
||||
- component: {fileID: 910000000000000002}
|
||||
m_Layer: 0
|
||||
m_Name: Arm Troops Table
|
||||
m_TagString: Untagged
|
||||
@@ -2365,13 +2429,33 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 1213384902002452052}
|
||||
- {fileID: 2133514339961208833}
|
||||
m_Father: {fileID: 2429126807788525227}
|
||||
m_Father: {fileID: 910000000000000004}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: -79, y: 0}
|
||||
m_SizeDelta: {x: 395, y: -20}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &910000000000000002
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6366334277554995035}
|
||||
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: 600
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 800
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: 1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &531306430670215360
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
+313
-52
@@ -1,5 +1,161 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &217594625717164528
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6057945560984678582}
|
||||
- component: {fileID: 315907523241977106}
|
||||
- component: {fileID: 4416349416423580090}
|
||||
- component: {fileID: 3403394604621129500}
|
||||
m_Layer: 0
|
||||
m_Name: Content
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6057945560984678582
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 217594625717164528}
|
||||
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: 2156765723954912222}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 180}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!222 &315907523241977106
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 217594625717164528}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &4416349416423580090
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 217594625717164528}
|
||||
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: 'Captured from The Ninth Gate Compact.
|
||||
|
||||
|
||||
Bridget is a faction leader.'
|
||||
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: 4294967295
|
||||
m_fontColor: {r: 0, g: 0, b: 0, 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: 0
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 17
|
||||
m_fontSizeBase: 17
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 1
|
||||
m_VerticalAlignment: 256
|
||||
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 &3403394604621129500
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 217594625717164528}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
|
||||
m_HorizontalFit: 0
|
||||
m_VerticalFit: 2
|
||||
--- !u!1 &309590178533267660
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -507,7 +663,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 5, y: -12.923078}
|
||||
m_SizeDelta: {x: 24, y: 24}
|
||||
m_SizeDelta: {x: 32, y: 32}
|
||||
m_Pivot: {x: 0, y: 0.5}
|
||||
--- !u!222 &2812250721490797743
|
||||
CanvasRenderer:
|
||||
@@ -557,12 +713,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 24
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_MinWidth: 32
|
||||
m_MinHeight: 32
|
||||
m_PreferredWidth: 32
|
||||
m_PreferredHeight: 32
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 1
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &1211474509175590511
|
||||
GameObject:
|
||||
@@ -1045,13 +1201,14 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 8962292495336518232}
|
||||
- {fileID: 2156765723954912222}
|
||||
- {fileID: 1910983489799631348}
|
||||
m_Father: {fileID: 8727722510168669489}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 410.35156, y: -95}
|
||||
m_SizeDelta: {x: 810.7031, y: 180}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: -95}
|
||||
m_SizeDelta: {x: -40, y: 180}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &8034445121229170357
|
||||
MonoBehaviour:
|
||||
@@ -1091,7 +1248,7 @@ MonoBehaviour:
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 1
|
||||
m_Spacing: 0
|
||||
m_Spacing: 24
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 1
|
||||
@@ -1667,7 +1824,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 5, y: -12.923078}
|
||||
m_SizeDelta: {x: 24, y: 24}
|
||||
m_SizeDelta: {x: 32, y: 32}
|
||||
m_Pivot: {x: 0, y: 0.5}
|
||||
--- !u!222 &7293334478758891821
|
||||
CanvasRenderer:
|
||||
@@ -1717,12 +1874,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 24
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_MinWidth: 32
|
||||
m_MinHeight: 32
|
||||
m_PreferredWidth: 32
|
||||
m_PreferredHeight: 32
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 1
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &3423825046551003777
|
||||
GameObject:
|
||||
@@ -2029,6 +2186,7 @@ RectTransform:
|
||||
- {fileID: 4188743959489053307}
|
||||
- {fileID: 8724010977067144246}
|
||||
- {fileID: 5699403500788722507}
|
||||
- {fileID: 8674504589829862267}
|
||||
m_Father: {fileID: 8854617199350090050}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
@@ -2310,7 +2468,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 5408870193947667072}
|
||||
- {fileID: 8674504589829862267}
|
||||
- {fileID: 8854617199350090050}
|
||||
m_Father: {fileID: 3293749606495420917}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
@@ -2381,7 +2538,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 5, y: -12.923078}
|
||||
m_SizeDelta: {x: 24, y: 24}
|
||||
m_SizeDelta: {x: 32, y: 32}
|
||||
m_Pivot: {x: 0, y: 0.5}
|
||||
--- !u!222 &2561976136182974797
|
||||
CanvasRenderer:
|
||||
@@ -2431,12 +2588,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 24
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_MinWidth: 32
|
||||
m_MinHeight: 32
|
||||
m_PreferredWidth: 32
|
||||
m_PreferredHeight: 32
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 1
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &5012345052674663814
|
||||
GameObject:
|
||||
@@ -2934,7 +3091,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 5, y: -12.923078}
|
||||
m_SizeDelta: {x: 24, y: 24}
|
||||
m_SizeDelta: {x: 32, y: 32}
|
||||
m_Pivot: {x: 0, y: 0.5}
|
||||
--- !u!222 &2936928241299030275
|
||||
CanvasRenderer:
|
||||
@@ -2984,12 +3141,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 24
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_MinWidth: 32
|
||||
m_MinHeight: 32
|
||||
m_PreferredWidth: 32
|
||||
m_PreferredHeight: 32
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 1
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &5290798991585313877
|
||||
GameObject:
|
||||
@@ -3389,10 +3546,10 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4849531796595395475}
|
||||
- {fileID: 7646253429728842762}
|
||||
- {fileID: 3163624482538184363}
|
||||
- {fileID: 2985415434360325071}
|
||||
- {fileID: 4105688894330716876}
|
||||
- {fileID: 7646253429728842762}
|
||||
- {fileID: 3897688492057273019}
|
||||
- {fileID: 1764692555304340678}
|
||||
m_Father: {fileID: 8727722510168669489}
|
||||
@@ -3400,7 +3557,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 410.35156, y: -230.07692}
|
||||
m_SizeDelta: {x: 810.7031, y: 44}
|
||||
m_SizeDelta: {x: 810.7031, y: 48}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &4496069533907013775
|
||||
CanvasRenderer:
|
||||
@@ -3461,7 +3618,7 @@ MonoBehaviour:
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 4
|
||||
m_Spacing: 20
|
||||
m_Spacing: 8
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 1
|
||||
@@ -3505,7 +3662,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 5, y: -12.923078}
|
||||
m_SizeDelta: {x: 24, y: 24}
|
||||
m_SizeDelta: {x: 32, y: 32}
|
||||
m_Pivot: {x: 0, y: 0.5}
|
||||
--- !u!222 &7125238889541449479
|
||||
CanvasRenderer:
|
||||
@@ -3555,12 +3712,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 24
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_MinWidth: 32
|
||||
m_MinHeight: 32
|
||||
m_PreferredWidth: 32
|
||||
m_PreferredHeight: 32
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 1
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &6613470828170084132
|
||||
GameObject:
|
||||
@@ -3918,7 +4075,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
m_IsActive: 0
|
||||
--- !u!224 &8674504589829862267
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -3934,13 +4091,13 @@ RectTransform:
|
||||
- {fileID: 6178109771874370456}
|
||||
- {fileID: 7388385801072749764}
|
||||
- {fileID: 8400884665445857398}
|
||||
m_Father: {fileID: 8727722510168669489}
|
||||
m_Father: {fileID: 7646253429728842762}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 410.35156, y: -201.07692}
|
||||
m_SizeDelta: {x: 250, y: 22.153847}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 8}
|
||||
m_SizeDelta: {x: 190, y: 48}
|
||||
m_Pivot: {x: 0.5, y: 0}
|
||||
--- !u!222 &1220066650368134757
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -4056,11 +4213,11 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 250
|
||||
m_PreferredHeight: 30
|
||||
m_FlexibleWidth: 0
|
||||
m_MinWidth: 160
|
||||
m_MinHeight: 48
|
||||
m_PreferredWidth: 190
|
||||
m_PreferredHeight: 48
|
||||
m_FlexibleWidth: 1
|
||||
m_FlexibleHeight: 1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &7522094328753451038
|
||||
@@ -4602,6 +4759,109 @@ MonoBehaviour:
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &8258851023267795968
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2156765723954912222}
|
||||
- component: {fileID: 2323458623403657949}
|
||||
- component: {fileID: 2719519746693893967}
|
||||
- component: {fileID: 6456472258987035459}
|
||||
m_Layer: 0
|
||||
m_Name: Prisoner Info
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &2156765723954912222
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8258851023267795968}
|
||||
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: 6057945560984678582}
|
||||
m_Father: {fileID: 5408870193947667072}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 520, y: 180}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &2323458623403657949
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8258851023267795968}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3312d7739989d2b4e91e6319e9a96d76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.RectMask2D
|
||||
m_Padding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Softness: {x: 0, y: 0}
|
||||
--- !u!114 &2719519746693893967
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8258851023267795968}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ScrollRect
|
||||
m_Content: {fileID: 6057945560984678582}
|
||||
m_Horizontal: 0
|
||||
m_Vertical: 1
|
||||
m_MovementType: 2
|
||||
m_Elasticity: 0.1
|
||||
m_Inertia: 1
|
||||
m_DecelerationRate: 0.135
|
||||
m_ScrollSensitivity: 12
|
||||
m_Viewport: {fileID: 2156765723954912222}
|
||||
m_HorizontalScrollbar: {fileID: 0}
|
||||
m_VerticalScrollbar: {fileID: 0}
|
||||
m_HorizontalScrollbarVisibility: 0
|
||||
m_VerticalScrollbarVisibility: 0
|
||||
m_HorizontalScrollbarSpacing: 0
|
||||
m_VerticalScrollbarSpacing: 0
|
||||
m_OnValueChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &6456472258987035459
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8258851023267795968}
|
||||
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: 0
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: 1
|
||||
m_FlexibleHeight: 1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &8552052792859198839
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -4880,6 +5140,7 @@ MonoBehaviour:
|
||||
moveToDropdown: {fileID: 9136850984056651894}
|
||||
heroDetails: {fileID: 7671674037592426843}
|
||||
MessageText: {fileID: 5923522230458371477}
|
||||
PrisonerInfoText: {fileID: 4416349416423580090}
|
||||
NextHeroButton: {fileID: 7463642923309765748}
|
||||
--- !u!222 &3375450489895352050
|
||||
CanvasRenderer:
|
||||
@@ -5717,7 +5978,7 @@ PrefabInstance:
|
||||
- target: {fileID: 4733968769770696120, guid: 5294d9ccdfeff49a7922b57958f1f36f,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 335
|
||||
value: 500
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4733968769770696120, guid: 5294d9ccdfeff49a7922b57958f1f36f,
|
||||
type: 3}
|
||||
@@ -5727,7 +5988,7 @@ PrefabInstance:
|
||||
- target: {fileID: 4733968769770696120, guid: 5294d9ccdfeff49a7922b57958f1f36f,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 332.5
|
||||
value: 250
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4733968769770696120, guid: 5294d9ccdfeff49a7922b57958f1f36f,
|
||||
type: 3}
|
||||
|
||||
+9
-10
@@ -689,9 +689,9 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 110
|
||||
m_MinWidth: 224
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 110
|
||||
m_PreferredWidth: 224
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
@@ -2365,7 +2365,6 @@ RectTransform:
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 6513069223245071550}
|
||||
- {fileID: 7350415193282674024}
|
||||
- {fileID: 7969189778685776836}
|
||||
- {fileID: 3917383894856241933}
|
||||
@@ -2378,7 +2377,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 209.96754, y: -7.5}
|
||||
m_SizeDelta: {x: 459, y: 15}
|
||||
m_SizeDelta: {x: 760, y: 15}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6612103987515864042
|
||||
CanvasRenderer:
|
||||
@@ -2431,7 +2430,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Padding:
|
||||
m_Left: 5
|
||||
m_Left: 13
|
||||
m_Right: 5
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
@@ -2534,7 +2533,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 450.83984, y: -121.5}
|
||||
m_SizeDelta: {x: 419.9351, y: 243}
|
||||
m_SizeDelta: {x: 720.9351, y: 243}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &3117222806062181928
|
||||
MonoBehaviour:
|
||||
@@ -2605,7 +2604,7 @@ MonoBehaviour:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 0
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 459
|
||||
m_PreferredWidth: 760
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 1
|
||||
@@ -3716,9 +3715,9 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 20
|
||||
m_MinWidth: 0
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 20
|
||||
m_PreferredWidth: 0
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
@@ -4040,7 +4039,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 209.96754, y: -130.5}
|
||||
m_SizeDelta: {x: 419.9351, y: 225}
|
||||
m_SizeDelta: {x: 720.9351, y: 225}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8745097594797046594
|
||||
CanvasRenderer:
|
||||
|
||||
+459
-9
@@ -20,6 +20,43 @@ namespace eagle {
|
||||
private int _availableGold;
|
||||
private int _maxArmament;
|
||||
private MaxAttainableEverywhereBattalions _maxAttainableEverywhereBattalions;
|
||||
private int? _selectedBattalionIndex;
|
||||
private GameObject _touchActionStrip;
|
||||
private TMP_Text _touchActionStripLabel;
|
||||
private Button _touchPlusButton;
|
||||
private Button _touchMinusButton;
|
||||
private Button _touchResetButton;
|
||||
private Button _touchMaxButton;
|
||||
private Image _touchPlusIcon;
|
||||
private Image _touchMinusIcon;
|
||||
private Image _touchResetIcon;
|
||||
private Image _touchMaxIcon;
|
||||
private Transform _touchActionButtonGrid;
|
||||
private GameObject _allBattalionsHeader;
|
||||
private GameObject _allBattalionsMaxButtonRow;
|
||||
private bool _capturedTableLayout;
|
||||
private bool _capturedAllBattalionsLayout;
|
||||
private Vector2 _tableAnchoredPosition;
|
||||
private Vector2 _tableSizeDelta;
|
||||
private LayoutElement _tableLayoutElement;
|
||||
private float _tableMinWidth;
|
||||
private float _tablePreferredWidth;
|
||||
private float _tableFlexibleWidth;
|
||||
private HorizontalLayoutGroup _mainContentLayout;
|
||||
private float _mainContentSpacing;
|
||||
private Transform _touchActionStripParent;
|
||||
private int _touchActionStripSiblingIndex;
|
||||
private RectTransform _allBattalionsControlsRectTransform;
|
||||
private Vector2 _allBattalionsControlsAnchoredPosition;
|
||||
private Vector2 _allBattalionsControlsSizeDelta;
|
||||
private VerticalLayoutGroup _allBattalionsControlsLayout;
|
||||
private TextAnchor _allBattalionsControlsAlignment;
|
||||
private RectTransform _maxAllButtonRectTransform;
|
||||
private RectTransform _maxEvenButtonRectTransform;
|
||||
private Transform _maxAllButtonParent;
|
||||
private Transform _maxEvenButtonParent;
|
||||
private int _maxAllButtonSiblingIndex;
|
||||
private int _maxEvenButtonSiblingIndex;
|
||||
|
||||
public EventBasedTable armTroopsTable;
|
||||
public TMP_Text totalCostText;
|
||||
@@ -34,6 +71,9 @@ namespace eagle {
|
||||
private ArmTroopsAvailableCommand ArmTroopsAvailableCommand =>
|
||||
_availableCommand.ArmTroopsCommand;
|
||||
private ProvinceId ActingProvinceId => ArmTroopsAvailableCommand.ActingProvinceId;
|
||||
private bool UseTouchActionStrip =>
|
||||
IsNarrowLayout && GameInput.HasTouchscreen() &&
|
||||
(!GameInput.HasMousePointer() || GameInput.HasSimulatedTouchscreen());
|
||||
|
||||
private struct MaxAttainableEverywhereBattalions {
|
||||
public int Armament;
|
||||
@@ -107,6 +147,14 @@ namespace eagle {
|
||||
}
|
||||
|
||||
protected override void SetUpUI() {
|
||||
EnsureTouchActionStrip();
|
||||
if (armTroopsTable.RowClickedHandler == null) {
|
||||
armTroopsTable.RowClickedHandler = new EventBasedTable.RowClickedEvent();
|
||||
}
|
||||
armTroopsTable.WireRuntimeRowClickHandlers = true;
|
||||
armTroopsTable.RowClickedHandler.RemoveListener(RowClicked);
|
||||
armTroopsTable.RowClickedHandler.AddListener(RowClicked);
|
||||
|
||||
var province = _model.Provinces[ActingProvinceId];
|
||||
_battalions =
|
||||
ArmTroopsAvailableCommand.AvailableBattalions
|
||||
@@ -126,16 +174,21 @@ namespace eagle {
|
||||
_maxArmament = ArmTroopsAvailableCommand.MaxArmament;
|
||||
_maxAttainableEverywhereBattalions = SpendRemainingGold(SpendEvenlyOnly());
|
||||
|
||||
if (_maxAttainableEverywhereBattalions.Armament == _maxArmament) {
|
||||
maxEvenButton.gameObject.SetActive(false);
|
||||
} else {
|
||||
maxEvenLabel.text = _maxAttainableEverywhereBattalions.Armament.ToString();
|
||||
maxEvenButton.gameObject.SetActive(true);
|
||||
}
|
||||
maxEvenLabel.text = _maxAttainableEverywhereBattalions.Armament.ToString();
|
||||
maxEvenButton.gameObject.SetActive(true);
|
||||
maxEvenButton.interactable =
|
||||
_maxAttainableEverywhereBattalions.Armament != _maxArmament;
|
||||
|
||||
maxLabel.text = _maxArmament.ToString();
|
||||
availableText.text = _availableGold.ToString();
|
||||
|
||||
ApplyInputModeLayout();
|
||||
UpdateTableAndCost();
|
||||
}
|
||||
|
||||
public override void OnLayoutModeChanged() {
|
||||
if (_battalions == null) return;
|
||||
ApplyInputModeLayout();
|
||||
UpdateTableAndCost();
|
||||
}
|
||||
|
||||
@@ -159,6 +212,12 @@ namespace eagle {
|
||||
row.MinusButtonClickedCallback = () => MinusClicked(index);
|
||||
row.ResetButtonClickedCallback = () => ResetClicked(index);
|
||||
row.MaxButtonClickedCallback = () => MaxClicked(index);
|
||||
row.RowClickedCallback = () => RowClicked(index);
|
||||
row.SetActionButtonsVisible(!UseTouchActionStrip);
|
||||
row.SetNameVisible(!UseTouchActionStrip);
|
||||
row.SetContentRaycastTargets(!UseTouchActionStrip);
|
||||
row.Selectable = true;
|
||||
row.Selected = UseTouchActionStrip && _selectedBattalionIndex == index;
|
||||
|
||||
SetValuesAndButtons(index);
|
||||
}
|
||||
@@ -172,12 +231,13 @@ namespace eagle {
|
||||
row.NewArmament = newArmament;
|
||||
row.Name = battalion.Name;
|
||||
|
||||
row.AllowMax = battalion.Armament < _maxArmament;
|
||||
row.AllowPlus = battalion.Armament < _maxArmament;
|
||||
row.AllowMax = newArmament < _maxArmament;
|
||||
row.AllowPlus = newArmament < _maxArmament;
|
||||
row.AllowMinus = battalion.Armament < newArmament;
|
||||
row.AllowReset = battalion.Armament != newArmament;
|
||||
|
||||
UpdateCost();
|
||||
UpdateTouchActionStrip();
|
||||
}
|
||||
|
||||
private void UpdateCost() {
|
||||
@@ -198,11 +258,28 @@ namespace eagle {
|
||||
}
|
||||
|
||||
private void UpdateTableAndCost() {
|
||||
if (UseTouchActionStrip && _battalions.Count > 0) {
|
||||
if (_selectedBattalionIndex == null || _selectedBattalionIndex < 0 ||
|
||||
_selectedBattalionIndex >= _battalions.Count) {
|
||||
_selectedBattalionIndex = 0;
|
||||
}
|
||||
} else {
|
||||
_selectedBattalionIndex = null;
|
||||
}
|
||||
|
||||
armTroopsTable.RowCount = _battalions.Count;
|
||||
|
||||
for (int i = 0; i < _battalions.Count; i++) { PopulateRow(i); }
|
||||
armTroopsTable.RefreshRowEventHandlers();
|
||||
|
||||
UpdateCost();
|
||||
UpdateTouchActionStrip();
|
||||
}
|
||||
|
||||
public void RowClicked(int index) {
|
||||
if (!UseTouchActionStrip) return;
|
||||
|
||||
SelectBattalion(index);
|
||||
}
|
||||
|
||||
public void PlusClicked(int index) {
|
||||
@@ -276,5 +353,378 @@ namespace eagle {
|
||||
}
|
||||
|
||||
private int Cost() { return Cost(_newArmaments); }
|
||||
|
||||
private void SelectBattalion(int index) {
|
||||
_selectedBattalionIndex = index;
|
||||
for (int i = 0; i < _battalions.Count; i++) {
|
||||
armTroopsTable.ComponentAt<ArmTroopsRowController>(i).Selected = i == index;
|
||||
}
|
||||
UpdateTouchActionStrip();
|
||||
}
|
||||
|
||||
private bool TrySelectedBattalionIndex(out int index) {
|
||||
index = _selectedBattalionIndex ?? -1;
|
||||
return UseTouchActionStrip && index >= 0 && index < _battalions.Count;
|
||||
}
|
||||
|
||||
private void UpdateTouchActionStrip() {
|
||||
if (_touchActionStrip == null) return;
|
||||
|
||||
if (!TrySelectedBattalionIndex(out int index)) {
|
||||
_touchActionStrip.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var battalion = _battalions[index];
|
||||
var originalArmament = (int)(battalion.Armament ?? 0);
|
||||
var newArmament = _newArmaments[index];
|
||||
|
||||
_touchActionStrip.SetActive(true);
|
||||
_touchActionStripLabel.text = battalion.Name;
|
||||
UpdateTouchActionButtonIcons();
|
||||
_touchPlusButton.interactable = newArmament < _maxArmament;
|
||||
_touchMinusButton.interactable = originalArmament < newArmament;
|
||||
_touchResetButton.interactable = originalArmament != newArmament;
|
||||
_touchMaxButton.interactable = newArmament < _maxArmament;
|
||||
}
|
||||
|
||||
private void EnsureTouchActionStrip() {
|
||||
if (_touchActionStrip != null) return;
|
||||
|
||||
_touchActionStrip = new GameObject(
|
||||
"Touch Action Strip",
|
||||
typeof(RectTransform),
|
||||
typeof(LayoutElement),
|
||||
typeof(Image),
|
||||
typeof(VerticalLayoutGroup));
|
||||
_touchActionStrip.transform.SetParent(transform, false);
|
||||
_touchActionStripParent = transform;
|
||||
_touchActionStripSiblingIndex = _touchActionStrip.transform.GetSiblingIndex();
|
||||
|
||||
var rectTransform = _touchActionStrip.GetComponent<RectTransform>();
|
||||
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
rectTransform.pivot = new Vector2(0.5f, 0.5f);
|
||||
rectTransform.anchoredPosition = new Vector2(55f, -16f);
|
||||
rectTransform.sizeDelta = new Vector2(176f, 186f);
|
||||
|
||||
var stripLayoutElement = _touchActionStrip.GetComponent<LayoutElement>();
|
||||
stripLayoutElement.minWidth = 176f;
|
||||
stripLayoutElement.preferredWidth = 176f;
|
||||
stripLayoutElement.minHeight = 202f;
|
||||
stripLayoutElement.preferredHeight = 202f;
|
||||
stripLayoutElement.flexibleWidth = -1f;
|
||||
stripLayoutElement.flexibleHeight = 1f;
|
||||
|
||||
var background = _touchActionStrip.GetComponent<Image>();
|
||||
background.color = Color.clear;
|
||||
|
||||
var layout = _touchActionStrip.GetComponent<VerticalLayoutGroup>();
|
||||
layout.padding = new RectOffset(0, 0, 0, 0);
|
||||
layout.spacing = 6f;
|
||||
layout.childAlignment = TextAnchor.UpperCenter;
|
||||
layout.childControlWidth = true;
|
||||
layout.childControlHeight = true;
|
||||
layout.childForceExpandWidth = false;
|
||||
layout.childForceExpandHeight = false;
|
||||
|
||||
_touchActionStripLabel = CreateActionStripLabel();
|
||||
_touchActionButtonGrid = CreateActionButtonGrid();
|
||||
_touchMinusButton = CreateActionStripButton("Minus Button", out _touchMinusIcon, () => {
|
||||
if (TrySelectedBattalionIndex(out int index)) { MinusClicked(index); }
|
||||
});
|
||||
_touchPlusButton = CreateActionStripButton("Plus Button", out _touchPlusIcon, () => {
|
||||
if (TrySelectedBattalionIndex(out int index)) { PlusClicked(index); }
|
||||
});
|
||||
_touchResetButton = CreateActionStripButton("Reset Button", out _touchResetIcon, () => {
|
||||
if (TrySelectedBattalionIndex(out int index)) { ResetClicked(index); }
|
||||
});
|
||||
_touchMaxButton = CreateActionStripButton("Max Button", out _touchMaxIcon, () => {
|
||||
if (TrySelectedBattalionIndex(out int index)) { MaxClicked(index); }
|
||||
});
|
||||
|
||||
_touchActionStrip.SetActive(false);
|
||||
EnsureAllBattalionsHeader();
|
||||
}
|
||||
|
||||
private void ApplyInputModeLayout() {
|
||||
var tableRectTransform = armTroopsTable.GetComponent<RectTransform>();
|
||||
if (!_capturedTableLayout) {
|
||||
_tableAnchoredPosition = tableRectTransform.anchoredPosition;
|
||||
_tableSizeDelta = tableRectTransform.sizeDelta;
|
||||
_tableLayoutElement = armTroopsTable.GetComponent<LayoutElement>();
|
||||
_tableMinWidth = _tableLayoutElement.minWidth;
|
||||
_tablePreferredWidth = _tableLayoutElement.preferredWidth;
|
||||
_tableFlexibleWidth = _tableLayoutElement.flexibleWidth;
|
||||
_mainContentLayout =
|
||||
tableRectTransform.parent.GetComponent<HorizontalLayoutGroup>();
|
||||
_mainContentSpacing = _mainContentLayout.spacing;
|
||||
_allBattalionsControlsRectTransform =
|
||||
maxEvenButton.transform.parent.GetComponent<RectTransform>();
|
||||
_allBattalionsControlsAnchoredPosition =
|
||||
_allBattalionsControlsRectTransform.anchoredPosition;
|
||||
_allBattalionsControlsSizeDelta = _allBattalionsControlsRectTransform.sizeDelta;
|
||||
_capturedTableLayout = true;
|
||||
}
|
||||
CaptureAllBattalionsLayout();
|
||||
EnsureAllBattalionsHeader();
|
||||
EnsureAllBattalionsMaxButtonRow();
|
||||
|
||||
if (UseTouchActionStrip) {
|
||||
var touchTableWidth = 270f;
|
||||
_mainContentLayout.spacing = _mainContentSpacing;
|
||||
_tableLayoutElement.minWidth = touchTableWidth;
|
||||
_tableLayoutElement.preferredWidth = touchTableWidth;
|
||||
_tableLayoutElement.flexibleWidth = -1f;
|
||||
tableRectTransform.anchoredPosition = _tableAnchoredPosition;
|
||||
tableRectTransform.sizeDelta = _tableSizeDelta;
|
||||
_allBattalionsControlsRectTransform.anchoredPosition =
|
||||
_allBattalionsControlsAnchoredPosition;
|
||||
_allBattalionsControlsRectTransform.sizeDelta = new Vector2(190f, 202f);
|
||||
_allBattalionsControlsLayout.childAlignment = TextAnchor.UpperCenter;
|
||||
|
||||
_touchActionStrip.transform.SetParent(tableRectTransform.parent, false);
|
||||
_touchActionStrip.transform.SetSiblingIndex(
|
||||
tableRectTransform.GetSiblingIndex() + 1);
|
||||
var touchActionStripRectTransform = _touchActionStrip.GetComponent<RectTransform>();
|
||||
touchActionStripRectTransform.anchoredPosition = Vector2.zero;
|
||||
touchActionStripRectTransform.sizeDelta = new Vector2(176f, 202f);
|
||||
|
||||
_allBattalionsHeader.transform.SetParent(
|
||||
_allBattalionsControlsRectTransform,
|
||||
false);
|
||||
_allBattalionsHeader.transform.SetSiblingIndex(0);
|
||||
_allBattalionsMaxButtonRow.transform.SetParent(
|
||||
_allBattalionsControlsRectTransform,
|
||||
false);
|
||||
_allBattalionsMaxButtonRow.transform.SetSiblingIndex(1);
|
||||
_maxAllButtonRectTransform.SetParent(_allBattalionsMaxButtonRow.transform, false);
|
||||
_maxAllButtonRectTransform.SetSiblingIndex(0);
|
||||
_maxEvenButtonRectTransform.SetParent(_allBattalionsMaxButtonRow.transform, false);
|
||||
_maxEvenButtonRectTransform.SetSiblingIndex(1);
|
||||
|
||||
_allBattalionsHeader.SetActive(true);
|
||||
_allBattalionsMaxButtonRow.SetActive(true);
|
||||
} else {
|
||||
_mainContentLayout.spacing = 18f;
|
||||
_tableLayoutElement.minWidth = _tableMinWidth;
|
||||
_tableLayoutElement.preferredWidth = _tablePreferredWidth;
|
||||
_tableLayoutElement.flexibleWidth = _tableFlexibleWidth;
|
||||
tableRectTransform.anchoredPosition = _tableAnchoredPosition;
|
||||
tableRectTransform.sizeDelta = _tableSizeDelta;
|
||||
_touchActionStrip.transform.SetParent(_touchActionStripParent, false);
|
||||
_touchActionStrip.transform.SetSiblingIndex(_touchActionStripSiblingIndex);
|
||||
_allBattalionsControlsRectTransform.anchoredPosition =
|
||||
_allBattalionsControlsAnchoredPosition;
|
||||
_allBattalionsControlsRectTransform.sizeDelta = _allBattalionsControlsSizeDelta;
|
||||
_allBattalionsControlsLayout.childAlignment = _allBattalionsControlsAlignment;
|
||||
RestoreAllBattalionsMaxButtons();
|
||||
if (_allBattalionsHeader != null) { _allBattalionsHeader.SetActive(false); }
|
||||
if (_allBattalionsMaxButtonRow != null) {
|
||||
_allBattalionsMaxButtonRow.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CaptureAllBattalionsLayout() {
|
||||
if (_capturedAllBattalionsLayout) return;
|
||||
|
||||
_allBattalionsControlsLayout =
|
||||
_allBattalionsControlsRectTransform.GetComponent<VerticalLayoutGroup>();
|
||||
_allBattalionsControlsAlignment = _allBattalionsControlsLayout.childAlignment;
|
||||
_maxAllButtonRectTransform =
|
||||
_allBattalionsControlsRectTransform.GetChild(0).GetComponent<RectTransform>();
|
||||
_maxEvenButtonRectTransform = maxEvenButton.GetComponent<RectTransform>();
|
||||
_maxAllButtonParent = _maxAllButtonRectTransform.parent;
|
||||
_maxEvenButtonParent = _maxEvenButtonRectTransform.parent;
|
||||
_maxAllButtonSiblingIndex = _maxAllButtonRectTransform.GetSiblingIndex();
|
||||
_maxEvenButtonSiblingIndex = _maxEvenButtonRectTransform.GetSiblingIndex();
|
||||
|
||||
_capturedAllBattalionsLayout = true;
|
||||
}
|
||||
|
||||
private void RestoreAllBattalionsMaxButtons() {
|
||||
_maxAllButtonRectTransform.SetParent(_maxAllButtonParent, false);
|
||||
_maxAllButtonRectTransform.SetSiblingIndex(_maxAllButtonSiblingIndex);
|
||||
_maxEvenButtonRectTransform.SetParent(_maxEvenButtonParent, false);
|
||||
_maxEvenButtonRectTransform.SetSiblingIndex(_maxEvenButtonSiblingIndex);
|
||||
}
|
||||
|
||||
private TMP_Text CreateActionStripLabel() {
|
||||
var label = new GameObject(
|
||||
"Selected Battalion Label",
|
||||
typeof(RectTransform),
|
||||
typeof(LayoutElement),
|
||||
typeof(TextMeshProUGUI));
|
||||
label.transform.SetParent(_touchActionStrip.transform, false);
|
||||
|
||||
var layoutElement = label.GetComponent<LayoutElement>();
|
||||
layoutElement.minWidth = 176f;
|
||||
layoutElement.preferredWidth = 176f;
|
||||
layoutElement.minHeight = 56f;
|
||||
layoutElement.preferredHeight = 56f;
|
||||
|
||||
var text = label.GetComponent<TextMeshProUGUI>();
|
||||
text.alignment = TextAlignmentOptions.Top;
|
||||
text.color = Color.black;
|
||||
text.font = maxLabel.font;
|
||||
text.fontSize = 16f;
|
||||
text.textWrappingMode = TextWrappingModes.Normal;
|
||||
text.overflowMode = TextOverflowModes.Ellipsis;
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private Transform CreateActionButtonGrid() {
|
||||
var gridObject = new GameObject(
|
||||
"Action Button Grid",
|
||||
typeof(RectTransform),
|
||||
typeof(LayoutElement),
|
||||
typeof(GridLayoutGroup));
|
||||
gridObject.transform.SetParent(_touchActionStrip.transform, false);
|
||||
|
||||
var layoutElement = gridObject.GetComponent<LayoutElement>();
|
||||
layoutElement.minWidth = 108f;
|
||||
layoutElement.preferredWidth = 108f;
|
||||
layoutElement.minHeight = 108f;
|
||||
layoutElement.preferredHeight = 108f;
|
||||
|
||||
var grid = gridObject.GetComponent<GridLayoutGroup>();
|
||||
grid.cellSize = new Vector2(48f, 48f);
|
||||
grid.spacing = new Vector2(6f, 6f);
|
||||
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
|
||||
grid.constraintCount = 2;
|
||||
grid.childAlignment = TextAnchor.MiddleCenter;
|
||||
|
||||
return gridObject.transform;
|
||||
}
|
||||
|
||||
private Button CreateActionStripButton(
|
||||
string name,
|
||||
out Image icon,
|
||||
UnityEngine.Events.UnityAction action) {
|
||||
var buttonObject = new GameObject(
|
||||
name,
|
||||
typeof(RectTransform),
|
||||
typeof(LayoutElement),
|
||||
typeof(Image),
|
||||
typeof(Button));
|
||||
buttonObject.transform.SetParent(_touchActionButtonGrid, false);
|
||||
|
||||
var layoutElement = buttonObject.GetComponent<LayoutElement>();
|
||||
layoutElement.minWidth = 48f;
|
||||
layoutElement.preferredWidth = 48f;
|
||||
layoutElement.minHeight = 48f;
|
||||
layoutElement.preferredHeight = 48f;
|
||||
|
||||
var image = buttonObject.GetComponent<Image>();
|
||||
image.color = new Color(0.94f, 0.94f, 0.96f, 1f);
|
||||
CopyButtonBackground(maxEvenButton, image);
|
||||
|
||||
var button = buttonObject.GetComponent<Button>();
|
||||
button.targetGraphic = image;
|
||||
button.onClick.AddListener(action);
|
||||
|
||||
var iconObject = new GameObject("Icon", typeof(RectTransform), typeof(Image));
|
||||
iconObject.transform.SetParent(buttonObject.transform, false);
|
||||
|
||||
var iconRectTransform = iconObject.GetComponent<RectTransform>();
|
||||
iconRectTransform.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
iconRectTransform.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
iconRectTransform.anchoredPosition = Vector2.zero;
|
||||
iconRectTransform.sizeDelta = new Vector2(34f, 34f);
|
||||
|
||||
icon = iconObject.GetComponent<Image>();
|
||||
icon.color = Color.black;
|
||||
icon.preserveAspect = true;
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private void UpdateTouchActionButtonIcons() {
|
||||
if (_battalions == null || _battalions.Count == 0) return;
|
||||
|
||||
var row = armTroopsTable.ComponentAt<ArmTroopsRowController>(0);
|
||||
_touchMinusIcon.sprite = ButtonIconSprite(row.minusButton);
|
||||
_touchPlusIcon.sprite = ButtonIconSprite(row.plusButton);
|
||||
_touchResetIcon.sprite = ButtonIconSprite(row.resetButton);
|
||||
_touchMaxIcon.sprite = ButtonIconSprite(row.maxButton);
|
||||
}
|
||||
|
||||
private Sprite ButtonIconSprite(Button button) {
|
||||
foreach (var image in button.GetComponentsInChildren<Image>(includeInactive: true)) {
|
||||
if (image.gameObject != button.gameObject && image.sprite != null) {
|
||||
return image.sprite;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CopyButtonBackground(Button sourceButton, Image targetImage) {
|
||||
var sourceImage = sourceButton.targetGraphic as Image;
|
||||
if (sourceImage == null) return;
|
||||
|
||||
targetImage.sprite = sourceImage.sprite;
|
||||
targetImage.type = sourceImage.type;
|
||||
targetImage.pixelsPerUnitMultiplier = sourceImage.pixelsPerUnitMultiplier;
|
||||
}
|
||||
|
||||
private void EnsureAllBattalionsMaxButtonRow() {
|
||||
if (_allBattalionsMaxButtonRow != null) return;
|
||||
|
||||
_allBattalionsMaxButtonRow = new GameObject(
|
||||
"All Battalions Max Button Row",
|
||||
typeof(RectTransform),
|
||||
typeof(LayoutElement),
|
||||
typeof(HorizontalLayoutGroup));
|
||||
_allBattalionsMaxButtonRow.transform.SetParent(
|
||||
_allBattalionsControlsRectTransform,
|
||||
false);
|
||||
|
||||
var layoutElement = _allBattalionsMaxButtonRow.GetComponent<LayoutElement>();
|
||||
layoutElement.minWidth = 190f;
|
||||
layoutElement.preferredWidth = 190f;
|
||||
layoutElement.minHeight = 60f;
|
||||
layoutElement.preferredHeight = 60f;
|
||||
|
||||
var layout = _allBattalionsMaxButtonRow.GetComponent<HorizontalLayoutGroup>();
|
||||
layout.padding = new RectOffset(0, 0, 0, 0);
|
||||
layout.spacing = 8f;
|
||||
layout.childAlignment = TextAnchor.MiddleCenter;
|
||||
layout.childControlWidth = true;
|
||||
layout.childControlHeight = true;
|
||||
layout.childForceExpandWidth = false;
|
||||
layout.childForceExpandHeight = false;
|
||||
|
||||
_allBattalionsMaxButtonRow.SetActive(false);
|
||||
}
|
||||
|
||||
private void EnsureAllBattalionsHeader() {
|
||||
if (_allBattalionsHeader != null) return;
|
||||
|
||||
_allBattalionsHeader = new GameObject(
|
||||
"All Battalions Header",
|
||||
typeof(RectTransform),
|
||||
typeof(LayoutElement),
|
||||
typeof(TextMeshProUGUI));
|
||||
_allBattalionsHeader.transform.SetParent(transform, false);
|
||||
|
||||
var layoutElement = _allBattalionsHeader.GetComponent<LayoutElement>();
|
||||
layoutElement.minWidth = 190f;
|
||||
layoutElement.preferredWidth = 190f;
|
||||
layoutElement.minHeight = 56f;
|
||||
layoutElement.preferredHeight = 56f;
|
||||
|
||||
var text = _allBattalionsHeader.GetComponent<TextMeshProUGUI>();
|
||||
text.text = "All Battalions";
|
||||
text.alignment = TextAlignmentOptions.Top;
|
||||
text.color = Color.black;
|
||||
text.font = maxLabel.font;
|
||||
text.fontSize = 16f;
|
||||
text.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
|
||||
_allBattalionsHeader.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-4
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Net.Eagle0.Eagle.Api;
|
||||
using Net.Eagle0.Eagle.Api.Command.Util;
|
||||
using Net.Eagle0.Eagle.Common;
|
||||
@@ -28,6 +27,7 @@ namespace eagle {
|
||||
public HeroDetailsController heroDetails;
|
||||
|
||||
public TMP_Text MessageText;
|
||||
public TMP_Text PrisonerInfoText;
|
||||
|
||||
public Button NextHeroButton;
|
||||
|
||||
@@ -139,6 +139,8 @@ namespace eagle {
|
||||
|
||||
protected override void SetUpUI() {
|
||||
EnsureTouchTargets();
|
||||
MoveToggle.onValueChanged.RemoveListener(MoveToggled);
|
||||
MoveToggle.onValueChanged.AddListener(MoveToggled);
|
||||
if (_selectedHeroIndex >= ManagePrisonersCommand.Prisoners.Count)
|
||||
_selectedHeroIndex = 0;
|
||||
NextHeroButton.gameObject.SetActive(ManagePrisonersCommand.Prisoners.Count > 1);
|
||||
@@ -162,7 +164,7 @@ namespace eagle {
|
||||
DisplaySelectedHero();
|
||||
}
|
||||
|
||||
public void MoveToggled(bool value) { moveToDropdown.gameObject.SetActive(value); }
|
||||
public void MoveToggled(bool value) { UpdateMoveDropdownVisibility(); }
|
||||
|
||||
private void DisplaySelectedHero() {
|
||||
heroDetails.SetHero(SelectedHero, _model);
|
||||
@@ -176,6 +178,8 @@ namespace eagle {
|
||||
.ToList();
|
||||
|
||||
MessageText.text = GetQuestMessagesForPrisoner(SelectedHero.Id);
|
||||
MessageText.gameObject.SetActive(!string.IsNullOrEmpty(MessageText.text));
|
||||
PrisonerInfoText.text = GetPrisonerInfoText();
|
||||
|
||||
var options = SelectedPrisonerToManage.AvailableOptions;
|
||||
bool hasRelease = options.Any(
|
||||
@@ -212,8 +216,46 @@ namespace eagle {
|
||||
else if (hasReturn)
|
||||
ReturnToggle.isOn = true;
|
||||
|
||||
// Update dropdown visibility based on selected toggle
|
||||
moveToDropdown.gameObject.SetActive(MoveToggle.isOn);
|
||||
UpdateMoveDropdownVisibility();
|
||||
}
|
||||
|
||||
private void UpdateMoveDropdownVisibility() {
|
||||
moveToDropdown.gameObject.SetActive(
|
||||
MoveToggle.isOn && moveToDropdown.options.Count > 0);
|
||||
}
|
||||
|
||||
private string GetPrisonerInfoText() {
|
||||
var prisoner = SelectedPrisonerToManage.Prisoner;
|
||||
var hero = prisoner.Hero;
|
||||
var heroName = GetHeroDisplayName(hero.Id);
|
||||
var paragraphs = new List<string>();
|
||||
|
||||
var lastFactionId = PrisonerLastFactionId(prisoner);
|
||||
if (lastFactionId.HasValue) {
|
||||
paragraphs.Add($"Captured from {_model.FactionName(lastFactionId.Value)}.");
|
||||
}
|
||||
|
||||
var factionLeaderSentence = FactionLeaderSentence(hero, heroName);
|
||||
if (!string.IsNullOrEmpty(factionLeaderSentence)) {
|
||||
paragraphs.Add(factionLeaderSentence);
|
||||
}
|
||||
|
||||
return string.Join("\n\n", paragraphs);
|
||||
}
|
||||
|
||||
private int? PrisonerLastFactionId(object prisoner) {
|
||||
var property = prisoner.GetType().GetProperty("LastFactionId");
|
||||
if (property?.GetValue(prisoner) is int lastFactionId) { return lastFactionId; }
|
||||
return null;
|
||||
}
|
||||
|
||||
private string FactionLeaderSentence(HeroView hero, string heroName) {
|
||||
if (!hero.IsFactionLeader) { return ""; }
|
||||
if (!hero.FactionId.HasValue) { return $"{heroName} is a faction leader."; }
|
||||
var faction = _model.MaybeDestroyedFaction(hero.FactionId.Value);
|
||||
return faction.Leaders.Count == 1 && faction.Leaders.Contains(hero.Id)
|
||||
? $"{heroName} is the sole leader of {_model.FactionName(hero.FactionId.Value)}."
|
||||
: $"{heroName} is a leader of {_model.FactionName(hero.FactionId.Value)}.";
|
||||
}
|
||||
|
||||
private string GetQuestMessagesForPrisoner(HeroId prisonerId) {
|
||||
|
||||
+2
-2
@@ -130,8 +130,8 @@ namespace eagle {
|
||||
|
||||
private static void EnsureContentTouchTargets(GameObject content) {
|
||||
if (content == null) return;
|
||||
foreach (var detector in content.GetComponentsInChildren<TableRowClickDetector>(true)) {
|
||||
TouchTargetUtils.EnsureMinimumTouchHeight(detector);
|
||||
foreach (var row in content.GetComponentsInChildren<TableRowController>(true)) {
|
||||
TouchTargetUtils.EnsureMinimumTouchHeight(row);
|
||||
}
|
||||
foreach (var button in content.GetComponentsInChildren<Button>(true)) {
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(button);
|
||||
|
||||
@@ -271,6 +271,15 @@ namespace eagle {
|
||||
return char.ToUpper(str[0]) + str[1..];
|
||||
}
|
||||
|
||||
public static string IndefiniteArticle(string word) {
|
||||
if (String.IsNullOrEmpty(word)) return "a";
|
||||
var firstChar = char.ToLower(word[0]);
|
||||
return firstChar == 'a' || firstChar == 'e' || firstChar == 'i' || firstChar == 'o' ||
|
||||
firstChar == 'u'
|
||||
? "an"
|
||||
: "a";
|
||||
}
|
||||
|
||||
public static string ListString(IList<String> strings) {
|
||||
if (strings.Count == 1) {
|
||||
return strings[0];
|
||||
|
||||
@@ -67,12 +67,7 @@ namespace eagle {
|
||||
}
|
||||
}
|
||||
|
||||
void Awake() {
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(dismissAllButton);
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(
|
||||
dismissOneButtonText.GetComponentInParent<Button>());
|
||||
HideBeastVisual();
|
||||
}
|
||||
void Awake() { HideBeastVisual(); }
|
||||
|
||||
void OnDestroy() {
|
||||
_isDestroying = true;
|
||||
|
||||
+1
-10
@@ -126,15 +126,6 @@ namespace eagle.Notifications.ARNNotifications {
|
||||
return ProfessionNames.TryGetValue(profession, out var name) ? name : "Unknown";
|
||||
}
|
||||
|
||||
private static string GetArticle(string word) {
|
||||
if (string.IsNullOrEmpty(word)) return "a";
|
||||
char firstChar = char.ToLower(word[0]);
|
||||
return (firstChar == 'a' || firstChar == 'e' || firstChar == 'i' || firstChar == 'o' ||
|
||||
firstChar == 'u')
|
||||
? "an"
|
||||
: "a";
|
||||
}
|
||||
|
||||
private static string GetProfessionTitle(Profession profession) {
|
||||
if (ProfessionTitles.TryGetValue(profession, out var titles) && titles.Count > 0) {
|
||||
return titles[Random.Next(titles.Count)];
|
||||
@@ -157,7 +148,7 @@ namespace eagle.Notifications.ARNNotifications {
|
||||
var details = notification.Details.ProfessionGainedDetails;
|
||||
var hero = currentModel.Heroes[details.HeroId];
|
||||
var professionName = GetProfessionName(details.NewProfession);
|
||||
var article = GetArticle(professionName);
|
||||
var article = DisplayNames.IndefiniteArticle(professionName);
|
||||
|
||||
DynamicTextTemplate textTemplate;
|
||||
List<ProvinceId> affectedProvinces;
|
||||
|
||||
+2
-1
@@ -55,11 +55,12 @@ namespace eagle.Notifications {
|
||||
if (beastEvent.Count == 1) {
|
||||
var typeName = beastInfo.SingularName;
|
||||
var upper = DisplayNames.Capitalized(typeName);
|
||||
var article = DisplayNames.IndefiniteArticle(typeName);
|
||||
|
||||
yield return new Notification(
|
||||
title: $"{upper} Appeared",
|
||||
text:
|
||||
$"A {typeName} is {DisplayNames.BeastEffectString(beastInfo)} in {province.Name}!",
|
||||
$"{DisplayNames.Capitalized(article)} {typeName} is {DisplayNames.BeastEffectString(beastInfo)} in {province.Name}!",
|
||||
singleProvinceId: cp.Id,
|
||||
displayedHeroes: displayedHeroes,
|
||||
beastVisualName: beastInfo.SingularName);
|
||||
|
||||
+197
-70
@@ -39,8 +39,8 @@ RectTransform:
|
||||
- {fileID: 4374038768321626813}
|
||||
- {fileID: 5678746054507640123}
|
||||
- {fileID: 5648968011886801047}
|
||||
- {fileID: 4032822672651613896}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -65,7 +65,7 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 0.5019608}
|
||||
@@ -95,11 +95,11 @@ MonoBehaviour:
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
|
||||
m_Name:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Padding:
|
||||
m_Left: 5
|
||||
m_Right: 5
|
||||
m_Right: 8
|
||||
m_Top: 5
|
||||
m_Bottom: 5
|
||||
m_ChildAlignment: 3
|
||||
@@ -128,7 +128,7 @@ MonoBehaviour:
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleWidth: 1
|
||||
m_FlexibleHeight: 1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &3473877888190431645
|
||||
@@ -186,7 +186,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 331828637643492292}
|
||||
m_Father: {fileID: 4964299467728387319}
|
||||
m_RootOrder: 2
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -260,20 +259,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_TextWrappingMode: 0
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
@@ -322,7 +324,7 @@ GameObject:
|
||||
- component: {fileID: 331828637643492292}
|
||||
- component: {fileID: 331828637643492290}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -341,7 +343,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 331828637631894914}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -367,6 +368,7 @@ GameObject:
|
||||
- component: {fileID: 1163976501966482210}
|
||||
- component: {fileID: 1783652932118155857}
|
||||
- component: {fileID: 1676033398013528719}
|
||||
- component: {fileID: 910000000000000001}
|
||||
m_Layer: 0
|
||||
m_Name: Battalion Name
|
||||
m_TagString: Untagged
|
||||
@@ -381,13 +383,12 @@ RectTransform:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 555977501120831083}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
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: 6184869508644083830}
|
||||
m_RootOrder: 1
|
||||
m_Father: {fileID: 4964299467728387319}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -457,24 +458,27 @@ MonoBehaviour:
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_HorizontalAlignment: 1
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_TextWrappingMode: 0
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
@@ -492,6 +496,26 @@ MonoBehaviour:
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!114 &910000000000000001
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 555977501120831083}
|
||||
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: 190
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 260
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: 1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &1918964757375486776
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -526,7 +550,6 @@ RectTransform:
|
||||
- {fileID: 8260846945135601422}
|
||||
- {fileID: 8531744033320629339}
|
||||
m_Father: {fileID: 4964299467728387319}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -612,8 +635,9 @@ RectTransform:
|
||||
- {fileID: 4239119347539344156}
|
||||
- {fileID: 6908382027793978762}
|
||||
- {fileID: 331828637631894914}
|
||||
- {fileID: 6956662806623897567}
|
||||
- {fileID: 1163976501966482210}
|
||||
m_Father: {fileID: 6184869508644083830}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -633,12 +657,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 207
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 207
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_FlexibleWidth: 1
|
||||
m_FlexibleHeight: 1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &3588802365339044119
|
||||
MonoBehaviour:
|
||||
@@ -699,7 +723,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 6771392726702988933}
|
||||
m_Father: {fileID: 4239119347539344156}
|
||||
m_RootOrder: 2
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -773,20 +796,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 256
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
@@ -835,7 +861,7 @@ GameObject:
|
||||
- component: {fileID: 6771392726702988933}
|
||||
- component: {fileID: 3449568995957449193}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Snobbery Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Snobbery Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -854,7 +880,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 8531744033320629339}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -869,6 +894,62 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2548513019161439917}
|
||||
m_CullTransparentMesh: 0
|
||||
--- !u!1 &2777967974789043054
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4032822672651613896}
|
||||
- component: {fileID: 6282693960667890533}
|
||||
m_Layer: 0
|
||||
m_Name: Spacer
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4032822672651613896
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2777967974789043054}
|
||||
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: 331828637151130388}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &6282693960667890533
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2777967974789043054}
|
||||
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: 5
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 10
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: 1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &3754875410318285988
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -900,9 +981,7 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4964299467728387319}
|
||||
- {fileID: 1163976501966482210}
|
||||
m_Father: {fileID: 331828637151130388}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -922,12 +1001,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 207
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 207
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_FlexibleWidth: 1
|
||||
m_FlexibleHeight: 1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &1156400750942105811
|
||||
MonoBehaviour:
|
||||
@@ -988,7 +1067,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 3421389973516920237}
|
||||
m_Father: {fileID: 4964299467728387319}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -1062,20 +1140,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_TextWrappingMode: 0
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
@@ -1144,12 +1225,11 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 5648968011886801047}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 25, y: 25}
|
||||
m_SizeDelta: {x: 26, y: 26}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &3462703612756757987
|
||||
CanvasRenderer:
|
||||
@@ -1220,12 +1300,11 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 5678746054507640123}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 25, y: 25}
|
||||
m_SizeDelta: {x: 26, y: 26}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7037872268356765622
|
||||
CanvasRenderer:
|
||||
@@ -1297,7 +1376,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4239119347539344156}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -1396,7 +1474,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 4155971713435252767}
|
||||
m_Father: {fileID: 331828637151130388}
|
||||
m_RootOrder: 3
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -1510,10 +1587,10 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 25
|
||||
m_PreferredHeight: 25
|
||||
m_MinWidth: 28
|
||||
m_MinHeight: 28
|
||||
m_PreferredWidth: 28
|
||||
m_PreferredHeight: 28
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -1548,12 +1625,11 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4374038768321626813}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 25, y: 25}
|
||||
m_SizeDelta: {x: 26, y: 26}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &4669985286182351739
|
||||
CanvasRenderer:
|
||||
@@ -1593,6 +1669,62 @@ MonoBehaviour:
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &6083412086545502385
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6956662806623897567}
|
||||
- component: {fileID: 4780899108218876874}
|
||||
m_Layer: 0
|
||||
m_Name: Spacer
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6956662806623897567
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6083412086545502385}
|
||||
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: 4964299467728387319}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &4780899108218876874
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6083412086545502385}
|
||||
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: 25
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 25
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: 1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &7288182720380425319
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1624,12 +1756,11 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 8728618441403129635}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 25, y: 25}
|
||||
m_SizeDelta: {x: 26, y: 26}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2353172733458224956
|
||||
CanvasRenderer:
|
||||
@@ -1680,7 +1811,7 @@ GameObject:
|
||||
- component: {fileID: 3421389973516920237}
|
||||
- component: {fileID: 1774122932945147255}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Irkel Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -1699,7 +1830,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 6908382027793978762}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -1748,7 +1878,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 1601713648220386999}
|
||||
m_Father: {fileID: 331828637151130388}
|
||||
m_RootOrder: 5
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -1862,10 +1991,10 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 25
|
||||
m_PreferredHeight: 25
|
||||
m_MinWidth: 28
|
||||
m_MinHeight: 28
|
||||
m_PreferredWidth: 28
|
||||
m_PreferredHeight: 28
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -1903,7 +2032,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 6135424429731102560}
|
||||
m_Father: {fileID: 331828637151130388}
|
||||
m_RootOrder: 4
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -2017,10 +2145,10 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 25
|
||||
m_PreferredHeight: 25
|
||||
m_MinWidth: 28
|
||||
m_MinHeight: 28
|
||||
m_PreferredWidth: 28
|
||||
m_PreferredHeight: 28
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -2056,7 +2184,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 331828637151130388}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -2111,8 +2238,8 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 32
|
||||
m_MinHeight: 32
|
||||
m_MinWidth: 28
|
||||
m_MinHeight: 28
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
@@ -2152,7 +2279,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 3471523905554074002}
|
||||
m_Father: {fileID: 331828637151130388}
|
||||
m_RootOrder: 2
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -2266,10 +2392,10 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: -1
|
||||
m_PreferredWidth: 25
|
||||
m_PreferredHeight: 25
|
||||
m_MinWidth: 28
|
||||
m_MinHeight: 28
|
||||
m_PreferredWidth: 28
|
||||
m_PreferredHeight: 28
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -2284,7 +2410,7 @@ GameObject:
|
||||
- component: {fileID: 6171902031725488231}
|
||||
- component: {fileID: 6881754971532079123}
|
||||
m_Layer: 0
|
||||
m_Name: TMP SubMeshUI [Snobbery Atlas Material + LiberationSans SDF Atlas]
|
||||
m_Name: 'TMP SubMeshUI [Snobbery Atlas Material + LiberationSans SDF Atlas]'
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@@ -2303,7 +2429,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 3419071138720789760}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
@@ -2351,7 +2476,6 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 6171902031725488231}
|
||||
m_Father: {fileID: 4239119347539344156}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -2425,20 +2549,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 256
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
|
||||
+32
-9
@@ -1,10 +1,14 @@
|
||||
using System;
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using static UnityEngine.EventSystems.PointerEventData;
|
||||
|
||||
namespace eagle {
|
||||
public class ArmTroopsRowController : TableRowController {
|
||||
public class ArmTroopsRowController : TableRowController, IPointerClickHandler {
|
||||
private static readonly Color SelectedBackgroundColor = new Color(0.68f, 0.84f, 0.96f, 1f);
|
||||
|
||||
public RawImage battalionImage;
|
||||
public TMP_Text oldArmamentLabel;
|
||||
public TMP_Text newArmamentLabel;
|
||||
@@ -21,16 +25,10 @@ namespace eagle {
|
||||
public Action MinusButtonClickedCallback;
|
||||
public Action ResetButtonClickedCallback;
|
||||
public Action MaxButtonClickedCallback;
|
||||
public Action RowClickedCallback;
|
||||
|
||||
public Texture BattalionImage { set => battalionImage.texture = value; }
|
||||
|
||||
private void Awake() {
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(plusButton);
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(minusButton);
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(resetButton);
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(maxButton);
|
||||
}
|
||||
|
||||
public double CostPerPoint { set => costPerPointLabel.text = value.ToString("0.##"); }
|
||||
|
||||
public double OldArmament { set => oldArmamentLabel.text = Math.Floor(value).ToString(); }
|
||||
@@ -49,6 +47,31 @@ namespace eagle {
|
||||
|
||||
public bool AllowMax { set => maxButton.interactable = value; }
|
||||
|
||||
public void SetActionButtonsVisible(bool visible) {
|
||||
plusButton.gameObject.SetActive(visible);
|
||||
minusButton.gameObject.SetActive(visible);
|
||||
resetButton.gameObject.SetActive(visible);
|
||||
maxButton.gameObject.SetActive(visible);
|
||||
}
|
||||
|
||||
public void SetNameVisible(bool visible) { nameLabel.gameObject.SetActive(visible); }
|
||||
|
||||
public void SetContentRaycastTargets(bool enabled) {
|
||||
foreach (var graphic in GetComponentsInChildren<Graphic>()) {
|
||||
if (graphic.gameObject == gameObject) continue;
|
||||
graphic.raycastTarget = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ColorRow() {
|
||||
base.ColorRow();
|
||||
if (Selected) { gameObject.GetComponent<Image>().color = SelectedBackgroundColor; }
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData) {
|
||||
if (eventData.button == InputButton.Left) { RowClickedCallback?.Invoke(); }
|
||||
}
|
||||
|
||||
public void PlusButtonClicked() { PlusButtonClickedCallback(); }
|
||||
|
||||
public void MinusButtonClicked() { MinusButtonClickedCallback(); }
|
||||
|
||||
+45
-45
@@ -484,10 +484,10 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: 24
|
||||
m_PreferredHeight: 24
|
||||
m_MinWidth: 48
|
||||
m_MinHeight: 48
|
||||
m_PreferredWidth: 48
|
||||
m_PreferredHeight: 48
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -609,7 +609,7 @@ RectTransform:
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 5358793647909427011}
|
||||
- {fileID: 6369420274474477703}
|
||||
- {fileID: 1314600028082361400}
|
||||
- {fileID: 7090910821347329837}
|
||||
m_Father: {fileID: 0}
|
||||
@@ -692,10 +692,6 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
layoutElement: {fileID: 1314600028779976542}
|
||||
resultRow: {fileID: 5486501805354810596}
|
||||
componentRowPrefab: {fileID: 1480082851842668559, guid: 16b7452b2773643678ccb6294dafdf51,
|
||||
type: 3}
|
||||
componentsLayoutTransform: {fileID: 1314600028082361400}
|
||||
disclosureTriangle: {fileID: 5358793647909427009}
|
||||
plusButton: {fileID: 5575876168650957846}
|
||||
minusButton: {fileID: 5575876168409340640}
|
||||
maxButton: {fileID: 1122515026039881622}
|
||||
@@ -713,13 +709,13 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Padding:
|
||||
m_Left: 0
|
||||
m_Left: 8
|
||||
m_Right: 0
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 0
|
||||
m_ChildAlignment: 3
|
||||
m_Spacing: 0
|
||||
m_ChildForceExpandWidth: 1
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 1
|
||||
m_ChildControlHeight: 1
|
||||
@@ -998,13 +994,13 @@ RectTransform:
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 3678831580683052525}
|
||||
m_Father: {fileID: 1314600028779976541}
|
||||
m_RootOrder: 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_SizeDelta: {x: 32, y: 32}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7569506076916496485
|
||||
CanvasRenderer:
|
||||
@@ -1054,12 +1050,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 24
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: -1
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_MinWidth: 32
|
||||
m_MinHeight: 32
|
||||
m_PreferredWidth: 32
|
||||
m_PreferredHeight: 32
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1 &4610200287437140885
|
||||
GameObject:
|
||||
@@ -1591,10 +1587,10 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: 24
|
||||
m_PreferredHeight: 24
|
||||
m_MinWidth: 48
|
||||
m_MinHeight: 48
|
||||
m_PreferredWidth: 48
|
||||
m_PreferredHeight: 48
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -1822,10 +1818,10 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: 24
|
||||
m_PreferredHeight: 24
|
||||
m_MinWidth: 48
|
||||
m_MinHeight: 48
|
||||
m_PreferredWidth: 48
|
||||
m_PreferredHeight: 48
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -2348,10 +2344,10 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: -1
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: 24
|
||||
m_PreferredHeight: 24
|
||||
m_MinWidth: 48
|
||||
m_MinHeight: 48
|
||||
m_PreferredWidth: 48
|
||||
m_PreferredHeight: 48
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -2385,7 +2381,6 @@ RectTransform:
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 6369420274474477703}
|
||||
- {fileID: 3728998876351387856}
|
||||
- {fileID: 3960851387598383265}
|
||||
- {fileID: 7242925745324046187}
|
||||
@@ -2414,11 +2409,11 @@ MonoBehaviour:
|
||||
m_Padding:
|
||||
m_Left: 5
|
||||
m_Right: 5
|
||||
m_Top: 5
|
||||
m_Bottom: 5
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 4
|
||||
m_Spacing: 0
|
||||
m_ChildForceExpandWidth: 1
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 1
|
||||
m_ChildControlHeight: 1
|
||||
@@ -2502,10 +2497,10 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 110
|
||||
m_MinHeight: 42
|
||||
m_PreferredWidth: 110
|
||||
m_PreferredHeight: 42
|
||||
m_MinWidth: 224
|
||||
m_MinHeight: 48
|
||||
m_PreferredWidth: 224
|
||||
m_PreferredHeight: 48
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
@@ -2523,7 +2518,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_Padding:
|
||||
m_Left: 0
|
||||
m_Right: 0
|
||||
m_Right: 12
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 4
|
||||
@@ -2729,7 +2724,7 @@ PrefabInstance:
|
||||
- target: {fileID: 1208082349148441403, guid: a8d1bc92db8384eea9ee531d7e642690,
|
||||
type: 3}
|
||||
propertyPath: onValueChanged.m_PersistentCalls.m_Calls.Array.size
|
||||
value: 1
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1208082349148441403, guid: a8d1bc92db8384eea9ee531d7e642690,
|
||||
type: 3}
|
||||
@@ -2866,6 +2861,11 @@ PrefabInstance:
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4309098217826861869, guid: a8d1bc92db8384eea9ee531d7e642690,
|
||||
type: 3}
|
||||
propertyPath: m_IsActive
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4309098217826861869, guid: a8d1bc92db8384eea9ee531d7e642690,
|
||||
type: 3}
|
||||
propertyPath: m_Name
|
||||
@@ -2903,10 +2903,10 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreLayout: 0
|
||||
m_MinWidth: 20
|
||||
m_IgnoreLayout: 1
|
||||
m_MinWidth: 0
|
||||
m_MinHeight: 32
|
||||
m_PreferredWidth: 20
|
||||
m_PreferredWidth: 0
|
||||
m_PreferredHeight: -1
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
|
||||
+1
-49
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
@@ -8,12 +6,6 @@ namespace eagle {
|
||||
public class OrganizeTroopsTableRow : TableRowController {
|
||||
public LayoutElement layoutElement;
|
||||
public OrganizeTroopsResultRow resultRow;
|
||||
public OrganizeTroopsComponentRow componentRowPrefab;
|
||||
public Transform componentsLayoutTransform;
|
||||
private List<OrganizeTroopsComponentRow> existingComponents =
|
||||
new List<OrganizeTroopsComponentRow>();
|
||||
|
||||
public Toggle disclosureTriangle;
|
||||
|
||||
public Button plusButton;
|
||||
public Button minusButton;
|
||||
@@ -57,18 +49,11 @@ namespace eagle {
|
||||
|
||||
SetButtons();
|
||||
|
||||
disclosureTriangle.isOn = value.Open;
|
||||
|
||||
_components = value.Components;
|
||||
DrawComponents();
|
||||
}
|
||||
}
|
||||
|
||||
private List<BattalionComponent> _components = new List<BattalionComponent>();
|
||||
public List<BattalionComponent> Components { get => _components; }
|
||||
|
||||
private void Awake() {
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(disclosureTriangle);
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(plusButton);
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(minusButton);
|
||||
TouchTargetUtils.EnsureMinimumTouchTarget(maxButton);
|
||||
@@ -76,41 +61,8 @@ namespace eagle {
|
||||
}
|
||||
|
||||
private void DrawComponents() {
|
||||
existingComponents.ForEach(t => Destroy(t.gameObject));
|
||||
existingComponents.Clear();
|
||||
|
||||
var resultHeight = resultRow.GetComponent<LayoutElement>().minHeight;
|
||||
|
||||
if (disclosureTriangle.isOn) {
|
||||
existingComponents = Components
|
||||
.Select(comp => {
|
||||
var cr = Instantiate(
|
||||
componentRowPrefab,
|
||||
componentsLayoutTransform);
|
||||
cr.BattalionComponent = comp;
|
||||
return cr;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
layoutElement.minHeight =
|
||||
resultHeight +
|
||||
existingComponents
|
||||
.Select(c => c.gameObject.GetComponent<LayoutElement>().minHeight)
|
||||
.Sum();
|
||||
} else {
|
||||
layoutElement.minHeight = resultHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Start is called before the first frame update
|
||||
private void Start() {
|
||||
disclosureTriangle.isOn = BattalionInfo != null && BattalionInfo.Open;
|
||||
}
|
||||
|
||||
public void DisclosureTriangleToggled(bool newValue) {
|
||||
BattalionInfo.Open = newValue;
|
||||
|
||||
DrawComponents();
|
||||
layoutElement.minHeight = resultHeight;
|
||||
}
|
||||
|
||||
public bool AllowPlus { set => plusButton.interactable = value; }
|
||||
|
||||
+10
-14
@@ -33,7 +33,6 @@ RectTransform:
|
||||
- {fileID: 7991703638448449907}
|
||||
- {fileID: 2740647173634940184}
|
||||
m_Father: {fileID: 5484066820717257546}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -118,7 +117,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 5484066820717257546}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -192,20 +190,23 @@ MonoBehaviour:
|
||||
m_VerticalAlignment: 256
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_characterHorizontalScale: 1
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
@@ -276,7 +277,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2740647173634940184}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -335,8 +335,8 @@ MonoBehaviour:
|
||||
m_MinHeight: 12
|
||||
m_PreferredWidth: 12
|
||||
m_PreferredHeight: 12
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &3108385525174059074
|
||||
MonoBehaviour:
|
||||
@@ -403,7 +403,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2740647173634940184}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -462,8 +461,8 @@ MonoBehaviour:
|
||||
m_MinHeight: 12
|
||||
m_PreferredWidth: 12
|
||||
m_PreferredHeight: 12
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &2128470998686725890
|
||||
MonoBehaviour:
|
||||
@@ -530,7 +529,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1874796160729705548}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -589,8 +587,8 @@ MonoBehaviour:
|
||||
m_MinHeight: 24
|
||||
m_PreferredWidth: 24
|
||||
m_PreferredHeight: 24
|
||||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_FlexibleWidth: 0
|
||||
m_FlexibleHeight: 0
|
||||
m_LayoutPriority: 1
|
||||
--- !u!114 &4395676847406292176
|
||||
MonoBehaviour:
|
||||
@@ -661,7 +659,6 @@ RectTransform:
|
||||
- {fileID: 1874796160729705548}
|
||||
- {fileID: 1054825972209814308}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
@@ -833,7 +830,6 @@ RectTransform:
|
||||
- {fileID: 2463376593358250422}
|
||||
- {fileID: 7191310699296920465}
|
||||
m_Father: {fileID: 1874796160729705548}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
|
||||
@@ -8,6 +8,7 @@ public interface IClickReceiver {
|
||||
bool CanReceiveGridInput { get; }
|
||||
void HandleLongHover(int? gridIndex, Vector3 mousePosition);
|
||||
void HandleLongUIHover(IEnumerable<GameObject> hoveredObjects);
|
||||
void HandleTouchHover(int? gridIndex, bool isTouching);
|
||||
void HandlePrimaryClick(int clickedIndex);
|
||||
void HandleSecondaryClick(int clickedIndex);
|
||||
void HandleDrag(int startIndex, int finishIndex);
|
||||
@@ -38,6 +39,8 @@ public class MouseHandler : MonoBehaviour {
|
||||
private float? _touchDownTime;
|
||||
private Vector2 _touchDownPosition;
|
||||
private int? _touchDownGridIndex;
|
||||
private int? _lastTouchHoverGridIndex;
|
||||
private bool _touchHoverActive;
|
||||
private bool _longPressTriggered;
|
||||
|
||||
// Use this for initialization
|
||||
@@ -45,12 +48,20 @@ public class MouseHandler : MonoBehaviour {
|
||||
|
||||
// Update is called once per frame
|
||||
void Update() {
|
||||
HandleTouchLongPress();
|
||||
bool handledTouchInput = HandleTouchLongPress();
|
||||
if (handledTouchInput) { return; }
|
||||
if (UseTouchPointerInput()) { return; }
|
||||
|
||||
HandleMouse();
|
||||
}
|
||||
|
||||
void HandleTouchLongPress() {
|
||||
if (clickReceiver?.CanReceiveGridInput != true) { return; }
|
||||
private bool UseTouchPointerInput() {
|
||||
return GameInput.HasTouchscreen() &&
|
||||
(!GameInput.HasMousePointer() || GameInput.HasSimulatedTouchscreen());
|
||||
}
|
||||
|
||||
bool HandleTouchLongPress() {
|
||||
if (clickReceiver?.CanReceiveGridInput != true) { return false; }
|
||||
|
||||
// Handle touch input for long-press detection
|
||||
if (GameInput.TouchCount() > 0) {
|
||||
@@ -65,8 +76,12 @@ public class MouseHandler : MonoBehaviour {
|
||||
_touchDownPosition = touch.Position;
|
||||
_touchDownGridIndex = touchDownGridIndex;
|
||||
_longPressTriggered = false;
|
||||
UpdateTouchHover(touchDownGridIndex);
|
||||
}
|
||||
} else if (touch.Phase == TouchPhase.Moved || touch.Phase == TouchPhase.Stationary) {
|
||||
var touchGridIndex = currentGridIndex(touch.Position);
|
||||
UpdateTouchHover(touchGridIndex);
|
||||
|
||||
// Touch ongoing - check for long-press
|
||||
if (_touchDownTime.HasValue && !_longPressTriggered) {
|
||||
var moved = Vector2.Distance(touch.Position, _touchDownPosition);
|
||||
@@ -82,16 +97,49 @@ public class MouseHandler : MonoBehaviour {
|
||||
}
|
||||
}
|
||||
} else if (touch.Phase == TouchPhase.Ended || touch.Phase == TouchPhase.Canceled) {
|
||||
// Touch ended
|
||||
if (touch.Phase == TouchPhase.Ended && _touchDownTime.HasValue &&
|
||||
!_longPressTriggered) {
|
||||
var moved = Vector2.Distance(touch.Position, _touchDownPosition);
|
||||
var touchUpGridIndex = currentGridIndex(touch.Position);
|
||||
if (moved <= MovementTolerancePixels && _touchDownGridIndex.HasValue &&
|
||||
touchUpGridIndex == _touchDownGridIndex) {
|
||||
clickReceiver.HandlePrimaryClick(_touchDownGridIndex.Value);
|
||||
} else if (
|
||||
_touchDownGridIndex.HasValue && touchUpGridIndex.HasValue &&
|
||||
touchUpGridIndex != _touchDownGridIndex) {
|
||||
clickReceiver.HandleDrag(_touchDownGridIndex.Value, touchUpGridIndex.Value);
|
||||
}
|
||||
}
|
||||
|
||||
_touchDownTime = null;
|
||||
_touchDownGridIndex = null;
|
||||
StopTouchHover();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
_touchDownTime = null;
|
||||
_touchDownGridIndex = null;
|
||||
StopTouchHover();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTouchHover(int? gridIndex) {
|
||||
if (_touchHoverActive && _lastTouchHoverGridIndex == gridIndex) { return; }
|
||||
|
||||
_touchHoverActive = true;
|
||||
_lastTouchHoverGridIndex = gridIndex;
|
||||
clickReceiver.HandleTouchHover(gridIndex, true);
|
||||
}
|
||||
|
||||
private void StopTouchHover() {
|
||||
if (!_touchHoverActive) { return; }
|
||||
|
||||
_touchHoverActive = false;
|
||||
_lastTouchHoverGridIndex = null;
|
||||
clickReceiver.HandleTouchHover(null, false);
|
||||
}
|
||||
|
||||
private List<RaycastResult> RaycastPointer(Vector2 position, int pointerId) {
|
||||
PointerEventData pointerData = new PointerEventData(
|
||||
EventSystem.current) { pointerId = pointerId, position = position };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user