mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 05:15:44 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c92ef3ac0 |
@@ -29,9 +29,6 @@ common --javacopt="-Xlint:-options"
|
||||
common --linkopt=-Wl
|
||||
common:macos --linkopt=-Wl,-no_warn_duplicate_libraries
|
||||
|
||||
# Fix Xcode version caching issue - avoids need for `bazel clean --expunge` after Xcode updates
|
||||
common:macos --repo_env=DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
|
||||
|
||||
common --java_language_version=17
|
||||
common --java_runtime_version=remotejdk_17
|
||||
common --tool_java_language_version=17
|
||||
|
||||
@@ -216,31 +216,6 @@ to be used for different players or game situations within the same server proce
|
||||
- Map validation tests ensure game content integrity
|
||||
- Use `GameSettings_test_utils.cpp` and `ShardokEngineBasedTestData.cpp` for C++ test helpers
|
||||
|
||||
### Scala Testing Patterns
|
||||
|
||||
**Use `inside()` instead of `asInstanceOf` for type matching in tests:**
|
||||
|
||||
Never use `asInstanceOf` in tests. Instead, use ScalaTest's `inside()` pattern for safe type matching:
|
||||
|
||||
```scala
|
||||
// BAD - don't do this
|
||||
val changedHero = result.changedHeroes.head.asInstanceOf[ChangedHeroC]
|
||||
changedHero.heroId shouldBe 19
|
||||
|
||||
// GOOD - use inside() pattern
|
||||
import org.scalatest.Inside.inside
|
||||
|
||||
inside(result.changedHeroes.head) { case changedHero: ChangedHeroC =>
|
||||
changedHero.heroId shouldBe 19
|
||||
changedHero.vigorChange shouldBe StatDelta(17.2)
|
||||
}
|
||||
```
|
||||
|
||||
The `inside()` pattern:
|
||||
- Provides better error messages when the type doesn't match
|
||||
- Is idiomatic ScalaTest
|
||||
- Works with pattern matching for more complex assertions
|
||||
|
||||
## Performance Testing
|
||||
|
||||
When making performance-related changes to the AI or engine:
|
||||
|
||||
+11
-63
@@ -45,12 +45,11 @@
|
||||
| Phase 4: ActionResultT | **Complete** | All 59 actions return `ActionResultT` |
|
||||
| Phase 5: Action Base Classes | **Complete** | All `RandomSequentialResultsAction` and `DeterministicSingleResultAction` converted to T-type base classes |
|
||||
| Phase 5b: Base Class Cleanup | **Complete** | `RandomSequentialResultsAction` and `DeterministicSingleResultAction` deleted |
|
||||
| Phase 5c: RoundPhaseAdvancer Actions | **Complete** | All actions called by RoundPhaseAdvancer accept Scala GameState |
|
||||
| Phase 5d: RoundPhaseAdvancer Itself | **Complete** | RoundPhaseAdvancer.checkForPhaseAdvancement takes Scala GameState |
|
||||
| Phase 5c: RoundPhaseAdvancer Actions | **In Progress** | Converting actions called by RoundPhaseAdvancer to take Scala GameState |
|
||||
|
||||
### Phase 5c/5d Progress (Complete)
|
||||
### Phase 5c Progress
|
||||
|
||||
`RoundPhaseAdvancer.checkForPhaseAdvancement` now accepts Scala `GameState` and `ActionResultApplier` directly (PR #4677).
|
||||
Actions called by `RoundPhaseAdvancer` that need to accept Scala `GameState`:
|
||||
|
||||
| Action | PR | Status |
|
||||
|--------|-----|--------|
|
||||
@@ -58,16 +57,10 @@
|
||||
| `PerformForcedTurnBackAction` | #4671 | ✅ Merged |
|
||||
| `PerformHeroDeparturesAction` | #4672 | ✅ Merged |
|
||||
| `RequestFreeForAllBattlesAction` | #4673 | ✅ Merged |
|
||||
| `EndPlayerCommandsPhaseAction` | #4674 | ✅ Merged |
|
||||
| `EndDiplomacyResolutionPhaseAction` | #4675 | ✅ Merged |
|
||||
| `RoundPhaseAdvancer` itself | #4677 | ✅ Merged |
|
||||
|
||||
### EngineImpl Progress
|
||||
|
||||
| Change | PR | Status |
|
||||
|--------|-----|--------|
|
||||
| `recursiveTransform` deleted | #4677 | ✅ Merged |
|
||||
| `recursiveTransformT` uses `RandomStateTSequencer` | #4677 | ✅ Merged |
|
||||
| `EndPlayerCommandsPhaseAction` | #4674 | ✅ Created |
|
||||
| `EndDiplomacyResolutionPhaseAction` | - | 🔄 In Progress |
|
||||
| `EndBattleAftermathPhaseAction` | - | Pending |
|
||||
| Other RoundPhaseAdvancer actions | - | Pending |
|
||||
|
||||
### Current Architecture
|
||||
|
||||
@@ -120,11 +113,11 @@ src/main/scala/net/eagle0/eagle/library/actions/applier/ActionResultProtoApplier
|
||||
```
|
||||
Create `ActionResultApplier` that applies `ActionResultT` directly to Scala `GameState`.
|
||||
|
||||
**Tier 2 - RoundPhaseAdvancer:** ✅ **Complete**
|
||||
**Tier 2 - RoundPhaseAdvancer:**
|
||||
```
|
||||
src/main/scala/net/eagle0/eagle/library/RoundPhaseAdvancer.scala
|
||||
```
|
||||
Now accepts Scala `GameState` and `ActionResultApplier`. Only converts to proto lazily for `AvailableCommandsFactory` calls.
|
||||
Currently has ~20 calls to `ActionResultProtoConverter.toProto()`. After Tier 1, these become unnecessary.
|
||||
|
||||
**Tier 3 - Sequencers:**
|
||||
```
|
||||
@@ -147,32 +140,6 @@ Modify `RandomStateTSequencer` to thread Scala `GameState` throughout (currently
|
||||
4. Once all actions migrated, deprecate/remove proto-based callbacks
|
||||
5. Remove `lastStateProto` once no longer used
|
||||
|
||||
**RandomStateSequencer Migration Progress** (PR #4679 introduced protoless `RandomStateSequencer`):
|
||||
|
||||
| Action | Status |
|
||||
|--------|--------|
|
||||
| `TruceTurnBackPhaseAction` | ✅ Migrated (PR #4680) |
|
||||
| `EndHandleRiotsPhaseAction` | ✅ Migrated (PR #4684) |
|
||||
| `PerformVassalCommandsPhaseAction` | ✅ Migrated |
|
||||
| `PerformVassalDefenseDecisionsAction` | ✅ Migrated |
|
||||
| `EndVassalCommandsPhaseAction` | ✅ Migrated |
|
||||
| `PerformReconResolutionAction` | ✅ Migrated |
|
||||
| `NewRoundAction` | ✅ Migrated (PR #4698) |
|
||||
| `EndBattleAftermathPhaseAction` | ✅ Migrated (PR #4699) |
|
||||
| `EndDiplomacyResolutionPhaseAction` | ✅ Migrated |
|
||||
| `PerformUnaffiliatedHeroesAction` | ✅ Migrated |
|
||||
| `EngineImpl.recursiveTransformT` | ✅ Migrated (PR #4704) |
|
||||
| `ProtolessSequentialResultsActionWrapper` | ✅ Migrated (PR #4705) |
|
||||
| `LegacyRandomStateTSequencer` | ✅ **Deleted** (PR #4705) |
|
||||
|
||||
**TCommandFactory Extraction** (PR #4684):
|
||||
|
||||
To enable lightweight mocking of command creation in tests, `TCommandFactory` trait was extracted from `CommandFactory`. This allows tests to mock just the `makeTCommand` method without pulling in all 40+ command dependencies that `CommandFactory` requires.
|
||||
|
||||
- `TCommandFactory` - lightweight trait with just `makeTCommand`
|
||||
- `CommandFactory extends TCommandFactory` - maintains backward compatibility
|
||||
- Actions accepting command factories now use `TCommandFactory` type for better testability
|
||||
|
||||
**Tier 4 - History APIs:**
|
||||
```
|
||||
src/main/scala/net/eagle0/eagle/service/InMemoryHistory.scala
|
||||
@@ -185,7 +152,7 @@ Change APIs to vend Scala `GameState` and `ActionResultT` instead of proto versi
|
||||
| File | Usage | Target |
|
||||
|------|-------|--------|
|
||||
| `ActionResultTApplierImpl.scala` | Converts T→Proto, delegates to proto applier | Replace with `ActionResultApplier` |
|
||||
| `RoundPhaseAdvancer.scala` | ~~20 converter calls~~ | ✅ **Complete** - uses Scala GameState |
|
||||
| `RoundPhaseAdvancer.scala` | ~20 converter calls | Eliminate after applier conversion |
|
||||
| `RandomStateTSequencer.scala` | Converts T→Proto internally | Thread Scala GameState throughout |
|
||||
| `RandomStateProtoSequencer.scala` | Returns `Vector[ActionResultProto]` | Evaluate if still needed |
|
||||
| `VigorXPApplier.scala` | Wraps proto results | Convert to work with T |
|
||||
@@ -229,30 +196,11 @@ Remove remaining direct proto imports from utility classes.
|
||||
|
||||
| File | Status |
|
||||
|------|--------|
|
||||
| `CommandChoiceHelpers.scala` | Accepts proto `GameState`; blocks full deproto of `PerformVassalCommandsPhaseAction` and `PerformVassalDefenseDecisionsAction` |
|
||||
| `LegacyProvinceUtils.scala` | Replace with `ProvinceUtils.scala` - `hasImminentRiot` added (PR #4683) |
|
||||
| `LegacyFactionUtils.scala` | Replace proto imports with `FactionT` |
|
||||
| `LegacyUnaffiliatedHeroUtils.scala` | Replace proto imports with Scala models |
|
||||
| `BattalionTypeLoader.scala` | Keep proto for file loading, convert immediately after |
|
||||
| `BeastUtils.scala` | **Complete** - now uses Scala `BeastInfo` only |
|
||||
|
||||
### View Filters (Blocking Full Deproto)
|
||||
|
||||
The `ProvinceViewFilter` utility currently works entirely with proto types, blocking full deproto of actions that generate province views:
|
||||
|
||||
| File | Issue | Needed |
|
||||
|------|-------|--------|
|
||||
| `ProvinceViewFilter.scala` | Takes proto `Province`/`GameState`, returns proto `ProvinceView` | Scala `ProvinceViewT` model |
|
||||
| `GameStateViewFilter.scala` | Uses proto types throughout | Depends on `ProvinceViewT` |
|
||||
| `GameStateViewDiffer.scala` | Works with view protos | Depends on `ProvinceViewT` |
|
||||
|
||||
**Blocked Actions**:
|
||||
- `EndBattleAftermathPhaseAction` - uses `ProvinceViewFilter` for `revelationChange`, requires lazy proto conversion
|
||||
- `PerformReconResolutionAction` - uses `ProvinceViewFilter` for reconned provinces
|
||||
- `GameStateFactionExtensions` - uses `ProvinceViewFilter` for `updatedReconnedProvinces`
|
||||
|
||||
**Solution**: Create Scala `ProvinceViewT` (and possibly `ProvinceViewC`) that mirrors the proto `ProvinceView`. Then create a protoless `ProvinceViewFilter` that operates on Scala types. The proto version can delegate to the Scala version + convert, or we maintain both during transition.
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Verify Boundaries
|
||||
@@ -279,7 +227,7 @@ Confirm protos are used correctly at boundaries — and ONLY there.
|
||||
|
||||
2. **Shardok Integration**: `ResolveBattleAction` communicates with Shardok. Should the Shardok interface use protos (external service) or Scala models?
|
||||
|
||||
3. **View Generation**: `GameStateViewDiffer` works with view protos for client updates. Views need Scala models (`ProvinceViewT`, etc.) to allow actions like `EndBattleAftermathPhaseAction` to be fully protoless. The Scala views would be converted to proto only at the gRPC boundary when sending updates to clients.
|
||||
3. **View Generation**: `GameStateViewDiffer` works with view protos for client updates. Should views also have Scala models, or is proto acceptable for client-facing projections?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ vector<shared_ptr<ShardokAIClient>> ShardokGameController::MakeAIClients(
|
||||
pi.is_defender(),
|
||||
e->GetCurrentGameState()->hex_map(),
|
||||
e->GetGameSettings()->GetGetter(),
|
||||
AIAlgorithmType::ITERATIVE_DEEPENING,
|
||||
AIAlgorithmType::MCTS,
|
||||
ScoringCalculatorType::MCTS_OPTIMIZED,
|
||||
mctsConfig);
|
||||
|
||||
|
||||
+2
-6
@@ -24,7 +24,7 @@ namespace eagle {
|
||||
|
||||
public void OnTextUpdate(string text, bool completed) { SetUp(); }
|
||||
|
||||
public string TextId() { return _entries.Count > 0 ? CurrentEntry.GeneratedTextId : null; }
|
||||
public string TextId() { return CurrentEntry.GeneratedTextId; }
|
||||
|
||||
private void OnEnable() {
|
||||
ClientTextProvider.Provider.AddListener(this);
|
||||
@@ -41,13 +41,11 @@ namespace eagle {
|
||||
public IList<ChronicleEntry> Entries {
|
||||
get => _entries;
|
||||
set {
|
||||
var wasEmpty = _entries.Count == 0;
|
||||
_entries = value != null ? value.ToList() : new List<ChronicleEntry>();
|
||||
|
||||
if (_entries.Count == 0) return;
|
||||
|
||||
// Jump to the last entry when first populating, or if not currently viewing
|
||||
if (wasEmpty || !gameObject.activeSelf) {
|
||||
if (!gameObject.activeSelf) {
|
||||
_currentIndex = _entries.Count - 1;
|
||||
|
||||
ScrollToTop();
|
||||
@@ -62,8 +60,6 @@ namespace eagle {
|
||||
|
||||
private const string TitleSplitPattern = @"\n\s*=====\s*\n";
|
||||
private void SetUp() {
|
||||
if (_entries.Count == 0) return;
|
||||
|
||||
previousButton.interactable = _currentIndex > 0;
|
||||
nextButton.interactable = _currentIndex < _entries.Count - 1;
|
||||
|
||||
|
||||
+20
-31
@@ -224,13 +224,13 @@ namespace eagle {
|
||||
tp => tp.TypeId == battalionTypeId && tp.MeetsRequirements);
|
||||
}
|
||||
|
||||
private void MaybeActivateRow(
|
||||
EventBasedTable table,
|
||||
BattalionTypeId battalionTypeId,
|
||||
Dictionary<BattalionTypeId, int> extraTroopsByType) {
|
||||
private void MaybeActivateRow(EventBasedTable table, BattalionTypeId battalionTypeId) {
|
||||
var parent = table.gameObject.transform.parent;
|
||||
|
||||
var allowed = TypeIsAllowed(battalionTypeId) || extraTroopsByType[battalionTypeId] > 0;
|
||||
var allowed = TypeIsAllowed(battalionTypeId) ||
|
||||
extraTroops.Where(tfb => tfb.type == battalionTypeId)
|
||||
.Select(tfb => tfb.count)
|
||||
.Sum() > 0;
|
||||
|
||||
table.gameObject.GetComponent<OrganizeExtrasTable>().Set(
|
||||
allowed,
|
||||
@@ -251,12 +251,12 @@ namespace eagle {
|
||||
parent.GetComponentInChildren<RawImage>().color = color;
|
||||
}
|
||||
|
||||
private void MaybeActivateRows(Dictionary<BattalionTypeId, int> extraTroopsByType) {
|
||||
MaybeActivateRow(lightInfantryTable, BattalionTypeId.LightInfantry, extraTroopsByType);
|
||||
MaybeActivateRow(heavyInfantryTable, BattalionTypeId.HeavyInfantry, extraTroopsByType);
|
||||
MaybeActivateRow(longbowmenTable, BattalionTypeId.Longbowmen, extraTroopsByType);
|
||||
MaybeActivateRow(dragoonsTable, BattalionTypeId.LightCavalry, extraTroopsByType);
|
||||
MaybeActivateRow(knightsTable, BattalionTypeId.HeavyCavalry, extraTroopsByType);
|
||||
private void MaybeActivateRows() {
|
||||
MaybeActivateRow(lightInfantryTable, BattalionTypeId.LightInfantry);
|
||||
MaybeActivateRow(heavyInfantryTable, BattalionTypeId.HeavyInfantry);
|
||||
MaybeActivateRow(longbowmenTable, BattalionTypeId.Longbowmen);
|
||||
MaybeActivateRow(dragoonsTable, BattalionTypeId.LightCavalry);
|
||||
MaybeActivateRow(knightsTable, BattalionTypeId.HeavyCavalry);
|
||||
}
|
||||
|
||||
protected override void SetUpUI() {
|
||||
@@ -330,8 +330,7 @@ namespace eagle {
|
||||
} else
|
||||
return false;
|
||||
|
||||
// Note: We don't call Update() here - the caller (UpdateTable or MaxClickedImpl)
|
||||
// will call it when needed. This avoids redundant recalculations.
|
||||
eb.Update(existingBattalions);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -368,6 +367,7 @@ namespace eagle {
|
||||
}
|
||||
// Remove original troops
|
||||
else {
|
||||
var updated = eb.Update(existingBattalions);
|
||||
var availableToRemove = eb.Original.Size - eb.troopsRemoved;
|
||||
var newlyRemovedCount = Math.Min(KeyModifiedAmount.Amount(), availableToRemove);
|
||||
|
||||
@@ -464,8 +464,7 @@ namespace eagle {
|
||||
} else
|
||||
return false;
|
||||
|
||||
// Note: We don't call Update() here - the caller (UpdateTable or MaxClickedImpl)
|
||||
// will call it when needed. This avoids redundant recalculations.
|
||||
newB.Update(existingBattalions);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -654,16 +653,7 @@ namespace eagle {
|
||||
{ BattalionTypeId.Longbowmen, 0 }
|
||||
};
|
||||
|
||||
// Cache extra troop counts by type to avoid repeated LINQ queries
|
||||
var extraTroopsByType = new Dictionary<BattalionTypeId, int> {
|
||||
{ BattalionTypeId.LightInfantry, 0 },
|
||||
{ BattalionTypeId.HeavyInfantry, 0 },
|
||||
{ BattalionTypeId.LightCavalry, 0 },
|
||||
{ BattalionTypeId.HeavyCavalry, 0 },
|
||||
{ BattalionTypeId.Longbowmen, 0 }
|
||||
};
|
||||
foreach (var et in extraTroops) { extraTroopsByType[et.type] += et.count; }
|
||||
|
||||
maxAllButton.gameObject.SetActive(false);
|
||||
foreach (var eb in existingBattalions) {
|
||||
if (eb.dismissed) continue;
|
||||
|
||||
@@ -676,7 +666,9 @@ namespace eagle {
|
||||
newRow.MaxButtonClickedCallback = () => MaxClicked(eb);
|
||||
newRow.DismissButtonClickedCallback = () => DismissClicked(eb);
|
||||
|
||||
bool canAugment = TypeIsAllowed(eb.TypeId) || extraTroopsByType[eb.TypeId] > 0;
|
||||
bool canAugment =
|
||||
TypeIsAllowed(eb.TypeId) ||
|
||||
extraTroops.Where(tfb => tfb.type == eb.TypeId).Sum(tfb => tfb.count) > 0;
|
||||
newRow.CanAugment = canAugment;
|
||||
|
||||
if (eb.Count < eb.Capacity) {
|
||||
@@ -747,18 +739,15 @@ namespace eagle {
|
||||
if (!sufficient) { _disabledReason = "Not enough gold"; }
|
||||
|
||||
// Also check that something has changed
|
||||
// Check newBattalion fields directly instead of calling Update() which is expensive
|
||||
bool somethingChanged =
|
||||
(newBattalions.Exists(
|
||||
b => b.newBattalion.NewTroops > 0 ||
|
||||
b.newBattalion.TroopsFromOtherBattalion.Count > 0) ||
|
||||
(newBattalions.Exists(b => b.Update(existingBattalions).Size > 0) ||
|
||||
existingBattalions.Exists(eb => eb.changed != null || eb.troopsRemoved > 0));
|
||||
if (!somethingChanged) { _disabledReason = "No battalions have changed"; }
|
||||
|
||||
_enableCommit = sufficient && somethingChanged;
|
||||
resetAllButton.gameObject.SetActive(somethingChanged);
|
||||
|
||||
MaybeActivateRows(extraTroopsByType);
|
||||
MaybeActivateRows();
|
||||
}
|
||||
|
||||
public override AvailableCommand.SealedValueOneofCase CommandType =>
|
||||
|
||||
@@ -105,13 +105,9 @@ namespace eagle {
|
||||
_currentModel.ShardokGameModels
|
||||
.Select(sgm => {
|
||||
var needsResync = _shardokNeedsResync.GetValueOrDefault(sgm.Key, false);
|
||||
// Use thread-safe count from gRPC thread updates, fall back to model
|
||||
var count = _shardokResultCounts.GetValueOrDefault(
|
||||
sgm.Key,
|
||||
sgm.Value.History.Count());
|
||||
return new IClientConnectionSubscriber.ShardokViewStatus {
|
||||
shardokGameId = sgm.Key,
|
||||
filteredResultCount = needsResync ? 0 : count,
|
||||
filteredResultCount = needsResync ? 0 : sgm.Value.History.Count(),
|
||||
requestFullResync = needsResync
|
||||
};
|
||||
})
|
||||
@@ -146,11 +142,6 @@ namespace eagle {
|
||||
private readonly ConcurrentDictionary<string, bool> _shardokNeedsResync =
|
||||
new ConcurrentDictionary<string, bool>();
|
||||
|
||||
// Thread-safe Shardok result counts: updated from gRPC thread via UpdateResultCounts
|
||||
// Used by ShardokViewStatuses to report accurate counts even when MainQueue is blocked
|
||||
private readonly ConcurrentDictionary<string, int> _shardokResultCounts =
|
||||
new ConcurrentDictionary<string, int>();
|
||||
|
||||
private readonly RollFetcher _rollFetcher;
|
||||
|
||||
// State synced with server
|
||||
@@ -254,9 +245,9 @@ namespace eagle {
|
||||
|
||||
if (battleView == null) {
|
||||
// Battle was removed (e.g., it ended) before we could create the model.
|
||||
// This is expected when Eagle's RemovedBattleIds update arrives before a
|
||||
// pending Shardok update - the UI already shows "Back to Eagle" via
|
||||
// MarkBattleEnded(), so we just skip this stale update.
|
||||
// This can happen due to race conditions between Eagle and Shardok updates.
|
||||
Debug.LogWarning(
|
||||
$"Cannot create ShardokGameModel for {shardokGameId}: battle not found in ShardokBattles (likely already ended)");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -340,7 +331,6 @@ namespace eagle {
|
||||
// any stale reference and skip this update
|
||||
if (shardokGameModel == null) {
|
||||
_currentModel.ShardokGameModels.Remove(oneResponse.ShardokGameId);
|
||||
_shardokResultCounts.TryRemove(oneResponse.ShardokGameId, out _);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -369,7 +359,6 @@ namespace eagle {
|
||||
} else {
|
||||
// Game ended - remove from active models so UI knows battle is over
|
||||
_currentModel.ShardokGameModels.Remove(oneResponse.ShardokGameId);
|
||||
_shardokResultCounts.TryRemove(oneResponse.ShardokGameId, out _);
|
||||
}
|
||||
}
|
||||
if (UpdateAction != null) UpdateAction.Invoke(_currentModel);
|
||||
@@ -404,12 +393,10 @@ namespace eagle {
|
||||
}
|
||||
break;
|
||||
|
||||
case GameUpdate.GameUpdateDetailsOneofCase.ShardokActionResultResponse:
|
||||
foreach (var response in update.ShardokActionResultResponse
|
||||
.ShardokGameResponses) {
|
||||
_shardokResultCounts[response.ShardokGameId] = response.NewResultViewCount;
|
||||
}
|
||||
break;
|
||||
// Note: Shardok counts are tracked in ShardokGameModel.History.Count
|
||||
// which is updated in ReceiveGameUpdate on the main thread.
|
||||
// For now, Shardok reconnects may still get duplicate data, but
|
||||
// the primary issue (Eagle duplicates) is fixed here.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,15 +762,6 @@ namespace eagle {
|
||||
foreach (ShardokBattleView bv in entry.NewBattles) _currentModel.ShardokBattles.Add(bv);
|
||||
|
||||
foreach (string rb in entry.RemovedBattleIds) {
|
||||
// If there's an active ShardokGameModel for this battle, mark it as ended
|
||||
// so the UI knows to return to Eagle. This handles the race condition where
|
||||
// the Eagle update removing the battle arrives before the Shardok Victory update.
|
||||
if (_currentModel.ShardokGameModels.TryGetValue(rb, out var sgm)) {
|
||||
sgm.MarkBattleEnded("Battle has ended.");
|
||||
_currentModel.ShardokGameModels.Remove(rb);
|
||||
}
|
||||
_shardokResultCounts.TryRemove(rb, out _);
|
||||
|
||||
for (int i = 0; i < _currentModel.ShardokBattles.Count; i++) {
|
||||
if (_currentModel.ShardokBattles[i].ShardokGameId == rb) {
|
||||
_currentModel.ShardokBattles.RemoveAt(i);
|
||||
|
||||
+3
-11
@@ -18,11 +18,10 @@ namespace eagle {
|
||||
if (scrollRect) { scrollRect.normalizedPosition = new Vector2(0, 1); }
|
||||
}
|
||||
|
||||
// Always update the view when TextId changes to clear any stale text
|
||||
if (!String.IsNullOrEmpty(_textId)) {
|
||||
// If the text ID is set, we want to update the view immediately
|
||||
// to reflect any existing text.
|
||||
OnTextUpdate(ClientTextProvider.Provider.GetTextEntry(TextId));
|
||||
} else {
|
||||
UpdateView();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,14 +59,7 @@ namespace eagle {
|
||||
}
|
||||
|
||||
private void OnTextUpdate(TextEntry entry) {
|
||||
if (entry != null) {
|
||||
OnTextUpdate(entry.Text, entry.Completed);
|
||||
} else {
|
||||
// Entry doesn't exist yet - clear text and update view to avoid stale content
|
||||
_currentText = "";
|
||||
_currentCompleted = false;
|
||||
UpdateView();
|
||||
}
|
||||
if (entry != null) OnTextUpdate(entry.Text, entry.Completed);
|
||||
}
|
||||
|
||||
public void OnTextUpdate(string text, bool completed) {
|
||||
|
||||
@@ -17,10 +17,6 @@ namespace eagle {
|
||||
|
||||
private readonly Queue<Notification> _notes = new();
|
||||
|
||||
// Incremented when DismissAll is clicked; pending AddNote calls check this
|
||||
// to skip adding if a dismiss happened since they were enqueued
|
||||
private int _dismissGeneration = 0;
|
||||
|
||||
private void SetPopupInfos() {
|
||||
PopupInfos = _notes.Select(note => new PopupInfo {
|
||||
titleText = note.Title,
|
||||
@@ -58,13 +54,7 @@ namespace eagle {
|
||||
string llmId,
|
||||
List<ProvinceId> provinceIds,
|
||||
List<HeroView> displayedHeroes) {
|
||||
// Capture current generation - if DismissAll is clicked before this executes,
|
||||
// we'll skip adding the note
|
||||
var capturedGeneration = _dismissGeneration;
|
||||
MainQueue.Q.Enqueue(() => {
|
||||
// Skip if DismissAll was clicked since this was enqueued
|
||||
if (capturedGeneration != _dismissGeneration) return;
|
||||
|
||||
var existingNote = _notes.FirstOrDefault(
|
||||
n => n.Title == title &&
|
||||
HeroListsMatch(n.DisplayedHeroes, displayedHeroes));
|
||||
@@ -89,8 +79,6 @@ namespace eagle {
|
||||
}
|
||||
|
||||
public void DismissAllClicked() {
|
||||
// Increment generation immediately so pending AddNote calls will skip
|
||||
_dismissGeneration++;
|
||||
_notes.Clear();
|
||||
SetPopupInfos();
|
||||
}
|
||||
|
||||
+6
-4
@@ -226,10 +226,12 @@ namespace eagle {
|
||||
$"[SUBSCRIBE] Subscription confirmed for game {gameId}, " +
|
||||
$"confirmedResultCount={ack.ConfirmedResultCount}");
|
||||
|
||||
// Note: Shardok resync flags are cleared in EagleGameModel.HandleOneGameUpdate
|
||||
// AFTER updates are actually received, not here. This ensures that if the
|
||||
// connection drops between acknowledgment and update delivery, the resync
|
||||
// will be requested again on the next reconnect.
|
||||
// Clear resync flags ONLY after successful acknowledgment
|
||||
if (subscriber is GameModelUpdater updater) {
|
||||
foreach (var status in shardokStatuses.Where(s => s.requestFullResync)) {
|
||||
updater.ClearShardokResyncFlag(status.shardokGameId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
public class MainQueue : MonoBehaviour {
|
||||
static MainQueue __singletonInstance;
|
||||
private readonly Queue<Action> _actionQueue = new();
|
||||
private readonly Queue<Action> _nextUpdateQueue = new();
|
||||
|
||||
// Time budget per frame to prevent blocking when resuming from background
|
||||
// 8ms leaves room for rendering within a 16ms (60fps) frame budget
|
||||
private const long MaxMillisecondsPerFrame = 8;
|
||||
// Limit actions per frame to prevent blocking when resuming from background
|
||||
private const int MaxActionsPerFrame = 10;
|
||||
|
||||
// Track queue depth for logging
|
||||
private int _lastLoggedQueueDepth = 0;
|
||||
@@ -24,29 +21,19 @@ public class MainQueue : MonoBehaviour {
|
||||
|
||||
// Update is called once per frame
|
||||
void Update() {
|
||||
int actionsProcessed = 0;
|
||||
int queueDepthBefore;
|
||||
|
||||
lock (_actionQueue) { queueDepthBefore = _actionQueue.Count; }
|
||||
|
||||
// Fast path: skip processing if queue is empty
|
||||
if (queueDepthBefore == 0) {
|
||||
lock (_nextUpdateQueue) {
|
||||
if (_nextUpdateQueue.Count > 0) {
|
||||
foreach (Action action in _nextUpdateQueue) { Enqueue(action); }
|
||||
_nextUpdateQueue.Clear();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Log when queue has built up (e.g., after resuming from background)
|
||||
if (queueDepthBefore > 100 && queueDepthBefore != _lastLoggedQueueDepth) {
|
||||
if (queueDepthBefore > MaxActionsPerFrame && queueDepthBefore != _lastLoggedQueueDepth) {
|
||||
Debug.Log($"[MainQueue] Processing backlog: {queueDepthBefore} actions queued");
|
||||
_lastLoggedQueueDepth = queueDepthBefore;
|
||||
} else if (queueDepthBefore <= 100) {
|
||||
} else if (queueDepthBefore <= MaxActionsPerFrame) {
|
||||
_lastLoggedQueueDepth = 0;
|
||||
}
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
Action possibleAction;
|
||||
do {
|
||||
possibleAction = null;
|
||||
@@ -54,8 +41,11 @@ public class MainQueue : MonoBehaviour {
|
||||
if (_actionQueue.Count > 0) { possibleAction = _actionQueue.Dequeue(); }
|
||||
}
|
||||
|
||||
if (possibleAction != null) { possibleAction.Invoke(); }
|
||||
} while (possibleAction != null && stopwatch.ElapsedMilliseconds < MaxMillisecondsPerFrame);
|
||||
if (possibleAction != null) {
|
||||
possibleAction.Invoke();
|
||||
actionsProcessed++;
|
||||
}
|
||||
} while (possibleAction != null && actionsProcessed < MaxActionsPerFrame);
|
||||
|
||||
lock (_nextUpdateQueue) {
|
||||
foreach (Action action in _nextUpdateQueue) { Enqueue(action); }
|
||||
|
||||
@@ -22,9 +22,6 @@ public class HexMesh : MonoBehaviour {
|
||||
}
|
||||
|
||||
public void Triangulate(IEnumerable<HexCell> cells) {
|
||||
// Guard against Update() being called before SetUp() initializes hexMesh
|
||||
if (hexMesh == null) return;
|
||||
|
||||
hexMesh.Clear();
|
||||
vertices.Clear();
|
||||
triangles.Clear();
|
||||
|
||||
+1
-1
@@ -961,7 +961,7 @@ namespace Shardok {
|
||||
commandTypeUIManager.CommandGroupForType(command.Type));
|
||||
}
|
||||
|
||||
if (allCommands.Any() && mapMouseCoords != null) {
|
||||
if (allCommands.Any()) {
|
||||
var meleeCommands = allCommands.Where(
|
||||
command => CommandTypeUIManager.MeleeAttackGroup ==
|
||||
commandTypeUIManager.CommandGroupForType(command.Type));
|
||||
|
||||
@@ -111,17 +111,6 @@ public class ShardokGameModel {
|
||||
|
||||
public bool InSetUp => GameStatus != null && GameStatus.State == GameStatus.Types.State.SetUp;
|
||||
|
||||
/// <summary>
|
||||
/// Mark this battle as ended (called when battle is removed from Eagle before
|
||||
/// we receive the final Shardok update, e.g., due to race conditions when
|
||||
/// Unity was backgrounded).
|
||||
/// </summary>
|
||||
public void MarkBattleEnded(string reason) {
|
||||
GameStatus =
|
||||
new GameStatus { State = GameStatus.Types.State.Victory, Description = reason };
|
||||
UpdateAction?.Invoke();
|
||||
}
|
||||
|
||||
private readonly PersistentClientConnection _persistentClientConnection;
|
||||
|
||||
private const int StartingHistoryCapacity = 100;
|
||||
|
||||
+9
-36
File diff suppressed because one or more lines are too long
@@ -23,7 +23,6 @@ message IncompleteText {
|
||||
string partial_text = 2;
|
||||
.net.eagle0.eagle.internal.GeneratedTextRequest llm_request = 3;
|
||||
int32 requested_after_history_count = 4;
|
||||
int64 requested_at_millis = 5;
|
||||
}
|
||||
|
||||
message UnrequestedText {
|
||||
|
||||
@@ -21,8 +21,7 @@ case class IncompleteClientText(
|
||||
id: ClientTextId,
|
||||
partialText: String,
|
||||
requestedAfterHistoryCount: Int,
|
||||
llmRequest: GeneratedTextRequest,
|
||||
requestedAtMillis: Long
|
||||
llmRequest: GeneratedTextRequest
|
||||
) extends ClientText {
|
||||
def append(newText: String): IncompleteClientText =
|
||||
copy(partialText = partialText + newText)
|
||||
|
||||
@@ -17,15 +17,6 @@ trait ClientTextStore {
|
||||
def unrequestedTexts: Map[ClientTextId, UnrequestedClientText]
|
||||
def accessibleTo: Map[ClientTextId, Vector[FactionId]]
|
||||
|
||||
/** Returns incomplete texts that have been waiting longer than the threshold */
|
||||
def stalledIncompleteTexts(
|
||||
thresholdMillis: Long,
|
||||
currentTimeMillis: Long = System.currentTimeMillis()
|
||||
): Vector[IncompleteClientText] =
|
||||
incompleteTexts.values
|
||||
.filter(ict => currentTimeMillis - ict.requestedAtMillis > thresholdMillis)
|
||||
.toVector
|
||||
|
||||
def saved: ClientTextStore
|
||||
|
||||
def withAddedTextRequest(
|
||||
|
||||
@@ -69,8 +69,7 @@ case class ClientTextStoreImpl(
|
||||
id = id,
|
||||
partialText = "",
|
||||
llmRequest = unrequested.llmRequest,
|
||||
requestedAfterHistoryCount = unrequested.requestedAfterHistoryCount,
|
||||
requestedAtMillis = System.currentTimeMillis()
|
||||
requestedAfterHistoryCount = unrequested.requestedAfterHistoryCount
|
||||
)),
|
||||
unrequestedTexts = unrequestedTexts - id,
|
||||
incompleteTextsAreSaved = false
|
||||
@@ -239,8 +238,7 @@ object ClientTextStoreImpl {
|
||||
id = ict.id,
|
||||
partialText = ict.text,
|
||||
llmRequest = Some(ict.llmRequest),
|
||||
requestedAfterHistoryCount = ict.requestedAfterHistoryCount,
|
||||
requestedAtMillis = ict.requestedAtMillis
|
||||
requestedAfterHistoryCount = ict.requestedAfterHistoryCount
|
||||
)
|
||||
}.toVector,
|
||||
unrequestedTexts = completeSaved.unrequestedTexts.map {
|
||||
@@ -332,12 +330,7 @@ object ClientTextStoreImpl {
|
||||
id = it.id,
|
||||
partialText = it.partialText,
|
||||
llmRequest = it.llmRequest.get,
|
||||
requestedAfterHistoryCount = it.requestedAfterHistoryCount,
|
||||
// Use persisted timestamp if available, otherwise use current time
|
||||
// (for backwards compatibility with old persisted data)
|
||||
requestedAtMillis =
|
||||
if it.requestedAtMillis > 0 then it.requestedAtMillis
|
||||
else System.currentTimeMillis()
|
||||
requestedAfterHistoryCount = it.requestedAfterHistoryCount
|
||||
)
|
||||
}.toVector,
|
||||
icts.unrequestedTexts.map { it =>
|
||||
|
||||
@@ -48,9 +48,7 @@ scala_library(
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:action_result_view_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/availability",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:check_for_fulfilled_quests_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:hero_backstory_update_action_generator",
|
||||
@@ -59,19 +57,18 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_proto_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/validations:runtime_validator",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/validations:scala_runtime_validator",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types/base:action_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
@@ -152,16 +149,11 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/api:action_result_view_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/api:command_descriptor_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/shardok/storage:action_result_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
@@ -211,6 +203,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:deterministic_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:vigor_xp_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator",
|
||||
@@ -225,8 +218,11 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type_id",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
|
||||
@@ -7,12 +7,7 @@ import net.eagle0.eagle.{FactionId, GameId, ProvinceId}
|
||||
import net.eagle0.eagle.api.command.AvailableCommands
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.library.actions.applier.{
|
||||
ActionResultApplierImpl,
|
||||
ActionResultProtoApplier,
|
||||
ActionResultProtoApplierImpl,
|
||||
ActionResultWithResultingState
|
||||
}
|
||||
import net.eagle0.eagle.library.actions.applier.{ActionResultProtoApplier, ActionResultProtoApplierImpl}
|
||||
import net.eagle0.eagle.library.actions.availability.AvailableCommandsFactory
|
||||
import net.eagle0.eagle.library.actions.impl.action.{
|
||||
CheckForFulfilledQuestsAction,
|
||||
@@ -20,12 +15,11 @@ import net.eagle0.eagle.library.actions.impl.action.{
|
||||
ResolveBattleAction
|
||||
}
|
||||
import net.eagle0.eagle.library.actions.impl.command.{AvailableCommandTypeMap, CommandFactory}
|
||||
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ActionWithResultingState, RandomStateProtoSequencer}
|
||||
import net.eagle0.eagle.library.util.hero_generator.HeroGenerator
|
||||
import net.eagle0.eagle.library.util.validations.{RuntimeValidator, ScalaRuntimeValidator}
|
||||
import net.eagle0.eagle.library.util.validations.RuntimeValidator
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
import net.eagle0.eagle.library.EngineImpl.{appliedResults, appliedResultsScala, withUpdateChecks}
|
||||
import net.eagle0.eagle.library.EngineImpl.{appliedResults, withUpdateChecks}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionTypeConverter
|
||||
@@ -49,10 +43,10 @@ object EngineImpl {
|
||||
private def withPhaseAdvancement(
|
||||
engineAndResultsImpl: EngineAndResultsImpl
|
||||
): EngineAndResultsImpl =
|
||||
engineAndResultsImpl.recursiveTransformScala(eng =>
|
||||
engineAndResultsImpl.recursiveTransform(eng =>
|
||||
RoundPhaseAdvancer.checkForPhaseAdvancement(
|
||||
currentState = eng.currentState,
|
||||
actionResultApplier = ActionResultApplierImpl(Some(ScalaRuntimeValidator)),
|
||||
currentState = GameStateConverter.toProto(eng.currentState),
|
||||
actionResultProtoApplier = eng.actionResultProtoApplier,
|
||||
history = eng.history,
|
||||
availableCommandsFactory = eng.availableCommandsFactory,
|
||||
heroGenerator = eng.heroGenerator,
|
||||
@@ -103,31 +97,14 @@ object EngineImpl {
|
||||
results = results.map(_.actionResult)
|
||||
)
|
||||
)
|
||||
|
||||
def appliedResultsScala(
|
||||
engine: EngineImpl,
|
||||
results: Vector[ActionResultWithResultingState]
|
||||
): EngineAndResults = withUpdateChecks(
|
||||
EngineAndResultsImpl(
|
||||
engine = engine.copy(
|
||||
currentState = results.lastOption
|
||||
.map(_.resultingState)
|
||||
.getOrElse(engine.currentState),
|
||||
history = engine.history.withNewResultsScala(results)
|
||||
),
|
||||
results = results.map(awrs =>
|
||||
net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter.toProto(awrs.actionResult)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
final case class EngineAndResultsImpl(
|
||||
engine: EngineImpl,
|
||||
results: Vector[ActionResult]
|
||||
) extends EngineAndResults {
|
||||
def recursiveTransformScala(
|
||||
f: EngineImpl => Vector[ActionResultWithResultingState]
|
||||
def recursiveTransform(
|
||||
f: EngineImpl => Vector[ActionWithResultingState]
|
||||
): EngineAndResultsImpl = {
|
||||
@tailrec
|
||||
def go(
|
||||
@@ -138,7 +115,7 @@ final case class EngineAndResultsImpl(
|
||||
|
||||
if goResults.isEmpty then EngineAndResultsImpl(eng, acc)
|
||||
else
|
||||
appliedResultsScala(eng, goResults) match {
|
||||
appliedResults(eng, goResults) match {
|
||||
case EngineAndResultsImpl(eng2, res) =>
|
||||
go(eng2, acc ++ res)
|
||||
}
|
||||
@@ -149,13 +126,13 @@ final case class EngineAndResultsImpl(
|
||||
|
||||
def recursiveTransformT(
|
||||
f: EngineImpl => Vector[ActionResultT]
|
||||
): EngineAndResultsImpl = recursiveTransformScala { eng =>
|
||||
val actionResultApplier = ActionResultApplierImpl(Some(ScalaRuntimeValidator))
|
||||
RandomStateSequencer(
|
||||
): EngineAndResultsImpl = recursiveTransform { eng =>
|
||||
val results = f(eng)
|
||||
RandomStateProtoSequencer(
|
||||
initialState = eng.currentState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
actionResultProtoApplier = eng.actionResultProtoApplier,
|
||||
functionalRandom = SeededRandom(eng.currentState.randomSeed)
|
||||
).withActionResults(_ => f(eng)).resultsWithStates.newValue
|
||||
).withActionResultTs(_ => results).results.newValue
|
||||
}
|
||||
|
||||
def saveNow: EngineAndResultsImpl =
|
||||
@@ -305,34 +282,37 @@ case class EngineImpl(
|
||||
)
|
||||
val availableCommand = availableCommandOpt.get
|
||||
|
||||
val sequencer = RandomStateSequencer(
|
||||
val sequencer = RandomStateProtoSequencer(
|
||||
initialState = this.currentState,
|
||||
actionResultApplier = ActionResultApplierImpl(Some(ScalaRuntimeValidator)),
|
||||
actionResultProtoApplier = actionResultProtoApplier,
|
||||
functionalRandom = SeededRandom(this.currentState.randomSeed)
|
||||
).withTCommand { gs =>
|
||||
commandFactory.makeTCommand(
|
||||
actingFactionId = factionId,
|
||||
gameState = gs,
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = selectedCommand
|
||||
)
|
||||
}.withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator(gs))
|
||||
).withActionResults { gs =>
|
||||
val results = commandFactory
|
||||
.makeCommand(
|
||||
actingFactionId = factionId,
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = selectedCommand
|
||||
)
|
||||
.execute(actionResultProtoApplier)
|
||||
.map(_.actionResult)
|
||||
|
||||
// Validate that the first result has an acting faction set (required for player commands)
|
||||
val firstResult = sequencer.actionResults.newValue.headOption
|
||||
if !firstResult.forall(_.actingFactionId.isDefined) then {
|
||||
print(
|
||||
"Result with type " + firstResult.map(_.actionResultType).getOrElse("unknown") + " did not have a player set"
|
||||
if !results.headOption.forall(_.player.isDefined) then {
|
||||
print(
|
||||
"Result with type " + results.head.`type` + " did not have a player set"
|
||||
)
|
||||
}
|
||||
internalRequire(
|
||||
results.headOption.forall(_.player.isDefined),
|
||||
s"Result with type ${results.head.`type`} did not have a player set"
|
||||
)
|
||||
}
|
||||
internalRequire(
|
||||
firstResult.forall(_.actingFactionId.isDefined),
|
||||
s"Result with type ${firstResult.map(_.actionResultType).getOrElse("unknown")} did not have a player set"
|
||||
)
|
||||
|
||||
appliedResultsScala(
|
||||
results
|
||||
}.withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator.fromGameState(gs))
|
||||
|
||||
appliedResults(
|
||||
engine = this,
|
||||
results = sequencer.resultsWithStates.newValue
|
||||
results = sequencer.results.newValue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package net.eagle0.eagle.library
|
||||
|
||||
import net.eagle0.eagle.{FactionId, RoundId, ShardokGameId}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultWithResultingState
|
||||
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.shardok.api.action_result_view.ActionResultView as ShardokActionResultView
|
||||
@@ -40,14 +37,6 @@ trait GameHistory {
|
||||
|
||||
def withNewResults(newResults: Vector[ActionWithResultingState]): GameHistory
|
||||
|
||||
def withNewResultsScala(newResults: Vector[ActionResultWithResultingState]): GameHistory =
|
||||
withNewResults(newResults.map { awrs =>
|
||||
ActionWithResultingState(
|
||||
actionResult = ActionResultProtoConverter.toProto(awrs.actionResult),
|
||||
gameState = GameStateConverter.toProto(awrs.resultingState)
|
||||
)
|
||||
})
|
||||
|
||||
def shardokCount(shardokGameId: ShardokGameId): Int
|
||||
|
||||
def shardokGameState(
|
||||
|
||||
@@ -3,34 +3,38 @@ package net.eagle0.eagle.library
|
||||
import scala.collection.mutable
|
||||
|
||||
import net.eagle0.common.SeededRandom
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.{
|
||||
ActionResultApplier,
|
||||
ActionResultTApplierImpl,
|
||||
ActionResultWithResultingState
|
||||
}
|
||||
import net.eagle0.eagle.common.round_phase.RoundPhase
|
||||
import net.eagle0.eagle.common.round_phase.RoundPhase.*
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.actions.applier.{ActionResultProtoApplier, ActionResultTApplierImpl}
|
||||
import net.eagle0.eagle.library.actions.availability.AvailableCommandsFactory
|
||||
import net.eagle0.eagle.library.actions.impl.action.*
|
||||
import net.eagle0.eagle.library.actions.impl.command.CommandFactory
|
||||
import net.eagle0.eagle.library.actions.impl.common.VigorXPApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
|
||||
import net.eagle0.eagle.library.util.hero_generator.HeroGenerator
|
||||
import net.eagle0.eagle.library.util.validations.ScalaRuntimeValidator
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalValidated
|
||||
import net.eagle0.eagle.model.proto_converters.date.DateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import net.eagle0.eagle.model.state.RoundPhase.*
|
||||
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
|
||||
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
|
||||
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionConverter
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionTypeConverter
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.RoundId
|
||||
|
||||
object RoundPhaseAdvancer {
|
||||
private val times: mutable.Map[RoundPhase, Long] =
|
||||
mutable.Map(RoundPhase.allValues.map(_ -> 0L)*)
|
||||
mutable.Map(RoundPhase.values.map(_ -> 0L)*)
|
||||
private val print = false
|
||||
private val roundsBetweenPrint = 100
|
||||
|
||||
private def printTimings(roundId: RoundId): Unit = {
|
||||
val totalTime = times.values.sum.toDouble / 1000.0
|
||||
|
||||
times.toVector.sortBy(_._2)(using Ordering.Long.reverse).foreach {
|
||||
times.toVector.sortBy(_._2)(Ordering.Long.reverse).foreach {
|
||||
case (phase, time) =>
|
||||
val timeInSecs = time.toDouble / 1000.0
|
||||
val msPerRound = time.toDouble / roundId.toDouble
|
||||
@@ -44,341 +48,332 @@ object RoundPhaseAdvancer {
|
||||
|
||||
def checkForPhaseAdvancement(
|
||||
currentState: GameState,
|
||||
actionResultApplier: ActionResultApplier,
|
||||
actionResultProtoApplier: ActionResultProtoApplier,
|
||||
history: GameHistory,
|
||||
availableCommandsFactory: AvailableCommandsFactory,
|
||||
heroGenerator: HeroGenerator,
|
||||
commandFactory: CommandFactory
|
||||
): Vector[ActionResultWithResultingState] = {
|
||||
// Lazy conversion to proto for AvailableCommandsFactory calls
|
||||
lazy val currentStateProto: GameStateProto = GameStateConverter.toProto(currentState)
|
||||
|
||||
// ActionResultTApplier for actions that need internal state tracking
|
||||
val actionResultTApplier = ActionResultTApplierImpl(actionResultApplier)
|
||||
|
||||
): Vector[ActionWithResultingState] = {
|
||||
val currentPhase = currentState.currentPhase
|
||||
val startTime = System.currentTimeMillis
|
||||
|
||||
if print && currentPhase == NewRound && currentState.currentRoundId % roundsBetweenPrint == 0
|
||||
if print && currentPhase == NEW_ROUND && currentState.currentRoundId % roundsBetweenPrint == 0
|
||||
then {
|
||||
printTimings(currentState.currentRoundId)
|
||||
}
|
||||
|
||||
val results: Vector[ActionResultWithResultingState] = currentPhase match {
|
||||
case NewRound =>
|
||||
val actionResults = NewRoundAction(currentState, history, actionResultApplier)
|
||||
.randomResults(SeededRandom(currentState.randomSeed))
|
||||
.newValue
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
actionResultApplier.applyActionResults(currentState, actionResults)
|
||||
|
||||
case PrisonerExchange =>
|
||||
actionResultApplier.applyActionResults(
|
||||
currentState,
|
||||
PrisonerExchangeAction(currentState).results
|
||||
val results: Vector[ActionWithResultingState] = currentPhase match {
|
||||
case UNKNOWN_PHASE =>
|
||||
throw new IllegalStateException(
|
||||
"Somehow we're in game state UNKNOWN_PHASE"
|
||||
)
|
||||
|
||||
case ProvinceEvents =>
|
||||
actionResultApplier.applyActionResults(
|
||||
case NEW_ROUND =>
|
||||
NewRoundAction(GameStateConverter.fromProto(currentState), history).execute(actionResultProtoApplier)
|
||||
|
||||
case PRISONER_EXCHANGE =>
|
||||
actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
PerformProvinceEventsAction(currentState)
|
||||
.results(SeededRandom(currentState.randomSeed))
|
||||
PrisonerExchangeAction(GameStateConverter.fromProto(currentState)).results
|
||||
.map(ActionResultProtoConverter.toProto)
|
||||
)
|
||||
|
||||
case ForcedTurnBack =>
|
||||
actionResultApplier.applyActionResults(
|
||||
case PROVINCE_EVENTS =>
|
||||
PerformProvinceEventsAction(
|
||||
GameStateConverter.fromProto(currentState)
|
||||
).execute(currentState, actionResultProtoApplier)
|
||||
|
||||
case FORCED_TURN_BACK =>
|
||||
actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
PerformForcedTurnBackAction(currentState).results
|
||||
PerformForcedTurnBackAction(GameStateConverter.fromProto(currentState)).results
|
||||
.map(ActionResultProtoConverter.toProto)
|
||||
)
|
||||
|
||||
case ProvinceMoveResolution =>
|
||||
val actionResults = PerformProvinceMoveResolutionAction(currentState)
|
||||
.results(SeededRandom(currentState.randomSeed), actionResultTApplier)
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
actionResultApplier.applyActionResults(currentState, actionResults)
|
||||
case PROVINCE_MOVE_RESOLUTION =>
|
||||
PerformProvinceMoveResolutionAction(
|
||||
GameStateConverter.fromProto(currentState)
|
||||
).execute(actionResultProtoApplier)
|
||||
|
||||
case HandleRiot =>
|
||||
case HANDLE_RIOT =>
|
||||
if availableCommandsFactory
|
||||
.hasAvailableHandleRiotPhaseCommands(currentStateProto)
|
||||
.hasAvailableHandleRiotPhaseCommands(currentState)
|
||||
then Vector.empty
|
||||
else
|
||||
val actionResults = EndHandleRiotsPhaseAction(
|
||||
gameState = currentState,
|
||||
EndHandleRiotsPhaseAction(
|
||||
gameState = GameStateConverter.fromProto(currentState),
|
||||
commandsForProvince = pid =>
|
||||
availableCommandsFactory
|
||||
.handleRiotPhaseCommandsForOneProvince(
|
||||
currentStateProto,
|
||||
currentStateProto.provinces(pid)
|
||||
currentState,
|
||||
currentState.provinces(pid)
|
||||
),
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).results(SeededRandom(currentState.randomSeed))
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
actionResultApplier.applyActionResults(currentState, actionResults)
|
||||
applier = ActionResultTApplierImpl(ScalaRuntimeValidator)
|
||||
).execute(currentState, actionResultProtoApplier)
|
||||
|
||||
case HeroDepartures =>
|
||||
val actionResults = PerformHeroDeparturesAction(gameState = currentState)
|
||||
.results(SeededRandom(currentState.randomSeed))
|
||||
actionResultApplier.applyActionResults(currentState, actionResults)
|
||||
case HERO_DEPARTURES =>
|
||||
PerformHeroDeparturesAction(
|
||||
gameState = GameStateConverter.fromProto(currentState)
|
||||
).execute(currentState, actionResultProtoApplier)
|
||||
|
||||
case UnaffiliatedHeroActions =>
|
||||
val actionResults = PerformUnaffiliatedHeroesAction(
|
||||
gameState = currentState,
|
||||
heroGenerator = heroGenerator,
|
||||
actionResultApplier = actionResultApplier
|
||||
).results(SeededRandom(currentState.randomSeed))
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
actionResultApplier.applyActionResults(currentState, actionResults)
|
||||
case UNAFFILIATED_HERO_ACTIONS =>
|
||||
PerformUnaffiliatedHeroesAction(
|
||||
gameState = GameStateConverter.fromProto(currentState),
|
||||
heroGenerator = heroGenerator
|
||||
).execute(actionResultProtoApplier)
|
||||
|
||||
case PleaseRecruitMe =>
|
||||
case PLEASE_RECRUIT_ME =>
|
||||
if availableCommandsFactory
|
||||
.hasAvailablePleaseRecruitMePhaseCommands(currentStateProto)
|
||||
.hasAvailablePleaseRecruitMePhaseCommands(currentState)
|
||||
then Vector.empty
|
||||
else
|
||||
Vector(
|
||||
actionResultApplier.applyActionResult(
|
||||
actionResultProtoApplier.applyActionResult(
|
||||
currentState,
|
||||
EndPleaseRecruitMePhaseAction(currentState).immediateExecute
|
||||
ActionResultProtoConverter.toProto(
|
||||
EndPleaseRecruitMePhaseAction(GameStateConverter.fromProto(currentState)).immediateExecute
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
case VassalCommands =>
|
||||
case VASSAL_COMMANDS =>
|
||||
val vassalCommandResults = PerformVassalCommandsPhaseAction(
|
||||
gameState = currentState,
|
||||
gameState = GameStateConverter.fromProto(currentState),
|
||||
commandsForProvince = availableCommandsFactory
|
||||
.commandPhaseCommandsForProvince(currentStateProto, _),
|
||||
.commandPhaseCommandsForProvince(currentState, _),
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).results(SeededRandom(currentState.randomSeed))
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
val appliedResults = actionResultApplier.applyActionResults(currentState, vassalCommandResults)
|
||||
applier = ActionResultTApplierImpl(ScalaRuntimeValidator)
|
||||
).execute(currentState, actionResultProtoApplier)
|
||||
|
||||
if appliedResults.nonEmpty then appliedResults
|
||||
if vassalCommandResults.nonEmpty then vassalCommandResults
|
||||
else
|
||||
val endResults = EndVassalCommandsPhaseAction(currentState, actionResultApplier)
|
||||
.results(SeededRandom(currentState.randomSeed))
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
actionResultApplier.applyActionResults(currentState, endResults)
|
||||
EndVassalCommandsPhaseAction(GameStateConverter.fromProto(currentState)).execute(
|
||||
actionResultProtoApplier
|
||||
)
|
||||
|
||||
case PlayerCommands =>
|
||||
case PLAYER_COMMANDS =>
|
||||
if availableCommandsFactory
|
||||
.hasAvailablePlayerCommandsPhaseCommands(
|
||||
currentStateProto
|
||||
currentState
|
||||
)
|
||||
then Vector.empty
|
||||
else
|
||||
actionResultApplier.applyActionResults(
|
||||
actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
EndPlayerCommandsPhaseAction(
|
||||
currentState,
|
||||
actionResultApplier
|
||||
).randomResults(functionalRandom = SeededRandom(currentState.randomSeed)).newValue
|
||||
GameStateConverter.fromProto(currentState),
|
||||
ActionResultTApplierImpl(ScalaRuntimeValidator)
|
||||
).randomResults(functionalRandom = SeededRandom(currentState.randomSeed))
|
||||
.newValue
|
||||
.map(
|
||||
ActionResultProtoConverter.toProto(_)
|
||||
)
|
||||
)
|
||||
|
||||
case HostileArmySetup =>
|
||||
case HOSTILE_ARMY_SETUP =>
|
||||
Vector(
|
||||
actionResultApplier.applyActionResult(
|
||||
actionResultProtoApplier.applyActionResult(
|
||||
currentState,
|
||||
PerformHostileArmySetupAction(currentState).immediateExecute
|
||||
ActionResultProtoConverter.toProto(
|
||||
PerformHostileArmySetupAction(
|
||||
GameStateConverter.fromProto(currentState)
|
||||
).immediateExecute
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
case FreeForAllDecision =>
|
||||
case FREE_FOR_ALL_DECISION =>
|
||||
if availableCommandsFactory
|
||||
.hasAvailableFreeForAllDecisionPhaseCommands(
|
||||
currentStateProto
|
||||
currentState
|
||||
)
|
||||
then Vector.empty
|
||||
else
|
||||
// There may eventually be VassalAttackDecisions, but for now we're leaving that on the player
|
||||
actionResultApplier.applyActionResults(
|
||||
actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
EndFreeForAllDecisionPhaseAction(currentState).results
|
||||
EndFreeForAllDecisionPhaseAction(GameStateConverter.fromProto(currentState)).results.map(
|
||||
ActionResultProtoConverter.toProto(_)
|
||||
)
|
||||
)
|
||||
|
||||
case FreeForAllBattleRequest =>
|
||||
val requestResults = actionResultApplier.applyActionResults(
|
||||
case FREE_FOR_ALL_BATTLE_REQUEST =>
|
||||
val requestResults = actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
RequestFreeForAllBattlesAction(currentState).results
|
||||
RequestFreeForAllBattlesAction(
|
||||
GameStateConverter.fromProto(currentState)
|
||||
).results.map(ActionResultProtoConverter.toProto)
|
||||
)
|
||||
val latestState = requestResults.lastOption.map(_.resultingState).getOrElse(currentState)
|
||||
requestResults :+ actionResultApplier.applyActionResult(
|
||||
val latestState = requestResults.lastOption.map(_.gameState).getOrElse(currentState)
|
||||
requestResults :+ actionResultProtoApplier.applyActionResult(
|
||||
latestState,
|
||||
EndFreeForAllBattleRequestPhaseAction.immediateExecute
|
||||
ActionResultProtoConverter.toProto(EndFreeForAllBattleRequestPhaseAction.immediateExecute)
|
||||
)
|
||||
|
||||
case FreeForAllBattleResolution =>
|
||||
case FREE_FOR_ALL_BATTLE_RESOLUTION =>
|
||||
if currentState.outstandingBattles.isEmpty then
|
||||
Vector(
|
||||
actionResultApplier.applyActionResult(
|
||||
actionResultProtoApplier.applyActionResult(
|
||||
currentState,
|
||||
EndFreeForAllBattleResolutionPhaseAction.immediateExecute
|
||||
ActionResultProtoConverter.toProto(EndFreeForAllBattleResolutionPhaseAction.immediateExecute)
|
||||
)
|
||||
)
|
||||
else Vector.empty // wait for battles to resolve
|
||||
|
||||
case UncontestedConquest =>
|
||||
actionResultApplier.applyActionResults(
|
||||
case UNCONTESTED_CONQUEST =>
|
||||
actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
PerformUncontestedConquestAction(
|
||||
gameId = currentState.gameId,
|
||||
currentRoundId = currentState.currentRoundId,
|
||||
currentDate = currentState.currentDate.get,
|
||||
provinces = currentState.provinces,
|
||||
factions = currentState.factions,
|
||||
heroes = currentState.heroes,
|
||||
battalions = currentState.battalions
|
||||
GameStateConverter.fromProto(currentState)
|
||||
).results
|
||||
.map(ActionResultProtoConverter.toProto)
|
||||
)
|
||||
|
||||
case AttackDecision =>
|
||||
case ATTACK_DECISION =>
|
||||
if availableCommandsFactory.hasAvailableAttackDecisionPhaseCommands(
|
||||
currentStateProto
|
||||
currentState
|
||||
)
|
||||
then Vector.empty
|
||||
else {
|
||||
// There may eventually be VassalAttackDecisions, but for now we're leaving that on the player
|
||||
actionResultApplier.applyActionResults(
|
||||
actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
EndAttackDecisionPhaseAction(
|
||||
gameId = currentState.gameId,
|
||||
currentRoundId = currentState.currentRoundId,
|
||||
currentDate = currentState.currentDate.get,
|
||||
provinces = currentState.provinces.values.toVector
|
||||
).results
|
||||
GameStateConverter.fromProto(currentState)
|
||||
).results.map(ActionResultProtoConverter.toProto)
|
||||
)
|
||||
}
|
||||
|
||||
case DefenseDecision =>
|
||||
case DEFENSE_DECISION =>
|
||||
if availableCommandsFactory.hasAvailablePlayerDefenseCommands(
|
||||
currentStateProto
|
||||
currentState
|
||||
)
|
||||
then Vector.empty
|
||||
else {
|
||||
val vassalCommandResults = PerformVassalDefenseDecisionsAction(
|
||||
gameState = currentState,
|
||||
gameState = GameStateConverter.fromProto(currentState),
|
||||
commandsForProvince = availableCommandsFactory
|
||||
.defensePhaseCommandsForProvince(currentStateProto, _),
|
||||
.defensePhaseCommandsForProvince(currentState, _),
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).results(SeededRandom(currentState.randomSeed))
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
val appliedResults = actionResultApplier.applyActionResults(currentState, vassalCommandResults)
|
||||
applier = ActionResultTApplierImpl(ScalaRuntimeValidator)
|
||||
).execute(currentState, actionResultProtoApplier)
|
||||
|
||||
if appliedResults.nonEmpty then appliedResults
|
||||
if vassalCommandResults.nonEmpty then vassalCommandResults
|
||||
else
|
||||
Vector(
|
||||
actionResultApplier.applyActionResult(
|
||||
actionResultProtoApplier.applyActionResult(
|
||||
currentState,
|
||||
EndDefenseDecisionPhaseAction(currentState).immediateExecute
|
||||
ActionResultProtoConverter.toProto(
|
||||
EndDefenseDecisionPhaseAction(GameStateConverter.fromProto(currentState)).immediateExecute
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case TruceTurnBack =>
|
||||
val actionResults = TruceTurnBackPhaseAction(currentState, actionResultApplier)
|
||||
.results(SeededRandom(currentState.randomSeed))
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
actionResultApplier.applyActionResults(currentState, actionResults)
|
||||
case TRUCE_TURN_BACK =>
|
||||
TruceTurnBackPhaseAction(GameStateConverter.fromProto(currentState)).execute(actionResultProtoApplier)
|
||||
|
||||
case BattleRequest =>
|
||||
case BATTLE_REQUEST =>
|
||||
val requestBattlesAction = RequestBattlesAction(
|
||||
gameId = currentState.gameId,
|
||||
currentRoundId = currentState.currentRoundId,
|
||||
currentDate = currentState.currentDate.get,
|
||||
battleCounter = currentState.battleCounter,
|
||||
heroes = currentState.heroes,
|
||||
battalions = currentState.battalions,
|
||||
provinces = currentState.provinces,
|
||||
factions = currentState.factions,
|
||||
battalionTypes = currentState.battalionTypes.map(bt => bt.typeId -> bt).toMap
|
||||
GameStateConverter.fromProto(currentState)
|
||||
)
|
||||
val requestResults = actionResultApplier.applyActionResults(
|
||||
val requestResults = actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
requestBattlesAction.results
|
||||
requestBattlesAction.results.map(
|
||||
ActionResultProtoConverter.toProto(_)
|
||||
)
|
||||
)
|
||||
val latestState = requestResults.lastOption.map(_.resultingState).getOrElse(currentState)
|
||||
requestResults :+ actionResultApplier.applyActionResult(
|
||||
val latestState = requestResults.lastOption.map(_.gameState).getOrElse(currentState)
|
||||
requestResults :+ actionResultProtoApplier.applyActionResult(
|
||||
latestState,
|
||||
EndBattleRequestPhaseAction(latestState).immediateExecute
|
||||
)
|
||||
|
||||
case FoodConsumption =>
|
||||
Vector(
|
||||
actionResultApplier.applyActionResult(
|
||||
currentState,
|
||||
PerformFoodConsumptionPhaseAction(currentState).immediateExecute
|
||||
ActionResultProtoConverter.toProto(
|
||||
EndBattleRequestPhaseAction(GameStateConverter.fromProto(latestState)).immediateExecute
|
||||
)
|
||||
)
|
||||
|
||||
case BattleResolution =>
|
||||
case FOOD_CONSUMPTION =>
|
||||
Vector(
|
||||
actionResultProtoApplier.applyActionResult(
|
||||
currentState,
|
||||
ActionResultProtoConverter.toProto(
|
||||
PerformFoodConsumptionPhaseAction(GameStateConverter.fromProto(currentState)).immediateExecute
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
case BATTLE_RESOLUTION =>
|
||||
if currentState.outstandingBattles.isEmpty then
|
||||
Vector(
|
||||
actionResultApplier.applyActionResult(
|
||||
actionResultProtoApplier.applyActionResult(
|
||||
currentState,
|
||||
EndBattleResolutionPhaseAction.immediateExecute
|
||||
ActionResultProtoConverter.toProto(EndBattleResolutionPhaseAction.immediateExecute)
|
||||
)
|
||||
)
|
||||
else Vector.empty // wait for battles to resolve
|
||||
|
||||
case BattleAftermath =>
|
||||
case BATTLE_AFTERMATH =>
|
||||
if currentState.provinces.values
|
||||
.flatMap(_.capturedHeroes)
|
||||
.isEmpty
|
||||
then
|
||||
actionResultApplier.applyActionResults(
|
||||
actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
EndBattleAftermathPhaseAction(
|
||||
currentState,
|
||||
actionResultApplier
|
||||
GameStateConverter.fromProto(currentState),
|
||||
ActionResultTApplierImpl(ScalaRuntimeValidator)
|
||||
)
|
||||
.randomResults(
|
||||
SeededRandom(currentState.randomSeed)
|
||||
)
|
||||
.newValue
|
||||
.map(ActionResultProtoConverter.toProto)
|
||||
)
|
||||
else Vector.empty
|
||||
|
||||
case DiplomacyResolution =>
|
||||
case DIPLOMACY_RESOLUTION =>
|
||||
if availableCommandsFactory.hasAvailablePlayerCommands(
|
||||
currentStateProto
|
||||
currentState
|
||||
)
|
||||
then Vector.empty
|
||||
else
|
||||
actionResultApplier.applyActionResults(
|
||||
actionResultProtoApplier.applyActionResults(
|
||||
currentState,
|
||||
EndDiplomacyResolutionPhaseAction(
|
||||
currentState,
|
||||
actionResultApplier = actionResultApplier
|
||||
).randomResults(SeededRandom(currentState.randomSeed)).newValue
|
||||
GameStateConverter.fromProto(currentState),
|
||||
actionResultTApplier = ActionResultTApplierImpl(ScalaRuntimeValidator)
|
||||
).randomResults(SeededRandom(currentState.randomSeed))
|
||||
.newValue
|
||||
.map(ActionResultProtoConverter.toProto)
|
||||
)
|
||||
|
||||
case ReconResolution =>
|
||||
val actionResults = PerformReconResolutionAction(currentState, actionResultApplier)
|
||||
.results(SeededRandom(currentState.randomSeed))
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
actionResultApplier.applyActionResults(currentState, actionResults)
|
||||
case RECON_RESOLUTION =>
|
||||
PerformReconResolutionAction(GameStateConverter.fromProto(currentState)).execute(
|
||||
actionResultProtoApplier
|
||||
)
|
||||
|
||||
case Unrecognized(x) =>
|
||||
throw new IllegalStateException(s"Unknown round phase $x")
|
||||
}
|
||||
val timeSpent = System.currentTimeMillis - startTime
|
||||
val timeSpent = System.currentTimeMillis - startTime
|
||||
times(currentPhase) = times(currentPhase) + timeSpent
|
||||
|
||||
validateResults(results, currentState, currentStateProto, availableCommandsFactory)
|
||||
validateResults(results, currentState, availableCommandsFactory)
|
||||
}
|
||||
|
||||
// We should always either return results, be waiting for an LLM request or battle to resolve,
|
||||
// or have available player commands
|
||||
private def validateResults(
|
||||
results: Vector[ActionResultWithResultingState],
|
||||
results: Vector[ActionWithResultingState],
|
||||
startingState: GameState,
|
||||
startingStateProto: GameStateProto,
|
||||
availableCommandsFactory: AvailableCommandsFactory
|
||||
): Vector[ActionResultWithResultingState] =
|
||||
): Vector[ActionWithResultingState] =
|
||||
internalValidated(
|
||||
results,
|
||||
(r: Vector[ActionResultWithResultingState]) =>
|
||||
(r: Vector[ActionWithResultingState]) =>
|
||||
r.nonEmpty ||
|
||||
startingState.outstandingBattles.nonEmpty ||
|
||||
availableCommandsFactory.hasAvailablePlayerCommands(startingStateProto),
|
||||
availableCommandsFactory.hasAvailablePlayerCommands(startingState),
|
||||
"No results were found, but we also don't seem to be waiting for anything"
|
||||
)
|
||||
}
|
||||
|
||||
+1
-7
@@ -16,13 +16,7 @@ object GameStateMiscExtensions {
|
||||
|
||||
def applyNewNotifications(notifications: Vector[NotificationT]): GameState =
|
||||
if notifications.isEmpty then gameState
|
||||
else {
|
||||
// Only add deferred notifications to the deferred list.
|
||||
// Non-deferred notifications are for immediate delivery and don't affect game state.
|
||||
val deferredOnly = notifications.filter(_.deferred)
|
||||
if deferredOnly.isEmpty then gameState
|
||||
else gameState.copy(deferredNotifications = gameState.deferredNotifications ++ deferredOnly)
|
||||
}
|
||||
else gameState.copy(deferredNotifications = gameState.deferredNotifications ++ notifications)
|
||||
|
||||
def applyRemovedNotifications(notifications: Vector[NotificationT]): GameState =
|
||||
gameState.copy(
|
||||
|
||||
@@ -172,19 +172,25 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_proto_sequencer",
|
||||
],
|
||||
deps = [
|
||||
":check_for_faction_changes_action",
|
||||
":hero_backstory_update_action_generator",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:battle_revelation_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:faction_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_proto_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/llm_request_generators/captured_hero_helpers:captured_hero_plea_generator",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:faction_bias_against_former_on_exile",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:faction_bias_from_exile",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:faction_bias_from_imprisonment",
|
||||
@@ -209,15 +215,18 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_aftermath_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battle_revelation_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:notification_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/llm_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battle_revelation",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction:faction_relationship",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:deferred_change_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
@@ -256,6 +265,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_attack_decision_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
@@ -320,7 +330,6 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
@@ -329,17 +338,20 @@ scala_library(
|
||||
":check_for_faction_changes_action",
|
||||
":hero_backstory_update_action",
|
||||
":hero_backstory_update_action_generator",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_status_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:faction_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action/diplomacy_helpers:alliance_resolution_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action/diplomacy_helpers:break_alliance_resolution_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action/diplomacy_helpers:invitation_resolution_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action/diplomacy_helpers:ransom_resolution_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action/diplomacy_helpers:truce_resolution_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/ransom_validity",
|
||||
@@ -352,17 +364,20 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_battalion_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_faction_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_diplomacy_resolution_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:ransom_invalidated_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:notification_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero/backstory_version",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest/concrete",
|
||||
@@ -385,15 +400,20 @@ scala_library(
|
||||
deps = [
|
||||
":check_for_faction_changes_action",
|
||||
":hero_backstory_update_action_generator",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:faction_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:vigor_xp_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:prisoner_escape_chance",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:province_event_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
@@ -411,11 +431,20 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:prisoner_escaped_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:prisoner_move_took_effect_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:weather_took_effect_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:notification_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:unaffiliated_hero_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province:event",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:deferred_change_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:event",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
],
|
||||
)
|
||||
@@ -482,24 +511,32 @@ scala_library(
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:handle_riot_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:t_command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_battalion_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_handle_riots_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:notification_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
@@ -674,7 +711,7 @@ scala_library(
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
],
|
||||
deps = [
|
||||
":check_for_faction_changes_action",
|
||||
@@ -683,9 +720,9 @@ scala_library(
|
||||
":hero_backstory_update_action_generator",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
@@ -697,7 +734,11 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_vassal_commands_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
@@ -855,18 +896,23 @@ scala_library(
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
],
|
||||
deps = [
|
||||
":chronicle_event_generator",
|
||||
":hero_stat_gain_action",
|
||||
":new_year_action",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:date_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:profession_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:faction_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:game_history",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:empty_province_monthly_devastation_delta",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:faction_bias_minimum_adjustment_per_round",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:faction_bias_multiplier_per_round",
|
||||
@@ -875,9 +921,10 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:over_hero_cap_loyalty_delta",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:over_resource_limit_loss",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:trust_delta_per_round",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:date_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:price_index_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
@@ -890,13 +937,18 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request/chronicle_event",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:new_round_action_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types/base:action_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:unaffiliated_hero_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/llm_request/chronicle_event",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/chronicle_entry",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
],
|
||||
@@ -1111,9 +1163,9 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
|
||||
@@ -1284,15 +1336,15 @@ scala_library(
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:province_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:returning_heroes",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:province_view_filter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
@@ -1301,17 +1353,18 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_faction_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:client_text_visibility_extension_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_recon_resolution_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:recon_succeeded_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:incoming_end_turn_action_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:incoming_end_turn_action",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1323,20 +1376,26 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator",
|
||||
],
|
||||
deps = [
|
||||
":end_unaffiliated_hero_actions_phase_action",
|
||||
":unaffiliated_hero_appeared_action",
|
||||
":unaffiliated_hero_rejoined_action",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:unaffiliated_hero_quest_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/name_generation_request",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:free_hero_move_vigor_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:min_vigor_for_free_hero_move",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:new_hero_chance",
|
||||
@@ -1367,6 +1426,10 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:hero_changed_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:hero_moved_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:new_quests_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
@@ -1386,31 +1449,40 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:t_command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:province_order_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:round_phase_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/common:more_option",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:t_command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_battalion_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1422,27 +1494,36 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:t_command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:t_command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province:legacy_province_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_battalion_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1617,6 +1698,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
@@ -1791,16 +1873,16 @@ scala_library(
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
],
|
||||
deps = [
|
||||
":withdrawn_army_returns_home_action",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/random_state_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_random_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
@@ -1813,6 +1895,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_truce_turn_back_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:withdrawal_for_truce_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
|
||||
+6
-6
@@ -1,23 +1,23 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.eagle.{GameId, RoundId}
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSequentialResultsAction
|
||||
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentUtils
|
||||
import net.eagle0.eagle.model.action_result.concrete.ActionResultC
|
||||
import net.eagle0.eagle.model.action_result.types.EndAttackDecisionPhaseResultType
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.concrete.TruceWithFactionQuest
|
||||
import net.eagle0.eagle.model.state.quest.QuestT
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
|
||||
case class EndAttackDecisionPhaseAction(
|
||||
gameId: GameId,
|
||||
currentRoundId: RoundId,
|
||||
currentDate: Date,
|
||||
provinces: Vector[ProvinceT]
|
||||
gameState: GameState
|
||||
) extends ProtolessSequentialResultsAction {
|
||||
private val gameId = gameState.gameId
|
||||
private val currentRoundId = gameState.currentRoundId
|
||||
private val currentDate = gameState.currentDate.get
|
||||
private val provinces = gameState.provinces.values.toVector
|
||||
|
||||
override def results: Vector[ActionResultT] =
|
||||
WithdrawnArmiesReturnHomeAction(
|
||||
|
||||
+110
-79
@@ -2,11 +2,25 @@ package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.internal.deferred_change.{
|
||||
BlizzardEnded,
|
||||
BlizzardStarted,
|
||||
CapturedHeroExecuted,
|
||||
CapturedHeroExiled,
|
||||
CapturedHeroImprisoned,
|
||||
CapturedHeroReturned,
|
||||
DeferredChange,
|
||||
DroughtEnded,
|
||||
DroughtStarted,
|
||||
EpidemicStarted,
|
||||
PrisonerMoved,
|
||||
PrisonerReturned
|
||||
}
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.generated_text_request_generators.captured_hero_helpers.CapturedHeroPleaGenerator
|
||||
import net.eagle0.eagle.library.actions.impl.action.EndBattleAftermathPhaseAction.RevelationChange
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessRandomSequentialResultsAction
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, RandomStateTSequencer}
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
FactionBiasAgainstFormerOnExile,
|
||||
FactionBiasFromExile,
|
||||
@@ -22,7 +36,12 @@ import net.eagle0.eagle.model.action_result.{ActionResultT, NotificationDetails,
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedFactionC, ChangedHeroC, NotificationC}
|
||||
import net.eagle0.eagle.model.action_result.types.{CapturedHeroResolvedResultType, EndAftermathPhaseResultType}
|
||||
import net.eagle0.eagle.model.proto_converters.{BattleRevelationConverter, NotificationConverter}
|
||||
import net.eagle0.eagle.model.proto_converters.date.DateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
|
||||
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
|
||||
import net.eagle0.eagle.model.state.{BattleRevelation, RoundPhase}
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
@@ -32,7 +51,6 @@ import net.eagle0.eagle.model.state.hero.{
|
||||
CapturedHeroReturnedBackstoryEvent,
|
||||
EventForHeroBackstoryT
|
||||
}
|
||||
import net.eagle0.eagle.model.state.province.{DeferredChange, DeferredChangeT}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.concrete.UnaffiliatedHeroC
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType
|
||||
import net.eagle0.eagle.model.state.BattleRevelationType.{DidBattle, Unknown, Withdrew}
|
||||
@@ -43,8 +61,8 @@ object EndBattleAftermathPhaseAction {
|
||||
changedProvince: ChangedProvinceC
|
||||
)
|
||||
|
||||
def allDeferredChanges(gameState: GameState): Vector[DeferredChangeT] =
|
||||
gameState.provinces.values
|
||||
def allDeferredChanges(gameStateProto: GameStateProto): Vector[DeferredChange] =
|
||||
gameStateProto.provinces.values
|
||||
.flatMap(_.deferredChanges)
|
||||
.toVector
|
||||
|
||||
@@ -56,15 +74,16 @@ object EndBattleAftermathPhaseAction {
|
||||
capturedHeroFactionId: FactionId,
|
||||
newFactionBias: Option[Double],
|
||||
oldFactionBias: Option[Double],
|
||||
gameState: GameState,
|
||||
gameStateProto: GameStateProto,
|
||||
functionalRandom: FunctionalRandom,
|
||||
newEventForHeroBackstoryDetails: EventForHeroBackstoryT
|
||||
): RandomState[ActionResultT] = {
|
||||
val hero = gameState.heroes(capturedHeroId)
|
||||
val initialUh = UnaffiliatedHeroC(
|
||||
val scalaGameState = GameStateConverter.fromProto(gameStateProto)
|
||||
val scalaHero = scalaGameState.heroes(capturedHeroId)
|
||||
val initialUh = UnaffiliatedHeroC(
|
||||
heroId = capturedHeroId,
|
||||
unaffiliatedHeroType = uhType,
|
||||
lastFactionId = hero.factionId,
|
||||
lastFactionId = scalaHero.factionId,
|
||||
factionBiases = (
|
||||
newFactionBias.map(b => actingFactionId -> b) ++ oldFactionBias.map(b => capturedHeroFactionId -> b)
|
||||
).toMap
|
||||
@@ -72,10 +91,10 @@ object EndBattleAftermathPhaseAction {
|
||||
|
||||
UnaffiliatedHeroUtils
|
||||
.updatedForQuest(
|
||||
gs = gameState,
|
||||
gs = scalaGameState,
|
||||
pid = provinceId,
|
||||
uh = initialUh,
|
||||
hero = hero,
|
||||
hero = scalaHero,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.map { uh =>
|
||||
@@ -102,22 +121,23 @@ object EndBattleAftermathPhaseAction {
|
||||
}
|
||||
|
||||
def deferredChangeAR(
|
||||
deferredChange: DeferredChangeT,
|
||||
gameState: GameState,
|
||||
deferredChange: DeferredChange,
|
||||
gameStateProto: GameStateProto,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[ActionResultT] =
|
||||
deferredChange match {
|
||||
case DeferredChange.CapturedHeroExiled(
|
||||
case CapturedHeroExiled(
|
||||
exiledHeroId,
|
||||
provinceId,
|
||||
exilingHeroId,
|
||||
exilingFactionId,
|
||||
prisonerFactionId
|
||||
prisonerFactionId,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
val notificationLlmRequest =
|
||||
CapturedHeroPleaGenerator.exiledNotification(
|
||||
gameId = gameState.gameId,
|
||||
currentRoundId = gameState.currentRoundId,
|
||||
gameId = gameStateProto.gameId,
|
||||
currentRoundId = gameStateProto.currentRoundId,
|
||||
capturedHeroId = exiledHeroId,
|
||||
capturedFromFactionId = prisonerFactionId,
|
||||
actingHeroId = exilingHeroId,
|
||||
@@ -132,10 +152,10 @@ object EndBattleAftermathPhaseAction {
|
||||
capturedHeroFactionId = prisonerFactionId,
|
||||
newFactionBias = Some(FactionBiasFromExile.doubleValue),
|
||||
oldFactionBias = Some(FactionBiasAgainstFormerOnExile.doubleValue),
|
||||
gameState = gameState,
|
||||
gameStateProto = gameStateProto,
|
||||
functionalRandom = functionalRandom,
|
||||
newEventForHeroBackstoryDetails = CapturedHeroExiledBackstoryEvent(
|
||||
date = gameState.currentDate.get,
|
||||
date = DateConverter.fromProto(gameStateProto.currentDate),
|
||||
capturingFactionId = exilingFactionId,
|
||||
capturingHeroId = exilingHeroId,
|
||||
provinceId = provinceId
|
||||
@@ -155,17 +175,18 @@ object EndBattleAftermathPhaseAction {
|
||||
)
|
||||
)
|
||||
}
|
||||
case DeferredChange.CapturedHeroExecuted(
|
||||
case CapturedHeroExecuted(
|
||||
capturedHeroId,
|
||||
provinceId,
|
||||
executingHeroId,
|
||||
executingFactionId,
|
||||
prisonerFactionId
|
||||
prisonerFactionId,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
val notificationLlmRequest =
|
||||
CapturedHeroPleaGenerator.executedNotification(
|
||||
gameId = gameState.gameId,
|
||||
currentRoundId = gameState.currentRoundId,
|
||||
gameId = gameStateProto.gameId,
|
||||
currentRoundId = gameStateProto.currentRoundId,
|
||||
capturedHeroId = capturedHeroId,
|
||||
capturedFromFactionId = prisonerFactionId,
|
||||
actingHeroId = executingHeroId,
|
||||
@@ -201,17 +222,18 @@ object EndBattleAftermathPhaseAction {
|
||||
),
|
||||
functionalRandom
|
||||
)
|
||||
case DeferredChange.CapturedHeroImprisoned(
|
||||
case CapturedHeroImprisoned(
|
||||
capturedHeroId,
|
||||
provinceId,
|
||||
imprisoningHeroId,
|
||||
imprisoningFactionId,
|
||||
prisonerFactionId
|
||||
prisonerFactionId,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
val notificationLlmRequest =
|
||||
CapturedHeroPleaGenerator.imprisonedNotification(
|
||||
gameId = gameState.gameId,
|
||||
currentRoundId = gameState.currentRoundId,
|
||||
gameId = gameStateProto.gameId,
|
||||
currentRoundId = gameStateProto.currentRoundId,
|
||||
capturedHeroId = capturedHeroId,
|
||||
actingHeroId = imprisoningHeroId,
|
||||
actingFactionId = imprisoningFactionId,
|
||||
@@ -226,10 +248,10 @@ object EndBattleAftermathPhaseAction {
|
||||
capturedHeroFactionId = prisonerFactionId,
|
||||
newFactionBias = Some(FactionBiasFromImprisonment.doubleValue),
|
||||
oldFactionBias = None,
|
||||
gameState = gameState,
|
||||
gameStateProto = gameStateProto,
|
||||
functionalRandom = functionalRandom,
|
||||
newEventForHeroBackstoryDetails = CapturedHeroImprisonedBackstoryEvent(
|
||||
date = gameState.currentDate.get,
|
||||
date = DateConverter.fromProto(gameStateProto.currentDate),
|
||||
capturingFactionId = imprisoningFactionId,
|
||||
capturingHeroId = imprisoningHeroId,
|
||||
provinceId = provinceId
|
||||
@@ -249,16 +271,22 @@ object EndBattleAftermathPhaseAction {
|
||||
)
|
||||
)
|
||||
}
|
||||
case DeferredChange.CapturedHeroReturned(
|
||||
case CapturedHeroReturned(
|
||||
returnedHeroId,
|
||||
actingHeroId,
|
||||
fromProvinceId,
|
||||
fromFactionId,
|
||||
toProvinceId,
|
||||
toFactionId
|
||||
toFactionId,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
val allFactions = gameState.factions.values
|
||||
val truceEndDate = gameState.currentDate.get.addMonths(TruceMonthsFromReturningLeader.intValue)
|
||||
val allFactionTs =
|
||||
gameStateProto.factions.values.map(FactionConverter.fromProto)
|
||||
val truceEndDate = DateConverter
|
||||
.fromProto(gameStateProto.currentDate)
|
||||
.addMonths(
|
||||
TruceMonthsFromReturningLeader.intValue
|
||||
)
|
||||
RandomState(
|
||||
ActionResultC(
|
||||
actionResultType = CapturedHeroResolvedResultType,
|
||||
@@ -269,7 +297,7 @@ object EndBattleAftermathPhaseAction {
|
||||
heroId = returnedHeroId,
|
||||
newEventsForHeroBackstory = Vector(
|
||||
CapturedHeroReturnedBackstoryEvent(
|
||||
date = gameState.currentDate.get,
|
||||
date = DateConverter.fromProto(gameStateProto.currentDate),
|
||||
capturingFactionId = fromFactionId,
|
||||
capturingHeroId = actingHeroId,
|
||||
provinceId = fromProvinceId
|
||||
@@ -296,7 +324,7 @@ object EndBattleAftermathPhaseAction {
|
||||
.factionRelationship(
|
||||
by = fromFactionId,
|
||||
of = toFactionId,
|
||||
factions = allFactions
|
||||
factions = allFactionTs
|
||||
)
|
||||
.copy(
|
||||
relationshipLevel = FactionRelationship.RelationshipLevel.Truce,
|
||||
@@ -311,7 +339,7 @@ object EndBattleAftermathPhaseAction {
|
||||
.factionRelationship(
|
||||
by = toFactionId,
|
||||
of = fromFactionId,
|
||||
factions = allFactions
|
||||
factions = allFactionTs
|
||||
)
|
||||
.copy(
|
||||
relationshipLevel = FactionRelationship.RelationshipLevel.Truce,
|
||||
@@ -335,10 +363,12 @@ object EndBattleAftermathPhaseAction {
|
||||
functionalRandom
|
||||
)
|
||||
|
||||
case DeferredChange.Empty =>
|
||||
throw new EagleInternalException("Empty deferred change")
|
||||
|
||||
// the rest are not for this phase
|
||||
case _: DeferredChange.EpidemicStarted | _: DeferredChange.DroughtStarted | _: DeferredChange.DroughtEnded |
|
||||
_: DeferredChange.PrisonerMoved | _: DeferredChange.PrisonerReturned | _: DeferredChange.BlizzardStarted |
|
||||
_: DeferredChange.BlizzardEnded =>
|
||||
case _: EpidemicStarted | _: DroughtStarted | _: DroughtEnded | _: PrisonerMoved | _: PrisonerReturned |
|
||||
_: BlizzardStarted | _: BlizzardEnded =>
|
||||
throw new EagleInternalException(
|
||||
"Event should not be present in EndBattleAftermathPhaseAction"
|
||||
)
|
||||
@@ -347,18 +377,13 @@ object EndBattleAftermathPhaseAction {
|
||||
|
||||
case class EndBattleAftermathPhaseAction(
|
||||
gameState: GameState,
|
||||
actionResultApplier: ActionResultApplier
|
||||
actionResultApplier: ActionResultTApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
|
||||
// Revelation changes still need proto for ProvinceViewFilter
|
||||
// Convert to proto for internal use - proto still needed for DeferredChange, ProvinceViewFilter, etc.
|
||||
private val gameStateProto: GameStateProto = GameStateConverter.toProto(gameState)
|
||||
private def revelationChange(
|
||||
battleRevelation: BattleRevelation,
|
||||
gs: GameState
|
||||
): RevelationChange = {
|
||||
// Lazy conversion to proto for ProvinceViewFilter calls
|
||||
lazy val gsProto = GameStateConverter.toProto(gs)
|
||||
lazy val provinceProto = gsProto.provinces(battleRevelation.provinceId)
|
||||
|
||||
battleRevelation: BattleRevelation
|
||||
): RevelationChange =
|
||||
RevelationChange(
|
||||
changedFaction = battleRevelation.revelationType match {
|
||||
case Unknown =>
|
||||
@@ -369,11 +394,11 @@ case class EndBattleAftermathPhaseAction(
|
||||
updatedReconnedProvinces = Vector(
|
||||
ProvinceViewFilter
|
||||
.withdrawnFromProvinceView(
|
||||
province = provinceProto,
|
||||
gs = gsProto,
|
||||
province = gameStateProto.provinces(battleRevelation.provinceId),
|
||||
gs = gameStateProto,
|
||||
factionId = battleRevelation.revealedToFactionId
|
||||
)
|
||||
.withAsOf(gsProto.currentDate.get)
|
||||
.withAsOf(gameStateProto.currentDate.get)
|
||||
)
|
||||
)
|
||||
case DidBattle =>
|
||||
@@ -382,12 +407,12 @@ case class EndBattleAftermathPhaseAction(
|
||||
updatedReconnedProvinces = Vector(
|
||||
ProvinceViewFilter
|
||||
.filteredProvinceView(
|
||||
provinceProto,
|
||||
gsProto
|
||||
gameStateProto.provinces(battleRevelation.provinceId),
|
||||
gameStateProto
|
||||
// FIXME: setting factionId to None right now to grab the full info. This
|
||||
// probably isn't exactly what we want.
|
||||
)
|
||||
.withAsOf(gsProto.currentDate.get)
|
||||
.withAsOf(gameStateProto.currentDate.get)
|
||||
)
|
||||
)
|
||||
},
|
||||
@@ -396,26 +421,27 @@ case class EndBattleAftermathPhaseAction(
|
||||
removedBattleRevelations = Vector(battleRevelation)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def revelationChanges(gs: GameState): Vector[RevelationChange] =
|
||||
def revelationChanges(gs: GameStateProto): Vector[RevelationChange] =
|
||||
for {
|
||||
province <- gs.provinces.values.toVector
|
||||
revelation <- province.battleRevelations
|
||||
if gs.factions.contains(revelation.revealedToFactionId)
|
||||
} yield revelationChange(revelation, gs)
|
||||
} yield revelationChange(BattleRevelationConverter.fromProto(revelation))
|
||||
|
||||
def sequencerWithDeferredChanges(
|
||||
initialState: GameState,
|
||||
actionResultApplier: ActionResultApplier,
|
||||
actionResultApplier: ActionResultTApplier,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomStateSequencer =
|
||||
RandomStateSequencer(
|
||||
): RandomStateTSequencer =
|
||||
RandomStateTSequencer(
|
||||
initialState = initialState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.foldIn(EndBattleAftermathPhaseAction.allDeferredChanges(initialState))(
|
||||
.foldIn(
|
||||
EndBattleAftermathPhaseAction.allDeferredChanges(GameStateConverter.toProto(initialState))
|
||||
)(
|
||||
EndBattleAftermathPhaseAction.deferredChangeAR
|
||||
)
|
||||
|
||||
@@ -441,25 +467,30 @@ case class EndBattleAftermathPhaseAction(
|
||||
.withRandomActionResults((gs, fr) =>
|
||||
CheckForFactionChangesAction(
|
||||
gameId = gs.gameId,
|
||||
factions = gs.factions.values.toVector,
|
||||
provinces = gs.provinces.values.toVector,
|
||||
heroes = gs.heroes.values.toVector,
|
||||
factions = gs.factions.values.toVector.map(FactionConverter.fromProto),
|
||||
provinces = gs.provinces.values.toVector.map(ProvinceConverter.fromProto),
|
||||
heroes = gs.heroes.values.toVector.map(HeroConverter.fromProto),
|
||||
killedHeroIds = gs.killedHeroes.keys.toVector
|
||||
).randomResults(fr)
|
||||
)
|
||||
.withActionResults(gs => HeroBackstoryUpdateActionGenerator(gs).results)
|
||||
.withRandomActionResult { (gs, fr) =>
|
||||
RandomState(
|
||||
ActionResultC(
|
||||
actionResultType = EndAftermathPhaseResultType,
|
||||
changedFactions = revelationChanges(gs).map(_.changedFaction),
|
||||
changedProvinces = revelationChanges(gs).map(_.changedProvince),
|
||||
newRoundPhase = Some(RoundPhase.DiplomacyResolution),
|
||||
removedNotifications = gs.deferredNotifications.map(_.withDeferred(true)),
|
||||
newNotifications = gs.deferredNotifications.map(_.withDeferred(false))
|
||||
),
|
||||
fr
|
||||
)
|
||||
.withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator.fromGameState(gs))
|
||||
.withRandomActionResult {
|
||||
case (gs, fr) =>
|
||||
RandomState(
|
||||
ActionResultC(
|
||||
actionResultType = EndAftermathPhaseResultType,
|
||||
changedFactions = revelationChanges(gs).map(_.changedFaction),
|
||||
changedProvinces = revelationChanges(gs).map(_.changedProvince),
|
||||
newRoundPhase = Some(RoundPhase.DiplomacyResolution),
|
||||
removedNotifications = gs.deferredNotifications
|
||||
.map(note => NotificationConverter.fromProto(note, deferred = true))
|
||||
.toVector,
|
||||
newNotifications = gs.deferredNotifications
|
||||
.map(note => NotificationConverter.fromProto(note, deferred = false))
|
||||
.toVector
|
||||
),
|
||||
fr
|
||||
)
|
||||
}
|
||||
.actionResults
|
||||
}
|
||||
|
||||
+80
-42
@@ -1,7 +1,8 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.action.diplomacy_helpers.{
|
||||
AllianceResolutionHelpers,
|
||||
BreakAllianceResolutionHelpers,
|
||||
@@ -9,8 +10,7 @@ import net.eagle0.eagle.library.actions.impl.action.diplomacy_helpers.{
|
||||
RansomResolutionHelpers,
|
||||
TruceResolutionHelpers
|
||||
}
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessRandomSequentialResultsAction
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, RandomStateTSequencer}
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentChecker
|
||||
import net.eagle0.eagle.library.util.ransom_validity.RansomValidity
|
||||
@@ -18,6 +18,11 @@ import net.eagle0.eagle.library.EagleInternalException
|
||||
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedFactionC}
|
||||
import net.eagle0.eagle.model.action_result.types.{EndDiplomacyResolutionPhaseResultType, RansomInvalidatedResultType}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.{BattalionConverter, NotificationConverter}
|
||||
import net.eagle0.eagle.model.proto_converters.date.DateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
|
||||
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
|
||||
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.diplomacy_offer.{AllianceOffer, BreakAlliance, Invitation, RansomOffer, TruceOffer}
|
||||
@@ -30,16 +35,16 @@ import net.eagle0.eagle.model.state.RoundPhase.ReconResolution
|
||||
|
||||
case class EndDiplomacyResolutionPhaseAction(
|
||||
gameState: GameState,
|
||||
actionResultApplier: ActionResultApplier
|
||||
actionResultTApplier: ActionResultTApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
|
||||
override def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[ActionResultT]] = {
|
||||
import EndDiplomacyResolutionPhaseActionHelpers.*
|
||||
RandomStateSequencer(
|
||||
RandomStateTSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
actionResultApplier = actionResultTApplier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.withActionResults(invalidationResultsForState)
|
||||
@@ -51,9 +56,15 @@ case class EndDiplomacyResolutionPhaseAction(
|
||||
.withRandomActionResults { (currentGameState, fr) =>
|
||||
CheckForFactionChangesAction(
|
||||
gameId = currentGameState.gameId,
|
||||
factions = currentGameState.factions.values.toVector,
|
||||
provinces = currentGameState.provinces.values.toVector,
|
||||
heroes = currentGameState.heroes.values.toVector,
|
||||
factions = currentGameState.factions.values.toVector.map(
|
||||
FactionConverter.fromProto
|
||||
),
|
||||
provinces = currentGameState.provinces.values.toVector.map(
|
||||
ProvinceConverter.fromProto
|
||||
),
|
||||
heroes = currentGameState.heroes.values.toVector.map(
|
||||
HeroConverter.fromProto
|
||||
),
|
||||
killedHeroIds = currentGameState.killedHeroes.keys.toVector
|
||||
).randomResults(fr)
|
||||
}
|
||||
@@ -61,14 +72,20 @@ case class EndDiplomacyResolutionPhaseAction(
|
||||
HeroBackstoryUpdateAction(
|
||||
gameId = currentGameState.gameId,
|
||||
roundId = currentGameState.currentRoundId,
|
||||
heroes = currentGameState.heroes.values.toVector,
|
||||
heroes = currentGameState.heroes.values
|
||||
.map(HeroConverter.fromProto)
|
||||
.toVector,
|
||||
visibleToFactionIds = toFid =>
|
||||
FactionUtils.alliedFactions(
|
||||
toFid,
|
||||
currentGameState.factions.values.toVector
|
||||
currentGameState.factions.values
|
||||
.map(FactionConverter.fromProto)
|
||||
.toVector
|
||||
),
|
||||
heroInProvinceOwnedBy = heroId => {
|
||||
val provinces = currentGameState.provinces.values.toVector
|
||||
val provinces = currentGameState.provinces.values
|
||||
.map(ProvinceConverter.fromProto)
|
||||
.toVector
|
||||
provinces
|
||||
.find(province =>
|
||||
province.rulingFactionHeroIds.contains(heroId) ||
|
||||
@@ -92,16 +109,19 @@ case class EndDiplomacyResolutionPhaseAction(
|
||||
* compiler can become confused about which one is being referenced during imports.
|
||||
*
|
||||
* All helper methods take explicit parameters for game state components to ensure they always operate on the current
|
||||
* state as provided by RandomStateSequencer, preventing use of stale data from the initial game state.
|
||||
* state as provided by RandomStateTSequencer, preventing use of stale data from the initial game state.
|
||||
*/
|
||||
private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
|
||||
// Methods that use the updated game state from the sequencer
|
||||
def invalidationResultsForState(
|
||||
currentGameState: GameState
|
||||
currentGameState: GameStateProto
|
||||
): Vector[ActionResultT] = {
|
||||
val factions = currentGameState.factions.values.toVector
|
||||
val provinces = currentGameState.provinces.values.toVector
|
||||
val factions =
|
||||
currentGameState.factions.values.map(FactionConverter.fromProto).toVector
|
||||
val provinces = currentGameState.provinces.values
|
||||
.map(ProvinceConverter.fromProto)
|
||||
.toVector
|
||||
|
||||
for {
|
||||
faction <- factions
|
||||
@@ -129,9 +149,11 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
}
|
||||
|
||||
def endPhaseResultForState(
|
||||
currentGameState: GameState
|
||||
currentGameState: GameStateProto
|
||||
): ActionResultT = {
|
||||
val deferredNotifications = currentGameState.deferredNotifications
|
||||
.map(n => NotificationConverter.fromProto(n, deferred = true))
|
||||
.toVector
|
||||
|
||||
ActionResultC(
|
||||
actionResultType = EndDiplomacyResolutionPhaseResultType,
|
||||
@@ -141,6 +163,9 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
)
|
||||
}
|
||||
|
||||
private def factionTs(gs: GameStateProto): Vector[FactionT] =
|
||||
gs.factions.values.map(FactionConverter.fromProto).toVector
|
||||
|
||||
private def resolutionsForType[A](
|
||||
getter: FactionT => Vector[A],
|
||||
resolver: A => Vector[ActionResultT]
|
||||
@@ -164,15 +189,17 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
}
|
||||
|
||||
def truceResolutionsForState(
|
||||
currentGameState: GameState,
|
||||
currentGameState: GameStateProto,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[ActionResultT]] = {
|
||||
val factions = currentGameState.factions.values.toVector
|
||||
val factions = factionTs(currentGameState)
|
||||
randomResolutionsForType(
|
||||
_.incomingDiplomacyOffers.collect { case to: TruceOffer => to },
|
||||
(to: TruceOffer, fr: FunctionalRandom) => {
|
||||
val provinces = currentGameState.provinces.values.toVector
|
||||
val currentDate = currentGameState.currentDate.get
|
||||
val provinces = currentGameState.provinces.values
|
||||
.map(ProvinceConverter.fromProto)
|
||||
.toVector
|
||||
val currentDate = DateConverter.fromProto(currentGameState.currentDate)
|
||||
resultsForTruceOffer(
|
||||
to,
|
||||
fr,
|
||||
@@ -187,16 +214,19 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
}
|
||||
|
||||
def allianceResolutionsForState(
|
||||
currentGameState: GameState,
|
||||
currentGameState: GameStateProto,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[ActionResultT]] = {
|
||||
val factions = currentGameState.factions.values.toVector
|
||||
val factions = factionTs(currentGameState)
|
||||
randomResolutionsForType(
|
||||
_.incomingDiplomacyOffers.collect { case ao: AllianceOffer => ao },
|
||||
(ao: AllianceOffer, fr: FunctionalRandom) => {
|
||||
val provinces = currentGameState.provinces.values.toVector
|
||||
val heroes = currentGameState.heroes.values.toVector
|
||||
val currentDate = currentGameState.currentDate.get
|
||||
val provinces = currentGameState.provinces.values
|
||||
.map(ProvinceConverter.fromProto)
|
||||
.toVector
|
||||
val heroes =
|
||||
currentGameState.heroes.values.map(HeroConverter.fromProto).toVector
|
||||
val currentDate = DateConverter.fromProto(currentGameState.currentDate)
|
||||
resultsForAllianceOffer(
|
||||
ao,
|
||||
fr,
|
||||
@@ -211,15 +241,17 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
}
|
||||
|
||||
def breakAllianceResolutionsForState(
|
||||
currentGameState: GameState,
|
||||
currentGameState: GameStateProto,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[ActionResultT]] = {
|
||||
val factions = currentGameState.factions.values.toVector
|
||||
val factions = factionTs(currentGameState)
|
||||
randomResolutionsForType(
|
||||
_.incomingDiplomacyOffers.collect { case ba: BreakAlliance => ba },
|
||||
(ba: BreakAlliance, fr: FunctionalRandom) => {
|
||||
val provinces = currentGameState.provinces.values.toVector
|
||||
val currentDate = currentGameState.currentDate.get
|
||||
val provinces = currentGameState.provinces.values
|
||||
.map(ProvinceConverter.fromProto)
|
||||
.toVector
|
||||
val currentDate = DateConverter.fromProto(currentGameState.currentDate)
|
||||
resultsForBreakAlliance(ba, fr, provinces, factions, currentDate)
|
||||
},
|
||||
functionalRandom
|
||||
@@ -227,16 +259,20 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
}
|
||||
|
||||
def inviteResolutionsForState(
|
||||
currentGameState: GameState,
|
||||
currentGameState: GameStateProto,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[ActionResultT]] = {
|
||||
val factions = currentGameState.factions.values.toVector
|
||||
val factions = factionTs(currentGameState)
|
||||
randomResolutionsForType(
|
||||
_.incomingDiplomacyOffers.collect { case id: Invitation => id },
|
||||
(id: Invitation, fr: FunctionalRandom) => {
|
||||
val provinces = currentGameState.provinces.values.toVector
|
||||
val battalions = currentGameState.battalions.values.toVector
|
||||
val currentDate = currentGameState.currentDate.get
|
||||
val provinces = currentGameState.provinces.values
|
||||
.map(ProvinceConverter.fromProto)
|
||||
.toVector
|
||||
val battalions = currentGameState.battalions.values
|
||||
.map(BattalionConverter.fromProto)
|
||||
.toVector
|
||||
val currentDate = DateConverter.fromProto(currentGameState.currentDate)
|
||||
resultsForInvitation(
|
||||
id,
|
||||
fr,
|
||||
@@ -251,16 +287,18 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
}
|
||||
|
||||
def ransomResolutionsForState(
|
||||
currentGameState: GameState
|
||||
currentGameState: GameStateProto
|
||||
): Vector[ActionResultT] = {
|
||||
val factions = currentGameState.factions.values.toVector
|
||||
val factions = factionTs(currentGameState)
|
||||
resolutionsForType(
|
||||
_.incomingDiplomacyOffers.collect { case ro: RansomOffer => ro },
|
||||
(ro: RansomOffer) => {
|
||||
val provinces = currentGameState.provinces.values.toVector
|
||||
val provinces = currentGameState.provinces.values
|
||||
.map(ProvinceConverter.fromProto)
|
||||
.toVector
|
||||
resultsForRansomOffer(
|
||||
ro,
|
||||
currentGameState.currentDate.get,
|
||||
DateConverter.fromProto(currentGameState.currentDate),
|
||||
provinces,
|
||||
currentGameState
|
||||
)
|
||||
@@ -272,7 +310,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
ransomOffer: RansomOffer,
|
||||
currentDate: Date,
|
||||
provinces: Vector[ProvinceT],
|
||||
gameState: GameState
|
||||
gameState: GameStateProto
|
||||
): Vector[ActionResultT] = ransomOffer.status match {
|
||||
case Accepted =>
|
||||
RansomResolutionHelpers.acceptedRansomResults(
|
||||
@@ -283,7 +321,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
gameId = gameState.gameId,
|
||||
currentDate = currentDate,
|
||||
currentRoundId = gameState.currentRoundId,
|
||||
previousBackstoryTextIdLookup = hid => gameState.heroes(hid).backstoryVersions.last.textId
|
||||
previousBackstoryTextIdLookup = hid => gameState.heroes(hid).backstoryVersions.toVector.last.textId
|
||||
)
|
||||
)
|
||||
case Rejected =>
|
||||
@@ -310,7 +348,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
|
||||
provinces: Vector[ProvinceT],
|
||||
factions: Vector[FactionT],
|
||||
currentDate: Date,
|
||||
gameState: GameState
|
||||
gameState: GameStateProto
|
||||
): RandomState[Vector[ActionResultT]] =
|
||||
truceOffer.status match {
|
||||
case Accepted =>
|
||||
|
||||
+55
-70
@@ -2,93 +2,78 @@ package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.api.command.OneProvinceAvailableCommands
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.command.{HandleRiotUtils, TCommandFactory}
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, TCommand}
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.command.{CommandFactory, HandleRiotUtils}
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, RandomStateTSequencer}
|
||||
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChoiceHelpers
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.action_result.concrete.ActionResultC
|
||||
import net.eagle0.eagle.model.action_result.types.EndHandleRiotsPhaseResultType
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import net.eagle0.eagle.ProvinceId
|
||||
|
||||
case class EndHandleRiotsPhaseAction(
|
||||
gameState: GameState,
|
||||
commandsForProvince: ProvinceId => Option[OneProvinceAvailableCommands],
|
||||
commandFactory: TCommandFactory,
|
||||
actionResultApplier: ActionResultApplier
|
||||
commandFactory: CommandFactory,
|
||||
applier: ActionResultTApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
|
||||
private def provincesWithImminentRiot(gs: GameState): Vector[ProvinceT] =
|
||||
gs.provinces.values.filter(ProvinceUtils.hasImminentRiot).toVector
|
||||
|
||||
private def vassalCommandResults(
|
||||
sequencer: RandomStateSequencer
|
||||
): RandomStateSequencer = {
|
||||
val provincesToProcess = provincesWithImminentRiot(sequencer.lastState)
|
||||
ars: RandomStateTSequencer
|
||||
): RandomStateTSequencer =
|
||||
ars.lastStateProto.provinces.values
|
||||
.filter(LegacyProvinceUtils.hasImminentRiot)
|
||||
.filterNot(_.hasActed)
|
||||
|
||||
provincesToProcess.foldLeft(sequencer) {
|
||||
case (seq, province) =>
|
||||
commandsForProvince(province.id).map { opac =>
|
||||
seq.withRandomActionResults { (gs, fr) =>
|
||||
// Convert to proto for CommandChoiceHelpers which expects proto GameState
|
||||
val gsProto = GameStateConverter.toProto(gs)
|
||||
CommandChoiceHelpers
|
||||
.handleRiotSelectedCommand(
|
||||
actingFactionId = province.rulingFactionId.get,
|
||||
gameState = gsProto,
|
||||
availableCommands = opac.commands.toVector,
|
||||
functionalRandom = fr
|
||||
)
|
||||
.continue {
|
||||
case (Some(cs), nextFr) =>
|
||||
val cmd = commandFactory.makeTCommand(
|
||||
actingFactionId = province.rulingFactionId.get,
|
||||
gameState = gs,
|
||||
availableCommand = cs.available,
|
||||
selectedCommand = cs.selected
|
||||
)
|
||||
cmd match {
|
||||
case TCommand.Simple(action) =>
|
||||
RandomState(Vector(action.immediateExecute), nextFr)
|
||||
case TCommand.RandomSimple(action) =>
|
||||
action.immediateExecute(nextFr).map(ar => Vector(ar))
|
||||
case TCommand.Sequential(action) =>
|
||||
RandomState(action.results, nextFr)
|
||||
.foldLeft(ars) {
|
||||
case (sequencer, p) =>
|
||||
commandsForProvince(p.id).map { opac =>
|
||||
sequencer.withOptionalRandomTCommand { (gs, fr) =>
|
||||
CommandChoiceHelpers
|
||||
.handleRiotSelectedCommand(
|
||||
actingFactionId = p.getRulingFactionId,
|
||||
gameState = gs,
|
||||
availableCommands = opac.commands.toVector,
|
||||
functionalRandom = fr
|
||||
)
|
||||
.map { optCS =>
|
||||
optCS.map { cs =>
|
||||
commandFactory.makeTCommand(
|
||||
actingFactionId = p.getRulingFactionId,
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
availableCommand = cs.available,
|
||||
selectedCommand = cs.selected
|
||||
)
|
||||
}
|
||||
case (None, nextFr) =>
|
||||
RandomState(Vector.empty[ActionResultT], nextFr)
|
||||
}
|
||||
}
|
||||
}
|
||||
.getOrElse(seq)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.get
|
||||
}
|
||||
|
||||
private def riotOccurredResults(
|
||||
sequencer: RandomStateSequencer
|
||||
): RandomStateSequencer =
|
||||
provincesWithImminentRiot(sequencer.lastState).foldLeft(sequencer) {
|
||||
case (seq, province) =>
|
||||
seq.withActionResult(_ =>
|
||||
HandleRiotUtils.riotOccurredAr(
|
||||
province.rulingFactionId.get,
|
||||
province
|
||||
ars: RandomStateTSequencer
|
||||
): RandomStateTSequencer =
|
||||
ars.lastStateProto.provinces.values
|
||||
.filter(LegacyProvinceUtils.hasImminentRiot)
|
||||
.foldLeft(ars) {
|
||||
case (ars, p) =>
|
||||
ars.withActionResult(_ =>
|
||||
HandleRiotUtils
|
||||
.riotOccurredAr(
|
||||
p.getRulingFactionId,
|
||||
ProvinceConverter.fromProto(p)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def endPhaseResult(
|
||||
sequencer: RandomStateSequencer
|
||||
): RandomStateSequencer =
|
||||
sequencer.withActionResult { gs =>
|
||||
ars: RandomStateTSequencer
|
||||
): RandomStateTSequencer =
|
||||
ars.withActionResultT(gs =>
|
||||
ActionResultC(
|
||||
actionResultType = EndHandleRiotsPhaseResultType,
|
||||
newRoundPhase = Some(RoundPhase.HeroDepartures),
|
||||
@@ -96,17 +81,17 @@ case class EndHandleRiotsPhaseAction(
|
||||
.filter(_.hasActed)
|
||||
.map(p => ChangedProvinceC(provinceId = p.id, setHasActed = Some(false)))
|
||||
.toVector,
|
||||
removedNotifications = gs.deferredNotifications,
|
||||
newNotifications = gs.deferredNotifications.map(_.withDeferred(false))
|
||||
removedNotifications = gameState.deferredNotifications,
|
||||
newNotifications = gameState.deferredNotifications.map(_.withDeferred(false))
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
override def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[ActionResultT]] =
|
||||
RandomStateSequencer(
|
||||
RandomStateTSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
actionResultApplier = applier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.withRandomContinuance(vassalCommandResults)
|
||||
|
||||
+130
-77
@@ -1,12 +1,19 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, VigorXPApplier}
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.internal.deferred_change.*
|
||||
import net.eagle0.eagle.internal.deferred_change.DeferredChange.Empty
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{
|
||||
ProtolessRandomSequentialResultsAction,
|
||||
RandomStateTSequencer,
|
||||
VigorXPApplier
|
||||
}
|
||||
import net.eagle0.eagle.library.settings.PrisonerEscapeChance
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
import net.eagle0.eagle.library.util.ProvinceEventUtils
|
||||
import net.eagle0.eagle.library.EagleInternalException
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.action_result.changed_province.ChangedProvinceT
|
||||
@@ -19,14 +26,14 @@ import net.eagle0.eagle.model.action_result.types.{
|
||||
WeatherTookEffectResultType
|
||||
}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.{NotificationConverter, UnaffiliatedHeroConverter}
|
||||
import net.eagle0.eagle.model.proto_converters.date.DateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
|
||||
import net.eagle0.eagle.model.proto_converters.province.{ProvinceConverter, ProvinceEventConverter}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.{
|
||||
BlizzardEvent,
|
||||
DeferredChange,
|
||||
DeferredChangeT,
|
||||
DroughtEvent,
|
||||
EpidemicEvent
|
||||
}
|
||||
import net.eagle0.eagle.model.state.province.{BlizzardEvent, DroughtEvent, EpidemicEvent}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.RecruitmentInfo
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.{Outlaw, Prisoner}
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
@@ -34,11 +41,13 @@ import net.eagle0.eagle.ProvinceId
|
||||
|
||||
case class EndPlayerCommandsPhaseAction(
|
||||
gameState: GameState,
|
||||
actionResultApplier: ActionResultApplier
|
||||
applier: ActionResultTApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
private val gameStateProto: GameStateProto = GameStateConverter.toProto(gameState)
|
||||
|
||||
private def endPhaseResult(gs: GameState): ActionResultT = {
|
||||
private def endPhaseResult(gs: GameStateProto): ActionResultT = {
|
||||
val abandonedProvinces: Vector[ChangedProvinceT] = gs.provinces.values
|
||||
.map(ProvinceConverter.fromProto)
|
||||
.filter(_.rulingFactionId.isDefined)
|
||||
.flatMap(ProvinceUtils.checkedForAbandonment)
|
||||
.toVector
|
||||
@@ -57,22 +66,27 @@ case class EndPlayerCommandsPhaseAction(
|
||||
)
|
||||
)
|
||||
},
|
||||
removedNotifications = gs.deferredNotifications.map(_.withDeferred(true)),
|
||||
newNotifications = gs.deferredNotifications.map(_.withDeferred(false))
|
||||
removedNotifications = gameStateProto.deferredNotifications.map { note =>
|
||||
NotificationConverter.fromProto(note, deferred = true)
|
||||
}.toVector,
|
||||
newNotifications = gameStateProto.deferredNotifications.map { note =>
|
||||
NotificationConverter.fromProto(note, deferred = false)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private def onePrisonerMovedChange(
|
||||
pid: ProvinceId,
|
||||
prisonerMoved: DeferredChange.PrisonerMoved,
|
||||
gs: GameState,
|
||||
prisonerMoved: PrisonerMoved,
|
||||
gs: GameStateProto,
|
||||
fr: FunctionalRandom
|
||||
): RandomState[ActionResultT] = {
|
||||
val uh = gs
|
||||
.provinces(pid)
|
||||
.unaffiliatedHeroes
|
||||
.find(_.heroId == prisonerMoved.heroId)
|
||||
.map(UnaffiliatedHeroConverter.fromProto)
|
||||
|
||||
fr.nextDouble.map { doubleValue =>
|
||||
val escaped = doubleValue < PrisonerEscapeChance.doubleValue
|
||||
@@ -112,8 +126,8 @@ case class EndPlayerCommandsPhaseAction(
|
||||
|
||||
private def onePrisonerReturnedChange(
|
||||
pid: ProvinceId,
|
||||
prisonerReturned: DeferredChange.PrisonerReturned,
|
||||
gs: GameState,
|
||||
prisonerReturned: PrisonerReturned,
|
||||
gs: GameStateProto,
|
||||
fr: FunctionalRandom
|
||||
): RandomState[ActionResultT] =
|
||||
RandomState(
|
||||
@@ -145,28 +159,28 @@ case class EndPlayerCommandsPhaseAction(
|
||||
|
||||
private def notPrisonerChange(
|
||||
pid: ProvinceId,
|
||||
deferredChange: DeferredChangeT,
|
||||
gs: GameState,
|
||||
deferredChange: DeferredChange,
|
||||
gs: GameStateProto,
|
||||
fr: FunctionalRandom
|
||||
): RandomState[ActionResultT] = {
|
||||
val currentDate = gs.currentDate.get
|
||||
val province = gs.provinces(pid)
|
||||
|
||||
): RandomState[ActionResultT] =
|
||||
RandomState(
|
||||
VigorXPApplier.withVigorXp(
|
||||
ActionResultC(
|
||||
actionResultType = deferredChange match {
|
||||
case _: DeferredChange.EpidemicStarted => EpidemicTookEffectResultType
|
||||
case _: DeferredChange.BlizzardEnded => WeatherTookEffectResultType
|
||||
case _: DeferredChange.BlizzardStarted => WeatherTookEffectResultType
|
||||
case _: DeferredChange.DroughtStarted => WeatherTookEffectResultType
|
||||
case _: DeferredChange.DroughtEnded => WeatherTookEffectResultType
|
||||
case _: DeferredChange.PrisonerMoved | _: DeferredChange.PrisonerReturned |
|
||||
_: DeferredChange.CapturedHeroImprisoned | _: DeferredChange.CapturedHeroExecuted |
|
||||
_: DeferredChange.CapturedHeroExiled | _: DeferredChange.CapturedHeroReturned =>
|
||||
case _: EpidemicStarted => EpidemicTookEffectResultType
|
||||
case _: BlizzardEnded => WeatherTookEffectResultType
|
||||
case _: BlizzardStarted => WeatherTookEffectResultType
|
||||
case _: DroughtStarted => WeatherTookEffectResultType
|
||||
case _: DroughtEnded => WeatherTookEffectResultType
|
||||
case _: PrisonerMoved | _: PrisonerReturned | _: CapturedHeroImprisoned | _: CapturedHeroExecuted |
|
||||
_: CapturedHeroExiled | _: CapturedHeroReturned =>
|
||||
throw new EagleInternalException(
|
||||
"Prisoner management changes should not be here"
|
||||
)
|
||||
case DeferredChange.Empty =>
|
||||
throw new EagleInternalException(
|
||||
"Empty deferred change should not be here"
|
||||
)
|
||||
},
|
||||
provinceId = Some(pid),
|
||||
changedProvinces = Vector(
|
||||
@@ -174,31 +188,70 @@ case class EndPlayerCommandsPhaseAction(
|
||||
provinceId = pid,
|
||||
newProvinceEvents = Some(
|
||||
deferredChange match {
|
||||
case DeferredChange.BlizzardStarted(_, durationMonths) =>
|
||||
province.activeEvents :+ BlizzardEvent(
|
||||
startDate = currentDate,
|
||||
endDate = currentDate.addMonths(durationMonths)
|
||||
case BlizzardStarted(
|
||||
_,
|
||||
durationMonths,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
gs.provinces(pid)
|
||||
.activeEvents
|
||||
.map(ProvinceEventConverter.fromProto)
|
||||
.toVector :+ BlizzardEvent(
|
||||
startDate = DateConverter.fromProto(gs.currentDate),
|
||||
endDate = DateConverter
|
||||
.fromProto(gs.currentDate)
|
||||
.addMonths(
|
||||
durationMonths
|
||||
)
|
||||
)
|
||||
case _: DeferredChange.BlizzardEnded =>
|
||||
province.activeEvents.filter { case _: BlizzardEvent => false; case _ => true }
|
||||
case BlizzardEnded(_, _ /* unknownFieldSet */ ) =>
|
||||
gs.provinces(pid)
|
||||
.activeEvents
|
||||
.filterNot(ProvinceEventUtils.isBlizzardEvent)
|
||||
.map(ProvinceEventConverter.fromProto)
|
||||
.toVector
|
||||
|
||||
case DeferredChange.DroughtStarted(_, durationMonths) =>
|
||||
province.activeEvents :+ DroughtEvent(
|
||||
startDate = currentDate,
|
||||
endDate = currentDate.addMonths(durationMonths)
|
||||
case DroughtStarted(
|
||||
_,
|
||||
durationMonths,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
gs.provinces(pid)
|
||||
.activeEvents
|
||||
.map(ProvinceEventConverter.fromProto)
|
||||
.toVector :+ DroughtEvent(
|
||||
startDate = DateConverter.fromProto(gs.currentDate),
|
||||
endDate = DateConverter
|
||||
.fromProto(gs.currentDate)
|
||||
.addMonths(durationMonths)
|
||||
)
|
||||
case _: DeferredChange.DroughtEnded =>
|
||||
province.activeEvents.filter { case _: DroughtEvent => false; case _ => true }
|
||||
|
||||
case _: DeferredChange.EpidemicStarted =>
|
||||
province.activeEvents :+ EpidemicEvent(startDate = currentDate)
|
||||
case DroughtEnded(_, _ /* unknownFieldSet */ ) =>
|
||||
gs.provinces(pid)
|
||||
.activeEvents
|
||||
.filterNot(ProvinceEventUtils.isDroughtEvent)
|
||||
.map(ProvinceEventConverter.fromProto)
|
||||
.toVector
|
||||
|
||||
case _: DeferredChange.PrisonerMoved => Vector()
|
||||
case _: DeferredChange.PrisonerReturned => Vector()
|
||||
case _: DeferredChange.CapturedHeroImprisoned => Vector()
|
||||
case _: DeferredChange.CapturedHeroExecuted => Vector()
|
||||
case _: DeferredChange.CapturedHeroExiled => Vector()
|
||||
case _: DeferredChange.CapturedHeroReturned => Vector()
|
||||
case EpidemicStarted(_, _ /* unknownFieldSet */ ) =>
|
||||
gs.provinces(pid)
|
||||
.activeEvents
|
||||
.map(ProvinceEventConverter.fromProto)
|
||||
.toVector :+ EpidemicEvent(
|
||||
startDate = DateConverter.fromProto(gs.currentDate)
|
||||
)
|
||||
|
||||
case _: PrisonerMoved => Vector()
|
||||
case _: PrisonerReturned => Vector()
|
||||
case _: CapturedHeroImprisoned => Vector()
|
||||
case _: CapturedHeroExecuted => Vector()
|
||||
case _: CapturedHeroExiled => Vector()
|
||||
case _: CapturedHeroReturned => Vector()
|
||||
|
||||
case Empty =>
|
||||
throw new EagleInternalException(
|
||||
"Empty deferred change should not be here"
|
||||
)
|
||||
}
|
||||
),
|
||||
removedDeferredChangeIndex = Some(0)
|
||||
@@ -208,27 +261,26 @@ case class EndPlayerCommandsPhaseAction(
|
||||
),
|
||||
fr
|
||||
)
|
||||
}
|
||||
|
||||
private def oneDeferredProvinceChange(
|
||||
pid: ProvinceId,
|
||||
deferredChange: DeferredChangeT,
|
||||
gs: GameState,
|
||||
deferredChange: DeferredChange,
|
||||
gs: GameStateProto,
|
||||
fr: FunctionalRandom
|
||||
): RandomState[ActionResultT] =
|
||||
deferredChange match {
|
||||
case prisonerMoved: DeferredChange.PrisonerMoved =>
|
||||
case prisonerMoved: PrisonerMoved =>
|
||||
onePrisonerMovedChange(pid, prisonerMoved, gs, fr)
|
||||
case prisonerReturned: DeferredChange.PrisonerReturned =>
|
||||
case prisonerReturned: PrisonerReturned =>
|
||||
onePrisonerReturnedChange(pid, prisonerReturned, gs, fr)
|
||||
case _ => notPrisonerChange(pid, deferredChange, gs, fr)
|
||||
case _ => notPrisonerChange(pid, deferredChange, gs, fr)
|
||||
}
|
||||
|
||||
private def deferredProvinceChangesResultsForProvince(
|
||||
pid: ProvinceId,
|
||||
sequencer: RandomStateSequencer
|
||||
): RandomStateSequencer =
|
||||
sequencer.lastState.provinces(pid).deferredChanges.foldLeft(sequencer) {
|
||||
arsRS: RandomStateTSequencer
|
||||
): RandomStateTSequencer =
|
||||
arsRS.lastStateProto.provinces(pid).deferredChanges.foldLeft(arsRS) {
|
||||
case (acc, dc) =>
|
||||
acc.withRandomActionResult {
|
||||
case (gs, fr) =>
|
||||
@@ -237,40 +289,41 @@ case class EndPlayerCommandsPhaseAction(
|
||||
}
|
||||
|
||||
private def deferredProvinceChangesResults(
|
||||
sequencer: RandomStateSequencer
|
||||
): RandomStateSequencer =
|
||||
sequencer.lastState.provinces.keys
|
||||
.foldLeft(sequencer) {
|
||||
case (newSequencer, pid) =>
|
||||
deferredProvinceChangesResultsForProvince(pid, newSequencer)
|
||||
ars: RandomStateTSequencer
|
||||
): RandomStateTSequencer =
|
||||
ars.lastStateProto.provinces.keys
|
||||
.foldLeft(ars) {
|
||||
case (newArs, pid) =>
|
||||
deferredProvinceChangesResultsForProvince(pid, newArs)
|
||||
}
|
||||
|
||||
override def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[ActionResultT]] = {
|
||||
gameState.provinces.values.foreach { p =>
|
||||
gameStateProto.provinces.values.foreach { p =>
|
||||
internalRequire(
|
||||
!p.rulerIsTraveling,
|
||||
s"Leader is traveling at the end of the PlayerCommandsPhase in province ${p.id}"
|
||||
)
|
||||
}
|
||||
|
||||
RandomStateSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
RandomStateTSequencer
|
||||
.fromProto(
|
||||
initialStateProto = gameStateProto,
|
||||
actionResultApplier = applier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.withRandomActionResults((gs, fr) =>
|
||||
CheckForFactionChangesAction(
|
||||
gameId = gs.gameId,
|
||||
factions = gs.factions.values.toVector,
|
||||
provinces = gs.provinces.values.toVector,
|
||||
heroes = gs.heroes.values.toVector,
|
||||
factions = gs.factions.values.toVector.map(FactionConverter.fromProto),
|
||||
provinces = gs.provinces.values.toVector.map(ProvinceConverter.fromProto),
|
||||
heroes = gs.heroes.values.toVector.map(HeroConverter.fromProto),
|
||||
killedHeroIds = gs.killedHeroes.keys.toVector
|
||||
).randomResults(fr)
|
||||
)
|
||||
.withContinuance(deferredProvinceChangesResults)
|
||||
.withActionResults(gs => HeroBackstoryUpdateActionGenerator(gs).results)
|
||||
.withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator.fromGameState(gs))
|
||||
.withActionResult(endPhaseResult)
|
||||
.actionResults
|
||||
}
|
||||
|
||||
+39
-35
@@ -1,23 +1,24 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessRandomSequentialResultsAction
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{RandomStateTSequencer, TRandomSequentialResultsAction}
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
import net.eagle0.eagle.model.action_result.concrete.ActionResultC
|
||||
import net.eagle0.eagle.model.action_result.types.EndVassalCommandsPhaseResultType
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionTypeConverter
|
||||
import net.eagle0.eagle.model.proto_converters.date.DateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
|
||||
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
|
||||
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
|
||||
case class EndVassalCommandsPhaseAction(
|
||||
gameState: GameState,
|
||||
actionResultApplier: ActionResultApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
case class EndVassalCommandsPhaseAction(gameState: GameState) extends TRandomSequentialResultsAction(gameState) {
|
||||
override protected def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
functionalRandom: FunctionalRandom,
|
||||
actionResultTApplier: ActionResultTApplier
|
||||
): RandomState[Vector[ActionResultT]] = {
|
||||
gameState.provinces.values.foreach { p =>
|
||||
internalRequire(
|
||||
@@ -26,53 +27,56 @@ case class EndVassalCommandsPhaseAction(
|
||||
)
|
||||
}
|
||||
|
||||
RandomStateSequencer(
|
||||
RandomStateTSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
actionResultApplier = actionResultTApplier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.withActionResults(gs =>
|
||||
.withProtolessSequentialResultsAction(gs =>
|
||||
CheckForFulfilledQuestsAction(
|
||||
gameId = gs.gameId,
|
||||
currentDate = gs.currentDate.get,
|
||||
currentDate = DateConverter.fromProto(gs.currentDate),
|
||||
currentRoundId = gs.currentRoundId,
|
||||
provinces = gs.provinces.values.toVector,
|
||||
factions = gs.factions.values.toVector,
|
||||
battalions = gs.battalions.values.toVector,
|
||||
getHero = hid => gs.heroes.get(hid),
|
||||
battalionTypes = gs.battalionTypes.map(BattalionTypeConverter.toProto),
|
||||
hid => gs.heroes(hid).backstoryTextId
|
||||
).results
|
||||
provinces = gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
|
||||
factions = gs.factions.values
|
||||
.map(FactionConverter.fromProto)
|
||||
.toVector,
|
||||
battalions = gs.battalions.values.toVector.map(BattalionConverter.fromProto),
|
||||
getHero = hid => gs.heroes.get(hid).map(HeroConverter.fromProto),
|
||||
battalionTypes = gs.battalionTypes.toVector,
|
||||
hid => gs.heroes(hid).backstoryVersions.last.textId
|
||||
)
|
||||
)
|
||||
.withActionResults(gs =>
|
||||
.withProtolessSequentialResultsAction(gs =>
|
||||
CheckForFailedQuestsAction(
|
||||
gameId = gs.gameId,
|
||||
currentDate = gs.currentDate.get,
|
||||
currentDate = DateConverter.fromProto(gs.currentDate),
|
||||
currentRoundId = gs.currentRoundId,
|
||||
provinces = gs.provinces.values.toVector,
|
||||
factions = gs.factions.values.toVector,
|
||||
hid => gs.heroes(hid).backstoryTextId
|
||||
).results
|
||||
provinces = gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
|
||||
factions = gs.factions.values
|
||||
.map(FactionConverter.fromProto)
|
||||
.toVector,
|
||||
hid => gs.heroes(hid).backstoryVersions.last.textId
|
||||
)
|
||||
)
|
||||
.withRandomActionResults((gs, fr) =>
|
||||
CheckForFactionChangesAction(
|
||||
gameId = gs.gameId,
|
||||
factions = gs.factions.values.toVector,
|
||||
provinces = gs.provinces.values.toVector,
|
||||
heroes = gs.heroes.values.toVector,
|
||||
factions = gs.factions.values.map(FactionConverter.fromProto).toVector,
|
||||
provinces = gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
|
||||
heroes = gs.heroes.values.map(HeroConverter.fromProto).toVector,
|
||||
killedHeroIds = gs.killedHeroes.keys.toVector
|
||||
).randomResults(fr)
|
||||
)
|
||||
.withActionResults(gs => HeroBackstoryUpdateActionGenerator(gs).results)
|
||||
.withActionResult { gs =>
|
||||
// Use current state's deferred notifications, not the initial gameState
|
||||
.withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator.fromGameState(gs))
|
||||
.withActionResultT(_ =>
|
||||
ActionResultC(
|
||||
actionResultType = EndVassalCommandsPhaseResultType,
|
||||
newRoundPhase = Some(RoundPhase.PlayerCommands),
|
||||
removedNotifications = gs.deferredNotifications.map(_.withDeferred(true)),
|
||||
newNotifications = gs.deferredNotifications.map(_.withDeferred(false))
|
||||
removedNotifications = gameState.deferredNotifications,
|
||||
newNotifications = gameState.deferredNotifications.map(_.withDeferred(false))
|
||||
)
|
||||
}
|
||||
)
|
||||
.actionResults
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.{HeroId, ProvinceId, RoundId}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessRandomSequentialResultsAction
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{RandomStateTSequencer, TRandomSequentialResultsAction}
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
EmptyProvinceMonthlyDevastationDelta,
|
||||
FactionBiasMinimumAdjustmentPerRound,
|
||||
@@ -15,7 +15,8 @@ import net.eagle0.eagle.library.settings.{
|
||||
OverResourceLimitLoss,
|
||||
TrustDeltaPerRound
|
||||
}
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
import net.eagle0.eagle.library.util.DateProtoUtils._Date
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
import net.eagle0.eagle.library.util.PriceIndexUtils
|
||||
import net.eagle0.eagle.library.GameHistory
|
||||
@@ -31,34 +32,37 @@ import net.eagle0.eagle.model.action_result.concrete.{
|
||||
import net.eagle0.eagle.model.action_result.generated_text_request.{ChronicleUpdatePreviousEntry, LlmRequestT}
|
||||
import net.eagle0.eagle.model.action_result.types.NewRoundActionResultType
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.date.DateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.generated_text_request.chronicle_event.ChronicleEventConverter
|
||||
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
|
||||
import net.eagle0.eagle.model.proto_converters.UnaffiliatedHeroConverter
|
||||
import net.eagle0.eagle.model.state.chronicle_entry.ChronicleEntry
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship.RelationshipLevel
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroT
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
|
||||
case class NewRoundAction(
|
||||
gameState: GameState,
|
||||
gameHistory: GameHistory,
|
||||
actionResultApplier: ActionResultApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
case class NewRoundAction(gameState: GameState, gameHistory: GameHistory)
|
||||
extends TRandomSequentialResultsAction(gameState) {
|
||||
|
||||
private val startingState: GameStateProto = GameStateConverter.toProto(gameState)
|
||||
|
||||
private val chronicleMonths = Vector(11)
|
||||
|
||||
private def isChronicleMonth(newDate: Date): Boolean =
|
||||
chronicleMonths.contains(newDate.month.value)
|
||||
|
||||
override def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
override protected def randomResults(
|
||||
functionalRandom: FunctionalRandom,
|
||||
actionResultTApplier: ActionResultTApplier
|
||||
): RandomState[Vector[ActionResultT]] = {
|
||||
val newRoundId = gameState.currentRoundId + 1
|
||||
val newRoundId = startingState.currentRoundId + 1
|
||||
|
||||
// Verify no old incoming armies
|
||||
gameState.provinces.foreach {
|
||||
startingState.provinces.foreach {
|
||||
case (pid, province) =>
|
||||
internalRequire(
|
||||
province.incomingArmies.forall(_.arrivalRound >= newRoundId),
|
||||
@@ -70,26 +74,28 @@ case class NewRoundAction(
|
||||
)
|
||||
}
|
||||
|
||||
val oldDate = gameState.currentDate.get
|
||||
val oldDate = DateConverter.fromProto(startingState.currentDate)
|
||||
val newDate = oldDate.addMonths(1)
|
||||
|
||||
// Build sequencer starting from starting state
|
||||
val initialSequencer = RandomStateSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
val initialSequencer = RandomStateTSequencer.fromProto(
|
||||
initialStateProto = startingState,
|
||||
actionResultApplier = actionResultTApplier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
|
||||
// Optionally add NewYearAction
|
||||
val afterNewYearSequencer =
|
||||
if newDate.month == Date.Month.January then
|
||||
initialSequencer.withActionResult(_ => NewYearAction(gameState).immediateExecute)
|
||||
initialSequencer.withActionResultT(_ =>
|
||||
NewYearAction(GameStateConverter.fromProto(startingState)).immediateExecute
|
||||
)
|
||||
else initialSequencer
|
||||
|
||||
// Add the main new round result
|
||||
val afterNewRoundSequencer = afterNewYearSequencer.withActionResult { currentState =>
|
||||
val afterNewRoundSequencer = afterNewYearSequencer.withActionResultT { currentState =>
|
||||
newRoundResult(
|
||||
gs = currentState,
|
||||
startingState = currentState,
|
||||
newRoundId = newRoundId,
|
||||
newDate = newDate
|
||||
)
|
||||
@@ -97,31 +103,34 @@ case class NewRoundAction(
|
||||
|
||||
// Add stat gain checks (and profession gain for stats that newly cross the threshold)
|
||||
afterNewRoundSequencer.withRandomActionResults { (latestState, nextRandom) =>
|
||||
HeroStatGainAction(latestState.heroes.values, latestState.gameId, newDate).randomResultsWithState(nextRandom)
|
||||
val scalaHeroes = latestState.heroes.values.map(HeroConverter.fromProto)
|
||||
val scalaDate = DateConverter.fromProto(Some(DateConverter.toProto(newDate)))
|
||||
HeroStatGainAction(scalaHeroes, latestState.gameId, scalaDate).randomResultsWithState(nextRandom)
|
||||
}.actionResults
|
||||
}
|
||||
|
||||
private def chronicleLlmRequests(
|
||||
newDate: Date,
|
||||
gs: GameState
|
||||
startingState: GameStateProto
|
||||
): Vector[LlmRequestT] =
|
||||
if isChronicleMonth(newDate) then
|
||||
Vector(
|
||||
LlmRequestT.ChronicleUpdateMessage(
|
||||
requestId = s"chronicle_update_${newDate.year}_${newDate.month.value}",
|
||||
eagleGameId = gs.gameId,
|
||||
eagleGameId = startingState.gameId,
|
||||
current_date = newDate,
|
||||
previous_entries = gs.chronicleEntries.map { entry =>
|
||||
previous_entries = startingState.chronicleEntries.map { entry =>
|
||||
ChronicleUpdatePreviousEntry(
|
||||
date = entry.date,
|
||||
date = DateConverter.fromProto(entry.date),
|
||||
generatedTextId = entry.generatedTextId
|
||||
)
|
||||
},
|
||||
}.toVector,
|
||||
new_entries = ChronicleEventGenerator
|
||||
.eventTextEntries(
|
||||
gameHistory = gameHistory,
|
||||
since = gs.chronicleEntries.lastOption
|
||||
.map(_.date)
|
||||
since = startingState.chronicleEntries.lastOption
|
||||
.flatMap(_.date)
|
||||
.map(d => DateConverter.fromProto(Some(d)))
|
||||
.getOrElse(Date(year = 0, month = Date.Month.January))
|
||||
)
|
||||
.map(ChronicleEventConverter.fromProto)
|
||||
@@ -129,11 +138,8 @@ case class NewRoundAction(
|
||||
)
|
||||
else Vector()
|
||||
|
||||
private def dateBefore(a: Date, b: Date): Boolean =
|
||||
a.year < b.year || (a.year == b.year && a.month.value < b.month.value)
|
||||
|
||||
private def newRoundResult(
|
||||
gs: GameState,
|
||||
startingState: GameStateProto,
|
||||
newRoundId: RoundId,
|
||||
newDate: Date
|
||||
): ActionResultT = {
|
||||
@@ -143,28 +149,29 @@ case class NewRoundAction(
|
||||
)
|
||||
|
||||
val changes: Iterable[Changes] =
|
||||
gs.provinces.values.map { p =>
|
||||
startingState.provinces.values.map { p =>
|
||||
val changedUHs: Vector[UnaffiliatedHeroT] =
|
||||
p.unaffiliatedHeroes.map { uh =>
|
||||
uh.copy(
|
||||
val tUh = UnaffiliatedHeroConverter.fromProto(uh)
|
||||
tUh.copy(
|
||||
recruitmentAttempted = false,
|
||||
roundsInType = uh.roundsInType + 1,
|
||||
factionBiases = uh.factionBiases.map {
|
||||
roundsInType = tUh.roundsInType + 1,
|
||||
factionBiases = tUh.factionBiases.map {
|
||||
case (fid, v) =>
|
||||
fid -> modifiedFactionBiasValue(v)
|
||||
}
|
||||
)
|
||||
}
|
||||
val changedHeroes = changedHeroesAfterOverage(gs, p.id)
|
||||
}.toVector
|
||||
val changedHeroes = changedHeroesAfterOverage(startingState, p.id)
|
||||
|
||||
// apply caps
|
||||
val capGoldLoss = (OverResourceLimitLoss.doubleValue * Math.max(
|
||||
0,
|
||||
p.gold - ProvinceUtils.goldCap(p)
|
||||
p.gold - LegacyProvinceUtils.goldCap(p)
|
||||
)).ceil.toInt
|
||||
val capFoodLoss = (OverResourceLimitLoss.doubleValue * Math.max(
|
||||
0,
|
||||
p.food - ProvinceUtils.foodCap(p)
|
||||
p.food - LegacyProvinceUtils.foodCap(p)
|
||||
)).ceil.toInt
|
||||
|
||||
val newPriceIndex = PriceIndexUtils.shiftedTowardSteadyState(
|
||||
@@ -221,7 +228,7 @@ case class NewRoundAction(
|
||||
val heroesAfterStipend: Map[HeroId, ChangedHeroC] =
|
||||
changes.flatMap(_.changedHeroes).map(h => h.heroId -> h).toMap
|
||||
|
||||
val uppedVigorHeroes: Iterable[ChangedHeroC] = gs.heroes.map {
|
||||
val uppedVigorHeroes: Iterable[ChangedHeroC] = startingState.heroes.map {
|
||||
case (hid, h) =>
|
||||
val baseChange = heroesAfterStipend.getOrElse(hid, ChangedHeroC(heroId = hid))
|
||||
if h.vigor >= h.constitution then baseChange
|
||||
@@ -236,9 +243,10 @@ case class NewRoundAction(
|
||||
val changedHeroes =
|
||||
uppedVigorHeroes.filter(ch => ch.hasChanges).toVector
|
||||
|
||||
val changedFactions = gs.factions.values.map { f =>
|
||||
val newDateProto = DateConverter.toProto(newDate)
|
||||
val changedFactions = startingState.factions.values.map { f =>
|
||||
val removedTruces =
|
||||
f.factionRelationships.filter(fr => fr.resetDate.exists(dateBefore(_, newDate)))
|
||||
f.factionRelationships.filter(fr => fr.resetDate.exists(_ < newDateProto))
|
||||
|
||||
ChangedFactionC(
|
||||
factionId = f.id,
|
||||
@@ -249,15 +257,15 @@ case class NewRoundAction(
|
||||
resetDate = None,
|
||||
trustValue = fr.trustValue
|
||||
)
|
||||
},
|
||||
trustLevelUpdates = gs.factions.keys.filterNot(_ == f.id).toVector.map { targetFid =>
|
||||
}.toVector,
|
||||
trustLevelUpdates = startingState.factions.keys.filterNot(_ == f.id).toVector.map { targetFid =>
|
||||
TrustLevelUpdate(targetFid, TrustDeltaPerRound.intValue)
|
||||
},
|
||||
clearLastActedProvinceId = true
|
||||
)
|
||||
}.toVector
|
||||
|
||||
val llmRequests = chronicleLlmRequests(newDate, gs)
|
||||
val llmRequests = chronicleLlmRequests(newDate, startingState)
|
||||
|
||||
ActionResultC(
|
||||
actionResultType = NewRoundActionResultType,
|
||||
@@ -278,21 +286,21 @@ case class NewRoundAction(
|
||||
}
|
||||
|
||||
private def changedHeroesAfterOverage(
|
||||
gs: GameState,
|
||||
gameState: GameStateProto,
|
||||
provinceId: ProvinceId
|
||||
): Vector[ChangedHeroC] = {
|
||||
val province = gs.provinces(provinceId)
|
||||
val province = gameState.provinces(provinceId)
|
||||
val notRecentHeroCount = province.rulingFactionHeroIds
|
||||
.map(gs.heroes)
|
||||
.map(gameState.heroes)
|
||||
.flatMap(_.roundIdJoined)
|
||||
.count(roundId => gs.currentRoundId - roundId > MinimumRoundsBeforeLoyaltyDegrades.intValue)
|
||||
.count(roundId => gameState.currentRoundId - roundId > MinimumRoundsBeforeLoyaltyDegrades.intValue)
|
||||
if province.heroCap >= notRecentHeroCount then Vector.empty
|
||||
else {
|
||||
val loyaltyHit =
|
||||
OverHeroCapLoyaltyDelta.doubleValue * (notRecentHeroCount - province.heroCap)
|
||||
|
||||
province.rulingFactionHeroIds
|
||||
.map(gs.heroes)
|
||||
.map(gameState.heroes)
|
||||
.map(h =>
|
||||
ChangedHeroC(
|
||||
heroId = h.id,
|
||||
@@ -301,6 +309,7 @@ case class NewRoundAction(
|
||||
else StatDelta(loyaltyHit)
|
||||
)
|
||||
)
|
||||
.toVector
|
||||
}
|
||||
end if
|
||||
}
|
||||
|
||||
+39
-33
@@ -2,9 +2,9 @@ package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessRandomSequentialResultsAction
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.internal.province.IncomingEndTurnAction as IncomingEndTurnActionProto
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{RandomStateTSequencer, TRandomSequentialResultsAction}
|
||||
import net.eagle0.eagle.library.util.view_filters.ProvinceViewFilter
|
||||
import net.eagle0.eagle.library.util.ReturningHeroes
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
@@ -12,34 +12,33 @@ import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedFact
|
||||
import net.eagle0.eagle.model.action_result.types.{EndReconResolutionPhaseResultType, ReconSucceededResultType}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
|
||||
import net.eagle0.eagle.model.proto_converters.IncomingEndTurnActionConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.{IncomingEndTurnAction, IncomingRecon}
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import net.eagle0.eagle.ProvinceId
|
||||
|
||||
case class PerformReconResolutionAction(
|
||||
gameState: GameState,
|
||||
actionResultApplier: ActionResultApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
gameState: GameState
|
||||
) extends TRandomSequentialResultsAction(gameState) {
|
||||
|
||||
// Proto GameState still needed for ProvinceViewFilter.filteredProvinceView
|
||||
private lazy val startingGameStateProto: GameStateProto = GameStateConverter.toProto(gameState)
|
||||
private val startingGameState: GameStateProto = GameStateConverter.toProto(gameState)
|
||||
|
||||
override protected def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
functionalRandom: FunctionalRandom,
|
||||
actionResultTApplier: ActionResultTApplier
|
||||
): RandomState[Vector[ActionResultT]] =
|
||||
gameState.provinces.values
|
||||
startingGameState.provinces.values
|
||||
.foldLeft(
|
||||
RandomStateSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
RandomStateTSequencer.fromProto(
|
||||
initialStateProto = startingGameState,
|
||||
actionResultApplier = actionResultTApplier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
) {
|
||||
case (sequencer, province) =>
|
||||
province.incomingEndTurnActions.collect {
|
||||
case action @ IncomingEndTurnAction(_, _, _: IncomingRecon) => action
|
||||
}
|
||||
province.incomingEndTurnActions
|
||||
.filter(_.action.isRecon)
|
||||
.foldLeft(sequencer) {
|
||||
case (innerSequencer, action) =>
|
||||
innerSequencer.withRandomActionResult {
|
||||
@@ -56,25 +55,26 @@ case class PerformReconResolutionAction(
|
||||
)
|
||||
}
|
||||
|
||||
// Package-private for testing
|
||||
private[action] def oneResult(
|
||||
def oneResult(
|
||||
provinceId: ProvinceId,
|
||||
incomingEndTurnAction: IncomingEndTurnAction,
|
||||
incomingEndTurnAction: IncomingEndTurnActionProto,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[ActionResultT] = {
|
||||
val originProvince = gameState.provinces(incomingEndTurnAction.fromProvinceId)
|
||||
val originProvince =
|
||||
startingGameState.provinces(incomingEndTurnAction.fromProvinceId)
|
||||
val fromFactionId = incomingEndTurnAction.fromFactionId
|
||||
val targetProvince = gameState.provinces(provinceId)
|
||||
val targetProvince = startingGameState.provinces(provinceId)
|
||||
|
||||
// Extract heroId from the recon action details
|
||||
val heroId = incomingEndTurnAction.details.asInstanceOf[IncomingRecon].heroId
|
||||
val recon = incomingEndTurnAction.getRecon
|
||||
|
||||
ReturningHeroes
|
||||
.heroesReturningToFaction(
|
||||
hids = Vector(heroId),
|
||||
hids = Vector(recon.heroId),
|
||||
factionId = fromFactionId,
|
||||
originProvince = originProvince,
|
||||
provinces = gameState.provinces.values.toVector,
|
||||
originProvince = ProvinceConverter.fromProto(originProvince),
|
||||
provinces = startingGameState.provinces.values
|
||||
.map(ProvinceConverter.fromProto)
|
||||
.toVector,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.map { returningHeroes =>
|
||||
@@ -85,29 +85,35 @@ case class PerformReconResolutionAction(
|
||||
// the CP for the destination province
|
||||
ChangedProvinceC(
|
||||
provinceId = provinceId,
|
||||
removedIncomingEndTurnActions = Vector(incomingEndTurnAction)
|
||||
removedIncomingEndTurnActions = Vector(
|
||||
IncomingEndTurnActionConverter.fromProto(incomingEndTurnAction)
|
||||
)
|
||||
),
|
||||
// the CP for the acting hero returning
|
||||
returningHeroes.changedProvince
|
||||
),
|
||||
changedFactions = Vector(
|
||||
ChangedFactionC(
|
||||
factionId = fromFactionId,
|
||||
factionId = incomingEndTurnAction.fromFactionId,
|
||||
updatedReconnedProvinces = Vector(
|
||||
ProvinceViewFilter
|
||||
.filteredProvinceView(
|
||||
startingGameStateProto.provinces(provinceId),
|
||||
startingGameStateProto
|
||||
targetProvince,
|
||||
startingGameState
|
||||
// FIXME: setting factionId to None right now to grab the full info. This
|
||||
// probably isn't exactly what we want.
|
||||
)
|
||||
.withAsOf(startingGameStateProto.currentDate.get)
|
||||
.withAsOf(startingGameState.currentDate.get)
|
||||
)
|
||||
)
|
||||
),
|
||||
clientTextVisibilityExtensions = targetProvince.rulingFactionHeroIds.map { hid =>
|
||||
ClientTextVisibilityExtensionC(
|
||||
textId = gameState.heroes(hid).backstoryTextId,
|
||||
textId = startingGameState
|
||||
.heroes(hid)
|
||||
.backstoryVersions
|
||||
.last
|
||||
.textId,
|
||||
recipientFactionIds = Vector(fromFactionId)
|
||||
)
|
||||
}.toVector
|
||||
|
||||
+20
-17
@@ -2,10 +2,9 @@ package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.{HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessRandomSequentialResultsAction
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{RandomStateTSequencer, TRandomSequentialResultsAction}
|
||||
import net.eagle0.eagle.library.actions.name_generation_request.NameGenerationRequestCreator
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
FreeHeroMoveVigorCost,
|
||||
MinVigorForFreeHeroMove,
|
||||
@@ -27,6 +26,9 @@ import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedHero
|
||||
import net.eagle0.eagle.model.action_result.types.{HeroChangedResultType, HeroMovedResultType, NewQuestsResultType}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.action_result.NotificationDetails
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
|
||||
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.concrete.HeroC
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
@@ -103,31 +105,31 @@ object PerformUnaffiliatedHeroesAction {
|
||||
|
||||
case class PerformUnaffiliatedHeroesAction(
|
||||
gameState: GameState,
|
||||
heroGenerator: HeroGenerator,
|
||||
actionResultApplier: ActionResultApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
heroGenerator: HeroGenerator
|
||||
) extends TRandomSequentialResultsAction(gameState) {
|
||||
private val currentDate = gameState.currentDate.get
|
||||
private val factions = gameState.factions.values.toVector
|
||||
|
||||
override protected def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
functionalRandom: FunctionalRandom,
|
||||
actionResultTApplier: ActionResultTApplier
|
||||
): RandomState[Vector[ActionResultT]] =
|
||||
RandomStateSequencer(
|
||||
RandomStateTSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
actionResultApplier = actionResultTApplier,
|
||||
functionalRandom = functionalRandom
|
||||
).withRandomActionResults((_, fr) => statusChangeResults(fr))
|
||||
.withRandomActionResults { (gs, fr) =>
|
||||
heroAppearsResults(gs, fr)
|
||||
heroAppearsResults(GameStateConverter.fromProto(gs), fr)
|
||||
}
|
||||
.withRandomContinuance { (randomSequencer: RandomStateSequencer) =>
|
||||
.withRandomContinuance { (randomSequencer: RandomStateTSequencer) =>
|
||||
val newHeroesMap =
|
||||
randomSequencer.actionResults.newValue
|
||||
.flatMap(_.newHeroes)
|
||||
.map(h => h.id -> h)
|
||||
.toMap
|
||||
|
||||
val currentGs = randomSequencer.lastState
|
||||
val currentGs = GameStateConverter.fromProto(randomSequencer.lastStateProto)
|
||||
val qcpsOpt = questChangedProvinces(
|
||||
currentGs = currentGs,
|
||||
newHeroesMap = newHeroesMap,
|
||||
@@ -142,14 +144,15 @@ case class PerformUnaffiliatedHeroesAction(
|
||||
}
|
||||
randomSequencer.withRandomActionResults((_, _) => qcpsOpt)
|
||||
}
|
||||
.withActionResults { gs =>
|
||||
.withActionResultTs { gs =>
|
||||
EndUnaffiliatedHeroActionsPhaseAction(
|
||||
gameId = gs.gameId,
|
||||
gameId = GameStateConverter.fromProto(gs).gameId,
|
||||
currentDate = currentDate,
|
||||
currentRoundId = gs.currentRoundId,
|
||||
provinces = gs.provinces.values.toVector,
|
||||
heroes = gs.heroes.values.toVector,
|
||||
factions = gs.factions.values.toVector
|
||||
provinces = gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
|
||||
heroes = gs.heroes.values.map(HeroConverter.fromProto).toVector,
|
||||
factions =
|
||||
gs.factions.values.map(net.eagle0.eagle.model.proto_converters.faction.FactionConverter.fromProto).toVector
|
||||
).results
|
||||
}
|
||||
.actionResults
|
||||
|
||||
+10
-12
@@ -1,6 +1,6 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, GameId, HeroId, ProvinceId, RoundId}
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSequentialResultsAction
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
@@ -13,21 +13,19 @@ import net.eagle0.eagle.model.action_result.types.{
|
||||
}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.state.{HostileArmyGroup, RoundPhase}
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.date.Date as DateT
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
|
||||
case class PerformUncontestedConquestAction(
|
||||
gameId: GameId,
|
||||
currentRoundId: RoundId,
|
||||
currentDate: DateT,
|
||||
provinces: Map[ProvinceId, ProvinceT],
|
||||
factions: Map[FactionId, FactionT],
|
||||
heroes: Map[HeroId, HeroT],
|
||||
battalions: Map[BattalionId, BattalionT]
|
||||
gameState: GameState
|
||||
) extends ProtolessSequentialResultsAction {
|
||||
private val gameId = gameState.gameId
|
||||
private val currentRoundId = gameState.currentRoundId
|
||||
private val currentDate = gameState.currentDate.get
|
||||
private val provinces = gameState.provinces
|
||||
private val factions = gameState.factions
|
||||
private val heroes = gameState.heroes
|
||||
private val battalions = gameState.battalions
|
||||
|
||||
private val endPhaseAction: ActionResultT = ActionResultC(
|
||||
actionResultType = EndUncontestedConquestPhaseResultType,
|
||||
|
||||
+40
-63
@@ -6,107 +6,89 @@ import net.eagle0.eagle.api.available_command.{AvailableCommand, RestAvailableCo
|
||||
import net.eagle0.eagle.api.command.OneProvinceAvailableCommands
|
||||
import net.eagle0.eagle.common.province_order_type.ProvinceOrderType.*
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.command.TCommandFactory
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, TCommand}
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.command.CommandFactory
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, RandomStateTSequencer}
|
||||
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChooser
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
import net.eagle0.eagle.library.util.CommandSelection
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
|
||||
case class PerformVassalCommandsPhaseAction(
|
||||
gameState: GameState,
|
||||
commandsForProvince: ProvinceId => Option[OneProvinceAvailableCommands],
|
||||
commandFactory: TCommandFactory,
|
||||
actionResultApplier: ActionResultApplier
|
||||
commandFactory: CommandFactory,
|
||||
applier: ActionResultTApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
private val gameStateProto: GameStateProto = GameStateConverter.toProto(gameState)
|
||||
|
||||
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChoiceHelpers.*
|
||||
|
||||
private def vassalProvinces(gs: GameState): Vector[ProvinceT] =
|
||||
gs.provinces.values
|
||||
.filter(_.rulingFactionId.isDefined)
|
||||
.filterNot(ProvinceUtils.ruledByFactionLeader(_, gs.factions.values.toVector))
|
||||
.filterNot(_.hasActed)
|
||||
.toVector
|
||||
|
||||
override def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[ActionResultT]] =
|
||||
vassalProvinces(gameState)
|
||||
gameStateProto.provinces.values
|
||||
.filter(_.rulingFactionId.isDefined)
|
||||
.filterNot(LegacyProvinceUtils.ruledByFactionLeader(_, gameStateProto))
|
||||
.filterNot(_.hasActed)
|
||||
.foldLeft(
|
||||
RandomStateSequencer(
|
||||
RandomStateTSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
actionResultApplier = applier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
) {
|
||||
case (sequencer, province) =>
|
||||
commandsForProvince(province.id).map { opac =>
|
||||
sequencer.withRandomActionResults { (gs, fr) =>
|
||||
chooseCommand(gs, province.id, fr).continue {
|
||||
case (Some(cs), nextFr) =>
|
||||
val cmd = commandFactory.makeTCommand(
|
||||
actingFactionId = cs.actingFactionId,
|
||||
gameState = gs,
|
||||
availableCommand = cs.available,
|
||||
selectedCommand = cs.selected
|
||||
)
|
||||
cmd match {
|
||||
case TCommand.Simple(action) =>
|
||||
RandomState(Vector(action.immediateExecute), nextFr)
|
||||
case TCommand.RandomSimple(action) =>
|
||||
action.immediateExecute(nextFr).map(ar => Vector(ar))
|
||||
case TCommand.Sequential(action) =>
|
||||
RandomState(action.results, nextFr)
|
||||
}
|
||||
case (None, nextFr) =>
|
||||
RandomState(Vector.empty[ActionResultT], nextFr)
|
||||
sequencer.withOptionalRandomTCommand { (gs, fr) =>
|
||||
chooseCommand(province.id, fr).map { maybeCS =>
|
||||
maybeCS.map { cs =>
|
||||
commandFactory.makeTCommand(
|
||||
actingFactionId = cs.actingFactionId,
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
availableCommand = cs.available,
|
||||
selectedCommand = cs.selected
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.getOrElse(sequencer)
|
||||
}
|
||||
.actionResults
|
||||
|
||||
private def maybeRestCommand(
|
||||
actingFactionId: FactionId,
|
||||
gsProto: GameStateProto,
|
||||
gs: GameStateProto,
|
||||
commandOptions: Vector[AvailableCommand],
|
||||
provinceId: ProvinceId,
|
||||
reason: String
|
||||
): Option[CommandSelection] = {
|
||||
val heroes =
|
||||
gsProto
|
||||
.provinces(provinceId)
|
||||
gs.provinces(provinceId)
|
||||
.rulingFactionHeroIds
|
||||
.map(gsProto.heroes)
|
||||
.map(gs.heroes)
|
||||
MoreOption.flatWhen(
|
||||
commandOptions.collectFirst {
|
||||
case ac: RestAvailableCommand =>
|
||||
ac
|
||||
}.isDefined && shouldRest(heroes)
|
||||
) {
|
||||
chosenRestCommand(actingFactionId, gsProto, commandOptions, reason)
|
||||
chosenRestCommand(actingFactionId, gs, commandOptions, reason)
|
||||
}
|
||||
}
|
||||
|
||||
private def selectedCommandFromOrders(
|
||||
actingFactionId: FactionId,
|
||||
gsProto: GameStateProto,
|
||||
gs: GameStateProto,
|
||||
commandOptions: Vector[AvailableCommand],
|
||||
functionalRandom: FunctionalRandom,
|
||||
provinceId: ProvinceId
|
||||
): RandomState[Option[CommandSelection]] =
|
||||
gsProto.provinces(provinceId).provinceOrders match {
|
||||
gs.provinces(provinceId).provinceOrders match {
|
||||
case ENTRUST =>
|
||||
chosenEntrustCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gsProto,
|
||||
gameState = gs,
|
||||
availableCommands = commandOptions,
|
||||
actingProvinceId = provinceId,
|
||||
functionalRandom = functionalRandom
|
||||
@@ -114,21 +96,21 @@ case class PerformVassalCommandsPhaseAction(
|
||||
case DEVELOP =>
|
||||
chosenDevelopCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gsProto,
|
||||
gameState = gs,
|
||||
availableCommands = commandOptions,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
case MOBILIZE =>
|
||||
chosenMobilizeCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gsProto,
|
||||
gameState = gs,
|
||||
availableCommands = commandOptions,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
case EXPAND =>
|
||||
chosenExpandCommand(
|
||||
actingFactionId,
|
||||
gsProto,
|
||||
gs,
|
||||
commandOptions,
|
||||
functionalRandom
|
||||
)
|
||||
@@ -137,7 +119,7 @@ case class PerformVassalCommandsPhaseAction(
|
||||
RandomState(
|
||||
chosenRestCommand(
|
||||
actingFactionId,
|
||||
gsProto,
|
||||
gs,
|
||||
commandOptions,
|
||||
"bad orders"
|
||||
),
|
||||
@@ -146,17 +128,13 @@ case class PerformVassalCommandsPhaseAction(
|
||||
}
|
||||
|
||||
def chooseCommand(
|
||||
gs: GameState,
|
||||
provinceId: ProvinceId,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Option[CommandSelection]] = {
|
||||
// Convert to proto for CommandChoiceHelpers which expects proto GameState
|
||||
val gsProto = GameStateConverter.toProto(gs)
|
||||
|
||||
): RandomState[Option[CommandSelection]] =
|
||||
commandsForProvince(provinceId).map { oneProvinceAvailableCommands =>
|
||||
CommandChooser.choose(
|
||||
gs.provinces(provinceId).rulingFactionId.get,
|
||||
gsProto,
|
||||
gameStateProto.provinces(provinceId).getRulingFactionId,
|
||||
gameStateProto,
|
||||
oneProvinceAvailableCommands.commands.toVector,
|
||||
Vector[CommandChooser](
|
||||
resolveTributeSelectedCommand,
|
||||
@@ -164,14 +142,14 @@ case class PerformVassalCommandsPhaseAction(
|
||||
handleRiotSelectedCommand,
|
||||
(
|
||||
fid: FactionId,
|
||||
gsP: GameStateProto,
|
||||
gs: GameStateProto,
|
||||
acs: Vector[AvailableCommand],
|
||||
fr: FunctionalRandom
|
||||
) =>
|
||||
RandomState(
|
||||
maybeRestCommand(
|
||||
fid,
|
||||
gsP,
|
||||
gs,
|
||||
acs,
|
||||
provinceId,
|
||||
"chosen vassal command: rest"
|
||||
@@ -180,13 +158,12 @@ case class PerformVassalCommandsPhaseAction(
|
||||
),
|
||||
(
|
||||
fid: FactionId,
|
||||
gsP: GameStateProto,
|
||||
gs: GameStateProto,
|
||||
acs: Vector[AvailableCommand],
|
||||
fr: FunctionalRandom
|
||||
) => selectedCommandFromOrders(fid, gsP, acs, fr, provinceId)
|
||||
) => selectedCommandFromOrders(fid, gs, acs, fr, provinceId)
|
||||
),
|
||||
functionalRandom
|
||||
)
|
||||
}.get
|
||||
}
|
||||
}
|
||||
|
||||
+44
-59
@@ -2,94 +2,79 @@ package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.api.command.OneProvinceAvailableCommands
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.command.TCommandFactory
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, TCommand}
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChoiceHelpers
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.command.CommandFactory
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, RandomStateTSequencer}
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
import net.eagle0.eagle.library.util.CommandSelection
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.ProvinceId
|
||||
|
||||
case class PerformVassalDefenseDecisionsAction(
|
||||
gameState: GameState,
|
||||
commandsForProvince: ProvinceId => Option[OneProvinceAvailableCommands],
|
||||
commandFactory: TCommandFactory,
|
||||
actionResultApplier: ActionResultApplier
|
||||
commandFactory: CommandFactory,
|
||||
applier: ActionResultTApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
private val gameStateProto: GameStateProto = GameStateConverter.toProto(gameState)
|
||||
|
||||
private def vassalProvinces(gs: GameState): Vector[ProvinceT] =
|
||||
gs.provinces.values
|
||||
.filter(_.rulingFactionId.isDefined)
|
||||
.filterNot(ProvinceUtils.ruledByFactionLeader(_, gs.factions.values.toVector))
|
||||
.toVector
|
||||
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChoiceHelpers.*
|
||||
|
||||
override def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[ActionResultT]] =
|
||||
vassalProvinces(gameState)
|
||||
gameStateProto.provinces.values
|
||||
.filter(_.rulingFactionId.isDefined)
|
||||
.filterNot(LegacyProvinceUtils.ruledByFactionLeader(_, gameStateProto))
|
||||
.toVector
|
||||
.foldLeft(
|
||||
RandomStateSequencer(
|
||||
RandomStateTSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
actionResultApplier = applier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
) {
|
||||
case (sequencer, province) =>
|
||||
commandsForProvince(province.id).map { opac =>
|
||||
sequencer.withRandomActionResults { (gs, fr) =>
|
||||
chooseCommand(gs, province.id, opac.commands.toVector, fr).continue {
|
||||
case (Some(cs), nextFr) =>
|
||||
val cmd = commandFactory.makeTCommand(
|
||||
actingFactionId = cs.actingFactionId,
|
||||
gameState = gs,
|
||||
availableCommand = cs.available,
|
||||
selectedCommand = cs.selected
|
||||
)
|
||||
cmd match {
|
||||
case TCommand.Simple(action) =>
|
||||
RandomState(Vector(action.immediateExecute), nextFr)
|
||||
case TCommand.RandomSimple(action) =>
|
||||
action.immediateExecute(nextFr).map(ar => Vector(ar))
|
||||
case TCommand.Sequential(action) =>
|
||||
RandomState(action.results, nextFr)
|
||||
}
|
||||
case (None, nextFr) =>
|
||||
RandomState(Vector.empty[ActionResultT], nextFr)
|
||||
sequencer.withOptionalRandomTCommand { (gs, fr) =>
|
||||
chooseCommand(province.id, fr).map { maybeCS =>
|
||||
maybeCS.map { cs =>
|
||||
commandFactory.makeTCommand(
|
||||
actingFactionId = cs.actingFactionId,
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
availableCommand = cs.available,
|
||||
selectedCommand = cs.selected
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.getOrElse(sequencer)
|
||||
}
|
||||
.actionResults
|
||||
|
||||
private def chooseCommand(
|
||||
gs: GameState,
|
||||
def chooseCommand(
|
||||
provinceId: ProvinceId,
|
||||
commandOptions: Seq[net.eagle0.eagle.api.available_command.AvailableCommand],
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Option[CommandSelection]] = {
|
||||
// Convert to proto for CommandChoiceHelpers which expects proto GameState
|
||||
val gsProto = GameStateConverter.toProto(gs)
|
||||
): RandomState[Option[CommandSelection]] =
|
||||
commandsForProvince(provinceId).map { oneProvinceAvailableCommands =>
|
||||
val commandOptions = oneProvinceAvailableCommands.commands
|
||||
|
||||
CommandChoiceHelpers.resolveTributeSelectedCommand(
|
||||
actingFactionId = gs.provinces(provinceId).rulingFactionId.get,
|
||||
gameState = gsProto,
|
||||
availableCommands = commandOptions.toVector,
|
||||
functionalRandom = functionalRandom
|
||||
) match {
|
||||
case rss @ RandomState(Some(_), _) => rss
|
||||
case RandomState(None, fr) =>
|
||||
CommandChoiceHelpers.defendSelectedCommand(
|
||||
gs.provinces(provinceId).rulingFactionId.get,
|
||||
gsProto,
|
||||
commandOptions.toVector,
|
||||
fr
|
||||
)
|
||||
resolveTributeSelectedCommand(
|
||||
actingFactionId = gameStateProto.provinces(provinceId).getRulingFactionId,
|
||||
gameState = gameStateProto,
|
||||
availableCommands = commandOptions.toVector,
|
||||
functionalRandom = functionalRandom
|
||||
) match {
|
||||
case rss @ RandomState(Some(_), _) => rss
|
||||
case RandomState(None, fr) =>
|
||||
defendSelectedCommand(
|
||||
gameStateProto.provinces(provinceId).getRulingFactionId,
|
||||
gameStateProto,
|
||||
commandOptions.toVector,
|
||||
fr
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.getOrElse(RandomState(None, functionalRandom))
|
||||
}
|
||||
|
||||
+13
-14
@@ -2,7 +2,7 @@ package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import scala.util.hashing.MurmurHash3
|
||||
|
||||
import net.eagle0.eagle.{BattalionId, FactionId, GameId, HeroId, ProvinceId, RoundId}
|
||||
import net.eagle0.eagle.{FactionId, GameId, ProvinceId, RoundId}
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSequentialResultsAction
|
||||
import net.eagle0.eagle.library.util.BattalionUtils
|
||||
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
|
||||
@@ -10,10 +10,8 @@ import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedPro
|
||||
import net.eagle0.eagle.model.action_result.concrete.ActionResultC
|
||||
import net.eagle0.eagle.model.action_result.types.{SentSuppliesResultType, StartBattleResultType}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.state.{Army, BattalionType, BattalionTypeId, HostileArmyGroup, MovingArmy, Supplies}
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.{Army, BattalionType, HostileArmyGroup, MovingArmy, Supplies}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.shardok_battle.{BattleType, ShardokBattle, ShardokPlayer, VictoryCondition}
|
||||
import net.eagle0.eagle.model.state.unit_status.UnitStatus
|
||||
@@ -21,16 +19,17 @@ import net.eagle0.eagle.model.state.HostileArmyGroupStatus.Attacking
|
||||
import net.eagle0.eagle.shardok_interface.ResolvedEagleUnit
|
||||
|
||||
case class RequestBattlesAction(
|
||||
gameId: GameId,
|
||||
currentRoundId: RoundId,
|
||||
currentDate: net.eagle0.eagle.model.state.date.Date,
|
||||
battleCounter: Int,
|
||||
heroes: Map[HeroId, HeroT],
|
||||
battalions: Map[BattalionId, BattalionT],
|
||||
provinces: Map[ProvinceId, ProvinceT],
|
||||
factions: Map[FactionId, FactionT],
|
||||
battalionTypes: Map[BattalionTypeId, BattalionType]
|
||||
gameState: GameState
|
||||
) extends ProtolessSequentialResultsAction {
|
||||
private val gameId = gameState.gameId
|
||||
private val currentRoundId = gameState.currentRoundId
|
||||
private val currentDate = gameState.currentDate.get
|
||||
private val battleCounter = gameState.battleCounter
|
||||
private val heroes = gameState.heroes
|
||||
private val battalions = gameState.battalions
|
||||
private val provinces = gameState.provinces
|
||||
private val factions = gameState.factions
|
||||
private val battalionTypes = gameState.battalionTypes.map(bt => bt.typeId -> bt).toMap
|
||||
override def results: Vector[ActionResultT] =
|
||||
provincesWithAttackingArmies.zipWithIndex.flatMap { case (pwaas, idx) => handleOneProvince(pwaas, idx) }
|
||||
|
||||
|
||||
+12
-14
@@ -2,24 +2,21 @@ package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessRandomSequentialResultsAction
|
||||
import net.eagle0.eagle.library.actions.random_state_sequencer.RandomStateSequencer
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{RandomStateTSequencer, TRandomSequentialResultsAction}
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.{ChangedProvinceC, HostileArmyStatusChange}
|
||||
import net.eagle0.eagle.model.action_result.concrete.ActionResultC
|
||||
import net.eagle0.eagle.model.action_result.types.{EndTruceTurnBackPhaseResultType, WithdrawalForTruceResultType}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.HostileArmyGroupStatus
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
|
||||
case class TruceTurnBackPhaseAction(
|
||||
gameState: GameState,
|
||||
actionResultApplier: ActionResultApplier
|
||||
) extends ProtolessRandomSequentialResultsAction {
|
||||
case class TruceTurnBackPhaseAction(gameState: GameState) extends TRandomSequentialResultsAction(gameState) {
|
||||
private val factions: Vector[FactionT] = gameState.factions.values.toVector
|
||||
|
||||
private def checkOneAttackingArmyGroup(
|
||||
@@ -63,21 +60,22 @@ case class TruceTurnBackPhaseAction(
|
||||
.getOrElse(Vector())
|
||||
|
||||
override protected def randomResults(
|
||||
functionalRandom: FunctionalRandom
|
||||
functionalRandom: FunctionalRandom,
|
||||
actionResultTApplier: ActionResultTApplier
|
||||
): RandomState[Vector[ActionResultT]] =
|
||||
RandomStateSequencer(
|
||||
RandomStateTSequencer(
|
||||
initialState = gameState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
actionResultApplier = actionResultTApplier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.withActionResults(_ => gameState.provinces.values.toVector.flatMap(checkOneProvince))
|
||||
.withActionResults(gs =>
|
||||
.withProtolessSequentialResultsAction(gs =>
|
||||
WithdrawnArmiesReturnHomeAction(
|
||||
gs.currentRoundId,
|
||||
gs.provinces.values.toVector
|
||||
).results
|
||||
gs.provinces.values.map(ProvinceConverter.fromProto).toVector
|
||||
)
|
||||
)
|
||||
.withActionResult(_ =>
|
||||
.withActionResultT(_ =>
|
||||
ActionResultC(
|
||||
actionResultType = EndTruceTurnBackPhaseResultType,
|
||||
newRoundPhase = Some(RoundPhase.BattleRequest)
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
load("@rules_scala//scala:scala.bzl", "scala_library")
|
||||
|
||||
scala_library(
|
||||
name = "t_command_factory",
|
||||
srcs = ["TCommandFactory.scala"],
|
||||
name = "command_base",
|
||||
srcs = ["Command.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
#"@maven//:com_thesamet_scalapb_lenses_3",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -27,13 +29,14 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
":t_command_factory",
|
||||
":command_base",
|
||||
],
|
||||
deps = [
|
||||
":alms_command",
|
||||
":apprehend_outlaw_command",
|
||||
":arm_troops_command",
|
||||
":attack_decision_command",
|
||||
":command_base",
|
||||
":control_weather_command",
|
||||
":decline_quest_command",
|
||||
":defend_command",
|
||||
@@ -53,6 +56,9 @@ scala_library(
|
||||
":march_command",
|
||||
":organize_troops_command",
|
||||
":please_recruit_me_command",
|
||||
":protoless_random_simple_action_wrapper",
|
||||
":protoless_sequential_results_action_wrapper",
|
||||
":protoless_simple_action_wrapper",
|
||||
":recon_command",
|
||||
":recruit_heroes_command",
|
||||
":resolve_alliance_offer_command",
|
||||
@@ -67,7 +73,6 @@ scala_library(
|
||||
":start_epidemic_command",
|
||||
":suppress_beasts_command",
|
||||
":swear_brotherhood_command",
|
||||
":t_command_factory",
|
||||
":trade_command",
|
||||
":train_command",
|
||||
":travel_command",
|
||||
@@ -908,6 +913,98 @@ scala_library(
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "protoless_random_simple_action_wrapper",
|
||||
srcs = ["ProtolessRandomSimpleActionWrapper.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":command_base",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_proto_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:vigor_xp_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "protoless_sequential_results_action_wrapper",
|
||||
srcs = ["ProtolessSequentialResultsActionWrapper.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":command_base",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:vigor_xp_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/validations:scala_runtime_validator",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_battalion_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "protoless_simple_action_wrapper",
|
||||
srcs = ["ProtolessSimpleActionWrapper.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
deps = [
|
||||
":command_base",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:vigor_xp_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "alms_command",
|
||||
srcs = ["AlmsCommand.scala"],
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.internal.changed_faction.ChangedFaction
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{Action, ActionWithResultingState}
|
||||
import net.eagle0.eagle.FactionId
|
||||
|
||||
object Command {
|
||||
private def newChangedFaction(
|
||||
result: ActionResult,
|
||||
factionId: Option[FactionId]
|
||||
): Option[ChangedFaction] =
|
||||
for {
|
||||
pid <- result.province
|
||||
fid <- factionId
|
||||
} yield ChangedFaction(
|
||||
id = fid,
|
||||
newLastActedProvinceId = Some(pid)
|
||||
)
|
||||
|
||||
def resultWithLastCommand(
|
||||
result: ActionResult,
|
||||
selectedCommand: SelectedCommand,
|
||||
factionId: Option[FactionId]
|
||||
): ActionResult =
|
||||
result.update(
|
||||
_.lastCommandTypeForActingProvince := result.province
|
||||
.map(_ => selectedCommand)
|
||||
.getOrElse(SelectedCommand.Empty),
|
||||
_.changedFactions :++= newChangedFaction(result, factionId)
|
||||
)
|
||||
}
|
||||
|
||||
trait Command extends Action {
|
||||
override def execute(
|
||||
actionResultProtoApplier: ActionResultProtoApplier
|
||||
): Vector[ActionWithResultingState]
|
||||
}
|
||||
@@ -66,7 +66,7 @@ import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.BattalionTypeId
|
||||
|
||||
class CommandFactory extends TCommandFactory {
|
||||
class CommandFactory {
|
||||
private def allProvinces(gameState: GameState): Vector[ProvinceT] =
|
||||
gameState.provinces.values.toVector
|
||||
|
||||
@@ -114,6 +114,34 @@ class CommandFactory extends TCommandFactory {
|
||||
previousBackstoryTextIdLookup = hid => gameState.heroes(hid).backstoryTextId
|
||||
)
|
||||
|
||||
def makeCommand(
|
||||
actingFactionId: FactionId,
|
||||
gameState: GameState,
|
||||
availableCommand: AvailableCommand,
|
||||
selectedCommand: SelectedCommand
|
||||
): Command =
|
||||
makeCommandInternal(actingFactionId, gameState, availableCommand, selectedCommand) match {
|
||||
case protolessSimpleAction: ProtolessSimpleAction =>
|
||||
new ProtolessSimpleActionWrapper(
|
||||
startingState = gameState,
|
||||
protolessSimpleAction = protolessSimpleAction,
|
||||
selectedCommand = selectedCommand
|
||||
)
|
||||
case protolessRandomSimpleAction: ProtolessRandomSimpleAction =>
|
||||
new ProtolessRandomSimpleActionWrapper(
|
||||
startingState = gameState,
|
||||
protolessRandomSimpleAction = protolessRandomSimpleAction,
|
||||
selectedCommand = selectedCommand
|
||||
)
|
||||
case protolessSequentialResultsAction: ProtolessSequentialResultsAction =>
|
||||
new ProtolessSequentialResultsActionWrapper(
|
||||
startingState = gameState,
|
||||
protolessSequentialResultsAction = protolessSequentialResultsAction
|
||||
)
|
||||
|
||||
case c: Command => c
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a T-type command action directly, without proto wrapping.
|
||||
*
|
||||
@@ -133,6 +161,10 @@ class CommandFactory extends TCommandFactory {
|
||||
TCommand.RandomSimple(protolessRandomSimpleAction)
|
||||
case protolessSequentialResultsAction: ProtolessSequentialResultsAction =>
|
||||
TCommand.Sequential(protolessSequentialResultsAction)
|
||||
case _: Command =>
|
||||
throw new EagleInternalException(
|
||||
"makeTCommand called on a command type that is not yet converted to T-types"
|
||||
)
|
||||
}
|
||||
|
||||
private def makeCommandInternal(
|
||||
@@ -140,7 +172,7 @@ class CommandFactory extends TCommandFactory {
|
||||
gameState: GameState,
|
||||
availableCommand: AvailableCommand,
|
||||
selectedCommand: SelectedCommand
|
||||
): ProtolessSimpleAction | ProtolessRandomSimpleAction | ProtolessSequentialResultsAction =
|
||||
): ProtolessSimpleAction | ProtolessRandomSimpleAction | ProtolessSequentialResultsAction | Command =
|
||||
(availableCommand, selectedCommand) match {
|
||||
case (
|
||||
aoac: ApprehendOutlawAvailableCommand,
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.common.{RandomState, SeededRandom}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType.NEW_RANDOM_SEED
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{
|
||||
ActionWithResultingState,
|
||||
ProtolessRandomSimpleAction,
|
||||
RandomStateProtoSequencer,
|
||||
VigorXPApplier
|
||||
}
|
||||
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
|
||||
class ProtolessRandomSimpleActionWrapper(
|
||||
startingState: GameState,
|
||||
protolessRandomSimpleAction: ProtolessRandomSimpleAction,
|
||||
selectedCommand: SelectedCommand
|
||||
) extends Command {
|
||||
override def execute(
|
||||
actionResultProtoApplier: ActionResultProtoApplier
|
||||
): Vector[ActionWithResultingState] =
|
||||
RandomStateProtoSequencer(
|
||||
initialState = startingState,
|
||||
actionResultProtoApplier = actionResultProtoApplier,
|
||||
functionalRandom = SeededRandom(startingState.randomSeed)
|
||||
).withRandomActionResult {
|
||||
case (gs, fr) =>
|
||||
protolessRandomSimpleAction
|
||||
.immediateExecute(fr)
|
||||
.map(ar => VigorXPApplier.withVigorXp(ActionResultProtoConverter.toProto(ar)))
|
||||
.map { resultWithVigorXp =>
|
||||
Command.resultWithLastCommand(
|
||||
result = resultWithVigorXp,
|
||||
selectedCommand = selectedCommand,
|
||||
factionId = resultWithVigorXp.province
|
||||
.map(startingState.provinces)
|
||||
.flatMap(_.rulingFactionId)
|
||||
)
|
||||
}
|
||||
}.withRandomActionResult {
|
||||
case (gs, fr) =>
|
||||
RandomState(
|
||||
ActionResult(
|
||||
`type` = NEW_RANDOM_SEED,
|
||||
newRandomSeed = Some(fr.seed)
|
||||
),
|
||||
fr
|
||||
)
|
||||
}.results.newValue
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.common.SeededRandom
|
||||
import net.eagle0.eagle.library.actions.applier.{ActionResultProtoApplier, ActionResultTApplierImpl}
|
||||
import net.eagle0.eagle.library.actions.impl.common.{
|
||||
ActionWithResultingState,
|
||||
ProtolessSequentialResultsAction,
|
||||
RandomStateTSequencer,
|
||||
VigorXPApplier
|
||||
}
|
||||
import net.eagle0.eagle.library.util.validations.ScalaRuntimeValidator
|
||||
import net.eagle0.eagle.library.EagleInternalException
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
class ProtolessSequentialResultsActionWrapper(
|
||||
startingState: GameState,
|
||||
protolessSequentialResultsAction: ProtolessSequentialResultsAction
|
||||
) extends Command {
|
||||
override def execute(
|
||||
actionResultProtoApplier: ActionResultProtoApplier
|
||||
): Vector[ActionWithResultingState] = {
|
||||
val arts = protolessSequentialResultsAction.results match {
|
||||
case items if items.isEmpty =>
|
||||
throw new EagleInternalException(
|
||||
"ProtolessSequentialResultsActionWrapper must have at least one result"
|
||||
)
|
||||
case h +: t =>
|
||||
VigorXPApplier.withVigorXp(h) +: t
|
||||
case _ => ??? // above cases should cover
|
||||
}
|
||||
|
||||
RandomStateTSequencer(
|
||||
initialState = startingState,
|
||||
actionResultApplier = ActionResultTApplierImpl(ScalaRuntimeValidator),
|
||||
functionalRandom = SeededRandom(startingState.randomSeed)
|
||||
).withActionResultTs(_ => arts).actionsWithResultingStates.newValue
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ActionWithResultingState, ProtolessSimpleAction, VigorXPApplier}
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
|
||||
class ProtolessSimpleActionWrapper(
|
||||
startingState: GameState,
|
||||
protolessSimpleAction: ProtolessSimpleAction,
|
||||
selectedCommand: SelectedCommand
|
||||
) extends Command {
|
||||
override def execute(
|
||||
actionResultProtoApplier: ActionResultProtoApplier
|
||||
): Vector[ActionWithResultingState] =
|
||||
VigorXPApplier.withVigorXp(
|
||||
ActionResultProtoConverter.toProto(protolessSimpleAction.immediateExecute)
|
||||
) match {
|
||||
case result =>
|
||||
Vector(
|
||||
actionResultProtoApplier.applyActionResult(
|
||||
startingState = GameStateConverter.toProto(startingState),
|
||||
result = Command.resultWithLastCommand(
|
||||
result = result,
|
||||
selectedCommand = selectedCommand,
|
||||
factionId = result.province
|
||||
.map(startingState.provinces)
|
||||
.flatMap(_.rulingFactionId)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState, SeededRandom}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType.NEW_RANDOM_SEED
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{
|
||||
ActionWithResultingState,
|
||||
RandomStateProtoSequencer,
|
||||
VigorXPApplier
|
||||
}
|
||||
|
||||
abstract class RandomSingleResultCommand(
|
||||
startingState: GameState,
|
||||
val selectedCommand: SelectedCommand
|
||||
) extends Command {
|
||||
def immediateExecute(
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[ActionResult]
|
||||
|
||||
override def execute(
|
||||
actionResultProtoApplier: ActionResultProtoApplier
|
||||
): Vector[ActionWithResultingState] =
|
||||
RandomStateProtoSequencer(
|
||||
initialStateProto = startingState,
|
||||
actionResultProtoApplier = actionResultProtoApplier,
|
||||
functionalRandom = SeededRandom(startingState.randomSeed)
|
||||
).withRandomActionResult {
|
||||
case (gs, fr) =>
|
||||
immediateExecute(fr).map(VigorXPApplier.withVigorXp).map { resultWithVigorXp =>
|
||||
Command.resultWithLastCommand(
|
||||
result = resultWithVigorXp,
|
||||
selectedCommand = selectedCommand,
|
||||
factionId = resultWithVigorXp.province
|
||||
.map(startingState.provinces)
|
||||
.flatMap(_.rulingFactionId)
|
||||
)
|
||||
}
|
||||
}.withRandomActionResult {
|
||||
case (gs, fr) =>
|
||||
RandomState(
|
||||
ActionResult(
|
||||
`type` = NEW_RANDOM_SEED,
|
||||
newRandomSeed = Some(fr.seed)
|
||||
),
|
||||
fr
|
||||
)
|
||||
}.results.newValue
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common.{ActionWithResultingState, SimpleAction, VigorXPApplier}
|
||||
|
||||
class SimpleActionWrapper(
|
||||
startingState: GameState,
|
||||
simpleAction: SimpleAction,
|
||||
selectedCommand: SelectedCommand
|
||||
) extends Command {
|
||||
override def execute(
|
||||
actionResultProtoApplier: ActionResultProtoApplier
|
||||
): Vector[ActionWithResultingState] =
|
||||
VigorXPApplier.withVigorXp(simpleAction.immediateExecute) match {
|
||||
case result =>
|
||||
Vector(
|
||||
actionResultProtoApplier.applyActionResult(
|
||||
startingState = startingState,
|
||||
result = Command.resultWithLastCommand(
|
||||
result = result,
|
||||
selectedCommand = selectedCommand,
|
||||
factionId = result.province
|
||||
.map(startingState.provinces)
|
||||
.flatMap(_.rulingFactionId)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.eagle.api.available_command.AvailableCommand
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
import net.eagle0.eagle.library.actions.impl.common.TCommand
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.FactionId
|
||||
|
||||
/** Trait for creating T-type command actions. Extracted from CommandFactory to allow lightweight mocking in tests. */
|
||||
trait TCommandFactory {
|
||||
def makeTCommand(
|
||||
actingFactionId: FactionId,
|
||||
gameState: GameState,
|
||||
availableCommand: AvailableCommand,
|
||||
selectedCommand: SelectedCommand
|
||||
): TCommand
|
||||
}
|
||||
@@ -33,6 +33,28 @@ scala_library(
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "deterministic_sequential_results_action",
|
||||
srcs = ["DeterministicSequentialResultsAction.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
":action",
|
||||
":action_with_resulting_state",
|
||||
],
|
||||
deps = [
|
||||
":action",
|
||||
":action_with_resulting_state",
|
||||
":vigor_xp_applier",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "t_random_sequential_results_action",
|
||||
srcs = ["TRandomSequentialResultsAction.scala"],
|
||||
@@ -78,7 +100,6 @@ scala_library(
|
||||
":action_with_resulting_state",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
],
|
||||
deps = [
|
||||
":action_with_resulting_state",
|
||||
@@ -178,6 +199,80 @@ scala_library(
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "random_state_proto_sequencer",
|
||||
srcs = ["RandomStateProtoSequencer.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
deps = [
|
||||
":action",
|
||||
":action_with_resulting_state",
|
||||
":protoless_sequential_results_action",
|
||||
":protoless_simple_action",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types/base:action_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "random_state_trait_sequencer",
|
||||
srcs = ["RandomStateTSequencer.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
":t_command",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
deps = [
|
||||
":action",
|
||||
":action_with_resulting_state",
|
||||
":protoless_random_simple_action",
|
||||
":protoless_sequential_results_action",
|
||||
":protoless_simple_action",
|
||||
":t_command",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types/base:action_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "vigor_xp_applier",
|
||||
srcs = ["VigorXPApplier.scala"],
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package net.eagle0.eagle.library.actions.impl.common
|
||||
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier
|
||||
import net.eagle0.eagle.library.actions.impl.common
|
||||
|
||||
abstract class DeterministicSequentialResultsAction(startingState: GameState) extends Action {
|
||||
def results(
|
||||
actionResultProtoApplier: ActionResultProtoApplier
|
||||
): Vector[ActionResult]
|
||||
|
||||
override def execute(
|
||||
actionResultProtoApplier: ActionResultProtoApplier
|
||||
): Vector[ActionWithResultingState] =
|
||||
if results(actionResultProtoApplier).isEmpty then Vector.empty
|
||||
else
|
||||
results(actionResultProtoApplier)
|
||||
.map(VigorXPApplier.withVigorXp)
|
||||
.foldLeft(
|
||||
Vector(
|
||||
common.ActionWithResultingState(
|
||||
gameState = startingState,
|
||||
actionResult = null
|
||||
)
|
||||
)
|
||||
)((aws, far) =>
|
||||
aws :+ actionResultProtoApplier
|
||||
.applyActionResult(aws.last.gameState, far)
|
||||
)
|
||||
.drop(1)
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@ abstract class ProtolessRandomSequentialResultsAction {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute this action with an explicit starting state and convert results to proto format.
|
||||
* Execute this action and convert results to proto format.
|
||||
*
|
||||
* This is the bridge between T-type actions and the proto-based engine interface.
|
||||
*/
|
||||
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
package net.eagle0.eagle.library.actions.impl.common
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult as ActionResultProto
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
trait RandomStateProtoSequencer {
|
||||
def lastStateProto: GameStateProto
|
||||
|
||||
def results: RandomState[Vector[ActionWithResultingState]]
|
||||
def actionResults: RandomState[Vector[ActionResultProto]]
|
||||
|
||||
def withRandomActionResult(
|
||||
actionResultGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[ActionResultProto]
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withRandomActionResults(
|
||||
actionResultsGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[Vector[ActionResultProto]]
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withRandomAction(
|
||||
actionGen: (GameStateProto, FunctionalRandom) => RandomState[Action]
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
// ActionResultSequencer passthroughs
|
||||
def withAction(action: GameStateProto => Action): RandomStateProtoSequencer
|
||||
|
||||
def withProtolessSimpleAction(
|
||||
action: GameStateProto => ProtolessSimpleAction
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withProtolessSequentialResultsAction(
|
||||
action: GameStateProto => ProtolessSequentialResultsAction
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withRandomActionResultT(
|
||||
actionResultGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[ActionResultT]
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withRandomActionResultTs(
|
||||
actionResultGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[Vector[ActionResultT]]
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withActionResult(
|
||||
actionResultGen: GameStateProto => ActionResultProto
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withActionResultT(
|
||||
actionResultGen: GameStateProto => ActionResultT
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withActionResultTs(
|
||||
actionResultGen: GameStateProto => Iterable[ActionResultT]
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withActionResults(
|
||||
actionResultsGen: GameStateProto => Iterable[ActionResultProto]
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withActionWithResultingState(
|
||||
awrsGen: GameStateProto => ActionWithResultingState
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withContinuance(
|
||||
continuance: RandomStateProtoSequencer => RandomStateProtoSequencer
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def withRandomContinuance(
|
||||
randomContinuance: RandomStateProtoSequencer => RandomStateProtoSequencer
|
||||
): RandomStateProtoSequencer
|
||||
|
||||
def foldIn[T](ts: Iterable[T])(
|
||||
f: (T, GameStateProto, FunctionalRandom) => RandomState[ActionResultProto]
|
||||
): RandomStateProtoSequencer
|
||||
}
|
||||
|
||||
object RandomStateProtoSequencer {
|
||||
def apply(
|
||||
initialState: GameState,
|
||||
actionResultProtoApplier: ActionResultProtoApplier,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomStateProtoSequencer =
|
||||
apply(
|
||||
initialStateProto = GameStateConverter.toProto(initialState),
|
||||
actionResultProtoApplier = actionResultProtoApplier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
|
||||
def apply(
|
||||
initialStateProto: GameStateProto,
|
||||
actionResultProtoApplier: ActionResultProtoApplier,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomStateProtoSequencer =
|
||||
RandomStateProtoSequencerImpl(
|
||||
initialStateProto = initialStateProto,
|
||||
actionResultProtoApplier = actionResultProtoApplier,
|
||||
results = RandomState(
|
||||
Vector(),
|
||||
functionalRandom
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private case class RandomStateProtoSequencerImpl(
|
||||
initialStateProto: GameStateProto,
|
||||
actionResultProtoApplier: ActionResultProtoApplier,
|
||||
results: RandomState[Vector[ActionWithResultingState]]
|
||||
) extends RandomStateProtoSequencer {
|
||||
override def actionResults: RandomState[Vector[ActionResultProto]] =
|
||||
results.map(_.map(_.actionResult))
|
||||
|
||||
override val lastStateProto: GameStateProto =
|
||||
results.newValue.lastOption.map(_.gameState).getOrElse(initialStateProto)
|
||||
|
||||
override def withAction(
|
||||
action: GameStateProto => Action
|
||||
): RandomStateProtoSequencer =
|
||||
copy(
|
||||
results = results.map(
|
||||
_ ++ action(lastStateProto).execute(actionResultProtoApplier)
|
||||
)
|
||||
)
|
||||
|
||||
override def withProtolessSimpleAction(
|
||||
action: GameStateProto => ProtolessSimpleAction
|
||||
): RandomStateProtoSequencer =
|
||||
withActionResultT(gs => action(gs).immediateExecute)
|
||||
|
||||
override def withProtolessSequentialResultsAction(
|
||||
action: GameStateProto => ProtolessSequentialResultsAction
|
||||
): RandomStateProtoSequencer =
|
||||
withActionResults(gs => action(gs).results.map(ActionResultProtoConverter.toProto))
|
||||
|
||||
override def withActionResultT(
|
||||
action: GameStateProto => ActionResultT
|
||||
): RandomStateProtoSequencer =
|
||||
withActionResult(gs =>
|
||||
ActionResultProtoConverter.toProto(
|
||||
action(gs)
|
||||
)
|
||||
)
|
||||
|
||||
override def withActionResultTs(
|
||||
action: GameStateProto => Iterable[ActionResultT]
|
||||
): RandomStateProtoSequencer =
|
||||
withActionResults(gs => action(gs).map(ActionResultProtoConverter.toProto))
|
||||
|
||||
override def withActionResult(
|
||||
actionResultGen: GameStateProto => ActionResultProto
|
||||
): RandomStateProtoSequencer =
|
||||
copy(
|
||||
results = results.map(
|
||||
_ :+ actionResultProtoApplier
|
||||
.applyActionResult(lastStateProto, actionResultGen(lastStateProto))
|
||||
)
|
||||
)
|
||||
|
||||
override def withActionResults(
|
||||
actionResultsGen: GameStateProto => Iterable[ActionResultProto]
|
||||
): RandomStateProtoSequencer =
|
||||
copy(
|
||||
results = results.map(
|
||||
_ ++ actionResultProtoApplier
|
||||
.applyActionResults(lastStateProto, actionResultsGen(lastStateProto))
|
||||
)
|
||||
)
|
||||
|
||||
override def foldIn[T](ts: Iterable[T])(
|
||||
f: (T, GameStateProto, FunctionalRandom) => RandomState[ActionResultProto]
|
||||
): RandomStateProtoSequencer =
|
||||
ts.foldLeft(this) {
|
||||
case (sequencer, t) =>
|
||||
sequencer.withRandomActionResult { case (gs, fr) => f(t, gs, fr) }
|
||||
}
|
||||
|
||||
override def withActionWithResultingState(
|
||||
awrsGen: GameStateProto => ActionWithResultingState
|
||||
): RandomStateProtoSequencer = copy(
|
||||
results = results.map(_ :+ awrsGen(lastStateProto))
|
||||
)
|
||||
|
||||
override def withContinuance(
|
||||
continuance: RandomStateProtoSequencer => RandomStateProtoSequencer
|
||||
): RandomStateProtoSequencer = continuance(this)
|
||||
|
||||
override def withRandomActionResult(
|
||||
actionResultGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[ActionResultProto]
|
||||
): RandomStateProtoSequencerImpl =
|
||||
results match {
|
||||
case RandomState(ars, fr) =>
|
||||
copy(
|
||||
results = actionResultGen(lastStateProto, fr).map { ar =>
|
||||
ars :+ actionResultProtoApplier
|
||||
.applyActionResult(lastStateProto, ar)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override def withRandomActionResultT(
|
||||
actionResultGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[ActionResultT]
|
||||
): RandomStateProtoSequencerImpl =
|
||||
results match {
|
||||
case RandomState(ars, fr) =>
|
||||
copy(
|
||||
results = actionResultGen(lastStateProto, fr).map { ar =>
|
||||
ars :+ actionResultProtoApplier
|
||||
.applyActionResult(
|
||||
lastStateProto,
|
||||
ActionResultProtoConverter.toProto(ar)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override def withRandomActionResults(
|
||||
actionResultsGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[Vector[ActionResultProto]]
|
||||
): RandomStateProtoSequencer =
|
||||
results match {
|
||||
case RandomState(ars, fr) =>
|
||||
copy(results = actionResultsGen(lastStateProto, fr).map { newArs =>
|
||||
ars ++ actionResultProtoApplier.applyActionResults(
|
||||
lastStateProto,
|
||||
newArs
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override def withRandomActionResultTs(
|
||||
actionResultGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[Vector[ActionResultT]]
|
||||
): RandomStateProtoSequencerImpl =
|
||||
results match {
|
||||
case RandomState(ars, fr) =>
|
||||
copy(
|
||||
results = actionResultGen(lastStateProto, fr).map { newArs =>
|
||||
ars ++ actionResultProtoApplier
|
||||
.applyActionResults(
|
||||
lastStateProto,
|
||||
newArs.map(ActionResultProtoConverter.toProto)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override def withRandomAction(
|
||||
actionGen: (GameStateProto, FunctionalRandom) => RandomState[Action]
|
||||
): RandomStateProtoSequencer =
|
||||
results match {
|
||||
case RandomState(ars, fr) =>
|
||||
copy(results = actionGen(lastStateProto, fr).map { action =>
|
||||
ars ++ action.execute(actionResultProtoApplier)
|
||||
})
|
||||
}
|
||||
|
||||
override def withRandomContinuance(
|
||||
randomContinuance: RandomStateProtoSequencer => RandomStateProtoSequencer
|
||||
): RandomStateProtoSequencer = randomContinuance(this)
|
||||
}
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
package net.eagle0.eagle.library.actions.impl.common
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.library.actions.applier.{ActionResultTApplier, ActionResultTWithResultingState}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
trait RandomStateTSequencer {
|
||||
def lastStateProto: GameStateProto
|
||||
|
||||
def actionResults: RandomState[Vector[ActionResultT]]
|
||||
|
||||
def actionResultTsWithResultingStates: RandomState[
|
||||
Vector[ActionResultTWithResultingState]
|
||||
]
|
||||
|
||||
def actionsWithResultingStates: RandomState[
|
||||
Vector[ActionWithResultingState]
|
||||
] = actionResultTsWithResultingStates.map {
|
||||
_.map {
|
||||
case ActionResultTWithResultingState(art, gs) =>
|
||||
ActionWithResultingState(ActionResultProtoConverter.toProto(art), gs)
|
||||
}
|
||||
}
|
||||
|
||||
def withRandomActionResult(
|
||||
actionResultGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[ActionResultT]
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withRandomActionResults(
|
||||
actionResultsGen: FunctionalRandom => RandomState[Vector[ActionResultT]]
|
||||
): RandomStateTSequencer =
|
||||
withRandomActionResults((_, fr) => actionResultsGen(fr))
|
||||
|
||||
def withRandomActionResults(
|
||||
actionResultsGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[Vector[ActionResultT]]
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withProtolessSimpleAction(
|
||||
action: GameStateProto => ProtolessSimpleAction
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withProtolessRandomSimpleAction(
|
||||
action: (GameStateProto, FunctionalRandom) => RandomState[ActionResultT]
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withProtolessSequentialResultsAction(
|
||||
action: GameStateProto => ProtolessSequentialResultsAction
|
||||
): RandomStateTSequencer
|
||||
|
||||
/**
|
||||
* Execute a T-type command and add its results to the sequencer.
|
||||
*
|
||||
* This handles all three T-type command types:
|
||||
* - Simple: single immediate result
|
||||
* - RandomSimple: single result requiring randomness
|
||||
* - Sequential: multiple results
|
||||
*/
|
||||
def withTCommand(
|
||||
command: GameStateProto => TCommand
|
||||
): RandomStateTSequencer
|
||||
|
||||
/**
|
||||
* Execute a T-type command that may or may not exist.
|
||||
*
|
||||
* If the command generator returns None, no action is taken.
|
||||
*/
|
||||
def withOptionalTCommand(
|
||||
command: GameStateProto => Option[TCommand]
|
||||
): RandomStateTSequencer
|
||||
|
||||
/**
|
||||
* Execute a T-type command with access to the FunctionalRandom.
|
||||
*
|
||||
* The command generator receives both the game state and the random state, useful when command selection itself
|
||||
* requires randomness.
|
||||
*/
|
||||
def withRandomTCommand(
|
||||
command: (GameStateProto, FunctionalRandom) => RandomState[TCommand]
|
||||
): RandomStateTSequencer
|
||||
|
||||
/**
|
||||
* Execute an optional T-type command with access to the FunctionalRandom.
|
||||
*/
|
||||
def withOptionalRandomTCommand(
|
||||
command: (GameStateProto, FunctionalRandom) => RandomState[Option[TCommand]]
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withActionResult(
|
||||
actionResultGen: GameStateProto => ActionResultT
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withActionResultT(
|
||||
actionResultGen: GameStateProto => ActionResultT
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withActionResultTs(
|
||||
actionResultGen: GameStateProto => Iterable[ActionResultT]
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withActionResults(
|
||||
actionResultsGen: GameStateProto => Iterable[ActionResultT]
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withContinuance(
|
||||
continuance: RandomStateTSequencer => RandomStateTSequencer
|
||||
): RandomStateTSequencer
|
||||
|
||||
def withRandomContinuance(
|
||||
randomContinuance: RandomStateTSequencer => RandomStateTSequencer
|
||||
): RandomStateTSequencer
|
||||
|
||||
def foldIn[T](ts: Iterable[T])(
|
||||
f: (T, GameStateProto, FunctionalRandom) => RandomState[ActionResultT]
|
||||
): RandomStateTSequencer
|
||||
}
|
||||
|
||||
object RandomStateTSequencer {
|
||||
def apply(
|
||||
initialState: GameState,
|
||||
actionResultApplier: ActionResultTApplier,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomStateTSequencer =
|
||||
fromProto(
|
||||
initialStateProto = GameStateConverter.toProto(initialState),
|
||||
actionResultApplier = actionResultApplier,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
|
||||
def fromProto(
|
||||
initialStateProto: GameStateProto,
|
||||
actionResultApplier: ActionResultTApplier,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomStateTSequencer =
|
||||
RandomStateTSequencerImpl(
|
||||
initialStateProto = initialStateProto,
|
||||
actionResultApplier = actionResultApplier,
|
||||
actionResultTsWithResultingStates = RandomState(
|
||||
Vector(),
|
||||
functionalRandom
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private case class RandomStateTSequencerImpl(
|
||||
initialStateProto: GameStateProto,
|
||||
actionResultApplier: ActionResultTApplier,
|
||||
actionResultTsWithResultingStates: RandomState[
|
||||
Vector[ActionResultTWithResultingState]
|
||||
]
|
||||
) extends RandomStateTSequencer {
|
||||
override def actionResults: RandomState[Vector[ActionResultT]] =
|
||||
actionResultTsWithResultingStates.map(_.map(_.actionResult))
|
||||
|
||||
override def lastStateProto: GameStateProto =
|
||||
actionResultTsWithResultingStates.newValue.lastOption
|
||||
.map(_.resultingState)
|
||||
.getOrElse(initialStateProto)
|
||||
|
||||
override def withProtolessSimpleAction(
|
||||
action: GameStateProto => ProtolessSimpleAction
|
||||
): RandomStateTSequencer =
|
||||
withActionResultT(gs => action(gs).immediateExecute)
|
||||
|
||||
override def withProtolessRandomSimpleAction(
|
||||
action: (GameStateProto, FunctionalRandom) => RandomState[ActionResultT]
|
||||
): RandomStateTSequencer =
|
||||
withRandomActionResult(action)
|
||||
|
||||
override def withProtolessSequentialResultsAction(
|
||||
action: GameStateProto => ProtolessSequentialResultsAction
|
||||
): RandomStateTSequencer =
|
||||
withActionResults(gs => action(gs).results)
|
||||
|
||||
override def withTCommand(
|
||||
command: GameStateProto => TCommand
|
||||
): RandomStateTSequencer =
|
||||
command(lastStateProto) match {
|
||||
case TCommand.Simple(action) => withActionResultT(_ => action.immediateExecute)
|
||||
case TCommand.RandomSimple(action) =>
|
||||
withRandomActionResult((_, fr) => action.immediateExecute(fr))
|
||||
case TCommand.Sequential(action) => withActionResults(_ => action.results)
|
||||
}
|
||||
|
||||
override def withOptionalTCommand(
|
||||
command: GameStateProto => Option[TCommand]
|
||||
): RandomStateTSequencer =
|
||||
command(lastStateProto) match {
|
||||
case Some(cmd) => withTCommand(_ => cmd)
|
||||
case None => this
|
||||
}
|
||||
|
||||
override def withRandomTCommand(
|
||||
command: (GameStateProto, FunctionalRandom) => RandomState[TCommand]
|
||||
): RandomStateTSequencer =
|
||||
actionResultTsWithResultingStates match {
|
||||
case RandomState(_, fr) =>
|
||||
command(lastStateProto, fr) match {
|
||||
case RandomState(TCommand.Simple(action), newFr) =>
|
||||
copy(
|
||||
actionResultTsWithResultingStates = RandomState(
|
||||
actionResultTsWithResultingStates.newValue,
|
||||
newFr
|
||||
)
|
||||
).withActionResultT(_ => action.immediateExecute)
|
||||
case RandomState(TCommand.RandomSimple(action), newFr) =>
|
||||
copy(
|
||||
actionResultTsWithResultingStates = RandomState(
|
||||
actionResultTsWithResultingStates.newValue,
|
||||
newFr
|
||||
)
|
||||
).withRandomActionResult((_, fr2) => action.immediateExecute(fr2))
|
||||
case RandomState(TCommand.Sequential(action), newFr) =>
|
||||
copy(
|
||||
actionResultTsWithResultingStates = RandomState(
|
||||
actionResultTsWithResultingStates.newValue,
|
||||
newFr
|
||||
)
|
||||
).withActionResults(_ => action.results)
|
||||
}
|
||||
}
|
||||
|
||||
override def withOptionalRandomTCommand(
|
||||
command: (GameStateProto, FunctionalRandom) => RandomState[Option[TCommand]]
|
||||
): RandomStateTSequencer =
|
||||
actionResultTsWithResultingStates match {
|
||||
case RandomState(_, fr) =>
|
||||
command(lastStateProto, fr) match {
|
||||
case RandomState(Some(cmd), newFr) =>
|
||||
copy(
|
||||
actionResultTsWithResultingStates = RandomState(
|
||||
actionResultTsWithResultingStates.newValue,
|
||||
newFr
|
||||
)
|
||||
).withTCommand(_ => cmd)
|
||||
case RandomState(None, newFr) =>
|
||||
copy(
|
||||
actionResultTsWithResultingStates = RandomState(
|
||||
actionResultTsWithResultingStates.newValue,
|
||||
newFr
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override def withActionResultT(
|
||||
action: GameStateProto => ActionResultT
|
||||
): RandomStateTSequencer =
|
||||
withActionResult(gs => action(gs))
|
||||
|
||||
override def withActionResultTs(
|
||||
action: GameStateProto => Iterable[ActionResultT]
|
||||
): RandomStateTSequencer =
|
||||
withActionResults(gs => action(gs))
|
||||
|
||||
override def withActionResult(
|
||||
actionResultGen: GameStateProto => ActionResultT
|
||||
): RandomStateTSequencer = actionResultGen(lastStateProto) match {
|
||||
case art =>
|
||||
copy(
|
||||
actionResultTsWithResultingStates = actionResultTsWithResultingStates.map(
|
||||
_ :+ ActionResultTWithResultingState(art, lastStateProto)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override def withActionResults(
|
||||
actionResultsGen: GameStateProto => Iterable[ActionResultT]
|
||||
): RandomStateTSequencer = actionResultsGen(lastStateProto).foldLeft(this) {
|
||||
case (sequencer, ar) =>
|
||||
sequencer.copy(actionResultTsWithResultingStates =
|
||||
sequencer.actionResultTsWithResultingStates.map(
|
||||
_ :+ actionResultApplier
|
||||
.applyActionResult(
|
||||
sequencer.lastStateProto,
|
||||
ar
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override def foldIn[T](ts: Iterable[T])(
|
||||
f: (T, GameStateProto, FunctionalRandom) => RandomState[ActionResultT]
|
||||
): RandomStateTSequencer =
|
||||
ts.foldLeft(this) {
|
||||
case (sequencer, t) =>
|
||||
sequencer.withRandomActionResult { case (gs, fr) => f(t, gs, fr) }
|
||||
}
|
||||
|
||||
override def withContinuance(
|
||||
continuance: RandomStateTSequencer => RandomStateTSequencer
|
||||
): RandomStateTSequencer = continuance(this)
|
||||
|
||||
override def withRandomActionResult(
|
||||
actionResultGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[ActionResultT]
|
||||
): RandomStateTSequencerImpl =
|
||||
actionResultTsWithResultingStates match {
|
||||
case RandomState(awrs, fr) =>
|
||||
actionResultGen(lastStateProto, fr) match {
|
||||
case RandomState(art, newFr) =>
|
||||
copy(actionResultTsWithResultingStates =
|
||||
RandomState(
|
||||
awrs :+ actionResultApplier
|
||||
.applyActionResult(
|
||||
lastStateProto,
|
||||
art
|
||||
),
|
||||
newFr
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override def withRandomActionResults(
|
||||
actionResultsGen: (
|
||||
GameStateProto,
|
||||
FunctionalRandom
|
||||
) => RandomState[Vector[ActionResultT]]
|
||||
): RandomStateTSequencer =
|
||||
actionResultTsWithResultingStates match {
|
||||
case RandomState(awrs, fr) =>
|
||||
actionResultsGen(lastStateProto, fr) match {
|
||||
case RandomState(Vector(), newFr) =>
|
||||
copy(
|
||||
actionResultTsWithResultingStates = RandomState(awrs, newFr)
|
||||
)
|
||||
case RandomState(arts, newFr) =>
|
||||
copy(
|
||||
actionResultTsWithResultingStates = RandomState(
|
||||
awrs ++ actionResultApplier
|
||||
.applyActionResults(
|
||||
lastStateProto,
|
||||
arts
|
||||
),
|
||||
newFr
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override def withRandomContinuance(
|
||||
randomContinuance: RandomStateTSequencer => RandomStateTSequencer
|
||||
): RandomStateTSequencer = randomContinuance(this)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
load("@rules_scala//scala:scala.bzl", "scala_library")
|
||||
|
||||
scala_library(
|
||||
name = "random_state_sequencer",
|
||||
srcs = ["RandomStateSequencer.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_random_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_sequential_results_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
package net.eagle0.eagle.library.actions.random_state_sequencer
|
||||
|
||||
import net.eagle0.common.{FunctionalRandom, RandomState}
|
||||
import net.eagle0.eagle.library.actions.applier.{ActionResultApplier, ActionResultWithResultingState}
|
||||
import net.eagle0.eagle.library.actions.impl.common.{
|
||||
ProtolessRandomSimpleAction,
|
||||
ProtolessSequentialResultsAction,
|
||||
ProtolessSimpleAction,
|
||||
TCommand
|
||||
}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
|
||||
/**
|
||||
* A fully protoless sequencer for chaining action results.
|
||||
*
|
||||
* - Uses Scala GameState throughout (no proto conversions)
|
||||
* - All callbacks receive Scala GameState
|
||||
* - Returns ActionResultWithResultingState (Scala GameState, not proto)
|
||||
*/
|
||||
trait RandomStateSequencer {
|
||||
def lastState: GameState
|
||||
|
||||
def actionResults: RandomState[Vector[ActionResultT]]
|
||||
|
||||
def resultsWithStates: RandomState[Vector[ActionResultWithResultingState]]
|
||||
|
||||
def withActionResult(
|
||||
actionResultGen: GameState => ActionResultT
|
||||
): RandomStateSequencer
|
||||
|
||||
def withActionResults(
|
||||
actionResultsGen: GameState => Iterable[ActionResultT]
|
||||
): RandomStateSequencer
|
||||
|
||||
def withRandomActionResult(
|
||||
actionResultGen: (GameState, FunctionalRandom) => RandomState[ActionResultT]
|
||||
): RandomStateSequencer
|
||||
|
||||
def withRandomActionResults(
|
||||
actionResultsGen: FunctionalRandom => RandomState[Vector[ActionResultT]]
|
||||
): RandomStateSequencer =
|
||||
withRandomActionResults((_, fr) => actionResultsGen(fr))
|
||||
|
||||
def withRandomActionResults(
|
||||
actionResultsGen: (GameState, FunctionalRandom) => RandomState[Vector[ActionResultT]]
|
||||
): RandomStateSequencer
|
||||
|
||||
def withContinuance(
|
||||
continuance: RandomStateSequencer => RandomStateSequencer
|
||||
): RandomStateSequencer
|
||||
|
||||
def withRandomContinuance(
|
||||
randomContinuance: RandomStateSequencer => RandomStateSequencer
|
||||
): RandomStateSequencer
|
||||
|
||||
def foldIn[T](ts: Iterable[T])(
|
||||
f: (T, GameState, FunctionalRandom) => RandomState[ActionResultT]
|
||||
): RandomStateSequencer
|
||||
|
||||
def withProtolessSimpleAction(
|
||||
actionGen: GameState => ProtolessSimpleAction
|
||||
): RandomStateSequencer =
|
||||
withActionResult(gs => actionGen(gs).immediateExecute)
|
||||
|
||||
def withProtolessRandomSimpleAction(
|
||||
actionGen: GameState => ProtolessRandomSimpleAction
|
||||
): RandomStateSequencer =
|
||||
withRandomActionResult((gs, fr) => actionGen(gs).immediateExecute(fr))
|
||||
|
||||
def withProtolessSequentialResultsAction(
|
||||
actionGen: GameState => ProtolessSequentialResultsAction
|
||||
): RandomStateSequencer =
|
||||
withActionResults(gs => actionGen(gs).results)
|
||||
|
||||
def withTCommand(commandGen: GameState => TCommand): RandomStateSequencer =
|
||||
commandGen(lastState) match {
|
||||
case TCommand.Simple(action) => withProtolessSimpleAction(_ => action)
|
||||
case TCommand.RandomSimple(action) => withProtolessRandomSimpleAction(_ => action)
|
||||
case TCommand.Sequential(action) => withProtolessSequentialResultsAction(_ => action)
|
||||
}
|
||||
}
|
||||
|
||||
object RandomStateSequencer {
|
||||
def apply(
|
||||
initialState: GameState,
|
||||
actionResultApplier: ActionResultApplier,
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomStateSequencer =
|
||||
RandomStateSequencerImpl(
|
||||
initialState = initialState,
|
||||
actionResultApplier = actionResultApplier,
|
||||
resultsWithStates = RandomState(Vector(), functionalRandom)
|
||||
)
|
||||
}
|
||||
|
||||
private case class RandomStateSequencerImpl(
|
||||
initialState: GameState,
|
||||
actionResultApplier: ActionResultApplier,
|
||||
resultsWithStates: RandomState[Vector[ActionResultWithResultingState]]
|
||||
) extends RandomStateSequencer {
|
||||
|
||||
override def lastState: GameState =
|
||||
resultsWithStates.newValue.lastOption
|
||||
.map(_.resultingState)
|
||||
.getOrElse(initialState)
|
||||
|
||||
override def actionResults: RandomState[Vector[ActionResultT]] =
|
||||
resultsWithStates.map(_.map(_.actionResult))
|
||||
|
||||
override def withActionResult(
|
||||
actionResultGen: GameState => ActionResultT
|
||||
): RandomStateSequencer = {
|
||||
val result = actionResultGen(lastState)
|
||||
copy(
|
||||
resultsWithStates = resultsWithStates.map(
|
||||
_ :+ actionResultApplier.applyActionResult(lastState, result)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override def withActionResults(
|
||||
actionResultsGen: GameState => Iterable[ActionResultT]
|
||||
): RandomStateSequencer =
|
||||
actionResultsGen(lastState).foldLeft[RandomStateSequencer](this) {
|
||||
case (sequencer, result) =>
|
||||
sequencer.withActionResult(_ => result)
|
||||
}
|
||||
|
||||
override def withRandomActionResult(
|
||||
actionResultGen: (GameState, FunctionalRandom) => RandomState[ActionResultT]
|
||||
): RandomStateSequencer =
|
||||
resultsWithStates match {
|
||||
case RandomState(currentResults, fr) =>
|
||||
actionResultGen(lastState, fr) match {
|
||||
case RandomState(result, newFr) =>
|
||||
copy(
|
||||
resultsWithStates = RandomState(
|
||||
currentResults :+ actionResultApplier.applyActionResult(lastState, result),
|
||||
newFr
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override def withRandomActionResults(
|
||||
actionResultsGen: (GameState, FunctionalRandom) => RandomState[Vector[ActionResultT]]
|
||||
): RandomStateSequencer =
|
||||
resultsWithStates match {
|
||||
case RandomState(currentResults, fr) =>
|
||||
actionResultsGen(lastState, fr) match {
|
||||
case RandomState(Vector(), newFr) =>
|
||||
copy(resultsWithStates = RandomState(currentResults, newFr))
|
||||
case RandomState(results, newFr) =>
|
||||
copy(
|
||||
resultsWithStates = RandomState(
|
||||
currentResults ++ actionResultApplier.applyActionResults(lastState, results),
|
||||
newFr
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override def withContinuance(
|
||||
continuance: RandomStateSequencer => RandomStateSequencer
|
||||
): RandomStateSequencer = continuance(this)
|
||||
|
||||
override def withRandomContinuance(
|
||||
randomContinuance: RandomStateSequencer => RandomStateSequencer
|
||||
): RandomStateSequencer = randomContinuance(this)
|
||||
|
||||
override def foldIn[T](ts: Iterable[T])(
|
||||
f: (T, GameState, FunctionalRandom) => RandomState[ActionResultT]
|
||||
): RandomStateSequencer =
|
||||
ts.foldLeft[RandomStateSequencer](this) {
|
||||
case (sequencer, t) =>
|
||||
sequencer.withRandomActionResult((gs, fr) => f(t, gs, fr))
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import net.eagle0.eagle.model.state.province.{
|
||||
EpidemicEvent,
|
||||
FestivalEvent,
|
||||
FloodEvent,
|
||||
ImminentRiotEvent,
|
||||
ProvinceEvent,
|
||||
ProvinceT
|
||||
}
|
||||
@@ -115,9 +114,6 @@ object ProvinceUtils {
|
||||
def hasEpidemic(p: ProvinceT): Boolean =
|
||||
p.activeEvents.collectFirst { case _: EpidemicEvent => () }.nonEmpty
|
||||
|
||||
def hasImminentRiot(p: ProvinceT): Boolean =
|
||||
p.activeEvents.collectFirst { case _: ImminentRiotEvent => () }.nonEmpty
|
||||
|
||||
def hasBeasts(p: ProvinceT): Boolean =
|
||||
p.activeEvents.collectFirst { case _: BeastsEvent => () }.nonEmpty
|
||||
|
||||
|
||||
@@ -27,32 +27,4 @@ object RoundPhase {
|
||||
case object BattleAftermath extends RoundPhase { val value = 23 }
|
||||
case object DiplomacyResolution extends RoundPhase { val value = 24 }
|
||||
case object ReconResolution extends RoundPhase { val value = 26 }
|
||||
|
||||
val allValues: Vector[RoundPhase] = Vector(
|
||||
NewRound,
|
||||
PrisonerExchange,
|
||||
ProvinceEvents,
|
||||
ForcedTurnBack,
|
||||
ProvinceMoveResolution,
|
||||
HandleRiot,
|
||||
HeroDepartures,
|
||||
PleaseRecruitMe,
|
||||
UnaffiliatedHeroActions,
|
||||
VassalCommands,
|
||||
PlayerCommands,
|
||||
HostileArmySetup,
|
||||
FreeForAllDecision,
|
||||
FreeForAllBattleRequest,
|
||||
FreeForAllBattleResolution,
|
||||
UncontestedConquest,
|
||||
AttackDecision,
|
||||
DefenseDecision,
|
||||
TruceTurnBack,
|
||||
BattleRequest,
|
||||
FoodConsumption,
|
||||
BattleResolution,
|
||||
BattleAftermath,
|
||||
DiplomacyResolution,
|
||||
ReconResolution
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ import net.eagle0.eagle.library.util.hero_name_fetcher.HeroNameFetcher
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
|
||||
class UnrequestedTextHandler(llmResolver: LlmResolver) {
|
||||
// 3 minutes in milliseconds
|
||||
private val StalledThresholdMillis: Long = 3 * 60 * 1000
|
||||
|
||||
def clientTextStoreWithHandledUnrequestedTexts(
|
||||
clientTextStore: ClientTextStore,
|
||||
gameHistory: GameHistory,
|
||||
@@ -156,40 +153,20 @@ class UnrequestedTextHandler(llmResolver: LlmResolver) {
|
||||
gameHistory: GameHistory,
|
||||
randomLong: Long
|
||||
): ClientTextStore = {
|
||||
// Check for stalled texts and log warnings
|
||||
val stalledTexts = clientTextStore.stalledIncompleteTexts(StalledThresholdMillis)
|
||||
if stalledTexts.nonEmpty then {
|
||||
val stalledDetails = stalledTexts.map { ict =>
|
||||
val waitingSeconds = (System.currentTimeMillis() - ict.requestedAtMillis) / 1000
|
||||
s"${ict.id} (waiting ${waitingSeconds}s, partial: ${ict.partialText.take(50)}...)"
|
||||
}.mkString("\n ")
|
||||
println(
|
||||
s"WARNING: ${stalledTexts.size} incomplete text(s) have been waiting > 3 minutes:\n $stalledDetails"
|
||||
)
|
||||
}
|
||||
|
||||
val results = llmResolver
|
||||
.resolveLlmRequests(
|
||||
llmRequests = clientTextStore.incompleteTexts.values
|
||||
.map((ict: IncompleteClientText) =>
|
||||
LlmResolver.LlmRequestWithGameState(
|
||||
llmRequest = ict.llmRequest,
|
||||
gameState = GameStateConverter.toProto(gameHistory.stateAfter(ict.requestedAfterHistoryCount)),
|
||||
partialCompletion = Some(ict.partialText)
|
||||
)
|
||||
llmResolver.resolveLlmRequests(
|
||||
llmRequests = clientTextStore.incompleteTexts.values
|
||||
.map((ict: IncompleteClientText) =>
|
||||
LlmResolver.LlmRequestWithGameState(
|
||||
llmRequest = ict.llmRequest,
|
||||
gameState = GameStateConverter.toProto(gameHistory.stateAfter(ict.requestedAfterHistoryCount)),
|
||||
partialCompletion = Some(ict.partialText)
|
||||
)
|
||||
.toVector,
|
||||
clientTextStore = clientTextStore,
|
||||
functionalRandom = SeededRandom(randomLong)
|
||||
)
|
||||
.newValue
|
||||
)
|
||||
.toVector,
|
||||
clientTextStore = clientTextStore,
|
||||
functionalRandom = SeededRandom(randomLong)
|
||||
)
|
||||
|
||||
// Move texts that couldn't be submitted back to unrequested so they get retried
|
||||
results.foldLeft(clientTextStore) {
|
||||
case (cts, (llmRequest, LlmResolverTooManyRequestsInFlight)) =>
|
||||
cts.withMovedBackToUnrequested(llmRequest.id)
|
||||
case (cts, _) =>
|
||||
cts
|
||||
}
|
||||
clientTextStore
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import io.grpc.{Status, StatusRuntimeException}
|
||||
import net.eagle0.common.{RandomState, SeededRandom, SimpleTimedLogger}
|
||||
import net.eagle0.eagle.{FactionId, GameId, ProvinceId}
|
||||
import net.eagle0.eagle.ai.AIClient
|
||||
import net.eagle0.eagle.api.eagle.ServerGameStatus
|
||||
import net.eagle0.eagle.api.eagle.ShardokActionResultResponse.SingleShardokGameResultResponse
|
||||
import net.eagle0.eagle.api.eagle.UpdateStreamRequest.StreamGameRequest.{ShardokViewStatus, StreamingTextStatus}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
@@ -143,34 +142,10 @@ object GameController {
|
||||
Some {
|
||||
val availableCommands =
|
||||
engine.getAvailablePlayerCommands(client.factionId)
|
||||
|
||||
// Calculate server game status based on available information
|
||||
val hasCommands = availableCommands.exists(_.commandsByProvince.nonEmpty)
|
||||
|
||||
// Check if any other human player has commands
|
||||
val otherHumanFactionIds = humanClients.map(_.factionId).filterNot(_ == client.factionId)
|
||||
val otherPlayersHaveCommands = otherHumanFactionIds.exists { fid =>
|
||||
engine.getAvailablePlayerCommands(fid).exists(_.commandsByProvince.nonEmpty)
|
||||
}
|
||||
|
||||
// Check if there are incomplete LLM texts accessible to this player
|
||||
val hasIncompleteTexts = clientTextStore.incompleteTexts.values.exists { incompleteText =>
|
||||
clientTextStore.accessibleTo
|
||||
.get(incompleteText.id)
|
||||
.exists(fids => fids.isEmpty || fids.contains(client.factionId))
|
||||
}
|
||||
|
||||
val serverGameStatus =
|
||||
if hasCommands then Some(ServerGameStatus(status = ServerGameStatus.Status.YOUR_TURN))
|
||||
else if hasIncompleteTexts then Some(ServerGameStatus(status = ServerGameStatus.Status.GENERATING_TEXT))
|
||||
else if otherPlayersHaveCommands then
|
||||
Some(ServerGameStatus(status = ServerGameStatus.Status.WAITING_FOR_PLAYERS))
|
||||
else None
|
||||
|
||||
val note =
|
||||
val note =
|
||||
s"fid ${client.factionId}: Updating client with ${updates.filteredResults.length} new results, " +
|
||||
s"${shardokUpdates.length} new Shardok results, "
|
||||
val note2 = availableCommands.map { acs =>
|
||||
val note2 = availableCommands.map { acs =>
|
||||
s"available commands with token ${acs.token}"
|
||||
}
|
||||
.getOrElse("no available commands")
|
||||
@@ -183,8 +158,7 @@ object GameController {
|
||||
availableCommands = engine.getAvailablePlayerCommands(client.factionId),
|
||||
filteredResults = updates.filteredResults,
|
||||
unfilteredCountAfter = updates.unfilteredCountAfter,
|
||||
knownShardokResults = shardokUpdates.toVector,
|
||||
serverGameStatus = serverGameStatus
|
||||
knownShardokResults = shardokUpdates.toVector
|
||||
)
|
||||
) {
|
||||
case (client, clientTextUpdate) =>
|
||||
|
||||
+9
-33
@@ -7,13 +7,7 @@ import scala.annotation.tailrec
|
||||
|
||||
import net.eagle0.eagle.{FactionId, GameId, ShardokGameId}
|
||||
import net.eagle0.eagle.api.command.AvailableCommands
|
||||
import net.eagle0.eagle.api.eagle.{
|
||||
ActionResultResponse,
|
||||
GameUpdate,
|
||||
ServerGameStatus,
|
||||
ShardokActionResultResponse,
|
||||
UpdateStreamResponse
|
||||
}
|
||||
import net.eagle0.eagle.api.eagle.{ActionResultResponse, GameUpdate, ShardokActionResultResponse, UpdateStreamResponse}
|
||||
import net.eagle0.eagle.api.eagle.GameUpdate.GameUpdateDetails
|
||||
import net.eagle0.eagle.api.eagle.ShardokActionResultResponse.SingleShardokGameResultResponse
|
||||
import net.eagle0.eagle.api.eagle.UpdateStreamRequest.StreamGameRequest.{ShardokViewStatus, StreamingTextStatus}
|
||||
@@ -179,8 +173,7 @@ case class HumanPlayerClientConnectionState(
|
||||
availableCommands: Option[AvailableCommands],
|
||||
filteredResults: Vector[ActionResultView],
|
||||
unfilteredCountAfter: Int,
|
||||
knownShardokResults: Vector[SingleShardokGameResultResponse],
|
||||
serverGameStatus: Option[ServerGameStatus]
|
||||
knownShardokResults: Vector[SingleShardokGameResultResponse]
|
||||
): HumanPlayerClientConnectionState = {
|
||||
// Send Shardok results FIRST, before Eagle results that may contain date
|
||||
// changes which clear ShardokGameModels on the client
|
||||
@@ -199,8 +192,7 @@ case class HumanPlayerClientConnectionState(
|
||||
availableCommands = availableCommands,
|
||||
startingState = None,
|
||||
filteredResults = filteredResults,
|
||||
unfilteredCountAfter = unfilteredCountAfter,
|
||||
serverGameStatus = serverGameStatus
|
||||
unfilteredCountAfter = unfilteredCountAfter
|
||||
)
|
||||
}
|
||||
|
||||
@@ -276,16 +268,6 @@ case class HumanPlayerClientConnectionState(
|
||||
// starting state and filter results from position 1 onwards.
|
||||
val effectiveStartCount = if unfilteredCount == 0 && history.count > 0 then 1 else unfilteredCount
|
||||
|
||||
// For initial connection, we only know about this player's commands.
|
||||
// Report YOUR_TURN if they have commands, otherwise don't report a status
|
||||
// (GameController.humanClientsAfterPostingResults has more context for updates).
|
||||
val serverGameStatus = availableCommands match {
|
||||
case Some(cmds) if cmds.commandsByProvince.nonEmpty =>
|
||||
Some(ServerGameStatus(status = ServerGameStatus.Status.YOUR_TURN))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
|
||||
if history.count <= effectiveStartCount + maxInitialResults then {
|
||||
val fr = filteredResultsFrom(history, effectiveStartCount)
|
||||
val startingState =
|
||||
@@ -302,8 +284,7 @@ case class HumanPlayerClientConnectionState(
|
||||
availableCommands = availableCommands,
|
||||
startingState = startingState,
|
||||
filteredResults = fr.filteredResults,
|
||||
unfilteredCountAfter = fr.unfilteredCountAfter,
|
||||
serverGameStatus = serverGameStatus
|
||||
unfilteredCountAfter = fr.unfilteredCountAfter
|
||||
)
|
||||
.withNewCount(fr.unfilteredCountAfter)
|
||||
.enqueueShardokResults(filteredShardokResults = sfr)
|
||||
@@ -322,8 +303,7 @@ case class HumanPlayerClientConnectionState(
|
||||
)
|
||||
),
|
||||
filteredResults = fr.filteredResults,
|
||||
unfilteredCountAfter = fr.unfilteredCountAfter,
|
||||
serverGameStatus = serverGameStatus
|
||||
unfilteredCountAfter = fr.unfilteredCountAfter
|
||||
)
|
||||
.withNewCount(fr.unfilteredCountAfter)
|
||||
.enqueueShardokResults(filteredShardokResults = sfr)
|
||||
@@ -350,23 +330,20 @@ case class HumanPlayerClientConnectionState(
|
||||
availableCommands: Option[AvailableCommands],
|
||||
startingState: Option[GameStateView],
|
||||
filteredResults: Vector[ActionResultView],
|
||||
unfilteredCountAfter: Int,
|
||||
serverGameStatus: Option[ServerGameStatus]
|
||||
unfilteredCountAfter: Int
|
||||
): HumanPlayerClientConnectionState =
|
||||
enqueueResults(
|
||||
startingState = startingState,
|
||||
filteredResults = filteredResults,
|
||||
unfilteredCountAfter = unfilteredCountAfter,
|
||||
availableCommands = availableCommands,
|
||||
serverGameStatus = serverGameStatus
|
||||
availableCommands = availableCommands
|
||||
)
|
||||
|
||||
private final def enqueueResults(
|
||||
startingState: Option[GameStateView],
|
||||
filteredResults: Vector[ActionResultView],
|
||||
unfilteredCountAfter: Int,
|
||||
availableCommands: Option[AvailableCommands],
|
||||
serverGameStatus: Option[ServerGameStatus]
|
||||
availableCommands: Option[AvailableCommands]
|
||||
): HumanPlayerClientConnectionState = {
|
||||
@tailrec
|
||||
def go(
|
||||
@@ -391,8 +368,7 @@ case class HumanPlayerClientConnectionState(
|
||||
ActionResultResponse(
|
||||
actionResultViews = filteredResults,
|
||||
unfilteredResultCountAfter = unfilteredCountAfter,
|
||||
availableCommands = availableCommands,
|
||||
serverGameStatus = serverGameStatus
|
||||
availableCommands = availableCommands
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -126,8 +126,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
id = "existingId",
|
||||
partialText = "firstText",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest,
|
||||
requestedAtMillis = 12345L
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
),
|
||||
unrequestedTexts = Map(),
|
||||
@@ -207,8 +206,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
id = "existingId",
|
||||
partialText = "firstText",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest,
|
||||
requestedAtMillis = 12345L
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
),
|
||||
unrequestedTexts = Map(),
|
||||
@@ -233,8 +231,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
id = "existingId",
|
||||
partialText = "firstText",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest,
|
||||
requestedAtMillis = 12345L
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
)
|
||||
updatedStore.unrequestedTexts should contain theSameElementsAs Map(
|
||||
@@ -300,8 +297,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
id = "id",
|
||||
partialText = "text",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest,
|
||||
requestedAtMillis = 12345L
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
),
|
||||
unrequestedTexts = Map(),
|
||||
@@ -360,8 +356,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
id = "id",
|
||||
partialText = "text",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest,
|
||||
requestedAtMillis = 12345L
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
),
|
||||
unrequestedTexts = Map(),
|
||||
@@ -378,7 +373,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
}
|
||||
|
||||
"withMarkedRequested" should "move unrequested to incomplete" in {
|
||||
val store =
|
||||
val store =
|
||||
ClientTextStoreImpl(
|
||||
pregenerated = pregeneratedClientTextStore,
|
||||
persister = mockPersister,
|
||||
@@ -397,14 +392,16 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
incompleteTextsAreSaved = false,
|
||||
accessibleToIsSaved = false
|
||||
)
|
||||
val updatedStore = store.withMarkedRequested("id")
|
||||
updatedStore.incompleteTexts.size shouldBe 1
|
||||
val incompleteText = updatedStore.incompleteTexts("id")
|
||||
incompleteText.id shouldBe "id"
|
||||
incompleteText.partialText shouldBe ""
|
||||
incompleteText.requestedAfterHistoryCount shouldBe requestedAfterHistoryCount
|
||||
incompleteText.llmRequest shouldBe genericLlmRequest
|
||||
incompleteText.requestedAtMillis should be > 0L
|
||||
val updatedStore = store.withMarkedRequested("id")
|
||||
updatedStore.incompleteTexts should contain theSameElementsAs Map(
|
||||
"id" ->
|
||||
IncompleteClientText(
|
||||
id = "id",
|
||||
partialText = "",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
)
|
||||
updatedStore.unrequestedTexts shouldBe empty
|
||||
}
|
||||
|
||||
@@ -420,8 +417,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
id = "id",
|
||||
partialText = "text",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest,
|
||||
requestedAtMillis = 12345L
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
),
|
||||
unrequestedTexts = Map(),
|
||||
@@ -439,8 +435,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
id = "id",
|
||||
partialText = "textnew text",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest,
|
||||
requestedAtMillis = 12345L
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -477,8 +472,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
id = "id",
|
||||
partialText = "text",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest,
|
||||
requestedAtMillis = 12345L
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
),
|
||||
unrequestedTexts = Map(),
|
||||
@@ -539,8 +533,7 @@ class ClientTextStoreImplTest extends AnyFlatSpec with MockFactory with Matchers
|
||||
id = "id",
|
||||
partialText = "text",
|
||||
requestedAfterHistoryCount = requestedAfterHistoryCount,
|
||||
llmRequest = genericLlmRequest,
|
||||
requestedAtMillis = 12345L
|
||||
llmRequest = genericLlmRequest
|
||||
)
|
||||
),
|
||||
unrequestedTexts = Map(),
|
||||
|
||||
@@ -93,8 +93,10 @@ scala_test(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero/concrete",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:action_impl_pkg",
|
||||
@@ -108,8 +110,10 @@ scala_test(
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:end_battle_aftermath_phase_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_proto_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:random_state_trait_sequencer",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:faction_bias_against_former_on_exile",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:faction_bias_from_exile",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:faction_bias_from_imprisonment",
|
||||
@@ -134,7 +138,6 @@ scala_test(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction:faction_relationship",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:deferred_change_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero/concrete",
|
||||
@@ -189,14 +192,21 @@ scala_test(
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:end_diplomacy_resolution_phase_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:idable",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/validations:scala_runtime_validator",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_diplomacy_resolution_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:action_impl_pkg",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -206,30 +216,47 @@ scala_test(
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:faction_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:province_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:end_handle_riots_phase_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:perform_province_events_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:attack_decision_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:free_for_all_decision_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:handle_captured_heroes_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:handle_riot_do_nothing_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:t_command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:manage_prisoners_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:protoless_simple_action_wrapper",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:vigor_xp_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:riot_economy_devastation_delta",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:riot_infrastructure_devastation_delta",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:riot_max_food",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:riot_max_gold",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:riot_support_delta",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/validations:scala_runtime_validator",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_handle_riots_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:riot_aversion_failed_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:riot_occurred_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_faction_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
"//src/test/scala/net/eagle0/common:proto_matchers",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:action_impl_pkg",
|
||||
"//src/test/scala/net/eagle0/eagle/library/util/validations:testing_noop_validator",
|
||||
"@maven//:org_scalamock_scalamock_3",
|
||||
],
|
||||
)
|
||||
@@ -241,8 +268,8 @@ scala_test(
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:end_player_commands_phase_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:prisoner_escape_chance",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:idable",
|
||||
@@ -334,11 +361,9 @@ scala_test(
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:province_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:supplies_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:unaffiliated_hero_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library:game_history",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:new_round_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:base_resource_limit",
|
||||
@@ -356,11 +381,8 @@ scala_test(
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:percent_degradation_per_year",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:trust_delta_per_round",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:idable",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:action_impl_pkg",
|
||||
"//src/test/scala/net/eagle0/eagle/library/util/validations:testing_noop_validator",
|
||||
"@maven//:org_scalamock_scalamock_3",
|
||||
@@ -602,7 +624,7 @@ scala_test(
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:unaffiliated_hero_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:perform_recon_resolution_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:base_resource_limit",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:per_development_resource_limit",
|
||||
@@ -632,8 +654,9 @@ scala_test(
|
||||
name = "perform_unaffiliated_heroes_action_test",
|
||||
srcs = ["PerformUnaffiliatedHeroesActionTest.scala"],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:province_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:perform_unaffiliated_heroes_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:free_hero_move_vigor_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:min_odds_for_please_recruit_me",
|
||||
@@ -656,25 +679,14 @@ scala_test(
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:traveler_to_resident_chance",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:vigor_to_constitution_xp_multiplier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:xp_for_stat_bump",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_unaffiliated_hero_actions_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:hero_changed_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:hero_moved_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:hero_rejoined_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:idable",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero/concrete",
|
||||
"//src/test/scala/net/eagle0/common:proto_matchers",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:action_impl_pkg",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -699,15 +711,20 @@ scala_test(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type_id",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:combat_unit",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:supplies",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province:orders",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:action_impl_pkg",
|
||||
],
|
||||
)
|
||||
@@ -728,13 +745,13 @@ scala_test(
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:unaffiliated_hero_scala_proto",
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_trait_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:perform_vassal_commands_phase_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:attack_decision_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:free_for_all_decision_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:handle_captured_heroes_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:manage_prisoners_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:t_command_factory",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:t_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:action_vigor_cost",
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:desired_loyalty_over_threshold",
|
||||
@@ -758,6 +775,7 @@ scala_test(
|
||||
"//src/main/scala/net/eagle0/eagle/library/settings:vigor_gain_from_feast",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:idable",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
@@ -910,11 +928,18 @@ scala_test(
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types/base:action_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:battalion_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/battalion/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/shardok_battle",
|
||||
],
|
||||
)
|
||||
@@ -978,21 +1003,14 @@ scala_test(
|
||||
name = "truce_turn_back_phase_action_test",
|
||||
srcs = ["TruceTurnBackPhaseActionTest.scala"],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:truce_turn_back_phase_action",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:end_truce_turn_back_phase_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:withdrawal_for_truce_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:idable",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
"//src/test/scala/net/eagle0/common:proto_matchers",
|
||||
"//src/test/scala/net/eagle0/eagle/library/util/validations:testing_noop_validator",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
+33
-28
@@ -20,9 +20,11 @@ import net.eagle0.eagle.model.state.{
|
||||
Supplies
|
||||
}
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.concrete.TruceWithFactionQuest
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.{RecruitmentInfo, UnaffiliatedHeroT}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.concrete.UnaffiliatedHeroC
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.{Outlaw, Resident, Traveler}
|
||||
@@ -38,6 +40,30 @@ class EndAttackDecisionPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
private val currentRoundId: RoundId = 921
|
||||
private val currentDate = Date(year = 221, month = Date.Month.April)
|
||||
|
||||
private def makeGameState(provinces: Vector[ProvinceT]): GameState = GameState(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentPhase = RoundPhase.AttackDecision,
|
||||
currentDate = Some(currentDate),
|
||||
actionResultCount = 0,
|
||||
provinces = provinces.map(p => p.id -> p).toMap,
|
||||
heroes = Map.empty,
|
||||
battalions = Map.empty,
|
||||
destroyedBattalions = Map.empty,
|
||||
factions = Map.empty,
|
||||
factionCommandCounts = Map.empty,
|
||||
killedHeroes = Map.empty,
|
||||
destroyedFactions = Map.empty,
|
||||
outstandingBattles = Vector.empty,
|
||||
battleCounter = 0,
|
||||
deferredNotifications = Vector.empty,
|
||||
runStatus = RunStatus.Running,
|
||||
victor = None,
|
||||
battalionTypes = Vector.empty,
|
||||
randomSeed = 12345L,
|
||||
chronicleEntries = Vector.empty
|
||||
)
|
||||
|
||||
private val provincesWithWithdrawingArmy: Vector[ProvinceT] = Vector(
|
||||
ProvinceC(
|
||||
id = 4,
|
||||
@@ -121,10 +147,7 @@ class EndAttackDecisionPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
"execute" should "set the basics" in {
|
||||
val results =
|
||||
EndAttackDecisionPhaseAction(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentDate = currentDate,
|
||||
provinces = Vector.empty
|
||||
makeGameState(Vector.empty)
|
||||
).results
|
||||
|
||||
results.head.actionResultType shouldBe EndAttackDecisionPhaseResultType
|
||||
@@ -149,10 +172,7 @@ class EndAttackDecisionPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
|
||||
val results =
|
||||
EndAttackDecisionPhaseAction(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentDate = currentDate,
|
||||
provinces = provincesWithTruceQuest
|
||||
makeGameState(provincesWithTruceQuest)
|
||||
).results
|
||||
|
||||
forExactly(1, results) { result =>
|
||||
@@ -175,10 +195,7 @@ class EndAttackDecisionPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
"incoming armies still present for this round" should "set the basics" in {
|
||||
val results =
|
||||
EndAttackDecisionPhaseAction(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentDate = currentDate,
|
||||
provinces = provincesWithWithdrawingArmy
|
||||
makeGameState(provincesWithWithdrawingArmy)
|
||||
).results
|
||||
|
||||
results should have size 3
|
||||
@@ -190,10 +207,7 @@ class EndAttackDecisionPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
it should "generate outlaws" in {
|
||||
val result =
|
||||
EndAttackDecisionPhaseAction(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentDate = currentDate,
|
||||
provinces = provincesWithWithdrawingArmy
|
||||
makeGameState(provincesWithWithdrawingArmy)
|
||||
)
|
||||
.results(1)
|
||||
|
||||
@@ -222,10 +236,7 @@ class EndAttackDecisionPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
it should "remove the relevant hostile army" in {
|
||||
val result =
|
||||
EndAttackDecisionPhaseAction(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentDate = currentDate,
|
||||
provinces = provincesWithWithdrawingArmy
|
||||
makeGameState(provincesWithWithdrawingArmy)
|
||||
).results.head
|
||||
|
||||
inside(result.changedProvinces.loneElement) {
|
||||
@@ -237,10 +248,7 @@ class EndAttackDecisionPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
it should "destroy the battalions" in {
|
||||
val result =
|
||||
EndAttackDecisionPhaseAction(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentDate = currentDate,
|
||||
provinces = provincesWithWithdrawingArmy
|
||||
makeGameState(provincesWithWithdrawingArmy)
|
||||
)
|
||||
.results(1)
|
||||
|
||||
@@ -250,10 +258,7 @@ class EndAttackDecisionPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
it should "generate a notification" in {
|
||||
val result =
|
||||
EndAttackDecisionPhaseAction(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentDate = currentDate,
|
||||
provinces = provincesWithWithdrawingArmy
|
||||
makeGameState(provincesWithWithdrawingArmy)
|
||||
)
|
||||
.results(1)
|
||||
|
||||
|
||||
+9
-12
@@ -18,7 +18,7 @@ import net.eagle0.eagle.internal.faction.Faction
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplierImpl
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplierImpl
|
||||
import net.eagle0.eagle.library.actions.impl.action.EndBattleAftermathPhaseAction.RevelationChange
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
FactionBiasAgainstFormerOnExile,
|
||||
@@ -66,7 +66,7 @@ class EndBattleAftermathPhaseActionTest extends AnyFlatSpec with Matchers with B
|
||||
TruceMonthsFromReturningLeader.setIntValue(12)
|
||||
}
|
||||
|
||||
private val actionResultApplier = ActionResultApplierImpl(validator = None)
|
||||
private val actionResultApplier = ActionResultTApplierImpl()
|
||||
|
||||
private val staticFr: FunctionalRandom = SeededRandom(0xcafe)
|
||||
|
||||
@@ -132,7 +132,7 @@ class EndBattleAftermathPhaseActionTest extends AnyFlatSpec with Matchers with B
|
||||
|
||||
val rcs =
|
||||
EndBattleAftermathPhaseAction(GameStateConverter.fromProto(gameState), actionResultApplier)
|
||||
.revelationChanges(GameStateConverter.fromProto(gameState))
|
||||
.revelationChanges(gameState)
|
||||
|
||||
inside(rcs.loneElement) {
|
||||
case RevelationChange(changedFaction, changedProvince) =>
|
||||
@@ -728,12 +728,11 @@ class EndBattleAftermathPhaseActionTest extends AnyFlatSpec with Matchers with B
|
||||
factions = mapifyFactions(Faction(id = 3))
|
||||
)
|
||||
|
||||
val gs = GameStateConverter.fromProto(gameState)
|
||||
inside(
|
||||
EndBattleAftermathPhaseAction
|
||||
.deferredChangeAR(
|
||||
deferredChange = EndBattleAftermathPhaseAction.allDeferredChanges(gs).head,
|
||||
gameState = gs,
|
||||
deferredChange = EndBattleAftermathPhaseAction.allDeferredChanges(gameState).head,
|
||||
gameStateProto = gameState,
|
||||
functionalRandom = SeededRandom(0xfeed)
|
||||
)
|
||||
.newValue
|
||||
@@ -803,12 +802,11 @@ class EndBattleAftermathPhaseActionTest extends AnyFlatSpec with Matchers with B
|
||||
factions = mapifyFactions(Faction(id = 3))
|
||||
)
|
||||
|
||||
val gs = GameStateConverter.fromProto(gameState)
|
||||
inside(
|
||||
EndBattleAftermathPhaseAction
|
||||
.deferredChangeAR(
|
||||
deferredChange = EndBattleAftermathPhaseAction.allDeferredChanges(gs).head,
|
||||
gameState = gs,
|
||||
deferredChange = EndBattleAftermathPhaseAction.allDeferredChanges(gameState).head,
|
||||
gameStateProto = gameState,
|
||||
functionalRandom = SeededRandom(0xfeed)
|
||||
)
|
||||
.newValue
|
||||
@@ -856,12 +854,11 @@ class EndBattleAftermathPhaseActionTest extends AnyFlatSpec with Matchers with B
|
||||
factions = mapifyFactions(Faction(id = 3))
|
||||
)
|
||||
|
||||
val gs = GameStateConverter.fromProto(gameState)
|
||||
inside(
|
||||
EndBattleAftermathPhaseAction
|
||||
.deferredChangeAR(
|
||||
deferredChange = EndBattleAftermathPhaseAction.allDeferredChanges(gs).head,
|
||||
gameState = gs,
|
||||
deferredChange = EndBattleAftermathPhaseAction.allDeferredChanges(gameState).head,
|
||||
gameStateProto = gameState,
|
||||
functionalRandom = SeededRandom(0xface)
|
||||
)
|
||||
.newValue
|
||||
|
||||
+6
-7
@@ -1,10 +1,9 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.SeededRandom
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplierImpl
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplierImpl
|
||||
import net.eagle0.eagle.library.util.validations.ScalaRuntimeValidator
|
||||
import net.eagle0.eagle.model.action_result.types.EndDiplomacyResolutionPhaseResultType
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
@@ -21,7 +20,7 @@ class EndDiplomacyResolutionPhaseActionTest extends AnyFlatSpec with Matchers wi
|
||||
gameId = 1L,
|
||||
currentRoundId = 1,
|
||||
currentPhase = RoundPhase.DiplomacyResolution,
|
||||
currentDate = Some(Date(year = 1, month = Date.Month.January)),
|
||||
currentDate = None,
|
||||
actionResultCount = 0,
|
||||
provinces = Map.empty,
|
||||
heroes = Map.empty,
|
||||
@@ -41,7 +40,7 @@ class EndDiplomacyResolutionPhaseActionTest extends AnyFlatSpec with Matchers wi
|
||||
chronicleEntries = Vector.empty
|
||||
)
|
||||
|
||||
private val applier = ActionResultApplierImpl(Some(ScalaRuntimeValidator))
|
||||
private val applier = ActionResultTApplierImpl(ScalaRuntimeValidator)
|
||||
private val functionalRandom = SeededRandom(282)
|
||||
|
||||
override def beforeEach(): Unit = {}
|
||||
@@ -65,7 +64,7 @@ class EndDiplomacyResolutionPhaseActionTest extends AnyFlatSpec with Matchers wi
|
||||
// as Unresolved in the stale initial game state
|
||||
|
||||
// The fix ensures that each action in the sequence sees the updated game state
|
||||
// from previous actions via the RandomStateSequencer
|
||||
// from previous actions via the RandomStateTSequencer
|
||||
|
||||
// Create a game state with diplomacy offers
|
||||
val testGameState = gameState.copy(
|
||||
@@ -74,7 +73,7 @@ class EndDiplomacyResolutionPhaseActionTest extends AnyFlatSpec with Matchers wi
|
||||
)
|
||||
|
||||
// This should not throw an exception even if offers get invalidated
|
||||
// The RandomStateSequencer ensures later actions see updated state
|
||||
// The RandomStateTSequencer ensures later actions see updated state
|
||||
val results = EndDiplomacyResolutionPhaseAction(testGameState, applier)
|
||||
.randomResults(functionalRandom)
|
||||
.newValue
|
||||
@@ -87,6 +86,6 @@ class EndDiplomacyResolutionPhaseActionTest extends AnyFlatSpec with Matchers wi
|
||||
|
||||
// The key improvement is that this no longer throws exceptions
|
||||
// when invalidation occurs, because later actions now receive
|
||||
// the updated game state via RandomStateSequencer
|
||||
// the updated game state via RandomStateTSequencer
|
||||
}
|
||||
}
|
||||
|
||||
+56
-68
@@ -1,10 +1,19 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.SeededRandom
|
||||
import net.eagle0.common.ProtoMatchers.equalProto
|
||||
import net.eagle0.eagle.api.available_command.HandleRiotDoNothingAvailableCommand
|
||||
import net.eagle0.eagle.api.command.OneProvinceAvailableCommands
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplierImpl
|
||||
import net.eagle0.eagle.library.actions.impl.command.{HandleRiotDoNothingCommand, TCommandFactory}
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType.{RIOT_AVERSION_FAILED, RIOT_AVERTED}
|
||||
import net.eagle0.eagle.common.date.Date
|
||||
import net.eagle0.eagle.common.province_event.ImminentRiotEvent
|
||||
import net.eagle0.eagle.common.round_phase.{NewRoundPhase, RoundPhase}
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.internal.faction.Faction
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.actions.applier.{ActionResultProtoApplierImpl, ActionResultTApplierImpl}
|
||||
import net.eagle0.eagle.library.actions.impl.command.{CommandFactory, HandleRiotDoNothingCommand}
|
||||
import net.eagle0.eagle.library.actions.impl.common.TCommand
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
RiotEconomyDevastationDelta,
|
||||
@@ -13,20 +22,9 @@ import net.eagle0.eagle.library.settings.{
|
||||
RiotMaxGold,
|
||||
RiotSupportDelta
|
||||
}
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.action_result.types.{
|
||||
EndHandleRiotsPhaseResultType,
|
||||
RiotAversionFailedResultType,
|
||||
RiotOccurredResultType
|
||||
}
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.concrete.FactionC
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.concrete.HeroC
|
||||
import net.eagle0.eagle.model.state.province.{ImminentRiotEvent, ProvinceOrderType}
|
||||
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import net.eagle0.eagle.library.util.validations.{ScalaRuntimeValidator, TestingNoopValidator}
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
|
||||
import org.scalamock.scalatest.MockFactory
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
@@ -34,43 +32,31 @@ import org.scalatest.BeforeAndAfterEach
|
||||
|
||||
class EndHandleRiotsPhaseActionTest extends AnyFlatSpec with BeforeAndAfterEach with Matchers with MockFactory {
|
||||
|
||||
private val applier = ActionResultApplierImpl(None)
|
||||
private val commandFactory = mock[TCommandFactory]
|
||||
private val actionResultProtoApplier = new ActionResultProtoApplierImpl(
|
||||
validator = TestingNoopValidator
|
||||
)
|
||||
private val applier = ActionResultTApplierImpl(ScalaRuntimeValidator)
|
||||
private val commandFactory = mock[CommandFactory]
|
||||
|
||||
val fid = 19
|
||||
val hid = 25
|
||||
val pid = 7
|
||||
|
||||
private val province = ProvinceC(
|
||||
private val provinceProto = Province(
|
||||
id = pid,
|
||||
rulingFactionId = Some(fid),
|
||||
rulingHeroId = Some(hid),
|
||||
rulingFactionHeroIds = Vector(hid),
|
||||
provinceOrders = ProvinceOrderType.UnknownOrderType
|
||||
rulingHeroId = Some(hid)
|
||||
)
|
||||
private val province = ProvinceConverter.fromProto(provinceProto)
|
||||
|
||||
private val gameState = GameState(
|
||||
gameId = 1,
|
||||
currentDate = Some(Date(year = 1501, month = Date.Month.June)),
|
||||
currentRoundId = 1,
|
||||
currentPhase = RoundPhase.HandleRiot,
|
||||
actionResultCount = 0,
|
||||
provinces = Map(pid -> province),
|
||||
factions = Map(fid -> FactionC(id = fid, factionHeadId = 99, name = "Test Faction", leaderIds = Vector(99))),
|
||||
heroes = Map(hid -> HeroC(id = hid, factionId = Some(fid))),
|
||||
battalions = Map.empty,
|
||||
destroyedBattalions = Map.empty,
|
||||
factionCommandCounts = Map.empty,
|
||||
killedHeroes = Map.empty,
|
||||
destroyedFactions = Map.empty,
|
||||
outstandingBattles = Vector.empty,
|
||||
battleCounter = 0,
|
||||
deferredNotifications = Vector.empty,
|
||||
runStatus = RunStatus.Running,
|
||||
victor = None,
|
||||
battalionTypes = Vector.empty,
|
||||
private val gameState: GameState = GameState(
|
||||
currentDate = Some(Date(year = 1501, month = 6)),
|
||||
randomSeed = 1L,
|
||||
chronicleEntries = Vector.empty
|
||||
currentPhase = RoundPhase.HANDLE_RIOT,
|
||||
provinces = Map(
|
||||
pid -> provinceProto
|
||||
),
|
||||
factions = Map(fid -> Faction(id = fid, factionHeadId = 99, leaders = Vector(99)))
|
||||
)
|
||||
|
||||
override def beforeEach(): Unit = {
|
||||
@@ -90,17 +76,23 @@ class EndHandleRiotsPhaseActionTest extends AnyFlatSpec with BeforeAndAfterEach
|
||||
)
|
||||
)
|
||||
|
||||
"results" should "include the end phase result" in {
|
||||
val results = EndHandleRiotsPhaseAction(
|
||||
gameState,
|
||||
commandsForProvince.get,
|
||||
commandFactory,
|
||||
applier
|
||||
).results(SeededRandom(gameState.randomSeed))
|
||||
|
||||
results.last.actionResultType shouldBe EndHandleRiotsPhaseResultType
|
||||
results.last.newRoundPhase shouldBe Some(RoundPhase.HeroDepartures)
|
||||
}
|
||||
"results" should "include the end phase result" in
|
||||
exactly(
|
||||
1,
|
||||
EndHandleRiotsPhaseAction(
|
||||
GameStateConverter.fromProto(gameState),
|
||||
commandsForProvince.get,
|
||||
commandFactory,
|
||||
applier
|
||||
).execute(gameState, actionResultProtoApplier).map(_.actionResult)
|
||||
).should(
|
||||
equalProto(
|
||||
ActionResult(
|
||||
`type` = ActionResultType.END_HANDLE_RIOTS_PHASE,
|
||||
newRoundPhase = Some(NewRoundPhase(RoundPhase.HERO_DEPARTURES))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
it should "include a handle riot result for a province led by a vassal with an imminent riot" in {
|
||||
commandFactory.makeTCommand
|
||||
@@ -114,25 +106,21 @@ class EndHandleRiotsPhaseActionTest extends AnyFlatSpec with BeforeAndAfterEach
|
||||
)
|
||||
)
|
||||
|
||||
val gsWithRiot = gameState.copy(
|
||||
provinces = Map(
|
||||
pid -> province.copy(activeEvents = Vector(ImminentRiotEvent()))
|
||||
)
|
||||
val gsWithRiot = gameState.update(
|
||||
_.provinces(pid).activeEvents := Vector(ImminentRiotEvent())
|
||||
)
|
||||
|
||||
val results = EndHandleRiotsPhaseAction(
|
||||
gsWithRiot,
|
||||
GameStateConverter.fromProto(gsWithRiot),
|
||||
commandsForProvince.get,
|
||||
commandFactory,
|
||||
applier
|
||||
).results(SeededRandom(gsWithRiot.randomSeed))
|
||||
|
||||
val result = results.head
|
||||
result.provinceId shouldBe Some(pid)
|
||||
result.actingFactionId shouldBe Some(fid)
|
||||
).execute(gsWithRiot, actionResultProtoApplier).map(_.actionResult)
|
||||
val result = results.head
|
||||
result.province.shouldBe(Some(pid))
|
||||
result.player.shouldBe(Some(fid))
|
||||
result.changedProvinces.should(have.size(1))
|
||||
val changedProvince = result.changedProvinces.head.asInstanceOf[ChangedProvinceC]
|
||||
changedProvince.setHasActed shouldBe Some(true)
|
||||
result.actionResultType should (be(RiotAversionFailedResultType) or be(RiotOccurredResultType))
|
||||
result.changedProvinces.head.setHasActed.should(contain(true))
|
||||
result.`type`.should(be(RIOT_AVERSION_FAILED) or be(RIOT_AVERTED))
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.internal.province.{Neighbor, Province}
|
||||
import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplierImpl
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplierImpl
|
||||
import net.eagle0.eagle.library.settings.PrisonerEscapeChance
|
||||
import net.eagle0.eagle.library.util.IDable.{mapifyFactions, mapifyHeroes, mapifyProvinces}
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
@@ -53,7 +53,7 @@ class EndPlayerCommandsPhaseActionTest extends AnyFlatSpec with Matchers with Be
|
||||
PrisonerEscapeChance.setDoubleValue(0.05)
|
||||
}
|
||||
|
||||
private val actionResultApplier = ActionResultApplierImpl(validator = None)
|
||||
private val actionResultApplier = ActionResultTApplierImpl()
|
||||
|
||||
private val rulingFactionId = 5
|
||||
private val enemyFactionId: FactionId = 15
|
||||
|
||||
+44
-43
@@ -1,6 +1,5 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.SeededRandom
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType.{NEW_ROUND_ACTION, NEW_YEAR_ACTION}
|
||||
import net.eagle0.eagle.common.date.Date as DateProto
|
||||
import net.eagle0.eagle.common.province_order_type.ProvinceOrderType.DEVELOP
|
||||
@@ -12,7 +11,6 @@ import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.{
|
||||
UNAFFILIATED_HERO_PRISONER,
|
||||
UNAFFILIATED_HERO_TRAVELER
|
||||
}
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.internal.army.{Army, HostileArmyGroup, MovingArmy, Withdrawing}
|
||||
import net.eagle0.eagle.internal.changed_faction.{ChangedFaction, TrustLevelUpdate}
|
||||
import net.eagle0.eagle.internal.changed_hero.ChangedHero
|
||||
@@ -28,7 +26,7 @@ import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
|
||||
import net.eagle0.eagle.library.{EagleInternalException, GameHistory}
|
||||
import net.eagle0.eagle.library.actions.applier.{ActionResultApplierImpl, ActionResultProtoApplierImpl}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplierImpl
|
||||
import net.eagle0.eagle.library.actions.impl.WaitingAction
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
BaseResourceLimit,
|
||||
@@ -49,9 +47,7 @@ import net.eagle0.eagle.library.settings.{
|
||||
import net.eagle0.eagle.library.util.validations.TestingNoopValidator
|
||||
import net.eagle0.eagle.library.util.IDable.{mapifyFactions, mapifyHeroes}
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
|
||||
import net.eagle0.eagle.model.state.date.Date as ScalaDate
|
||||
import net.eagle0.eagle.model.state.game_state.GameState as ScalaGameState
|
||||
import org.scalamock.scalatest.MockFactory
|
||||
import org.scalatest.{BeforeAndAfterEach, OneInstancePerTest}
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
@@ -72,17 +68,6 @@ class NewRoundActionTest
|
||||
validator = TestingNoopValidator
|
||||
)
|
||||
|
||||
private val actionResultApplier = new ActionResultApplierImpl(
|
||||
validator = None
|
||||
)
|
||||
|
||||
// Helper to run NewRoundAction and convert results to proto for testing
|
||||
private def runNewRoundAction(gameState: ScalaGameState): Vector[ActionResult] =
|
||||
NewRoundAction(gameState, mockHistory, actionResultApplier)
|
||||
.randomResults(SeededRandom(0))
|
||||
.newValue
|
||||
.map(ActionResultProtoConverter.toProto)
|
||||
|
||||
mockHistory.sinceDate
|
||||
.expects(ScalaDate(year = 0, month = ScalaDate.Month.January))
|
||||
.returning(Vector.empty)
|
||||
@@ -119,7 +104,7 @@ class NewRoundActionTest
|
||||
|
||||
"execute" should "advance the round ID" in {
|
||||
val results =
|
||||
runNewRoundAction(GameStateConverter.fromProto(midyearGameState))
|
||||
NewRoundAction(GameStateConverter.fromProto(midyearGameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.newRoundId shouldBe Some(8)
|
||||
}
|
||||
@@ -137,7 +122,7 @@ class NewRoundActionTest
|
||||
)
|
||||
|
||||
val results =
|
||||
runNewRoundAction(GameStateConverter.fromProto(gsWithDevastation))
|
||||
NewRoundAction(GameStateConverter.fromProto(gsWithDevastation), mockHistory).resultsOfExecute()
|
||||
|
||||
inside(results.head.changedProvinces.head) {
|
||||
case cp: ChangedProvince =>
|
||||
@@ -162,7 +147,7 @@ class NewRoundActionTest
|
||||
)
|
||||
|
||||
val results =
|
||||
runNewRoundAction(GameStateConverter.fromProto(gsWithDevastation))
|
||||
NewRoundAction(GameStateConverter.fromProto(gsWithDevastation), mockHistory).resultsOfExecute()
|
||||
|
||||
inside(results.head.changedProvinces.head) {
|
||||
case cp: ChangedProvince =>
|
||||
@@ -176,7 +161,7 @@ class NewRoundActionTest
|
||||
|
||||
it should "advance the round phase" in {
|
||||
val results =
|
||||
runNewRoundAction(GameStateConverter.fromProto(midyearGameState))
|
||||
NewRoundAction(GameStateConverter.fromProto(midyearGameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.newRoundPhase shouldBe Some(
|
||||
NewRoundPhase(RoundPhase.PRISONER_EXCHANGE)
|
||||
@@ -185,14 +170,14 @@ class NewRoundActionTest
|
||||
|
||||
it should "advance the month" in {
|
||||
val results =
|
||||
runNewRoundAction(GameStateConverter.fromProto(midyearGameState))
|
||||
NewRoundAction(GameStateConverter.fromProto(midyearGameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.newDate shouldBe Some(DateProto(year = 252, month = 8))
|
||||
}
|
||||
|
||||
it should "not include a NewYearAction if the month is not 12" in {
|
||||
val results =
|
||||
runNewRoundAction(GameStateConverter.fromProto(midyearGameState))
|
||||
NewRoundAction(GameStateConverter.fromProto(midyearGameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.map(_.`type`) should not contain NEW_YEAR_ACTION
|
||||
}
|
||||
@@ -201,7 +186,7 @@ class NewRoundActionTest
|
||||
val endOfYearGameState =
|
||||
midyearGameState.withCurrentDate(DateProto(year = 252, month = 12))
|
||||
val results =
|
||||
runNewRoundAction(GameStateConverter.fromProto(endOfYearGameState))
|
||||
NewRoundAction(GameStateConverter.fromProto(endOfYearGameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.find(_.`type` == NEW_ROUND_ACTION).get.newDate shouldBe Some(
|
||||
DateProto(year = 253, month = 1)
|
||||
@@ -212,7 +197,7 @@ class NewRoundActionTest
|
||||
val endOfYearGameState =
|
||||
midyearGameState.withCurrentDate(DateProto(year = 252, month = 12))
|
||||
val results =
|
||||
runNewRoundAction(GameStateConverter.fromProto(endOfYearGameState))
|
||||
NewRoundAction(GameStateConverter.fromProto(endOfYearGameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.`type` shouldBe NEW_YEAR_ACTION
|
||||
}
|
||||
@@ -226,7 +211,7 @@ class NewRoundActionTest
|
||||
val gameState =
|
||||
midyearGameState.withHeroes(Map(startingHero.id -> startingHero))
|
||||
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.changedHeroes shouldBe Vector(
|
||||
ChangedHero(id = startingHero.id, vigor = VigorDelta(7))
|
||||
@@ -242,7 +227,7 @@ class NewRoundActionTest
|
||||
val gameState =
|
||||
midyearGameState.withHeroes(Map(startingHero.id -> startingHero))
|
||||
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.changedHeroes shouldBe Vector(
|
||||
ChangedHero(id = startingHero.id, vigor = VigorDelta(2))
|
||||
@@ -264,7 +249,7 @@ class NewRoundActionTest
|
||||
val gameState =
|
||||
midyearGameState.withHeroes(Map(startingHero.id -> startingHero))
|
||||
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.newHeroes shouldBe empty
|
||||
}
|
||||
@@ -320,7 +305,7 @@ class NewRoundActionTest
|
||||
)
|
||||
)
|
||||
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.changedHeroes should contain theSameElementsAs (Vector(
|
||||
ChangedHero(id = 1, loyalty = LoyaltyDelta(-2)),
|
||||
@@ -379,7 +364,7 @@ class NewRoundActionTest
|
||||
)
|
||||
)
|
||||
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.changedHeroes shouldBe empty
|
||||
}
|
||||
@@ -435,7 +420,7 @@ class NewRoundActionTest
|
||||
)
|
||||
)
|
||||
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.changedHeroes should contain theSameElementsAs (1 to 12).toVector
|
||||
.map(hid => ChangedHero(id = hid, loyalty = LoyaltyDelta(-1)))
|
||||
@@ -458,7 +443,7 @@ class NewRoundActionTest
|
||||
// overage is 15000
|
||||
// loss is 15000 * 0.2 = 3000
|
||||
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.changedProvinces.head.foodDelta shouldBe Some(-3000)
|
||||
}
|
||||
@@ -480,7 +465,7 @@ class NewRoundActionTest
|
||||
// overage is 5500
|
||||
// loss is 5500 * 0.2 = 1100
|
||||
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
results.head.changedProvinces.head.goldDelta shouldBe Some(-1100)
|
||||
}
|
||||
@@ -525,7 +510,10 @@ class NewRoundActionTest
|
||||
)
|
||||
)
|
||||
|
||||
val cps = runNewRoundAction(GameStateConverter.fromProto(gameState)).head.changedProvinces
|
||||
val cps = NewRoundAction(
|
||||
GameStateConverter.fromProto(gameState),
|
||||
mockHistory
|
||||
).resultsOfExecute().head.changedProvinces
|
||||
|
||||
forExactly(1, cps) { cp =>
|
||||
cp.id shouldBe 7
|
||||
@@ -605,7 +593,10 @@ class NewRoundActionTest
|
||||
)
|
||||
)
|
||||
|
||||
val cps = runNewRoundAction(GameStateConverter.fromProto(gameState)).head.changedProvinces
|
||||
val cps = NewRoundAction(
|
||||
GameStateConverter.fromProto(gameState),
|
||||
mockHistory
|
||||
).resultsOfExecute().head.changedProvinces
|
||||
|
||||
inside(cps.head) {
|
||||
case cp: ChangedProvince =>
|
||||
@@ -707,7 +698,11 @@ class NewRoundActionTest
|
||||
)
|
||||
)
|
||||
|
||||
val changedFactions = runNewRoundAction(GameStateConverter.fromProto(gsWithTruces)).head.changedFactions
|
||||
val changedFactions =
|
||||
NewRoundAction(
|
||||
GameStateConverter.fromProto(gsWithTruces),
|
||||
mockHistory
|
||||
).resultsOfExecute().head.changedFactions
|
||||
|
||||
changedFactions
|
||||
.find(_.id == 3)
|
||||
@@ -772,7 +767,11 @@ class NewRoundActionTest
|
||||
)
|
||||
)
|
||||
|
||||
val changedFactions = runNewRoundAction(GameStateConverter.fromProto(gsWithTruces)).head.changedFactions
|
||||
val changedFactions =
|
||||
NewRoundAction(
|
||||
GameStateConverter.fromProto(gsWithTruces),
|
||||
mockHistory
|
||||
).resultsOfExecute().head.changedFactions
|
||||
|
||||
changedFactions should contain theSameElementsAs Vector(
|
||||
ChangedFaction(
|
||||
@@ -812,8 +811,9 @@ class NewRoundActionTest
|
||||
)
|
||||
|
||||
val ex = the[EagleInternalException] thrownBy
|
||||
NewRoundAction(GameStateConverter.fromProto(gameStateWithOldArmy), mockHistory, actionResultApplier)
|
||||
.randomResults(SeededRandom(0))
|
||||
NewRoundAction(GameStateConverter.fromProto(gameStateWithOldArmy), mockHistory).execute(
|
||||
actionResultProtoApplier
|
||||
)
|
||||
ex.getMessage shouldBe "requirement failed: Province 9 has an army that should have already arrived"
|
||||
}
|
||||
|
||||
@@ -832,8 +832,9 @@ class NewRoundActionTest
|
||||
)
|
||||
|
||||
val ex = the[EagleInternalException] thrownBy
|
||||
NewRoundAction(GameStateConverter.fromProto(gameStateWithHostileArmy), mockHistory, actionResultApplier)
|
||||
.randomResults(SeededRandom(0))
|
||||
NewRoundAction(GameStateConverter.fromProto(gameStateWithHostileArmy), mockHistory).execute(
|
||||
actionResultProtoApplier
|
||||
)
|
||||
|
||||
ex.getMessage shouldBe "requirement failed: Province 9 has hostile armies at the round start"
|
||||
}
|
||||
@@ -841,7 +842,7 @@ class NewRoundActionTest
|
||||
it should "include a chronicle notification if in November" in {
|
||||
val gameState =
|
||||
midyearGameState.withCurrentDate(DateProto(year = 252, month = 10))
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
val relevantAction = results.find(_.`type` == NEW_ROUND_ACTION).get
|
||||
relevantAction.newChronicleEntry should contain(
|
||||
@@ -855,7 +856,7 @@ class NewRoundActionTest
|
||||
it should "include a chronicle LLM request if in November" in {
|
||||
val gameState =
|
||||
midyearGameState.withCurrentDate(DateProto(year = 252, month = 10))
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
val relevantAction = results.find(_.`type` == NEW_ROUND_ACTION).get
|
||||
relevantAction.newGeneratedTextRequests should have size 1
|
||||
@@ -878,7 +879,7 @@ class NewRoundActionTest
|
||||
)
|
||||
)
|
||||
|
||||
val results = runNewRoundAction(GameStateConverter.fromProto(gameState))
|
||||
val results = NewRoundAction(GameStateConverter.fromProto(gameState), mockHistory).resultsOfExecute()
|
||||
|
||||
forExactly(1, results) { result =>
|
||||
result.changedProvinces.head.newPriceIndex shouldBe Some(
|
||||
|
||||
+21
-20
@@ -13,12 +13,12 @@ import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.internal.province.{IncomingEndTurnAction, Province}
|
||||
import net.eagle0.eagle.internal.province.IncomingEndTurnAction.IncomingRecon
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplierImpl
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplierImpl
|
||||
import net.eagle0.eagle.library.settings.{BaseResourceLimit, PerDevelopmentResourceLimit}
|
||||
import net.eagle0.eagle.library.util.validations.TestingNoopValidator
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedFactionC, ClientTextVisibilityExtensionC}
|
||||
import net.eagle0.eagle.model.action_result.types.{EndReconResolutionPhaseResultType, ReconSucceededResultType}
|
||||
import net.eagle0.eagle.model.action_result.types.ReconSucceededResultType
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.state.province.{
|
||||
IncomingEndTurnAction as IncomingEndTurnActionT,
|
||||
@@ -30,7 +30,9 @@ import org.scalatest.BeforeAndAfterEach
|
||||
|
||||
class PerformReconResolutionActionTest extends AnyFlatSpec with BeforeAndAfterEach with Matchers {
|
||||
|
||||
private val actionResultApplier = ActionResultApplierImpl(validator = None)
|
||||
private val actionResultProtoApplier = new ActionResultProtoApplierImpl(
|
||||
validator = TestingNoopValidator
|
||||
)
|
||||
|
||||
private val actingFaction = 7
|
||||
private val incomingRecon: IncomingEndTurnAction = IncomingEndTurnAction(
|
||||
@@ -104,10 +106,10 @@ class PerformReconResolutionActionTest extends AnyFlatSpec with BeforeAndAfterEa
|
||||
}
|
||||
|
||||
"perform one" should "remove the incoming recon" in {
|
||||
val oneResult = PerformReconResolutionAction(GameStateConverter.fromProto(gameState), actionResultApplier)
|
||||
val oneResult = PerformReconResolutionAction(GameStateConverter.fromProto(gameState))
|
||||
.oneResult(
|
||||
provinceId = 3,
|
||||
incomingEndTurnAction = incomingReconT,
|
||||
incomingEndTurnAction = incomingRecon,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.newValue
|
||||
@@ -122,10 +124,10 @@ class PerformReconResolutionActionTest extends AnyFlatSpec with BeforeAndAfterEa
|
||||
}
|
||||
|
||||
it should "return the hero to the origin province if it's still owned" in {
|
||||
val oneResult = PerformReconResolutionAction(GameStateConverter.fromProto(gameState), actionResultApplier)
|
||||
val oneResult = PerformReconResolutionAction(GameStateConverter.fromProto(gameState))
|
||||
.oneResult(
|
||||
provinceId = 3,
|
||||
incomingEndTurnAction = incomingReconT,
|
||||
incomingEndTurnAction = incomingRecon,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.newValue
|
||||
@@ -144,10 +146,10 @@ class PerformReconResolutionActionTest extends AnyFlatSpec with BeforeAndAfterEa
|
||||
gameState.update(_.provinces(9).rulingFactionId := 2)
|
||||
|
||||
val oneResult =
|
||||
PerformReconResolutionAction(GameStateConverter.fromProto(gsWithLostProvince), actionResultApplier)
|
||||
PerformReconResolutionAction(GameStateConverter.fromProto(gsWithLostProvince))
|
||||
.oneResult(
|
||||
provinceId = 3,
|
||||
incomingEndTurnAction = incomingReconT,
|
||||
incomingEndTurnAction = incomingRecon,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.newValue
|
||||
@@ -168,10 +170,10 @@ class PerformReconResolutionActionTest extends AnyFlatSpec with BeforeAndAfterEa
|
||||
)
|
||||
|
||||
val oneResult =
|
||||
PerformReconResolutionAction(GameStateConverter.fromProto(gsWithLostProvinces), actionResultApplier)
|
||||
PerformReconResolutionAction(GameStateConverter.fromProto(gsWithLostProvinces))
|
||||
.oneResult(
|
||||
provinceId = 3,
|
||||
incomingEndTurnAction = incomingReconT,
|
||||
incomingEndTurnAction = incomingRecon,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.newValue
|
||||
@@ -186,10 +188,10 @@ class PerformReconResolutionActionTest extends AnyFlatSpec with BeforeAndAfterEa
|
||||
}
|
||||
|
||||
it should "update the faction with the info about the province" in {
|
||||
val oneResult = PerformReconResolutionAction(GameStateConverter.fromProto(gameState), actionResultApplier)
|
||||
val oneResult = PerformReconResolutionAction(GameStateConverter.fromProto(gameState))
|
||||
.oneResult(
|
||||
provinceId = 3,
|
||||
incomingEndTurnAction = incomingReconT,
|
||||
incomingEndTurnAction = incomingRecon,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.newValue
|
||||
@@ -207,10 +209,10 @@ class PerformReconResolutionActionTest extends AnyFlatSpec with BeforeAndAfterEa
|
||||
}
|
||||
|
||||
it should "extend client text visibility for the heroes in the reconned province" in {
|
||||
val oneResult = PerformReconResolutionAction(GameStateConverter.fromProto(gameState), actionResultApplier)
|
||||
val oneResult = PerformReconResolutionAction(GameStateConverter.fromProto(gameState))
|
||||
.oneResult(
|
||||
provinceId = 3,
|
||||
incomingEndTurnAction = incomingReconT,
|
||||
incomingEndTurnAction = incomingRecon,
|
||||
functionalRandom = functionalRandom
|
||||
)
|
||||
.newValue
|
||||
@@ -260,11 +262,10 @@ class PerformReconResolutionActionTest extends AnyFlatSpec with BeforeAndAfterEa
|
||||
)
|
||||
|
||||
val allResults = PerformReconResolutionAction(
|
||||
GameStateConverter.fromProto(gameStateWithMoreRecons),
|
||||
actionResultApplier
|
||||
).results(functionalRandom)
|
||||
GameStateConverter.fromProto(gameStateWithMoreRecons)
|
||||
).execute(actionResultProtoApplier).map(_.actionResult)
|
||||
|
||||
allResults.count(_.actionResultType == ReconSucceededResultType) shouldBe 3
|
||||
allResults.count(_.actionResultType == EndReconResolutionPhaseResultType) shouldBe 1
|
||||
allResults.count(_.`type` == RECON_SUCCEEDED) shouldBe 3
|
||||
allResults.count(_.`type` == END_RECON_RESOLUTION_PHASE) shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
+463
-368
@@ -1,7 +1,42 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.SeededRandom
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplierImpl
|
||||
import net.eagle0.common.ProtoMatchers.equalProto
|
||||
import net.eagle0.eagle.common.action_result_notification_details.{Notification, OutlawSpottedDetails}
|
||||
import net.eagle0.eagle.common.action_result_notification_details.Notification.TargetFaction
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType.{
|
||||
END_UNAFFILIATED_HERO_ACTIONS_PHASE,
|
||||
HERO_CHANGED,
|
||||
HERO_MOVED,
|
||||
HERO_REJOINED
|
||||
}
|
||||
import net.eagle0.eagle.common.date.Date
|
||||
import net.eagle0.eagle.common.gender.Gender.GENDER_FEMALE
|
||||
import net.eagle0.eagle.common.profession.Profession.NO_PROFESSION
|
||||
import net.eagle0.eagle.common.province_order_type.ProvinceOrderType.ENTRUST
|
||||
import net.eagle0.eagle.common.recruitment_info.RecruitmentInfo
|
||||
import net.eagle0.eagle.common.recruitment_info.RecruitmentStatus.{
|
||||
RECRUITMENT_STATUS_NO_RULER_IN_PROVINCE,
|
||||
RECRUITMENT_STATUS_OUTLAW,
|
||||
RECRUITMENT_STATUS_PRISONER,
|
||||
RECRUITMENT_STATUS_TRAVELER
|
||||
}
|
||||
import net.eagle0.eagle.common.round_phase.RoundPhase.UNAFFILIATED_HERO_ACTIONS
|
||||
import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.{
|
||||
UNAFFILIATED_HERO_OUTLAW,
|
||||
UNAFFILIATED_HERO_PRISONER,
|
||||
UNAFFILIATED_HERO_RESIDENT,
|
||||
UNAFFILIATED_HERO_TRAVELER
|
||||
}
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.internal.changed_hero.ChangedHero
|
||||
import net.eagle0.eagle.internal.changed_hero.ChangedHero.Vigor.VigorDelta
|
||||
import net.eagle0.eagle.internal.changed_province.ChangedProvince
|
||||
import net.eagle0.eagle.internal.faction.Faction
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.internal.province.{Neighbor, Province}
|
||||
import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
|
||||
import net.eagle0.eagle.library.actions.impl.WaitingAction
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
FreeHeroMoveVigorCost,
|
||||
MinOddsForPleaseRecruitMe,
|
||||
@@ -25,59 +60,16 @@ import net.eagle0.eagle.library.settings.{
|
||||
VigorToConstitutionXpMultiplier,
|
||||
XpForStatBump
|
||||
}
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.action_result.concrete.{ChangedHeroC, StatDelta}
|
||||
import net.eagle0.eagle.model.action_result.types.{
|
||||
EndUnaffiliatedHeroActionsPhaseResultType,
|
||||
HeroChangedResultType,
|
||||
HeroMovedResultType,
|
||||
HeroRejoinedResultType
|
||||
}
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.concrete.FactionC
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.concrete.HeroC
|
||||
import net.eagle0.eagle.model.state.hero.Gender
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.province.{BlizzardEvent, Neighbor, ProvinceOrderType, ProvinceT}
|
||||
import net.eagle0.eagle.library.util.IDable.{mapifyFactions, mapifyHeroes, mapifyProvinces}
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.state.date.Date as ScalaDate
|
||||
import net.eagle0.eagle.model.state.province.{BlizzardEvent as ScalaBlizzardEvent, Neighbor as ScalaNeighbor, ProvinceT}
|
||||
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.{RecruitmentInfo, UnaffiliatedHeroType}
|
||||
import net.eagle0.eagle.model.state.unaffiliated_hero.concrete.UnaffiliatedHeroC
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.BeforeAndAfterEach
|
||||
import org.scalatest.Inside.inside
|
||||
|
||||
class PerformUnaffiliatedHeroesActionTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach {
|
||||
private val applier = ActionResultApplierImpl(None)
|
||||
|
||||
private val baseGameState: GameState = GameState(
|
||||
gameId = 1L,
|
||||
currentRoundId = 1,
|
||||
currentPhase = RoundPhase.UnaffiliatedHeroActions,
|
||||
currentDate = Some(Date(year = 3, month = Date.Month.October)),
|
||||
actionResultCount = 0,
|
||||
provinces = Map.empty,
|
||||
heroes = Map.empty,
|
||||
battalions = Map.empty,
|
||||
destroyedBattalions = Map.empty,
|
||||
factions = Map.empty,
|
||||
factionCommandCounts = Map.empty,
|
||||
killedHeroes = Map.empty,
|
||||
destroyedFactions = Map.empty,
|
||||
outstandingBattles = Vector.empty,
|
||||
battleCounter = 0,
|
||||
deferredNotifications = Vector.empty,
|
||||
runStatus = RunStatus.Running,
|
||||
victor = None,
|
||||
battalionTypes = Vector.empty,
|
||||
randomSeed = 12345L,
|
||||
chronicleEntries = Vector.empty
|
||||
)
|
||||
|
||||
override def beforeEach(): Unit = {
|
||||
RestVigorGain.setDoubleValue(17.2)
|
||||
MinVigorForFreeHeroMove.setDoubleValue(60)
|
||||
@@ -104,22 +96,23 @@ class PerformUnaffiliatedHeroesActionTest extends AnyFlatSpec with Matchers with
|
||||
}
|
||||
|
||||
"movableNeighbors" should "include exactly neighbors without a blizzard" in {
|
||||
// Use Scala types directly to avoid proto conversion issues with BlizzardEvent
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
3 -> ProvinceC(id = 3),
|
||||
6 -> ProvinceC(
|
||||
id = 6,
|
||||
neighbors = Vector(
|
||||
Neighbor(provinceId = 3, startingPositionIndex = 0),
|
||||
Neighbor(provinceId = 7, startingPositionIndex = 1),
|
||||
Neighbor(provinceId = 12, startingPositionIndex = 2)
|
||||
ScalaNeighbor(provinceId = 3, startingPositionIndex = 0),
|
||||
ScalaNeighbor(provinceId = 7, startingPositionIndex = 1),
|
||||
ScalaNeighbor(provinceId = 12, startingPositionIndex = 2)
|
||||
)
|
||||
),
|
||||
7 -> ProvinceC(
|
||||
id = 7,
|
||||
activeEvents = Vector(
|
||||
BlizzardEvent(
|
||||
startDate = Date(1, Date.Month.January),
|
||||
endDate = Date(1, Date.Month.March)
|
||||
ScalaBlizzardEvent(
|
||||
startDate = ScalaDate(1, ScalaDate.Month.January),
|
||||
endDate = ScalaDate(1, ScalaDate.Month.March)
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -134,410 +127,512 @@ class PerformUnaffiliatedHeroesActionTest extends AnyFlatSpec with Matchers with
|
||||
}
|
||||
|
||||
"an unaffiliated hero with low vigor" should "rest" in {
|
||||
val heroes: Map[Int, HeroT] = Map(
|
||||
19 -> HeroC(id = 19, vigor = 33, pronounGender = Gender.Female)
|
||||
)
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
4 -> ProvinceC(
|
||||
id = 4,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 19,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Traveler,
|
||||
recruitmentInfo = RecruitmentInfo.Traveler
|
||||
val gs = GameState(
|
||||
currentPhase = UNAFFILIATED_HERO_ACTIONS,
|
||||
currentDate = Some(Date(3, 273)),
|
||||
heroes = Map(
|
||||
19 -> Hero(
|
||||
id = 19,
|
||||
vigor = 33,
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE
|
||||
)
|
||||
),
|
||||
provinces = Map(
|
||||
4 -> Province(
|
||||
id = 4,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
`type` = UNAFFILIATED_HERO_TRAVELER,
|
||||
heroId = 19,
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_TRAVELER))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val gs = baseGameState.copy(heroes = heroes, provinces = provinces)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = gs,
|
||||
heroGenerator = null,
|
||||
actionResultApplier = applier
|
||||
).results(SeededRandom(gs.randomSeed))
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
heroGenerator = null
|
||||
).resultsOfExecute()
|
||||
|
||||
results should have size 4
|
||||
val firstResult = results.head
|
||||
firstResult.actionResultType shouldBe HeroChangedResultType
|
||||
firstResult.changedHeroes should have size 1
|
||||
inside(firstResult.changedHeroes.head) {
|
||||
case changedHero: ChangedHeroC =>
|
||||
changedHero.heroId shouldBe 19
|
||||
changedHero.vigorChange shouldBe StatDelta(17.2)
|
||||
}
|
||||
// The batched ActionResult contains only changedHeroes for vigor changes
|
||||
// (no province field since vigor changes are keyed by hero ID)
|
||||
results.head should equalProto(
|
||||
ActionResult(
|
||||
`type` = HERO_CHANGED,
|
||||
changedHeroes = Vector(
|
||||
ChangedHero(id = 19, vigor = VigorDelta(17.2))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"an unaffiliated hero attached to a faction" should "return to the faction and rejoin" in {
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
3 -> ProvinceC(
|
||||
id = 3,
|
||||
neighbors = Vector(Neighbor(provinceId = 8, startingPositionIndex = 0)),
|
||||
rulingFactionId = Some(6),
|
||||
rulingFactionHeroIds = Vector(9, 11),
|
||||
provinceOrders = ProvinceOrderType.Entrust
|
||||
val gs = GameState(
|
||||
currentPhase = UNAFFILIATED_HERO_ACTIONS,
|
||||
currentDate = Some(Date(3, 273)),
|
||||
provinces = mapifyProvinces(
|
||||
Province(
|
||||
id = 3,
|
||||
neighbors = Vector(Neighbor(provinceId = 8)),
|
||||
rulingFactionId = Some(6),
|
||||
rulingFactionHeroIds = Vector(9, 11),
|
||||
provinceOrders = ENTRUST
|
||||
),
|
||||
Province(
|
||||
id = 8,
|
||||
neighbors = Vector(Neighbor(provinceId = 3)),
|
||||
rulingFactionId = Some(23),
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_OUTLAW,
|
||||
lastFaction = Some(6),
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_OUTLAW))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
8 -> ProvinceC(
|
||||
id = 8,
|
||||
neighbors = Vector(Neighbor(provinceId = 3, startingPositionIndex = 0)),
|
||||
rulingFactionId = Some(23),
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 17,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Outlaw,
|
||||
lastFactionId = Some(6),
|
||||
recruitmentInfo = RecruitmentInfo.Outlaw
|
||||
heroes = mapifyHeroes(
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 17,
|
||||
vigor = 90
|
||||
),
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 9,
|
||||
factionId = Some(6)
|
||||
),
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 11,
|
||||
factionId = Some(6)
|
||||
)
|
||||
),
|
||||
factions = mapifyFactions(Faction(id = 6, factionHeadId = 9))
|
||||
)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
heroGenerator = null
|
||||
).resultsOfExecute()
|
||||
|
||||
results should have size 5
|
||||
results.head should equalProto(
|
||||
ActionResult(
|
||||
`type` = HERO_MOVED,
|
||||
changedProvinces = Vector(
|
||||
ChangedProvince(
|
||||
id = 8,
|
||||
removedUnaffiliatedHeroIds = Vector(17)
|
||||
),
|
||||
ChangedProvince(
|
||||
id = 3,
|
||||
newUnaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_OUTLAW,
|
||||
lastFaction = Some(6),
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_OUTLAW))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
changedHeroes = Vector(
|
||||
ChangedHero(
|
||||
id = 17,
|
||||
vigor = VigorDelta(-10),
|
||||
constitutionXpDelta = Some(1)
|
||||
)
|
||||
),
|
||||
notificationsToDeliver = Vector(
|
||||
Notification(
|
||||
details = OutlawSpottedDetails(outlawHeroId = 17, provinceId = 3),
|
||||
targetFactions = Vector(TargetFaction(23), TargetFaction(6))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val heroes: Map[Int, HeroT] = Map(
|
||||
17 -> HeroC(id = 17, pronounGender = Gender.Female, vigor = 90),
|
||||
9 -> HeroC(id = 9, pronounGender = Gender.Female, factionId = Some(6)),
|
||||
11 -> HeroC(id = 11, pronounGender = Gender.Female, factionId = Some(6))
|
||||
results(2) should equalProto(
|
||||
ActionResult(
|
||||
`type` = HERO_REJOINED,
|
||||
changedProvinces = Vector(
|
||||
ChangedProvince(
|
||||
id = 3,
|
||||
removedUnaffiliatedHeroIds = Vector(17),
|
||||
addedRulingPlayerHeroIds = Vector(17)
|
||||
)
|
||||
),
|
||||
changedHeroes = Vector(
|
||||
ChangedHero(
|
||||
id = 17,
|
||||
newFactionId = Some(6)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val factions: Map[Int, FactionT] = Map(
|
||||
6 -> FactionC(id = 6, factionHeadId = 9, name = "", leaderIds = Vector())
|
||||
)
|
||||
val gs = baseGameState.copy(provinces = provinces, heroes = heroes, factions = factions)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = gs,
|
||||
heroGenerator = null,
|
||||
actionResultApplier = applier
|
||||
).results(SeededRandom(gs.randomSeed))
|
||||
|
||||
results should have size 5
|
||||
|
||||
val moveResult = results.head
|
||||
moveResult.actionResultType shouldBe HeroMovedResultType
|
||||
moveResult.changedProvinces should have size 2
|
||||
|
||||
inside(moveResult.changedProvinces.find(_.provinceId == 8).get) {
|
||||
case moveFromProvince: ChangedProvinceC =>
|
||||
moveFromProvince.removedUnaffiliatedHeroIds should contain(17)
|
||||
}
|
||||
inside(moveResult.changedProvinces.find(_.provinceId == 3).get) {
|
||||
case moveToProvince: ChangedProvinceC =>
|
||||
moveToProvince.newUnaffiliatedHeroes.head.heroId shouldBe 17
|
||||
}
|
||||
inside(moveResult.changedHeroes.head) {
|
||||
case movedHero: ChangedHeroC =>
|
||||
movedHero.heroId shouldBe 17
|
||||
movedHero.vigorChange shouldBe StatDelta(-10.0)
|
||||
}
|
||||
|
||||
moveResult.newNotifications should have size 1
|
||||
moveResult.newNotifications.head.targetFactionIds should contain allOf (23, 6)
|
||||
|
||||
val rejoinResult = results(2)
|
||||
rejoinResult.actionResultType shouldBe HeroRejoinedResultType
|
||||
inside(rejoinResult.changedProvinces.head) {
|
||||
case rejoinProvince: ChangedProvinceC =>
|
||||
rejoinProvince.provinceId shouldBe 3
|
||||
rejoinProvince.removedUnaffiliatedHeroIds should contain(17)
|
||||
rejoinProvince.newRulingFactionHeroIds should contain(17)
|
||||
}
|
||||
inside(rejoinResult.changedHeroes.head) {
|
||||
case rejoinedHero: ChangedHeroC =>
|
||||
rejoinedHero.heroId shouldBe 17
|
||||
rejoinedHero.newFactionId shouldBe Some(6)
|
||||
}
|
||||
|
||||
results(3).actionResultType shouldBe EndUnaffiliatedHeroActionsPhaseResultType
|
||||
results(3).`type` shouldBe END_UNAFFILIATED_HERO_ACTIONS_PHASE
|
||||
}
|
||||
|
||||
"a prisoner in an owned province" should "not generate a result" in {
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
8 -> ProvinceC(
|
||||
id = 8,
|
||||
rulingFactionId = Some(23),
|
||||
rulingFactionHeroIds = Vector(9, 11),
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 17,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Prisoner,
|
||||
lastFactionId = Some(6),
|
||||
recruitmentInfo = RecruitmentInfo.Prisoner
|
||||
)
|
||||
val gs = GameState(
|
||||
currentPhase = UNAFFILIATED_HERO_ACTIONS,
|
||||
currentDate = Some(Date(3, 273)),
|
||||
provinces = mapifyProvinces(
|
||||
Province(
|
||||
id = 8,
|
||||
rulingFactionId = Some(23),
|
||||
rulingFactionHeroIds = Vector(9, 11),
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_PRISONER,
|
||||
lastFaction = Some(6),
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_PRISONER))
|
||||
)
|
||||
),
|
||||
provinceOrders = ENTRUST
|
||||
)
|
||||
),
|
||||
heroes = mapifyHeroes(
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 17,
|
||||
vigor = 90
|
||||
),
|
||||
provinceOrders = ProvinceOrderType.Entrust
|
||||
)
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 9,
|
||||
factionId = Some(23)
|
||||
),
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 11,
|
||||
factionId = Some(23)
|
||||
)
|
||||
),
|
||||
factions = mapifyFactions(Faction(id = 23, factionHeadId = 9), Faction(id = 6))
|
||||
)
|
||||
val heroes: Map[Int, HeroT] = Map(
|
||||
17 -> HeroC(id = 17, pronounGender = Gender.Female, vigor = 90),
|
||||
9 -> HeroC(id = 9, pronounGender = Gender.Female, factionId = Some(23)),
|
||||
11 -> HeroC(id = 11, pronounGender = Gender.Female, factionId = Some(23))
|
||||
)
|
||||
val factions: Map[Int, FactionT] = Map(
|
||||
23 -> FactionC(id = 23, factionHeadId = 9, name = "", leaderIds = Vector()),
|
||||
6 -> FactionC(id = 6, factionHeadId = 0, name = "", leaderIds = Vector())
|
||||
)
|
||||
val gs = baseGameState.copy(provinces = provinces, heroes = heroes, factions = factions)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = gs,
|
||||
heroGenerator = null,
|
||||
actionResultApplier = applier
|
||||
).results(SeededRandom(gs.randomSeed))
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
heroGenerator = null
|
||||
).resultsOfExecute()
|
||||
|
||||
results
|
||||
.flatMap(_.changedProvinces)
|
||||
.collect { case cp: ChangedProvinceC => cp }
|
||||
.flatMap(_.changedUnaffiliatedHeroes) shouldBe empty
|
||||
}
|
||||
|
||||
"a prisoner in an unowned province" should "become a resident" in {
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
8 -> ProvinceC(
|
||||
id = 8,
|
||||
rulingFactionId = None,
|
||||
rulingFactionHeroIds = Vector(),
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 17,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Prisoner,
|
||||
lastFactionId = Some(6),
|
||||
recruitmentInfo = RecruitmentInfo.Prisoner
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val heroes: Map[Int, HeroT] = Map(
|
||||
17 -> HeroC(id = 17, pronounGender = Gender.Female, vigor = 90),
|
||||
9 -> HeroC(id = 9, pronounGender = Gender.Female, factionId = Some(23)),
|
||||
11 -> HeroC(id = 11, pronounGender = Gender.Female, factionId = Some(23))
|
||||
)
|
||||
val factions: Map[Int, FactionT] = Map(
|
||||
23 -> FactionC(id = 23, factionHeadId = 0, name = "", leaderIds = Vector()),
|
||||
6 -> FactionC(id = 6, factionHeadId = 0, name = "", leaderIds = Vector())
|
||||
)
|
||||
val gs = baseGameState.copy(provinces = provinces, heroes = heroes, factions = factions)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = gs,
|
||||
heroGenerator = null,
|
||||
actionResultApplier = applier
|
||||
).results(SeededRandom(gs.randomSeed))
|
||||
|
||||
val changedUHs = results
|
||||
.flatMap(_.changedProvinces)
|
||||
.collect { case cp: ChangedProvinceC => cp }
|
||||
.flatMap(_.changedUnaffiliatedHeroes)
|
||||
|
||||
changedUHs.find(_.heroId == 17) shouldBe defined
|
||||
changedUHs.find(_.heroId == 17).get.unaffiliatedHeroType shouldBe UnaffiliatedHeroType.Resident
|
||||
}
|
||||
|
||||
"a faction leader prisoner in an unowned province" should "become an outlaw" in {
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
8 -> ProvinceC(
|
||||
id = 8,
|
||||
rulingFactionId = None,
|
||||
rulingFactionHeroIds = Vector(),
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 17,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Prisoner,
|
||||
lastFactionId = Some(6),
|
||||
recruitmentInfo = RecruitmentInfo.Prisoner
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val heroes: Map[Int, HeroT] = Map(
|
||||
17 -> HeroC(id = 17, pronounGender = Gender.Female, vigor = 90),
|
||||
9 -> HeroC(id = 9, pronounGender = Gender.Female, factionId = Some(23)),
|
||||
11 -> HeroC(id = 11, pronounGender = Gender.Female, factionId = Some(23))
|
||||
)
|
||||
val factions: Map[Int, FactionT] = Map(
|
||||
23 -> FactionC(id = 23, factionHeadId = 0, name = "", leaderIds = Vector()),
|
||||
6 -> FactionC(id = 6, factionHeadId = 0, name = "", leaderIds = Vector(17))
|
||||
)
|
||||
val gs = baseGameState.copy(provinces = provinces, heroes = heroes, factions = factions)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = gs,
|
||||
heroGenerator = null,
|
||||
actionResultApplier = applier
|
||||
).results(SeededRandom(gs.randomSeed))
|
||||
|
||||
val changedUHs = results
|
||||
.flatMap(_.changedProvinces)
|
||||
.collect { case cp: ChangedProvinceC => cp }
|
||||
.flatMap(_.changedUnaffiliatedHeroes)
|
||||
|
||||
changedUHs.head.heroId shouldBe 17
|
||||
changedUHs.head.unaffiliatedHeroType shouldBe UnaffiliatedHeroType.Outlaw
|
||||
}
|
||||
|
||||
"multiple prisoners in different provinces" should "be batched into a single HERO_CHANGED result" in {
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
4 -> ProvinceC(
|
||||
id = 4,
|
||||
rulingFactionId = None,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 17,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Prisoner,
|
||||
recruitmentInfo = RecruitmentInfo.Prisoner
|
||||
val gs = GameState(
|
||||
currentPhase = UNAFFILIATED_HERO_ACTIONS,
|
||||
currentDate = Some(Date(3, 273)),
|
||||
provinces = mapifyProvinces(
|
||||
Province(
|
||||
id = 8,
|
||||
rulingFactionId = None,
|
||||
rulingFactionHeroIds = Vector(),
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_PRISONER,
|
||||
lastFaction = Some(6),
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_PRISONER))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
8 -> ProvinceC(
|
||||
id = 8,
|
||||
rulingFactionId = None,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 19,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Prisoner,
|
||||
recruitmentInfo = RecruitmentInfo.Prisoner
|
||||
)
|
||||
heroes = mapifyHeroes(
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 17,
|
||||
vigor = 90
|
||||
),
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 9,
|
||||
factionId = Some(23)
|
||||
),
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 11,
|
||||
factionId = Some(23)
|
||||
)
|
||||
),
|
||||
factions = mapifyFactions(Faction(id = 23), Faction(id = 6))
|
||||
)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
heroGenerator = null
|
||||
).resultsOfExecute()
|
||||
|
||||
results
|
||||
.flatMap(_.changedProvinces)
|
||||
.flatMap(_.changedUnaffiliatedHeroes) should contain(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_RESIDENT,
|
||||
lastFaction = Some(6),
|
||||
recruitmentInfo = Some(
|
||||
RecruitmentInfo(status = RECRUITMENT_STATUS_NO_RULER_IN_PROVINCE)
|
||||
)
|
||||
)
|
||||
)
|
||||
val heroes: Map[Int, HeroT] = Map(
|
||||
17 -> HeroC(id = 17, vigor = 90, pronounGender = Gender.Female),
|
||||
19 -> HeroC(id = 19, vigor = 90, pronounGender = Gender.Female)
|
||||
}
|
||||
|
||||
"a faction leader prisoner in an unowned province" should "become an outlaw" in {
|
||||
val gs = GameState(
|
||||
currentPhase = UNAFFILIATED_HERO_ACTIONS,
|
||||
currentDate = Some(Date(3, 273)),
|
||||
provinces = mapifyProvinces(
|
||||
Province(
|
||||
id = 8,
|
||||
rulingFactionId = None,
|
||||
rulingFactionHeroIds = Vector(),
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_PRISONER,
|
||||
lastFaction = Some(6),
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_PRISONER))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
heroes = mapifyHeroes(
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 17,
|
||||
vigor = 90
|
||||
),
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 9,
|
||||
factionId = Some(23)
|
||||
),
|
||||
Hero(
|
||||
profession = NO_PROFESSION,
|
||||
pronounGender = GENDER_FEMALE,
|
||||
id = 11,
|
||||
factionId = Some(23)
|
||||
)
|
||||
),
|
||||
factions = mapifyFactions(Faction(id = 23), Faction(id = 6, leaders = Vector(17)))
|
||||
)
|
||||
val factions: Map[Int, FactionT] = Map(
|
||||
6 -> FactionC(id = 6, factionHeadId = 0, name = "", leaderIds = Vector())
|
||||
)
|
||||
val gs = baseGameState.copy(provinces = provinces, heroes = heroes, factions = factions)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = gs,
|
||||
heroGenerator = null,
|
||||
actionResultApplier = applier
|
||||
).results(SeededRandom(gs.randomSeed))
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
heroGenerator = null
|
||||
).resultsOfExecute()
|
||||
|
||||
val heroChangedResults = results.filter(_.actionResultType == HeroChangedResultType)
|
||||
results
|
||||
.flatMap(_.changedProvinces)
|
||||
.flatMap(_.changedUnaffiliatedHeroes)
|
||||
.head should equalProto(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_OUTLAW,
|
||||
lastFaction = Some(6),
|
||||
recruitmentInfo = Some(
|
||||
RecruitmentInfo(status = RECRUITMENT_STATUS_OUTLAW)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"multiple prisoners in different provinces" should "be batched into a single HERO_CHANGED result" in {
|
||||
val gs = GameState(
|
||||
currentPhase = UNAFFILIATED_HERO_ACTIONS,
|
||||
currentDate = Some(Date(3, 273)),
|
||||
provinces = mapifyProvinces(
|
||||
Province(
|
||||
id = 4,
|
||||
rulingFactionId = None,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_PRISONER,
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_PRISONER))
|
||||
)
|
||||
)
|
||||
),
|
||||
Province(
|
||||
id = 8,
|
||||
rulingFactionId = None,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 19,
|
||||
`type` = UNAFFILIATED_HERO_PRISONER,
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_PRISONER))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
heroes = mapifyHeroes(
|
||||
Hero(id = 17, vigor = 90, profession = NO_PROFESSION, pronounGender = GENDER_FEMALE),
|
||||
Hero(id = 19, vigor = 90, profession = NO_PROFESSION, pronounGender = GENDER_FEMALE)
|
||||
),
|
||||
factions = mapifyFactions(Faction(id = 6))
|
||||
)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
heroGenerator = null
|
||||
).resultsOfExecute()
|
||||
|
||||
// Should have exactly one HERO_CHANGED result containing both status changes
|
||||
val heroChangedResults = results.filter(_.`type` == HERO_CHANGED)
|
||||
heroChangedResults should have size 1
|
||||
|
||||
val changedProvinces = heroChangedResults.head.changedProvinces.collect { case cp: ChangedProvinceC => cp }
|
||||
val changedProvinces = heroChangedResults.head.changedProvinces
|
||||
changedProvinces should have size 2
|
||||
changedProvinces.map(_.provinceId) should contain allOf (4, 8)
|
||||
changedProvinces.map(_.id) should contain allOf (4, 8)
|
||||
|
||||
// Both prisoners should become residents
|
||||
changedProvinces
|
||||
.flatMap(_.changedUnaffiliatedHeroes)
|
||||
.map(_.heroId) should contain allOf (17, 19)
|
||||
changedProvinces
|
||||
.flatMap(_.changedUnaffiliatedHeroes)
|
||||
.foreach(_.unaffiliatedHeroType shouldBe UnaffiliatedHeroType.Resident)
|
||||
.foreach(_.`type` shouldBe UNAFFILIATED_HERO_RESIDENT)
|
||||
}
|
||||
|
||||
"a resident" should "become a traveler when random chance triggers" in {
|
||||
ResidentToTravelerChance.setDoubleValue(1.0)
|
||||
ResidentToTravelerChance.setDoubleValue(1.0) // Always trigger
|
||||
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
4 -> ProvinceC(
|
||||
id = 4,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 17,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Resident,
|
||||
recruitmentInfo = RecruitmentInfo.NoRulerInProvince
|
||||
val gs = GameState(
|
||||
currentPhase = UNAFFILIATED_HERO_ACTIONS,
|
||||
currentDate = Some(Date(3, 273)),
|
||||
provinces = mapifyProvinces(
|
||||
Province(
|
||||
id = 4,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_RESIDENT,
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_NO_RULER_IN_PROVINCE))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
heroes = mapifyHeroes(
|
||||
Hero(id = 17, vigor = 90, profession = NO_PROFESSION, pronounGender = GENDER_FEMALE)
|
||||
)
|
||||
)
|
||||
val heroes: Map[Int, HeroT] = Map(
|
||||
17 -> HeroC(id = 17, vigor = 90, pronounGender = Gender.Female)
|
||||
)
|
||||
val gs = baseGameState.copy(provinces = provinces, heroes = heroes)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = gs,
|
||||
heroGenerator = null,
|
||||
actionResultApplier = applier
|
||||
).results(SeededRandom(gs.randomSeed))
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
heroGenerator = null
|
||||
).resultsOfExecute()
|
||||
|
||||
val changedUHs = results
|
||||
results
|
||||
.flatMap(_.changedProvinces)
|
||||
.collect { case cp: ChangedProvinceC => cp }
|
||||
.flatMap(_.changedUnaffiliatedHeroes)
|
||||
|
||||
changedUHs.find(_.heroId == 17) shouldBe defined
|
||||
changedUHs.find(_.heroId == 17).get.unaffiliatedHeroType shouldBe UnaffiliatedHeroType.Traveler
|
||||
.flatMap(_.changedUnaffiliatedHeroes) should contain(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_TRAVELER,
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_TRAVELER))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"a traveler" should "become a resident when random chance triggers" in {
|
||||
TravelerMoveChance.setDoubleValue(0)
|
||||
TravelerToResidentChance.setDoubleValue(1.0)
|
||||
TravelerMoveChance.setDoubleValue(0) // Don't move
|
||||
TravelerToResidentChance.setDoubleValue(1.0) // Always become resident
|
||||
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
4 -> ProvinceC(
|
||||
id = 4,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 17,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Traveler,
|
||||
recruitmentInfo = RecruitmentInfo.Traveler
|
||||
val gs = GameState(
|
||||
currentPhase = UNAFFILIATED_HERO_ACTIONS,
|
||||
currentDate = Some(Date(3, 273)),
|
||||
provinces = mapifyProvinces(
|
||||
Province(
|
||||
id = 4,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_TRAVELER,
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_TRAVELER))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
heroes = mapifyHeroes(
|
||||
Hero(id = 17, vigor = 90, profession = NO_PROFESSION, pronounGender = GENDER_FEMALE)
|
||||
)
|
||||
)
|
||||
val heroes: Map[Int, HeroT] = Map(
|
||||
17 -> HeroC(id = 17, vigor = 90, pronounGender = Gender.Female)
|
||||
)
|
||||
val gs = baseGameState.copy(provinces = provinces, heroes = heroes)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = gs,
|
||||
heroGenerator = null,
|
||||
actionResultApplier = applier
|
||||
).results(SeededRandom(gs.randomSeed))
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
heroGenerator = null
|
||||
).resultsOfExecute()
|
||||
|
||||
val changedHeroes = results
|
||||
.flatMap(_.changedProvinces)
|
||||
.collect { case cp: ChangedProvinceC => cp }
|
||||
.flatMap(_.changedUnaffiliatedHeroes)
|
||||
|
||||
changedHeroes should have size 1
|
||||
changedHeroes.head.heroId shouldBe 17
|
||||
changedHeroes.head.unaffiliatedHeroType shouldBe UnaffiliatedHeroType.Resident
|
||||
changedHeroes.head.`type` shouldBe UNAFFILIATED_HERO_RESIDENT
|
||||
}
|
||||
|
||||
"multiple heroes in the same province" should "be batched into a single HERO_CHANGED result" in {
|
||||
val provinces: Map[Int, ProvinceT] = Map(
|
||||
4 -> ProvinceC(
|
||||
id = 4,
|
||||
rulingFactionId = None,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 17,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Prisoner,
|
||||
recruitmentInfo = RecruitmentInfo.Prisoner
|
||||
),
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 19,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Prisoner,
|
||||
recruitmentInfo = RecruitmentInfo.Prisoner
|
||||
val gs = GameState(
|
||||
currentPhase = UNAFFILIATED_HERO_ACTIONS,
|
||||
currentDate = Some(Date(3, 273)),
|
||||
provinces = mapifyProvinces(
|
||||
Province(
|
||||
id = 4,
|
||||
rulingFactionId = None,
|
||||
unaffiliatedHeroes = Vector(
|
||||
UnaffiliatedHero(
|
||||
heroId = 17,
|
||||
`type` = UNAFFILIATED_HERO_PRISONER,
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_PRISONER))
|
||||
),
|
||||
UnaffiliatedHero(
|
||||
heroId = 19,
|
||||
`type` = UNAFFILIATED_HERO_PRISONER,
|
||||
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_PRISONER))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
heroes = mapifyHeroes(
|
||||
Hero(id = 17, vigor = 90, profession = NO_PROFESSION, pronounGender = GENDER_FEMALE),
|
||||
Hero(id = 19, vigor = 90, profession = NO_PROFESSION, pronounGender = GENDER_FEMALE)
|
||||
),
|
||||
factions = mapifyFactions(Faction(id = 6))
|
||||
)
|
||||
val heroes: Map[Int, HeroT] = Map(
|
||||
17 -> HeroC(id = 17, vigor = 90, pronounGender = Gender.Female),
|
||||
19 -> HeroC(id = 19, vigor = 90, pronounGender = Gender.Female)
|
||||
)
|
||||
val factions: Map[Int, FactionT] = Map(
|
||||
6 -> FactionC(id = 6, factionHeadId = 0, name = "", leaderIds = Vector())
|
||||
)
|
||||
val gs = baseGameState.copy(provinces = provinces, heroes = heroes, factions = factions)
|
||||
|
||||
val results = PerformUnaffiliatedHeroesAction(
|
||||
gameState = gs,
|
||||
heroGenerator = null,
|
||||
actionResultApplier = applier
|
||||
).results(SeededRandom(gs.randomSeed))
|
||||
gameState = GameStateConverter.fromProto(gs),
|
||||
heroGenerator = null
|
||||
).resultsOfExecute()
|
||||
|
||||
val heroChangedResults = results.filter(_.actionResultType == HeroChangedResultType)
|
||||
// Should have exactly one HERO_CHANGED result
|
||||
val heroChangedResults = results.filter(_.`type` == HERO_CHANGED)
|
||||
heroChangedResults should have size 1
|
||||
|
||||
val changedProvinces = heroChangedResults.head.changedProvinces.collect { case cp: ChangedProvinceC => cp }
|
||||
// Should have one changedProvince entry with both heroes
|
||||
val changedProvinces = heroChangedResults.head.changedProvinces
|
||||
changedProvinces should have size 1
|
||||
changedProvinces.head.provinceId shouldBe 4
|
||||
changedProvinces.head.id shouldBe 4
|
||||
|
||||
val changedHeroes = changedProvinces.head.changedUnaffiliatedHeroes
|
||||
changedHeroes should have size 2
|
||||
|
||||
+46
-43
@@ -18,15 +18,22 @@ import net.eagle0.eagle.model.state.{
|
||||
Supplies
|
||||
}
|
||||
import net.eagle0.eagle.model.state.battalion.concrete.BattalionC
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.concrete.FactionC
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship.RelationshipLevel
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.{Gender, Profession}
|
||||
import net.eagle0.eagle.model.state.hero.concrete.HeroC
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.province.ProvinceOrderType
|
||||
import net.eagle0.eagle.model.state.province.ProvinceOrderType.Entrust
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.Inside.inside
|
||||
@@ -34,7 +41,39 @@ import org.scalatest.Inspectors.forExactly
|
||||
import org.scalatest.LoneElement.convertToCollectionLoneElementWrapper
|
||||
|
||||
class PerformUncontestedConquestActionTest extends AnyFlatSpec with Matchers {
|
||||
private val attackerId = 1
|
||||
private val attackerId = 1
|
||||
private val gameId = 12345L
|
||||
private val currentRoundId = 9
|
||||
private val currentDate = Date(year = 1, month = Date.Month.January)
|
||||
|
||||
private def makeGameState(
|
||||
provinces: Map[Int, ProvinceT],
|
||||
factions: Map[Int, FactionT],
|
||||
heroes: Map[Int, HeroT] = this.heroes,
|
||||
battalions: Map[Int, BattalionT] = this.battalions
|
||||
): GameState = GameState(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentPhase = RoundPhase.UncontestedConquest,
|
||||
currentDate = Some(currentDate),
|
||||
actionResultCount = 0,
|
||||
provinces = provinces,
|
||||
heroes = heroes,
|
||||
battalions = battalions,
|
||||
destroyedBattalions = Map.empty,
|
||||
factions = factions,
|
||||
factionCommandCounts = Map.empty,
|
||||
killedHeroes = Map.empty,
|
||||
destroyedFactions = Map.empty,
|
||||
outstandingBattles = Vector.empty,
|
||||
battleCounter = 0,
|
||||
deferredNotifications = Vector.empty,
|
||||
runStatus = RunStatus.Running,
|
||||
victor = None,
|
||||
battalionTypes = Vector.empty,
|
||||
randomSeed = 12345L,
|
||||
chronicleEntries = Vector.empty
|
||||
)
|
||||
|
||||
private val heroes: Map[Int, HeroC] = Map(
|
||||
92 -> HeroC(
|
||||
@@ -117,13 +156,7 @@ class PerformUncontestedConquestActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val results = PerformUncontestedConquestAction(
|
||||
gameId = 12345L,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1, month = Date.Month.January),
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
heroes = heroes,
|
||||
battalions = battalions
|
||||
makeGameState(provinces, factions)
|
||||
).results
|
||||
|
||||
results.loneElement.actionResultType.shouldBe(
|
||||
@@ -176,13 +209,7 @@ class PerformUncontestedConquestActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val results = PerformUncontestedConquestAction(
|
||||
gameId = 12345L,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1, month = Date.Month.January),
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
heroes = heroes,
|
||||
battalions = battalions
|
||||
makeGameState(provinces, factions)
|
||||
).results
|
||||
|
||||
results.size.shouldBe(3)
|
||||
@@ -240,13 +267,7 @@ class PerformUncontestedConquestActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val results = PerformUncontestedConquestAction(
|
||||
gameId = 12345L,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1, month = Date.Month.January),
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
heroes = heroes,
|
||||
battalions = battalions
|
||||
makeGameState(provinces, factions)
|
||||
).results
|
||||
|
||||
inside(results.head.changedProvinces.head) {
|
||||
@@ -312,13 +333,7 @@ class PerformUncontestedConquestActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val results = PerformUncontestedConquestAction(
|
||||
gameId = 12345L,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1, month = Date.Month.January),
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
heroes = heroes,
|
||||
battalions = battalions
|
||||
makeGameState(provinces, factions)
|
||||
).results
|
||||
|
||||
results.size.shouldBe(3)
|
||||
@@ -414,13 +429,7 @@ class PerformUncontestedConquestActionTest extends AnyFlatSpec with Matchers {
|
||||
|
||||
val ex = the[EagleInternalException] thrownBy
|
||||
PerformUncontestedConquestAction(
|
||||
gameId = 12345L,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1, month = Date.Month.January),
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
heroes = heroes,
|
||||
battalions = battalions
|
||||
makeGameState(provinces, factions)
|
||||
).results
|
||||
|
||||
ex.getMessage.shouldBe(
|
||||
@@ -511,13 +520,7 @@ class PerformUncontestedConquestActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val result = PerformUncontestedConquestAction(
|
||||
gameId = 12345L,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1, month = Date.Month.January),
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
heroes = heroes,
|
||||
battalions = battalions
|
||||
makeGameState(provinces, factions)
|
||||
).results.head
|
||||
|
||||
result.actionResultType.shouldBe(WithdrawalForTruceResultType)
|
||||
|
||||
+25
-32
@@ -19,8 +19,8 @@ import net.eagle0.eagle.internal.faction.Faction
|
||||
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
|
||||
import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.internal.province.{Neighbor, Province}
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplier
|
||||
import net.eagle0.eagle.library.actions.impl.command.TCommandFactory
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
|
||||
import net.eagle0.eagle.library.actions.impl.command.CommandFactory
|
||||
import net.eagle0.eagle.library.settings.{
|
||||
ActionVigorCost,
|
||||
DesiredLoyaltyOverThreshold,
|
||||
@@ -61,8 +61,8 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
|
||||
private val functionalRandom = SeededRandom(new Random().nextLong())
|
||||
|
||||
private val commandFactory: TCommandFactory = mock[TCommandFactory]
|
||||
private val actionResultApplier: ActionResultApplier = mock[ActionResultApplier]
|
||||
private val commandFactory: CommandFactory = mock[CommandFactory]
|
||||
private val applier: ActionResultTApplier = mock[ActionResultTApplier]
|
||||
|
||||
private val actingFactionId = 19
|
||||
|
||||
@@ -339,10 +339,9 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
val developingProvince = actingProvince.withProvinceOrders(DEVELOP)
|
||||
val developingGS =
|
||||
gameState.update(_.provinces(developingProvince.id) := developingProvince)
|
||||
val scalaGs = GameStateConverter.fromProto(developingGS)
|
||||
|
||||
val selection = PerformVassalCommandsPhaseAction(
|
||||
gameState = scalaGs,
|
||||
gameState = GameStateConverter.fromProto(developingGS),
|
||||
commandsForProvince = Map(
|
||||
developingProvince.id -> Some(
|
||||
OneProvinceAvailableCommands(
|
||||
@@ -358,8 +357,8 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
)
|
||||
),
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).chooseCommand(scalaGs, developingProvince.id, functionalRandom).newValue.get
|
||||
applier = applier
|
||||
).chooseCommand(developingProvince.id, functionalRandom).newValue.get
|
||||
|
||||
inside(selection.available) {
|
||||
case FeastAvailableCommand(
|
||||
@@ -382,10 +381,9 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
_.heroes(4).loyalty := 70,
|
||||
_.heroes(5).loyalty := 70
|
||||
)
|
||||
val scalaGs = GameStateConverter.fromProto(developingGS)
|
||||
|
||||
val selection = PerformVassalCommandsPhaseAction(
|
||||
gameState = scalaGs,
|
||||
gameState = GameStateConverter.fromProto(developingGS),
|
||||
commandsForProvince = Map(
|
||||
developingProvince.id -> Some(
|
||||
OneProvinceAvailableCommands(
|
||||
@@ -401,8 +399,8 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
)
|
||||
),
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).chooseCommand(scalaGs, developingProvince.id, functionalRandom).newValue.get
|
||||
applier = applier
|
||||
).chooseCommand(developingProvince.id, functionalRandom).newValue.get
|
||||
|
||||
inside(selection) {
|
||||
case CommandSelection(
|
||||
@@ -436,10 +434,9 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
_.heroes(4).loyalty := 70,
|
||||
_.heroes(5).loyalty := 70
|
||||
)
|
||||
val scalaGs = GameStateConverter.fromProto(developingGS)
|
||||
|
||||
val selection = PerformVassalCommandsPhaseAction(
|
||||
gameState = scalaGs,
|
||||
gameState = GameStateConverter.fromProto(developingGS),
|
||||
commandsForProvince = Map(
|
||||
developingProvince.id -> Some(
|
||||
OneProvinceAvailableCommands(
|
||||
@@ -455,8 +452,8 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
)
|
||||
),
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).chooseCommand(scalaGs, developingProvince.id, functionalRandom).newValue.get
|
||||
applier = applier
|
||||
).chooseCommand(developingProvince.id, functionalRandom).newValue.get
|
||||
|
||||
selection.available should not matchPattern {
|
||||
case _: HeroGiftAvailableCommand =>
|
||||
@@ -491,10 +488,9 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
val developingProvince = actingProvince.withProvinceOrders(DEVELOP)
|
||||
val developingGS =
|
||||
gameState.update(_.provinces(developingProvince.id) := developingProvince)
|
||||
val scalaGs = GameStateConverter.fromProto(developingGS)
|
||||
|
||||
val selection = PerformVassalCommandsPhaseAction(
|
||||
gameState = scalaGs,
|
||||
gameState = GameStateConverter.fromProto(developingGS),
|
||||
commandsForProvince = Map(
|
||||
developingProvince.id -> Some(
|
||||
OneProvinceAvailableCommands(
|
||||
@@ -504,8 +500,8 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
)
|
||||
),
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).chooseCommand(scalaGs, developingProvince.id, functionalRandom).newValue.get
|
||||
applier = applier
|
||||
).chooseCommand(developingProvince.id, functionalRandom).newValue.get
|
||||
|
||||
selection.available shouldBe improveCommand
|
||||
}
|
||||
@@ -514,10 +510,9 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
val developingProvince = actingProvince.withProvinceOrders(DEVELOP)
|
||||
val developingGS =
|
||||
gameState.update(_.provinces(developingProvince.id) := developingProvince)
|
||||
val scalaGs = GameStateConverter.fromProto(developingGS)
|
||||
|
||||
val selection = PerformVassalCommandsPhaseAction(
|
||||
gameState = scalaGs,
|
||||
gameState = GameStateConverter.fromProto(developingGS),
|
||||
commandsForProvince = Map(
|
||||
developingProvince.id -> Some(
|
||||
OneProvinceAvailableCommands(
|
||||
@@ -527,8 +522,8 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
)
|
||||
),
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).chooseCommand(scalaGs, developingProvince.id, functionalRandom).newValue.get
|
||||
applier = applier
|
||||
).chooseCommand(developingProvince.id, functionalRandom).newValue.get
|
||||
|
||||
selection.available shouldBe restCommand
|
||||
}
|
||||
@@ -538,7 +533,6 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
val marchingGS = marchGameState.update(
|
||||
_.provinces(marchingProvince.id) := marchingProvince
|
||||
)
|
||||
val scalaGs = GameStateConverter.fromProto(marchingGS)
|
||||
|
||||
val commandsMap = Map(
|
||||
marchingProvince.id -> Some(
|
||||
@@ -554,11 +548,11 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
)
|
||||
|
||||
val selection = PerformVassalCommandsPhaseAction(
|
||||
gameState = scalaGs,
|
||||
gameState = GameStateConverter.fromProto(marchingGS),
|
||||
commandsForProvince = commandsMap,
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).chooseCommand(scalaGs, actingProvince.id, functionalRandom).newValue.get
|
||||
applier = applier
|
||||
).chooseCommand(actingProvince.id, functionalRandom).newValue.get
|
||||
|
||||
inside(selection.available) {
|
||||
case mac: MarchAvailableCommand =>
|
||||
@@ -571,7 +565,6 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
val marchingGS = marchGameState.update(
|
||||
_.provinces(marchingProvince.id) := marchingProvince
|
||||
)
|
||||
val scalaGs = GameStateConverter.fromProto(marchingGS)
|
||||
|
||||
val commandsMap = Map(
|
||||
marchingProvince.id -> Some(
|
||||
@@ -583,11 +576,11 @@ class PerformVassalCommandsPhaseActionTest extends AnyFlatSpec with Matchers wit
|
||||
)
|
||||
|
||||
val selection = PerformVassalCommandsPhaseAction(
|
||||
gameState = scalaGs,
|
||||
gameState = GameStateConverter.fromProto(marchingGS),
|
||||
commandsForProvince = commandsMap,
|
||||
commandFactory = commandFactory,
|
||||
actionResultApplier = actionResultApplier
|
||||
).chooseCommand(scalaGs, actingProvince.id, functionalRandom).newValue.get
|
||||
applier = applier
|
||||
).chooseCommand(actingProvince.id, functionalRandom).newValue.get
|
||||
|
||||
selection.available shouldBe improveCommand
|
||||
}
|
||||
|
||||
+89
-54
@@ -5,15 +5,22 @@ import net.eagle0.eagle.library.EagleInternalException
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
|
||||
import net.eagle0.eagle.model.state.*
|
||||
import net.eagle0.eagle.model.state.battalion.concrete.BattalionC
|
||||
import net.eagle0.eagle.model.state.battalion.BattalionT
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.concrete.FactionC
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.concrete.HeroC
|
||||
import net.eagle0.eagle.model.state.hero.Gender.{Female, Male}
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.hero.Profession.NoProfession
|
||||
import net.eagle0.eagle.model.state.province.{Neighbor, ProvinceOrderType}
|
||||
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import net.eagle0.eagle.model.state.shardok_battle.{BattleType, VictoryCondition}
|
||||
import net.eagle0.eagle.model.state.HostileArmyGroupStatus.{Attacking, AwaitingDecision}
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.Inside.inside
|
||||
@@ -23,6 +30,40 @@ class RequestBattlesActionTest extends AnyFlatSpec with Matchers {
|
||||
private val attackerId = 1
|
||||
private val defenderId = 2
|
||||
|
||||
private def makeGameState(
|
||||
gameId: GameId,
|
||||
currentRoundId: RoundId,
|
||||
currentDate: Date,
|
||||
battleCounter: Int,
|
||||
provinces: Map[ProvinceId, ProvinceT],
|
||||
factions: Map[FactionId, FactionT],
|
||||
heroes: Map[HeroId, HeroT] = this.heroes,
|
||||
battalions: Map[BattalionId, BattalionT] = this.battalions,
|
||||
battalionTypes: Map[BattalionTypeId, BattalionType] = this.battalionTypes
|
||||
): GameState = GameState(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
currentPhase = RoundPhase.BattleRequest,
|
||||
currentDate = Some(currentDate),
|
||||
actionResultCount = 0,
|
||||
provinces = provinces,
|
||||
heroes = heroes,
|
||||
battalions = battalions,
|
||||
destroyedBattalions = Map.empty,
|
||||
factions = factions,
|
||||
factionCommandCounts = Map.empty,
|
||||
killedHeroes = Map.empty,
|
||||
destroyedFactions = Map.empty,
|
||||
outstandingBattles = Vector.empty,
|
||||
battleCounter = battleCounter,
|
||||
deferredNotifications = Vector.empty,
|
||||
runStatus = RunStatus.Running,
|
||||
victor = None,
|
||||
battalionTypes = battalionTypes.values.toVector,
|
||||
randomSeed = 12345L,
|
||||
chronicleEntries = Vector.empty
|
||||
)
|
||||
|
||||
private val heroes = Map[HeroId, HeroC](
|
||||
92 -> HeroC(
|
||||
id = 92,
|
||||
@@ -137,15 +178,14 @@ class RequestBattlesActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val action = RequestBattlesAction(
|
||||
gameId = 0xcafe,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0x17,
|
||||
heroes = heroes,
|
||||
battalions = battalions,
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
battalionTypes = battalionTypes
|
||||
makeGameState(
|
||||
gameId = 0xcafe,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0x17,
|
||||
provinces = provinces,
|
||||
factions = factions
|
||||
)
|
||||
)
|
||||
|
||||
val results = action.results
|
||||
@@ -226,15 +266,14 @@ class RequestBattlesActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val results = RequestBattlesAction(
|
||||
gameId = 0x123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0,
|
||||
heroes = heroes,
|
||||
battalions = battalions,
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
battalionTypes = battalionTypes
|
||||
makeGameState(
|
||||
gameId = 0x123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0,
|
||||
provinces = provinces,
|
||||
factions = factions
|
||||
)
|
||||
).results
|
||||
|
||||
results.size shouldBe 2
|
||||
@@ -283,15 +322,14 @@ class RequestBattlesActionTest extends AnyFlatSpec with Matchers {
|
||||
|
||||
val ex = the[EagleInternalException] thrownBy
|
||||
RequestBattlesAction(
|
||||
gameId = 0x123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0,
|
||||
heroes = heroes,
|
||||
battalions = battalions,
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
battalionTypes = battalionTypes
|
||||
makeGameState(
|
||||
gameId = 0x123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0,
|
||||
provinces = provinces,
|
||||
factions = factions
|
||||
)
|
||||
).results
|
||||
|
||||
ex.getMessage shouldBe "requirement failed: all armies must be attacking in the battle request phase"
|
||||
@@ -330,15 +368,14 @@ class RequestBattlesActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val results = RequestBattlesAction(
|
||||
gameId = 0x123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0,
|
||||
heroes = heroes,
|
||||
battalions = battalions,
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
battalionTypes = battalionTypes
|
||||
makeGameState(
|
||||
gameId = 0x123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0,
|
||||
provinces = provinces,
|
||||
factions = factions
|
||||
)
|
||||
).results
|
||||
|
||||
inside(results.head.changedProvinces.head) {
|
||||
@@ -402,15 +439,14 @@ class RequestBattlesActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val results = RequestBattlesAction(
|
||||
gameId = 0xabc123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0xa5,
|
||||
heroes = heroes,
|
||||
battalions = battalions,
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
battalionTypes = battalionTypes
|
||||
makeGameState(
|
||||
gameId = 0xabc123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0xa5,
|
||||
provinces = provinces,
|
||||
factions = factions
|
||||
)
|
||||
).results
|
||||
|
||||
results should have size 1
|
||||
@@ -473,15 +509,14 @@ class RequestBattlesActionTest extends AnyFlatSpec with Matchers {
|
||||
)
|
||||
|
||||
val action = RequestBattlesAction(
|
||||
gameId = 0x123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0,
|
||||
heroes = heroes,
|
||||
battalions = battalions,
|
||||
provinces = provinces,
|
||||
factions = factions,
|
||||
battalionTypes = battalionTypes
|
||||
makeGameState(
|
||||
gameId = 0x123,
|
||||
currentRoundId = 9,
|
||||
currentDate = Date(year = 1501, month = Date.Month.June),
|
||||
battleCounter = 0,
|
||||
provinces = provinces,
|
||||
factions = factions
|
||||
)
|
||||
)
|
||||
|
||||
val results = action.results
|
||||
|
||||
+115
-126
@@ -1,26 +1,30 @@
|
||||
package net.eagle0.eagle.library.actions.impl.action
|
||||
|
||||
import net.eagle0.common.SeededRandom
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultApplierImpl
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.{ChangedProvinceC, HostileArmyStatusChange}
|
||||
import net.eagle0.eagle.model.action_result.concrete.ActionResultC
|
||||
import net.eagle0.eagle.model.action_result.types.{EndTruceTurnBackPhaseResultType, WithdrawalForTruceResultType}
|
||||
import net.eagle0.eagle.model.state.{Army, HostileArmyGroup, HostileArmyGroupStatus, MovingArmy, Supplies}
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.concrete.FactionC
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship.RelationshipLevel
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import net.eagle0.common.ProtoMatchers.equalProto
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType.{END_TRUCE_TURN_BACK_PHASE, WITHDRAWAL_FOR_TRUCE}
|
||||
import net.eagle0.eagle.common.round_phase.{NewRoundPhase, RoundPhase}
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.internal.army.*
|
||||
import net.eagle0.eagle.internal.changed_province.ChangedProvince
|
||||
import net.eagle0.eagle.internal.changed_province.ChangedProvince.HostileArmyGroupStatusChange
|
||||
import net.eagle0.eagle.internal.faction.Faction
|
||||
import net.eagle0.eagle.internal.faction_relationship.FactionRelationship
|
||||
import net.eagle0.eagle.internal.faction_relationship.FactionRelationship.RelationshipLevel.TRUCE
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplierImpl
|
||||
import net.eagle0.eagle.library.util.validations.TestingNoopValidator
|
||||
import net.eagle0.eagle.library.util.IDable.{mapifyFactions, mapifyProvinces}
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.FactionId
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class TruceTurnBackPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
|
||||
private val actionResultApplier = ActionResultApplierImpl(None)
|
||||
private val actionResultProtoApplier = new ActionResultProtoApplierImpl(
|
||||
validator = TestingNoopValidator
|
||||
)
|
||||
|
||||
private val attackingFid: FactionId = 9
|
||||
private val defendingFid: FactionId = 23
|
||||
@@ -28,118 +32,103 @@ class TruceTurnBackPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
private val destinationPid = 17
|
||||
private val fleePid = 98
|
||||
|
||||
private val fleeProvince = ProvinceC(
|
||||
id = fleePid,
|
||||
rulingFactionId = Some(attackingFid)
|
||||
)
|
||||
private val fleeProvince =
|
||||
Province(id = fleePid, rulingFactionId = Some(attackingFid))
|
||||
|
||||
private val destinationProvince = ProvinceC(
|
||||
id = destinationPid,
|
||||
rulingFactionId = Some(defendingFid),
|
||||
hostileArmies = Vector(
|
||||
HostileArmyGroup(
|
||||
status = HostileArmyGroupStatus.Attacking,
|
||||
factionId = attackingFid,
|
||||
armies = Vector(
|
||||
MovingArmy(
|
||||
army = Army(
|
||||
factionId = attackingFid,
|
||||
units = Vector(),
|
||||
fleeProvinceId = Some(fleePid)
|
||||
),
|
||||
arrivalRound = 155,
|
||||
destinationProvinceId = destinationPid,
|
||||
originProvinceId = 3,
|
||||
supplies = Supplies(gold = 0, food = 0),
|
||||
suppliesLoss = 0,
|
||||
id = 991,
|
||||
startingPositionIndex = Some(5)
|
||||
private val destinationProvince =
|
||||
Province(
|
||||
id = destinationPid,
|
||||
rulingFactionId = Some(defendingFid),
|
||||
hostileArmies = Vector(
|
||||
HostileArmyGroup(
|
||||
status = Attacking(),
|
||||
factionId = attackingFid,
|
||||
armies = Vector(
|
||||
MovingArmy(
|
||||
army = Some(
|
||||
Army(
|
||||
factionId = attackingFid,
|
||||
units = Vector(),
|
||||
fleeProvinceId = Some(fleePid)
|
||||
)
|
||||
),
|
||||
arrivalRound = 155,
|
||||
destinationProvince = destinationPid,
|
||||
originProvince = 3,
|
||||
supplies = None,
|
||||
suppliesLoss = 0,
|
||||
id = 991,
|
||||
startingPositionIndex = Some(5)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private val gameState = GameState(
|
||||
currentDate = Some(net.eagle0.eagle.common.date.Date(year = 400, month = 3)),
|
||||
currentRoundId = 155,
|
||||
currentPhase = RoundPhase.TRUCE_TURN_BACK,
|
||||
provinces = mapifyProvinces(destinationProvince, fleeProvince),
|
||||
factions = mapifyFactions(
|
||||
Faction(
|
||||
id = attackingFid,
|
||||
factionRelationships = Vector(
|
||||
FactionRelationship(
|
||||
targetFactionId = defendingFid,
|
||||
relationshipLevel = TRUCE
|
||||
)
|
||||
)
|
||||
),
|
||||
Faction(
|
||||
id = defendingFid,
|
||||
factionRelationships = Vector(
|
||||
FactionRelationship(
|
||||
targetFactionId = attackingFid,
|
||||
relationshipLevel = TRUCE
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private val attackingFaction = FactionC(
|
||||
id = attackingFid,
|
||||
factionHeadId = 1,
|
||||
name = "Attacking Faction",
|
||||
leaderIds = Vector(),
|
||||
factionRelationships = Vector(
|
||||
FactionRelationship(
|
||||
targetFactionId = defendingFid,
|
||||
relationshipLevel = RelationshipLevel.Truce
|
||||
)
|
||||
"execute" should "prove to the next phase" in {
|
||||
val results = TruceTurnBackPhaseAction(GameStateConverter.fromProto(gameState)).execute(
|
||||
actionResultProtoApplier
|
||||
)
|
||||
)
|
||||
|
||||
private val defendingFaction = FactionC(
|
||||
id = defendingFid,
|
||||
factionHeadId = 2,
|
||||
name = "Defending Faction",
|
||||
leaderIds = Vector(),
|
||||
factionRelationships = Vector(
|
||||
FactionRelationship(
|
||||
targetFactionId = attackingFid,
|
||||
relationshipLevel = RelationshipLevel.Truce
|
||||
exactly(1, results.map(_.actionResult)).should(
|
||||
equalProto(
|
||||
ActionResult(
|
||||
`type` = END_TRUCE_TURN_BACK_PHASE,
|
||||
newRoundPhase = Some(NewRoundPhase(RoundPhase.BATTLE_REQUEST))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private val gameState = GameState(
|
||||
gameId = 1,
|
||||
currentDate = Some(Date(year = 400, month = Date.Month.March)),
|
||||
currentRoundId = 155,
|
||||
currentPhase = RoundPhase.TruceTurnBack,
|
||||
actionResultCount = 0,
|
||||
provinces = Map(
|
||||
destinationPid -> destinationProvince,
|
||||
fleePid -> fleeProvince
|
||||
),
|
||||
heroes = Map.empty,
|
||||
battalions = Map.empty,
|
||||
destroyedBattalions = Map.empty,
|
||||
factions = Map(
|
||||
attackingFid -> attackingFaction,
|
||||
defendingFid -> defendingFaction
|
||||
),
|
||||
factionCommandCounts = Map.empty,
|
||||
killedHeroes = Map.empty,
|
||||
destroyedFactions = Map.empty,
|
||||
outstandingBattles = Vector.empty,
|
||||
battleCounter = 0,
|
||||
deferredNotifications = Vector.empty,
|
||||
runStatus = RunStatus.Running,
|
||||
victor = None,
|
||||
battalionTypes = Vector.empty,
|
||||
randomSeed = 1L,
|
||||
chronicleEntries = Vector.empty
|
||||
)
|
||||
|
||||
"results" should "advance to the next phase" in {
|
||||
val results = TruceTurnBackPhaseAction(gameState, actionResultApplier)
|
||||
.results(SeededRandom(gameState.randomSeed))
|
||||
|
||||
results.last shouldBe ActionResultC(
|
||||
actionResultType = EndTruceTurnBackPhaseResultType,
|
||||
newRoundPhase = Some(RoundPhase.BattleRequest)
|
||||
)
|
||||
}
|
||||
|
||||
it should "turn back an army with a truce" in {
|
||||
val results = TruceTurnBackPhaseAction(gameState, actionResultApplier)
|
||||
.results(SeededRandom(gameState.randomSeed))
|
||||
val results = TruceTurnBackPhaseAction(GameStateConverter.fromProto(gameState)).execute(
|
||||
actionResultProtoApplier
|
||||
)
|
||||
|
||||
results.head shouldBe ActionResultC(
|
||||
actionResultType = WithdrawalForTruceResultType,
|
||||
actingFactionId = Some(attackingFid),
|
||||
provinceId = Some(destinationPid),
|
||||
changedProvinces = Vector(
|
||||
ChangedProvinceC(
|
||||
provinceId = destinationPid,
|
||||
clearDefendingArmy = true,
|
||||
hostileArmyStatusChanges = Vector(
|
||||
HostileArmyStatusChange(attackingFid, HostileArmyGroupStatus.Withdrawing)
|
||||
results.head.actionResult.should(
|
||||
equalProto(
|
||||
ActionResult(
|
||||
`type` = WITHDRAWAL_FOR_TRUCE,
|
||||
player = Some(attackingFid),
|
||||
province = Some(destinationPid),
|
||||
changedProvinces = Vector(
|
||||
ChangedProvince(
|
||||
id = destinationPid,
|
||||
clearDefendingArmy = true,
|
||||
hostileArmyStatusChanges = Vector(
|
||||
HostileArmyGroupStatusChange(
|
||||
factionId = attackingFid,
|
||||
newStatus = Withdrawing()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -147,23 +136,23 @@ class TruceTurnBackPhaseActionTest extends AnyFlatSpec with Matchers {
|
||||
}
|
||||
|
||||
it should "just end the phase if there's no truce" in {
|
||||
val attackingFactionWithoutTruce = attackingFaction.copy(factionRelationships = Vector())
|
||||
val defendingFactionWithoutTruce = defendingFaction.copy(factionRelationships = Vector())
|
||||
|
||||
val gsWithoutTruce = gameState.copy(
|
||||
factions = Map(
|
||||
attackingFid -> attackingFactionWithoutTruce,
|
||||
defendingFid -> defendingFactionWithoutTruce
|
||||
)
|
||||
val gsWithoutTruce = gameState.update(
|
||||
_.factions(attackingFid).factionRelationships := Vector(),
|
||||
_.factions(defendingFid).factionRelationships := Vector()
|
||||
)
|
||||
|
||||
val results = TruceTurnBackPhaseAction(gsWithoutTruce, actionResultApplier)
|
||||
.results(SeededRandom(gsWithoutTruce.randomSeed))
|
||||
val results =
|
||||
TruceTurnBackPhaseAction(GameStateConverter.fromProto(gsWithoutTruce)).execute(
|
||||
actionResultProtoApplier
|
||||
)
|
||||
|
||||
results.size shouldBe 1
|
||||
results.head shouldBe ActionResultC(
|
||||
actionResultType = EndTruceTurnBackPhaseResultType,
|
||||
newRoundPhase = Some(RoundPhase.BattleRequest)
|
||||
exactly(1, results.map(_.actionResult)).should(
|
||||
equalProto(
|
||||
ActionResult(
|
||||
`type` = END_TRUCE_TURN_BACK_PHASE,
|
||||
newRoundPhase = Some(NewRoundPhase(RoundPhase.BATTLE_REQUEST))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,11 @@ import net.eagle0.eagle.library.settings.{
|
||||
TurnsToResolveConquest
|
||||
}
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.concrete.FactionC
|
||||
import net.eagle0.eagle.model.state.faction.FactionT
|
||||
import net.eagle0.eagle.model.state.hero.concrete.HeroC
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.hero.Profession.NoProfession
|
||||
import net.eagle0.eagle.model.state.province.{BlizzardEvent, ImminentRiotEvent}
|
||||
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.BattalionTypeId.{
|
||||
HeavyCavalry,
|
||||
@@ -255,33 +253,4 @@ class ProvinceUtilsTest extends AnyFlatSpec with Matchers with BeforeAndAfterEac
|
||||
HeavyCavalry
|
||||
)
|
||||
}
|
||||
|
||||
"hasImminentRiot" should "return true when province has imminent riot event" in {
|
||||
val province = ProvinceC(
|
||||
id = 17,
|
||||
activeEvents = Vector(ImminentRiotEvent())
|
||||
)
|
||||
|
||||
ProvinceUtils.hasImminentRiot(province) shouldBe true
|
||||
}
|
||||
|
||||
it should "return false when province has no imminent riot event" in {
|
||||
val province = ProvinceC(id = 17, activeEvents = Vector.empty)
|
||||
|
||||
ProvinceUtils.hasImminentRiot(province) shouldBe false
|
||||
}
|
||||
|
||||
it should "return false when province has other events but no imminent riot" in {
|
||||
val province = ProvinceC(
|
||||
id = 17,
|
||||
activeEvents = Vector(
|
||||
BlizzardEvent(
|
||||
startDate = Date(400, Date.Month.January),
|
||||
endDate = Date(400, Date.Month.March)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
ProvinceUtils.hasImminentRiot(province) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,8 +215,7 @@ class HumanPlayerClientConnectionStateTest
|
||||
availableCommands = Some(availableCommandsAfter),
|
||||
filteredResults = Vector(),
|
||||
unfilteredCountAfter = 4,
|
||||
knownShardokResults = Vector(),
|
||||
serverGameStatus = None
|
||||
knownShardokResults = Vector()
|
||||
)
|
||||
|
||||
mockObserver.onNext
|
||||
|
||||
@@ -37,11 +37,6 @@ class GameControllerTest extends AnyFlatSpec with MockFactory with Matchers with
|
||||
)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(() => mockClientTextStore.incompleteTexts)
|
||||
.expects()
|
||||
.returning(Map.empty)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
"gameId" should "return the gameId of the engine" in {
|
||||
(() => mockEngine.gameId)
|
||||
.expects()
|
||||
|
||||
Reference in New Issue
Block a user