compare: trace invader lifecycle across months
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"action": "InvaderLifecycle",
|
||||||
|
"args": [10, 150, 100, 20],
|
||||||
|
"setup": {
|
||||||
|
"resetCities": true,
|
||||||
|
"resetGenerals": true,
|
||||||
|
"deleteOtherGenerals": true,
|
||||||
|
"keepGeneralIds": [1],
|
||||||
|
"resetNations": true,
|
||||||
|
"deleteOtherNations": true,
|
||||||
|
"keepNationIds": [1],
|
||||||
|
"resetDiplomacy": true,
|
||||||
|
"resetEvents": true,
|
||||||
|
"city": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"values": {
|
||||||
|
"name": "기준도시",
|
||||||
|
"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": {
|
||||||
|
"name": "남만",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gameEnvironment": {
|
||||||
|
"isunited": 0,
|
||||||
|
"refreshLimit": 3
|
||||||
|
},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
@@ -263,6 +263,230 @@ function comparisonMonthlyDetails(array $observe, int $worldHistoryAfterId): arr
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exercise the dynamically-created invader events against one evolving database.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $request
|
||||||
|
* @param array<string, mixed> $environment
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
function comparisonInvaderLifecycleTrace(
|
||||||
|
array $request,
|
||||||
|
array $environment,
|
||||||
|
int $generalIdBeforeAction,
|
||||||
|
int $nationIdBeforeAction,
|
||||||
|
int $eventIdBeforeAction,
|
||||||
|
int $worldHistoryIdBeforeAction,
|
||||||
|
): array {
|
||||||
|
$db = DB::db();
|
||||||
|
$args = $request['args'] ?? [];
|
||||||
|
if (!is_array($args) || count($args) > 4) {
|
||||||
|
throw new \InvalidArgumentException('InvaderLifecycle args must have at most four entries');
|
||||||
|
}
|
||||||
|
$year = $environment['year'];
|
||||||
|
$month = $environment['month'];
|
||||||
|
$startYear = $environment['startyear'];
|
||||||
|
$raiseEnvironment = [
|
||||||
|
'year' => $year,
|
||||||
|
'month' => $month,
|
||||||
|
'startyear' => $startYear,
|
||||||
|
'turnterm' => $environment['turnterm'],
|
||||||
|
'turntime' => $environment['turntime'],
|
||||||
|
'show_img_level' => $environment['show_img_level'] ?? 3,
|
||||||
|
'stored_icons' => $environment['stored_icons'] ?? [],
|
||||||
|
'icon_path' => $environment['icon_path'] ?? '.',
|
||||||
|
'fiction' => $environment['fiction'] ?? [0],
|
||||||
|
];
|
||||||
|
|
||||||
|
(new \sammo\Event\Action\RaiseInvader(...$args))->run($raiseEnvironment);
|
||||||
|
|
||||||
|
$invaderNationIds = array_map(
|
||||||
|
'intval',
|
||||||
|
$db->queryFirstColumn('SELECT nation FROM nation WHERE nation > %i ORDER BY nation', $nationIdBeforeAction),
|
||||||
|
);
|
||||||
|
$existingNationId = (int)$db->queryFirstField(
|
||||||
|
'SELECT nation FROM nation WHERE nation <= %i ORDER BY nation LIMIT 1',
|
||||||
|
$nationIdBeforeAction,
|
||||||
|
);
|
||||||
|
$rulerIds = array_map(
|
||||||
|
'intval',
|
||||||
|
$db->queryFirstColumn(
|
||||||
|
'SELECT no FROM general WHERE nation IN %li AND officer_level = 12 ORDER BY nation',
|
||||||
|
$invaderNationIds,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if ($invaderNationIds === [] || $existingNationId <= 0 || count($rulerIds) !== count($invaderNationIds)) {
|
||||||
|
throw new \RuntimeException('InvaderLifecycle failed to create the expected entities');
|
||||||
|
}
|
||||||
|
|
||||||
|
$autoDeleteEvents = [];
|
||||||
|
$endingEventId = 0;
|
||||||
|
foreach ($db->query('SELECT id, action FROM event WHERE id > %i ORDER BY id', $eventIdBeforeAction) as $row) {
|
||||||
|
$action = comparisonJsonValue($row['action']);
|
||||||
|
$actionName = $action[0][0] ?? null;
|
||||||
|
if ($actionName === 'AutoDeleteInvader') {
|
||||||
|
$autoDeleteEvents[] = [
|
||||||
|
'eventId' => (int)$row['id'],
|
||||||
|
'nationId' => (int)($action[0][1] ?? 0),
|
||||||
|
];
|
||||||
|
} elseif ($actionName === 'InvaderEnding') {
|
||||||
|
$endingEventId = (int)$row['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($autoDeleteEvents) !== count($invaderNationIds) || $endingEventId <= 0) {
|
||||||
|
throw new \RuntimeException('InvaderLifecycle failed to create follow-up events');
|
||||||
|
}
|
||||||
|
|
||||||
|
$diplomacyRows = iterator_to_array($db->query(
|
||||||
|
'SELECT me, you, state, term FROM diplomacy WHERE (me IN %li AND you = %i) OR (me = %i AND you IN %li) ORDER BY me, you',
|
||||||
|
$invaderNationIds,
|
||||||
|
$existingNationId,
|
||||||
|
$existingNationId,
|
||||||
|
$invaderNationIds,
|
||||||
|
));
|
||||||
|
$diplomacyStates = array_values(array_unique(array_map(
|
||||||
|
static fn(array $row): string => "{$row['state']}:{$row['term']}",
|
||||||
|
$diplomacyRows,
|
||||||
|
)));
|
||||||
|
sort($diplomacyStates);
|
||||||
|
$generalCounts = array_map(
|
||||||
|
static fn(int $nationId): int => (int)DB::db()->queryFirstField(
|
||||||
|
'SELECT count(*) FROM general WHERE nation = %i',
|
||||||
|
$nationId,
|
||||||
|
),
|
||||||
|
$invaderNationIds,
|
||||||
|
);
|
||||||
|
$diplomacyCounts = array_map(
|
||||||
|
static fn(int $nationId): int => (int)DB::db()->queryFirstField(
|
||||||
|
'SELECT count(*) FROM diplomacy WHERE (me = %i AND you = %i) OR (me = %i AND you = %i)',
|
||||||
|
$nationId,
|
||||||
|
$existingNationId,
|
||||||
|
$existingNationId,
|
||||||
|
$nationId,
|
||||||
|
),
|
||||||
|
$invaderNationIds,
|
||||||
|
);
|
||||||
|
$gameStorage = KVStorage::getStorage($db, 'game_env');
|
||||||
|
$afterRaise = [
|
||||||
|
'createdNationCount' => count($invaderNationIds),
|
||||||
|
'createdGeneralCount' => (int)$db->queryFirstField(
|
||||||
|
'SELECT count(*) FROM general WHERE no > %i',
|
||||||
|
$generalIdBeforeAction,
|
||||||
|
),
|
||||||
|
'followUpEventCount' => (int)$db->queryFirstField(
|
||||||
|
'SELECT count(*) FROM event WHERE id > %i',
|
||||||
|
$eventIdBeforeAction,
|
||||||
|
),
|
||||||
|
'generalCountsPerNation' => array_values(array_unique($generalCounts)),
|
||||||
|
'diplomacyCountsPerNation' => array_values(array_unique($diplomacyCounts)),
|
||||||
|
'diplomacyStates' => $diplomacyStates,
|
||||||
|
'isunited' => (int)$gameStorage->isunited,
|
||||||
|
'blockChangeScout' => (bool)$gameStorage->block_change_scout,
|
||||||
|
];
|
||||||
|
|
||||||
|
$warResults = [];
|
||||||
|
foreach ($autoDeleteEvents as $autoDeleteEvent) {
|
||||||
|
$warResult = (new \sammo\Event\Action\AutoDeleteInvader($autoDeleteEvent['nationId']))->run([
|
||||||
|
'year' => $year,
|
||||||
|
'month' => $month + 1,
|
||||||
|
'startyear' => $startYear,
|
||||||
|
'currentEventID' => $autoDeleteEvent['eventId'],
|
||||||
|
]);
|
||||||
|
$warResults[] = $warResult[1] ?? null;
|
||||||
|
}
|
||||||
|
$atWar = [
|
||||||
|
'results' => array_values(array_unique($warResults)),
|
||||||
|
'autoDeleteEventCount' => (int)$db->queryFirstField(
|
||||||
|
'SELECT count(*) FROM event WHERE id IN %li',
|
||||||
|
array_column($autoDeleteEvents, 'eventId'),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
$db->update(
|
||||||
|
'diplomacy',
|
||||||
|
['state' => 2, 'term' => 0],
|
||||||
|
'me IN %li OR you IN %li',
|
||||||
|
$invaderNationIds,
|
||||||
|
$invaderNationIds,
|
||||||
|
);
|
||||||
|
$peaceResults = [];
|
||||||
|
foreach ($autoDeleteEvents as $autoDeleteEvent) {
|
||||||
|
$peaceResult = (new \sammo\Event\Action\AutoDeleteInvader($autoDeleteEvent['nationId']))->run([
|
||||||
|
'year' => $year,
|
||||||
|
'month' => $month + 2,
|
||||||
|
'startyear' => $startYear,
|
||||||
|
'currentEventID' => $autoDeleteEvent['eventId'],
|
||||||
|
]);
|
||||||
|
$peaceResults[] = $peaceResult[1] ?? null;
|
||||||
|
}
|
||||||
|
$atPeace = [
|
||||||
|
'results' => array_values(array_unique($peaceResults)),
|
||||||
|
'autoDeleteEventCount' => (int)$db->queryFirstField(
|
||||||
|
'SELECT count(*) FROM event WHERE id IN %li',
|
||||||
|
array_column($autoDeleteEvents, 'eventId'),
|
||||||
|
),
|
||||||
|
'rulerWanderTurnCount' => (int)$db->queryFirstField(
|
||||||
|
'SELECT count(*) FROM general_turn WHERE general_id IN %li AND action = %s',
|
||||||
|
$rulerIds,
|
||||||
|
'che_방랑',
|
||||||
|
),
|
||||||
|
'endingEventPresent' => (bool)$db->queryFirstField(
|
||||||
|
'SELECT count(*) FROM event WHERE id = %i',
|
||||||
|
$endingEventId,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
$invaderGeneralIds = array_map('intval', $db->queryFirstColumn(
|
||||||
|
'SELECT no FROM general WHERE nation IN %li',
|
||||||
|
$invaderNationIds,
|
||||||
|
));
|
||||||
|
if ($invaderGeneralIds !== []) {
|
||||||
|
$db->delete('general_turn', 'general_id IN %li', $invaderGeneralIds);
|
||||||
|
$db->delete('rank_data', 'general_id IN %li', $invaderGeneralIds);
|
||||||
|
$db->delete('troop', 'nation IN %li', $invaderNationIds);
|
||||||
|
$db->delete('general', 'no IN %li', $invaderGeneralIds);
|
||||||
|
}
|
||||||
|
$db->delete('nation_turn', 'nation_id IN %li', $invaderNationIds);
|
||||||
|
$db->delete('diplomacy', 'me IN %li OR you IN %li', $invaderNationIds, $invaderNationIds);
|
||||||
|
$db->delete('nation', 'nation IN %li', $invaderNationIds);
|
||||||
|
$db->update('city', ['nation' => $existingNationId], true);
|
||||||
|
|
||||||
|
$endingResult = (new \sammo\Event\Action\InvaderEnding())->run([
|
||||||
|
'year' => $year,
|
||||||
|
'month' => $month + 3,
|
||||||
|
'startyear' => $startYear,
|
||||||
|
'currentEventID' => $endingEventId,
|
||||||
|
]);
|
||||||
|
$gameStorage->resetCache();
|
||||||
|
$afterUserWin = [
|
||||||
|
'result' => $endingResult[1] ?? null,
|
||||||
|
'endingEventPresent' => (bool)$db->queryFirstField(
|
||||||
|
'SELECT count(*) FROM event WHERE id = %i',
|
||||||
|
$endingEventId,
|
||||||
|
),
|
||||||
|
'isunited' => (int)$gameStorage->isunited,
|
||||||
|
'refreshLimit' => (int)$gameStorage->refreshLimit,
|
||||||
|
'logs' => array_values($db->queryFirstColumn(
|
||||||
|
'SELECT text FROM world_history WHERE id > %i AND nation_id = 0 AND year = %i AND month = %i ORDER BY id',
|
||||||
|
$worldHistoryIdBeforeAction,
|
||||||
|
$year,
|
||||||
|
$month + 3,
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'schemaVersion' => 1,
|
||||||
|
'engine' => 'ref',
|
||||||
|
'action' => 'InvaderLifecycle',
|
||||||
|
'phases' => [
|
||||||
|
'afterRaise' => $afterRaise,
|
||||||
|
'atWar' => $atWar,
|
||||||
|
'atPeace' => $atPeace,
|
||||||
|
'afterUserWin' => $afterUserWin,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function comparisonMonthlyEventTraceMain(): void
|
function comparisonMonthlyEventTraceMain(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -284,6 +508,7 @@ function comparisonMonthlyEventTraceMain(): void
|
|||||||
'RegNeutralNPC',
|
'RegNeutralNPC',
|
||||||
'RaiseNPCNation',
|
'RaiseNPCNation',
|
||||||
'RaiseInvader',
|
'RaiseInvader',
|
||||||
|
'InvaderLifecycle',
|
||||||
'AutoDeleteInvader',
|
'AutoDeleteInvader',
|
||||||
'InvaderEnding',
|
'InvaderEnding',
|
||||||
'ChangeCity',
|
'ChangeCity',
|
||||||
@@ -623,6 +848,28 @@ function comparisonMonthlyEventTraceMain(): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($actionName === 'InvaderLifecycle') {
|
||||||
|
foreach (['turnterm', 'turntime'] as $requiredEnvironmentKey) {
|
||||||
|
if (!array_key_exists($requiredEnvironmentKey, $environment)) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
"InvaderLifecycle requires environment.{$requiredEnvironmentKey}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo json_encode(
|
||||||
|
comparisonInvaderLifecycleTrace(
|
||||||
|
$request,
|
||||||
|
$environment,
|
||||||
|
$generalIdBeforeAction,
|
||||||
|
$nationIdBeforeAction,
|
||||||
|
$eventIdBeforeAction,
|
||||||
|
$worldHistoryAfterId,
|
||||||
|
),
|
||||||
|
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
|
||||||
|
), PHP_EOL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$actionClass = "\\sammo\\Event\\Action\\{$actionName}";
|
$actionClass = "\\sammo\\Event\\Action\\{$actionName}";
|
||||||
if (in_array($actionName, ['PreUpdateMonthly', 'PostUpdateMonthly', 'MonthlyBoundary'], true)) {
|
if (in_array($actionName, ['PreUpdateMonthly', 'PostUpdateMonthly', 'MonthlyBoundary'], true)) {
|
||||||
$action = null;
|
$action = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user