From 1a17fad0a5b5271a1f45642f5bcddd95665170a2 Mon Sep 17 00:00:00 2001 From: hided62 Date: Sat, 25 Jul 2026 19:19:56 +0000 Subject: [PATCH] test: trace monthly invader lifecycle --- .../fixtures/monthly_auto_delete_invader.json | 54 ++++++ .../fixtures/monthly_invader_ending.json | 40 ++++ .../fixtures/monthly_raise_invader.json | 105 +++++++++++ hwe/compare/monthly_event_trace.php | 176 +++++++++++++++++- 4 files changed, 370 insertions(+), 5 deletions(-) create mode 100644 hwe/compare/fixtures/monthly_auto_delete_invader.json create mode 100644 hwe/compare/fixtures/monthly_invader_ending.json create mode 100644 hwe/compare/fixtures/monthly_raise_invader.json diff --git a/hwe/compare/fixtures/monthly_auto_delete_invader.json b/hwe/compare/fixtures/monthly_auto_delete_invader.json new file mode 100644 index 00000000..f4aafc6a --- /dev/null +++ b/hwe/compare/fixtures/monthly_auto_delete_invader.json @@ -0,0 +1,54 @@ +{ + "action": "AutoDeleteInvader", + "compactOutput": true, + "args": [1], + "setup": { + "resetGenerals": true, + "deleteOtherGenerals": true, + "keepGeneralIds": [1], + "resetNations": true, + "deleteOtherNations": true, + "keepNationIds": [1], + "resetDiplomacy": true, + "nation": [ + { + "id": 1, + "values": { + "name": "ⓞ남만족", + "capital": 69, + "gennum": 1, + "tech": 100, + "level": 2, + "type": "che_병가" + } + } + ], + "general": [ + { + "id": 1, + "values": { + "name": "ⓞ남만대왕", + "nation": 1, + "city": 69, + "officer_level": 12, + "npc": 9 + } + } + ], + "event": [ + { + "id": 990001, + "action": [["AutoDeleteInvader", 1]] + } + ] + }, + "environment": { + "year": 200, + "month": 1, + "startyear": 190, + "currentEventID": 990001 + }, + "observe": { + "generalIds": [1] + } +} diff --git a/hwe/compare/fixtures/monthly_invader_ending.json b/hwe/compare/fixtures/monthly_invader_ending.json new file mode 100644 index 00000000..82adbfd7 --- /dev/null +++ b/hwe/compare/fixtures/monthly_invader_ending.json @@ -0,0 +1,40 @@ +{ + "action": "InvaderEnding", + "compactOutput": true, + "setup": { + "resetNations": true, + "deleteOtherNations": true, + "keepNationIds": [1], + "setAllCitiesNation": 1, + "nation": [ + { + "id": 1, + "values": { + "name": "기준국", + "capital": 1, + "gennum": 1, + "tech": 100, + "level": 2, + "type": "che_유가" + } + } + ], + "gameEnvironment": { + "isunited": 1, + "refreshLimit": 3 + }, + "event": [ + { + "id": 990002, + "action": [["InvaderEnding"]] + } + ] + }, + "environment": { + "year": 200, + "month": 1, + "startyear": 190, + "currentEventID": 990002 + }, + "observe": {} +} diff --git a/hwe/compare/fixtures/monthly_raise_invader.json b/hwe/compare/fixtures/monthly_raise_invader.json new file mode 100644 index 00000000..239efb54 --- /dev/null +++ b/hwe/compare/fixtures/monthly_raise_invader.json @@ -0,0 +1,105 @@ +{ + "action": "RaiseInvader", + "compactOutput": true, + "args": [10, 150, 100, 20], + "setup": { + "resetCities": true, + "resetGenerals": true, + "deleteOtherGenerals": true, + "keepGeneralIds": [1], + "resetNations": true, + "deleteOtherNations": true, + "keepNationIds": [1], + "city": [ + { + "id": 1, + "values": { + "level": 3, + "nation": 1, + "pop": 1000, + "pop_max": 10000, + "agri": 2000, + "agri_max": 20000, + "comm": 3000, + "comm_max": 30000, + "secu": 4000, + "secu_max": 40000, + "def": 5000, + "def_max": 50000, + "wall": 6000, + "wall_max": 60000 + } + }, + { + "id": 48, + "values": { + "level": 4, + "nation": 0, + "pop": 1000, + "pop_max": 10000, + "agri": 2000, + "agri_max": 20000, + "comm": 3000, + "comm_max": 30000, + "secu": 4000, + "secu_max": 40000, + "def": 5000, + "def_max": 50000, + "wall": 6000, + "wall_max": 60000 + } + } + ], + "nation": [ + { + "id": 1, + "values": { + "name": "기준국", + "capital": 1, + "gennum": 1, + "tech": 100, + "level": 2, + "type": "che_유가" + } + } + ], + "general": [ + { + "id": 1, + "values": { + "name": "군주", + "nation": 1, + "city": 1, + "officer_level": 12, + "npc": 0, + "leadership": 50, + "strength": 50, + "intel": 50, + "experience": 1000, + "dedication": 1000, + "dex1": 10, + "dex2": 20, + "dex3": 30, + "dex4": 40, + "dex5": 50, + "turntime": "0200-01-01 00:05:00" + } + } + ], + "syncEnvironment": true + }, + "environment": { + "year": 200, + "month": 1, + "startyear": 190, + "turnterm": 10, + "turntime": "0200-01-01 00:00:00", + "show_img_level": 3, + "fiction": [0] + }, + "observe": { + "generalIds": [1], + "cityIds": [1, 48], + "nationIds": [1] + } +} diff --git a/hwe/compare/monthly_event_trace.php b/hwe/compare/monthly_event_trace.php index 6e19d40d..216511b2 100644 --- a/hwe/compare/monthly_event_trace.php +++ b/hwe/compare/monthly_event_trace.php @@ -42,6 +42,9 @@ function comparisonMonthlyPatch(string $table, int $id, array $values): void 'name', 'nation', 'city', 'officer_level', 'officer_city', 'crew', 'train', 'atmos', 'owner', 'npc', 'killturn', 'belong', 'gold', 'rice', 'horse', 'weapon', 'book', 'item', 'aux', + 'leadership', 'strength', 'intel', 'experience', 'dedication', + 'dex1', 'dex2', 'dex3', 'dex4', 'dex5', 'personal', 'special', + 'special2', 'bornyear', 'deadyear', 'turntime', ], ], ]; @@ -192,6 +195,9 @@ function comparisonMonthlyEventTraceMain(): void 'RegNPC', 'RegNeutralNPC', 'RaiseNPCNation', + 'RaiseInvader', + 'AutoDeleteInvader', + 'InvaderEnding', ]; if (!in_array($actionName, $supportedActions, true)) { throw new \InvalidArgumentException('unsupported monthly action'); @@ -230,12 +236,34 @@ function comparisonMonthlyEventTraceMain(): void comparisonMonthlyPatch('general', $row['id'], $row['values']); } } + if (($setup['deleteOtherGenerals'] ?? false) === true) { + $keepGeneralIds = comparisonIntegerList($setup['keepGeneralIds'] ?? [], 'keepGeneralIds'); + if (!$keepGeneralIds) { + throw new \InvalidArgumentException('deleteOtherGenerals requires keepGeneralIds'); + } + DB::db()->query('DELETE FROM general_turn WHERE general_id NOT IN %li', $keepGeneralIds); + DB::db()->query('DELETE FROM rank_data WHERE general_id NOT IN %li', $keepGeneralIds); + DB::db()->query('DELETE FROM general WHERE no NOT IN %li', $keepGeneralIds); + } if (($setup['resetNations'] ?? false) === true) { DB::db()->query('UPDATE nation SET level = 0, gennum = 0, tech = 0'); foreach ($setup['nation'] ?? [] as $row) { comparisonMonthlyPatch('nation', $row['id'], $row['values']); } } + if (($setup['deleteOtherNations'] ?? false) === true) { + $keepNationIds = comparisonIntegerList($setup['keepNationIds'] ?? [], 'keepNationIds'); + if (!$keepNationIds) { + throw new \InvalidArgumentException('deleteOtherNations requires keepNationIds'); + } + DB::db()->query( + 'DELETE FROM diplomacy WHERE me NOT IN %li OR you NOT IN %li', + $keepNationIds, + $keepNationIds, + ); + DB::db()->query('DELETE FROM nation_turn WHERE nation_id NOT IN %li', $keepNationIds); + DB::db()->query('DELETE FROM nation WHERE nation NOT IN %li', $keepNationIds); + } if (($setup['resetUniqueOccupancy'] ?? false) === true) { DB::db()->delete('ng_auction', '`type` = %s AND finished = 0', 'uniqueItem'); DB::db()->query('DELETE FROM storage WHERE namespace LIKE %s', 'ut\\_%'); @@ -246,12 +274,64 @@ function comparisonMonthlyEventTraceMain(): void foreach (comparisonIntegerList($setup['clearInheritanceOwnerIds'] ?? [], 'clearInheritanceOwnerIds') as $ownerId) { DB::db()->delete('storage', 'namespace = %s', "inheritance_{$ownerId}"); } + if (($setup['resetDiplomacy'] ?? false) === true) { + DB::db()->query('DELETE FROM diplomacy'); + } + foreach ($setup['diplomacy'] ?? [] as $row) { + if ( + !is_array($row) + || !is_int($row['fromNationId'] ?? null) + || !is_int($row['toNationId'] ?? null) + || !is_int($row['state'] ?? null) + || !is_int($row['term'] ?? null) + ) { + throw new \InvalidArgumentException('invalid setup.diplomacy row'); + } + $diplomacyPatch = [ + 'me' => $row['fromNationId'], + 'you' => $row['toNationId'], + 'state' => $row['state'], + 'term' => $row['term'], + ]; + DB::db()->insertUpdate('diplomacy', $diplomacyPatch, $diplomacyPatch); + } + foreach ($setup['event'] ?? [] as $row) { + if (!is_array($row) || !is_int($row['id'] ?? null) || !is_array($row['action'] ?? null)) { + throw new \InvalidArgumentException('invalid setup.event row'); + } + $eventPatch = [ + 'id' => $row['id'], + 'target' => $row['target'] ?? 'month', + 'priority' => $row['priority'] ?? 1000, + 'condition' => Json::encode($row['condition'] ?? true), + 'action' => Json::encode($row['action']), + ]; + DB::db()->insertUpdate('event', $eventPatch, $eventPatch); + } + $gameEnvironmentPatch = $setup['gameEnvironment'] ?? []; + if (!is_array($gameEnvironmentPatch)) { + throw new \InvalidArgumentException('setup.gameEnvironment must be an object'); + } + $gameStorage = KVStorage::getStorage(DB::db(), 'game_env'); + foreach (['isunited', 'refreshLimit', 'block_change_scout'] as $key) { + if (array_key_exists($key, $gameEnvironmentPatch)) { + $gameStorage->setValue($key, $gameEnvironmentPatch[$key]); + } + } + if (($setup['setAllCitiesNation'] ?? null) !== null) { + if (!is_int($setup['setAllCitiesNation'])) { + throw new \InvalidArgumentException('setup.setAllCitiesNation must be an integer'); + } + DB::db()->update('city', ['nation' => $setup['setAllCitiesNation']], true); + } $db = DB::db(); $generalIdBeforeAction = (int)($db->queryFirstField('SELECT COALESCE(MAX(no), 0) FROM general') ?? 0); $nationIdBeforeAction = (int)($db->queryFirstField('SELECT COALESCE(MAX(nation), 0) FROM nation') ?? 0); + $eventIdBeforeAction = + (int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM event') ?? 0); $snapshotRequest = ['observe' => $request['observe'] ?? []]; $snapshotRequest['observe']['logAfterId'] = (int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM general_record') ?? 0); @@ -290,7 +370,7 @@ function comparisonMonthlyEventTraceMain(): void throw new \InvalidArgumentException('ProcessSemiAnnual requires resource'); } $action = new $actionClass($resource); - } elseif (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC'], true)) { + } elseif (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC', 'RaiseInvader', 'AutoDeleteInvader'], true)) { $args = $request['args'] ?? []; if (!is_array($args)) { throw new \InvalidArgumentException("{$actionName} args must be an array"); @@ -298,6 +378,9 @@ function comparisonMonthlyEventTraceMain(): void if ($actionName === 'CreateManyNPC' && count($args) > 2) { throw new \InvalidArgumentException('CreateManyNPC args must have at most two entries'); } + if ($actionName === 'RaiseInvader' && count($args) > 4) { + throw new \InvalidArgumentException('RaiseInvader args must have at most four entries'); + } $action = new $actionClass(...$args); } else { $action = new $actionClass(); @@ -307,6 +390,12 @@ function comparisonMonthlyEventTraceMain(): void 'month' => $month, 'startyear' => $startYear, ]; + if (isset($environment['currentEventID'])) { + if (!is_int($environment['currentEventID'])) { + throw new \InvalidArgumentException('currentEventID must be an integer'); + } + $actionEnvironment['currentEventID'] = $environment['currentEventID']; + } if ($actionName === 'UpdateNationLevel') { $killturn = $environment['killturn'] ?? null; $turnterm = $environment['turnterm'] ?? null; @@ -316,7 +405,7 @@ function comparisonMonthlyEventTraceMain(): void $actionEnvironment['killturn'] = $killturn; $actionEnvironment['turnterm'] = $turnterm; } - if (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC', 'RaiseNPCNation'], true)) { + if (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC', 'RaiseNPCNation', 'RaiseInvader'], true)) { $turnterm = $environment['turnterm'] ?? null; $turntime = $environment['turntime'] ?? null; $showImgLevel = $environment['show_img_level'] ?? 3; @@ -348,7 +437,7 @@ function comparisonMonthlyEventTraceMain(): void $after = comparisonTurnStateSnapshot($snapshotRequest); $afterDetails = comparisonMonthlyDetails($snapshotRequest['observe'], $worldHistoryAfterId); $createdGenerals = []; - if (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC', 'RaiseNPCNation'], true)) { + if (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC', 'RaiseNPCNation', 'RaiseInvader'], true)) { foreach ($db->query('SELECT * FROM general WHERE no > %i ORDER BY no', $generalIdBeforeAction) as $row) { $generalId = (int)$row['no']; $turnRows = $db->query( @@ -388,6 +477,11 @@ function comparisonMonthlyEventTraceMain(): void 'bornYear' => 'bornyear', 'deadYear' => 'deadyear', 'picture' => 'picture', + 'dex1' => 'dex1', + 'dex2' => 'dex2', + 'dex3' => 'dex3', + 'dex4' => 'dex4', + 'dex5' => 'dex5', ], ) + [ 'turnCount' => count($turnRows), @@ -404,7 +498,7 @@ function comparisonMonthlyEventTraceMain(): void } } $createdNations = []; - if ($actionName === 'RaiseNPCNation') { + if (in_array($actionName, ['RaiseNPCNation', 'RaiseInvader'], true)) { foreach ($db->query('SELECT * FROM nation WHERE nation > %i ORDER BY nation', $nationIdBeforeAction) as $row) { $nationId = (int)$row['nation']; $createdNations[] = comparisonPickRow( @@ -454,10 +548,82 @@ function comparisonMonthlyEventTraceMain(): void if (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC'], true)) { $response['createdGenerals'] = $createdGenerals; } - if ($actionName === 'RaiseNPCNation') { + if (in_array($actionName, ['RaiseNPCNation', 'RaiseInvader'], true)) { $response['createdGenerals'] = $createdGenerals; $response['createdNations'] = $createdNations; } + if ($actionName === 'RaiseInvader') { + $response['createdEvents'] = array_map( + static fn(array $row): array => comparisonPickRow( + $row, + [ + 'id' => 'id', + 'targetCode' => 'target', + 'priority' => 'priority', + 'condition' => 'condition', + 'action' => 'action', + ], + ['condition', 'action'], + ), + iterator_to_array( + $db->query( + 'SELECT id, target, priority, `condition`, action FROM event WHERE id > %i ORDER BY id', + $eventIdBeforeAction, + ), + ), + ); + $response['createdDiplomacy'] = iterator_to_array( + $db->query( + 'SELECT me AS fromNationId, you AS toNationId, state, term FROM diplomacy WHERE me > %i OR you > %i ORDER BY me, you', + $nationIdBeforeAction, + $nationIdBeforeAction, + ), + ); + $gameStorage = KVStorage::getStorage(DB::db(), 'game_env'); + $response['gameEnvironment'] = $gameStorage->getValues([ + 'isunited', + 'turnterm', + 'block_change_scout', + 'refreshLimit', + ]); + } + if (in_array($actionName, ['AutoDeleteInvader', 'InvaderEnding'], true)) { + $gameStorage = KVStorage::getStorage(DB::db(), 'game_env'); + $response['gameEnvironment'] = $gameStorage->getValues([ + 'isunited', + 'turnterm', + 'block_change_scout', + 'refreshLimit', + ]); + } + if (($request['compactOutput'] ?? false) === true) { + $observedGeneralTurns = []; + foreach (comparisonIntegerList($request['observe']['generalIds'] ?? [], 'generalIds') as $generalId) { + $observedGeneralTurns[(string)$generalId] = iterator_to_array( + $db->query( + 'SELECT turn_idx AS turnIndex, action, arg FROM general_turn WHERE general_id = %i ORDER BY turn_idx', + $generalId, + ), + ); + } + $response = [ + 'schemaVersion' => 1, + 'engine' => 'ref', + 'action' => $actionName, + 'afterDetails' => $afterDetails, + 'createdGeneralCount' => count($createdGenerals), + 'firstCreatedGenerals' => array_slice($createdGenerals, 0, 3), + 'createdNations' => $response['createdNations'] ?? [], + 'createdEvents' => $response['createdEvents'] ?? [], + 'createdDiplomacy' => $response['createdDiplomacy'] ?? [], + 'gameEnvironment' => $response['gameEnvironment'] ?? [], + 'observedGeneralTurns' => $observedGeneralTurns, + 'remainingEventIds' => array_map( + 'intval', + $db->queryFirstColumn('SELECT id FROM event ORDER BY id'), + ), + ]; + } echo json_encode( $response, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,