diff --git a/hwe/compare/battle_log_snapshot.php b/hwe/compare/battle_log_snapshot.php new file mode 100644 index 00000000..155c302d --- /dev/null +++ b/hwe/compare/battle_log_snapshot.php @@ -0,0 +1,32 @@ +query( + 'SELECT general_id, log_type, year, month, text + FROM general_record + WHERE log_type IN ("battle_brief", "battle") + ORDER BY id' +); + +echo json_encode([ + 'schemaVersion' => 1, + 'engine' => 'ref', + 'logs' => array_map(static fn(array $row): array => [ + 'generalId' => (int)$row['general_id'], + 'category' => $row['log_type'] === 'battle_brief' ? 'BATTLE_BRIEF' : 'BATTLE_DETAIL', + 'year' => (int)$row['year'], + 'month' => (int)$row['month'], + 'text' => (string)$row['text'], + ], $rows), +], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL; diff --git a/hwe/compare/monthly_state_snapshot.php b/hwe/compare/monthly_state_snapshot.php index af7921c8..03e20de4 100644 --- a/hwe/compare/monthly_state_snapshot.php +++ b/hwe/compare/monthly_state_snapshot.php @@ -75,7 +75,11 @@ foreach ($db->query('SELECT * FROM general ORDER BY no') as $row) { 'dex4', 'dex5', 'killTurn', 'affinity', 'birthYear', 'deathYear', 'specAge', 'specAge2'] as $key) { $general[$key] = (int)$general[$key]; } - $general['turnTime'] = $clock->formatTick(Util::toInt($general['turnTime']), true); + $general['turnTick'] = Util::toInt($general['turnTime']); + $general['turnTime'] = $clock->formatTick($general['turnTick'], true); + $general['recentWarTick'] = $general['recentWarTime'] === null + ? null + : Util::toInt($general['recentWarTime']); $general['recentWarTime'] = $general['recentWarTime'] === null ? null : $clock->formatTick(Util::toInt($general['recentWarTime']), true); @@ -102,7 +106,9 @@ foreach ($db->query('SELECT * FROM city ORDER BY city') as $row) { } $nations = []; -foreach ($db->query('SELECT * FROM nation ORDER BY nation') as $row) { +$lastAttackableByNation = KVStorage::getValuesFromInterNamespace($db, 'nation_env', 'last_attackable'); +$lastCapitalMoveTrialByNation = KVStorage::getValuesFromInterNamespace($db, 'nation_env', 'last천도Trial'); +foreach ($db->query('SELECT *, CAST(tech AS DOUBLE) AS tech_raw FROM nation ORDER BY nation') as $row) { $nation = monthlySnapshotPick($row, [ 'id' => 'nation', 'name' => 'name', 'color' => 'color', 'capitalCityId' => 'capital', 'gold' => 'gold', 'rice' => 'rice', 'tech' => 'tech', 'level' => 'level', @@ -117,6 +123,12 @@ foreach ($db->query('SELECT * FROM nation ORDER BY nation') as $row) { $nation[$key] = (int)$nation[$key]; } $nation['tech'] = (float)$nation['tech']; + $nation['techRaw'] = (float)$row['tech_raw']; + $nation['lastAttackable'] = (int)($lastAttackableByNation[$nation['id']] ?? 0); + $lastCapitalMoveTrial = $lastCapitalMoveTrialByNation[$nation['id']] ?? null; + $nation['lastCapitalMoveTrial'] = is_array($lastCapitalMoveTrial) + ? [(int)($lastCapitalMoveTrial[0] ?? 0), (int)($lastCapitalMoveTrial[1] ?? 0)] + : null; $nations[] = $nation; } diff --git a/hwe/func.php b/hwe/func.php index f3ca231d..167eaf87 100644 --- a/hwe/func.php +++ b/hwe/func.php @@ -1734,7 +1734,7 @@ function deleteNation(General $lord, bool $applyDB): array $nationGeneralList = General::createObjListFromDB( $db->queryFirstColumn( - 'SELECT `no` FROM general WHERE nation=%i AND no != %i', + 'SELECT `no` FROM general WHERE nation=%i AND no != %i ORDER BY no ASC', $nationID, $lordID ), diff --git a/hwe/process_war.php b/hwe/process_war.php index 68490380..b411afa6 100644 --- a/hwe/process_war.php +++ b/hwe/process_war.php @@ -37,7 +37,7 @@ function processWar(string $warSeed, General $attackerGeneral, array $rawAttacke $city = new WarUnitCity($rng, $rawDefenderCity, $rawDefenderNation, $year, $month, $startYear); - $defenderCityGeneralIDList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND city=%i AND nation!=0', $city->getVar('nation'), $city->getVar('city')); + $defenderCityGeneralIDList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND city=%i AND nation!=0 ORDER BY no', $city->getVar('nation'), $city->getVar('city')); $defenderCityGeneralList = General::createObjListFromDB($defenderCityGeneralIDList, null); $captureBattleFixture = diff --git a/hwe/sammo/GeneralAI.php b/hwe/sammo/GeneralAI.php index b93bb27c..7ba87443 100644 --- a/hwe/sammo/GeneralAI.php +++ b/hwe/sammo/GeneralAI.php @@ -2916,22 +2916,40 @@ class GeneralAI } - protected function do출병(): ?GeneralCommand - { - if (!$this->attackable) { - return null; - } - - if ($this->dipState !== self::d전쟁) { - return null; + protected function do출병(): ?GeneralCommand + { + $traceEnabled = in_array((string)$this->general->getID(), array_filter(explode(',', (string)getenv('REF_AI_TRACE_GENERAL_IDS')), 'strlen'), true); + $trace = static function (string $stage, array $values = []) use ($traceEnabled): void { + if ($traceEnabled) { + fwrite(STDOUT, 'AI_WAR_TRACE ' . Json::encode(['engine' => 'ref', 'stage' => $stage] + $values) . "\n"); + } + }; + $trace('start', [ + 'generalId' => $this->general->getID(), 'attackable' => $this->attackable, + 'dipState' => $this->dipState, 'nationRice' => $this->nation['rice'], + 'baseRice' => GameConst::$baserice, 'train' => $this->general->getVar('train'), + 'atmos' => $this->general->getVar('atmos'), 'crew' => $this->general->getVar('crew'), + 'fullLeadership' => $this->fullLeadership, 'minWarCrew' => $this->nationPolicy->minWarCrew, + 'cityID' => $this->city['city'], 'front' => $this->city['front'], + 'warTargetNation' => $this->warTargetNation, + ]); + if (!$this->attackable) { + $trace('not-attackable'); + return null; + } + + if ($this->dipState !== self::d전쟁) { + $trace('not-at-war'); + return null; } $general = $this->getGeneralObj(); $city = $this->city; $nation = $this->nation; - if (($nation['rice'] < GameConst::$baserice) && $general->getNPCType() >= 2 && $this->rng->nextBool(0.7)) { - return null; + if (($nation['rice'] < GameConst::$baserice) && $general->getNPCType() >= 2 && $this->rng->nextBool(0.7)) { + $trace('low-nation-rice'); + return null; } $cityID = $city['city']; @@ -2939,22 +2957,27 @@ class GeneralAI $db = DB::db(); - if ($general->getVar('train') < min(100, $this->nationPolicy->properWarTrainAtmos)) { - return null; - } - if ($general->getVar('atmos') < min(100, $this->nationPolicy->properWarTrainAtmos)) { - return null; - } - if ($general->getVar('crew') < min(($this->fullLeadership - 2) * 100, $this->nationPolicy->minWarCrew)) { - return null; - } - - if ($city['front'] === 0) { - return null; - } - - if ($city['front'] === 1) { - return null; + if ($general->getVar('train') < min(100, $this->nationPolicy->properWarTrainAtmos)) { + $trace('low-train'); + return null; + } + if ($general->getVar('atmos') < min(100, $this->nationPolicy->properWarTrainAtmos)) { + $trace('low-atmos'); + return null; + } + if ($general->getVar('crew') < min(($this->fullLeadership - 2) * 100, $this->nationPolicy->minWarCrew)) { + $trace('low-crew'); + return null; + } + + if ($city['front'] === 0) { + $trace('front-zero'); + return null; + } + + if ($city['front'] === 1) { + $trace('front-one'); + return null; } $attackableNations = []; @@ -2969,11 +2992,12 @@ class GeneralAI } $nearCities = array_keys(CityConst::byID($cityID)->path); - $attackableCities = $db->queryFirstColumn( + $attackableCities = $db->queryFirstColumn( 'SELECT city, nation FROM city WHERE nation IN %li AND city IN %li', $attackableNations, $nearCities - ); + ); + $trace('destinations', ['attackableNations' => $attackableNations, 'nearCities' => $nearCities, 'attackableCities' => $attackableCities]); if (count($attackableCities) == 0) { throw new \RuntimeException('출병 불가' . $cityID . var_export($attackableNations, true) . var_export($nearCities, true));