Compare commits

..
Author SHA1 Message Date
Hide_D 84b0b69b67 j_install_db 초기화 코드 정리 2024-05-17 18:11:56 +00:00
Hide_D 47a4144757 사소하게 수정 2024-05-17 18:03:20 +00:00
Hide_D 625cf81f1f clearDB에 $db 전달 2024-05-17 17:59:27 +00:00
Hide_D 0b8e91efe8 설치 메시지 수정 2024-05-17 17:50:07 +00:00
Hide_D 8db7aa3528 종료 처리 2024-05-17 17:42:34 +00:00
Hide_D 726679c933 db unset 2024-05-17 17:36:39 +00:00
Hide_D ec9e7be5d8 install_db 2024-05-17 17:32:39 +00:00
Hide_D 43ee0015ea 인스톨 코드 수정 2024-05-17 17:12:58 +00:00
Hide_D a146735007 APIHelper에 MeekroDBExceptionr에러시 logError 2024-05-17 17:10:43 +00:00
Hide_D 46bf5531d9 dep 2024-05-17 17:02:25 +00:00
328 changed files with 2640 additions and 138323 deletions
-1
View File
@@ -29,7 +29,6 @@ d_pic/*.jpg
d_pic/*.gif d_pic/*.gif
d_pic/*.png d_pic/*.png
d_pic/*.webp d_pic/*.webp
d_pic/*.avif
d_pic/uploaded_image d_pic/uploaded_image
/dist_js /dist_js
+1
View File
@@ -12,6 +12,7 @@
} }
}, },
"require": { "require": {
"brandonwamboldt/utilphp": "^1.1",
"sergeytsalkov/meekrodb": "^2.5", "sergeytsalkov/meekrodb": "^2.5",
"league/plates": "^3.5", "league/plates": "^3.5",
"vlucas/valitron": "^1.4", "vlucas/valitron": "^1.4",
Generated
+52 -2
View File
@@ -4,8 +4,58 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7edae9b6be35e66ffd0d180f5fac6679", "content-hash": "deed9afdc95bc42f84784df67d295223",
"packages": [ "packages": [
{
"name": "brandonwamboldt/utilphp",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/brandonwamboldt/utilphp.git",
"reference": "36c32efc4f0679c05163464a550f45c8d83fe683"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brandonwamboldt/utilphp/zipball/36c32efc4f0679c05163464a550f45c8d83fe683",
"reference": "36c32efc4f0679c05163464a550f45c8d83fe683",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"satooshi/php-coveralls": "dev-master"
},
"type": "library",
"autoload": {
"psr-0": {
"utilphp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brandon Wamboldt",
"email": "brandon.wamboldt@gmail.com"
}
],
"description": "util.php is a collection of useful functions and snippets that you need or could use every day, designed to avoid conflicts with existing projects",
"homepage": "https://github.com/brandonwamboldt/utilphp",
"keywords": [
"collection",
"helpers",
"php",
"utility"
],
"support": {
"issues": "https://github.com/brandonwamboldt/utilphp/issues",
"source": "https://github.com/brandonwamboldt/utilphp/tree/master"
},
"time": "2015-02-02T17:56:14+00:00"
},
{ {
"name": "brick/math", "name": "brick/math",
"version": "0.12.1", "version": "0.12.1",
@@ -5671,5 +5721,5 @@
"platform-overrides": { "platform-overrides": {
"php": "8.3" "php": "8.3"
}, },
"plugin-api-version": "2.6.0" "plugin-api-version": "2.3.0"
} }
BIN
View File
Binary file not shown.
+1 -4
View File
@@ -99,10 +99,7 @@ function logError(string $err, string $errstr, string $errpath, array $trace)
$err = str_replace(ROOT, '{ROOT}', $err); $err = str_replace(ROOT, '{ROOT}', $err);
$errstr = str_replace(ROOT, '{ROOT}', $errstr); $errstr = str_replace(ROOT, '{ROOT}', $errstr);
$errpath = str_replace(ROOT, '{ROOT}', $errpath); $errpath = str_replace(ROOT, '{ROOT}', $errpath);
$trace = array_map(function (string|array $text) { $trace = array_map(function (string $text) {
if (is_array($text)) {
$text = Json::encode($text);
}
return str_replace(ROOT, '{ROOT}', $text); return str_replace(ROOT, '{ROOT}', $text);
}, $trace); }, $trace);
+1 -1
View File
@@ -56,7 +56,7 @@ $rootDB->insert('member', [
'token_valid_until' => '2999-01-01 00:00:00', 'token_valid_until' => '2999-01-01 00:00:00',
'pw' => $finalPassword, 'pw' => $finalPassword,
'salt' => $userSalt, 'salt' => $userSalt,
'grade' => 7, 'grade' => 6,
'name' => $nickname, 'name' => $nickname,
'reg_date' => $nowDate 'reg_date' => $nowDate
]); ]);
-10
View File
@@ -93,13 +93,3 @@ CREATE TABLE `login_token` (
) )
COLLATE='utf8mb4_general_ci' COLLATE='utf8mb4_general_ci'
ENGINE=Aria; ENGINE=Aria;
CREATE TABLE `banned_member` (
`no` INT NOT NULL AUTO_INCREMENT,
`hashed_email` VARCHAR(128) NOT NULL COLLATE 'utf8mb4_general_ci' COMMENT 'SHA512(salt | email | salt )',
`info` TEXT NULL COLLATE 'utf8mb4_general_ci' COMMENT '부가정보',
PRIMARY KEY (`no`),
UNIQUE INDEX `email` (`hashed_email`(128))
)
COLLATE='utf8mb4_general_ci'
ENGINE = Aria;
+1 -1
View File
@@ -6,7 +6,7 @@ include "func.php";
//로그인 검사 //로그인 검사
$session = Session::requireGameLogin()->setReadOnly(); $session = Session::requireGameLogin()->setReadOnly();
if($session->userGrade < 6) { if($session->userGrade < 5) {
die(requireAdminPermissionHTML()); die(requireAdminPermissionHTML());
} }
+1 -1
View File
@@ -6,7 +6,7 @@ include "func.php";
//로그인 검사 //로그인 검사
$session = Session::requireGameLogin()->setReadOnly(); $session = Session::requireGameLogin()->setReadOnly();
if ($session->userGrade < 6) { if ($session->userGrade < 5) {
header('location:_admin1.php'); header('location:_admin1.php');
die(); die();
} }
+1 -1
View File
@@ -7,7 +7,7 @@ include "func.php";
//로그인 검사 //로그인 검사
$session = Session::requireGameLogin()->setReadOnly(); $session = Session::requireGameLogin()->setReadOnly();
if ($session->userGrade < 6) { if ($session->userGrade < 5) {
die(requireAdminPermissionHTML()); die(requireAdminPermissionHTML());
} }
+1 -1
View File
@@ -15,7 +15,7 @@ $msg = Util::getPost('msg', 'string');
//로그인 검사 //로그인 검사
$session = Session::requireLogin()->loginGame()->setReadOnly(); $session = Session::requireLogin()->loginGame()->setReadOnly();
if ($session->userGrade < 6) { if ($session->userGrade < 5) {
header('location:_admin2.php'); header('location:_admin2.php');
} }
+1 -1
View File
@@ -20,7 +20,7 @@ if ($type2 < 0 || $type2 > 6) {
//로그인 검사 //로그인 검사
$session = Session::requireGameLogin()->setReadOnly(); $session = Session::requireGameLogin()->setReadOnly();
if ($session->userGrade < 6) { if ($session->userGrade < 5) {
die(requireAdminPermissionHTML()); die(requireAdminPermissionHTML());
} }
+1 -1
View File
@@ -12,7 +12,7 @@ $nation = Util::getPost('nation', 'int');
$session = Session::requireGameLogin()->setReadOnly(); $session = Session::requireGameLogin()->setReadOnly();
$userID = Session::getUserID(); $userID = Session::getUserID();
if ($session->userGrade < 6) { if ($session->userGrade < 5) {
header('location:_admin5.php'); header('location:_admin5.php');
die(); die();
} }
+2 -2
View File
@@ -31,13 +31,13 @@ $queryMap = [
]; ];
if ($reqQueryType === null || !key_exists($reqQueryType, $queryMap)) { if ($reqQueryType === null || !key_exists($reqQueryType, $queryMap)) {
$reqQueryType = array_key_first($queryMap); $reqQueryType = Util::array_first_key($queryMap);
} }
//로그인 검사 //로그인 검사
$session = Session::requireGameLogin()->setReadOnly(); $session = Session::requireGameLogin()->setReadOnly();
if ($session->userGrade < 6) { if ($session->userGrade < 5) {
die(requireAdminPermissionHTML()); die(requireAdminPermissionHTML());
} }
+1 -1
View File
@@ -12,7 +12,7 @@ $type = 0;
//로그인 검사 //로그인 검사
$session = Session::requireGameLogin()->setReadOnly(); $session = Session::requireGameLogin()->setReadOnly();
if ($session->userGrade < 6) { if ($session->userGrade < 5) {
die(requireAdminPermissionHTML()); die(requireAdminPermissionHTML());
} }
+1 -1
View File
@@ -7,7 +7,7 @@ include "func.php";
//로그인 검사 //로그인 검사
$session = Session::requireGameLogin()->setReadOnly(); $session = Session::requireGameLogin()->setReadOnly();
if ($session->userGrade < 6) { if ($session->userGrade < 5) {
die('권한 부족'); die('권한 부족');
} }
+1 -4
View File
@@ -243,14 +243,11 @@ $templates = new \League\Plates\Engine(__DIR__ . '/templates');
"SELECT nation,no,name,owner_name as ownerName, owner, picture, imgsvr, "SELECT nation,no,name,owner_name as ownerName, owner, picture, imgsvr,
experience, dedication, experience, dedication,
dex1, dex2, dex3, dex4, dex5, dex1, dex2, dex3, dex4, dex5,
horse, weapon, book, item, aux horse, weapon, book, item
FROM general WHERE %l", FROM general WHERE %l",
$btn == "NPC 보기" ? "npc>=2" : "npc<2" $btn == "NPC 보기" ? "npc>=2" : "npc<2"
) as $general) { ) as $general) {
$generalID = $general['no']; $generalID = $general['no'];
foreach (['dex1', 'dex2', 'dex3', 'dex4', 'dex5'] as $dexKey) {
$general[$dexKey] = CentennialAllStarGrowthService::recordableRawValue($general, $dexKey);
}
$general['bgColor'] = $nationColor[$general['nation']] ?? GameConst::$basecolor4; $general['bgColor'] = $nationColor[$general['nation']] ?? GameConst::$basecolor4;
$general['fgColor'] = newColor($general['bgColor']); $general['fgColor'] = newColor($general['bgColor']);
$general['nationName'] = $nationName[$general['nation']]; $general['nationName'] = $nationName[$general['nation']];
+2 -2
View File
@@ -177,7 +177,7 @@ $templates = new \League\Plates\Engine('templates');
$valid = 1; $valid = 1;
} }
if ($userGrade >= 6) { if ($userGrade >= 5) {
$valid = true; $valid = true;
$showDetailedInfo = true; $showDetailedInfo = true;
} }
@@ -275,7 +275,7 @@ $templates = new \League\Plates\Engine('templates');
$ourGeneral = false; $ourGeneral = false;
} }
if ($userGrade == 7) { if ($userGrade == 6) {
$ourGeneral = true; $ourGeneral = true;
} }
+3 -11
View File
@@ -136,21 +136,13 @@ $changeDefence999Atmos = $me->onCalcDomestic('changeDefenceTrain', "atmos999", $
<option value='<?= $targetDefenceTrain ?>' <?= $me->getVar('defence_train') == $targetDefenceTrain ? "selected" : ""; ?>><?= formatDefenceTrain($targetDefenceTrain) ?>(훈사<?= $targetDefenceTrain ?>)</option> <option value='<?= $targetDefenceTrain ?>' <?= $me->getVar('defence_train') == $targetDefenceTrain ? "selected" : ""; ?>><?= formatDefenceTrain($targetDefenceTrain) ?>(훈사<?= $targetDefenceTrain ?>)</option>
<?php endforeach; ?> <?php endforeach; ?>
<option value=999 <?= $me->getVar('defence_train') == 999 ? "selected" : ""; ?>><?= formatDefenceTrain(999) ?> <option value=999 <?= $me->getVar('defence_train') == 999 ? "selected" : ""; ?>><?= formatDefenceTrain(999) ?>
<?php if ($changeDefence999Train != 0 || $changeDefence999Train != 0) : <?php if($changeDefence999Train != 0 || $changeDefence999Train != 0):
?>[<?= $changeDefence999Train != 0 ? "훈련 {$changeDefence999Train}," : '' ?><?= $changeDefence999Atmos != 0 ? "사기 {$changeDefence999Atmos}" : '' ?>[<?=$changeDefence999Train!=0?"훈련 {$changeDefence999Train},":''?><?=$changeDefence999Atmos!=0?"사기 {$changeDefence999Atmos}":''
?>]<?php endif ?></option> ?>]<?php endif?></option>
</select> </select>
】<br><br> 】<br><br>
<input type=<?= $submit ?> id='set_my_setting' name=btn style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px; value=설정저장><br> <input type=<?= $submit ?> id='set_my_setting' name=btn style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px; value=설정저장><br>
∞<span style='color:orange'>설정저장은 이달중 <?= $myset ?>회 남았습니다.</span><br><br> ∞<span style='color:orange'>설정저장은 이달중 <?= $myset ?>회 남았습니다.</span><br><br>
<?php if ($me->getPenaltyList()->count()) : ?>
징계 목록(저장 시 갱신)<br>
<?php foreach ($me->getPenaltyList() as $penaltyKey => $penalty) : ?>
<span style='color:red'><?= $penaltyKey->getHelptext() ?> : <?= $penalty ?></span><br>
<?php endforeach; ?>
<br />
<?php endif; ?>
<?php if (!($gameStor->autorun_user['limit_minutes'] ?? false)) : ?> <?php if (!($gameStor->autorun_user['limit_minutes'] ?? false)) : ?>
휴 가 신 청<br> 휴 가 신 청<br>
<button type="button" id='vacation' style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>휴가 신청</button><br><br> <button type="button" id='vacation' style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>휴가 신청</button><br><br>
+57 -1
View File
@@ -125,7 +125,63 @@ $globalBetTotal = array_sum($globalBet);
</tr> </tr>
</table> </table>
<table align=center class='tb_layout bg0'> <table align=center class='tb_layout bg0'>
<?php if ($me['no'] > 0 && $me['tournament'] == 0 && $admin['tournament'] == 1) : ?> <?php if ($session->userGrade >= 5) : ?>
<form method=post action=c_tournament.php>
<tr>
<td colspan=8><input type=textarea size=150 style=color:white;background-color:black; name=msg><input type=submit name=btn value='메시지'></td>
</tr>
<tr>
<td colspan=8>
<button type="button" class="btn btn-secondary" onclick='location.reload()'>갱신</button>
<?php if ($admin['tournament'] == 0) : ?>
<select name=trig size=1 style=color:white;background-color:black;>
<option style=color:white; value=0 <?= !$admin['tnmt_trig'] ? 'selected' : '' ?>>수동진행</option>
<option style=color:white; value=1 <?= $admin['tnmt_trig'] ? 'selected' : '' ?>>자동진행</option>
</select>
<input type=submit name=btn value='자동개최설정'>
<?php else : ?>
<input type=submit name=btn value='중단' onclick='return confirm("진짜 중단하시겠습니까?")'>
<?php endif; ?>
<?php switch ($admin['tournament']) {
case 1:
echo "<input type=submit name=btn value='랜덤투입'>";
echo "<input type=submit name=btn value='랜덤전부투입'>";
break;
case 2:
echo "<input type=submit name=btn value='예선'><input type=submit name=btn value='예선전부'>";
break;
case 3:
echo "<input type=submit name=btn value='추첨'><input type=submit name=btn value='추첨전부'>";
break;
case 4:
echo "<input type=submit name=btn value='본선'><input type=submit name=btn value='본선전부'>";
break;
case 5:
echo "<input type=submit name=btn value='배정'>";
break;
case 6:
echo "<input type=submit name=btn value='베팅마감'>";
break;
case 7:
echo "<input type=submit name=btn value='16강'>";
break;
case 8:
echo "<input type=submit name=btn value='8강'>";
break;
case 9:
echo "<input type=submit name=btn value='4강'>";
break;
case 10:
echo "<input type=submit name=btn value='결승'>";
break;
} ?>
</td>
</tr>
</form>
<?php elseif ($me['no'] > 0 && $me['tournament'] == 0 && $admin['tournament'] == 1) : ?>
<form method=post action=c_tournament.php> <form method=post action=c_tournament.php>
<tr> <tr>
<td colspan=8><button type="button" class="btn btn-secondary" onclick='location.reload()'>갱신</button><button type="submit" class="btn btn-sammo-base2" onclick='return confirm("참가비 금<?= $admin['develcost'] ?>이 필요합니다. 참가하시겠습니까?")'>참가</button><input type='hidden' name='btn' value='참가'/></td> <td colspan=8><button type="button" class="btn btn-secondary" onclick='location.reload()'>갱신</button><button type="submit" class="btn btn-sammo-base2" onclick='return confirm("참가비 금<?= $admin['develcost'] ?>이 필요합니다. 참가하시겠습니까?")'>참가</button><input type='hidden' name='btn' value='참가'/></td>
-242
View File
@@ -1,242 +0,0 @@
<?php
declare(strict_types=1);
namespace sammo;
use Ds\Map;
use sammo\Enums\RankColumn;
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit;
}
chdir(dirname(__DIR__));
require_once 'lib.php';
require_once 'func.php';
final class ComparisonTracingRNG implements RNG
{
private int $sequence = 0;
/** @var array<int, array<string, mixed>> */
public array $calls = [];
public function __construct(private readonly RNG $inner)
{
}
public static function getMaxInt(): int
{
return LiteHashDRBG::getMaxInt();
}
public function nextBytes(int $bytes): string
{
$value = $this->inner->nextBytes($bytes);
$this->record('nextBytes', ['bytes' => $bytes], bin2hex($value));
return $value;
}
public function nextBits(int $bits): string
{
$value = $this->inner->nextBits($bits);
$this->record('nextBits', ['bits' => $bits], bin2hex($value));
return $value;
}
public function nextInt(?int $max = null): int
{
$value = $this->inner->nextInt($max);
$this->record('nextInt', ['maxInclusive' => $max], $value);
return $value;
}
public function nextFloat1(): float
{
$value = $this->inner->nextFloat1();
$this->record('nextFloat1', [], $value);
return $value;
}
private function record(string $operation, array $arguments, mixed $result): void
{
$this->calls[] = [
'seq' => $this->sequence++,
'operation' => $operation,
'arguments' => $arguments,
'result' => $result,
];
}
}
function comparisonExtractRankVar(array $raw): Map
{
$rankVars = new Map();
foreach ($raw as $rawKey => $rawValue) {
$key = RankColumn::tryFrom($rawKey);
if ($key !== null) {
$rankVars[$key] = $rawValue;
}
}
return $rankVars;
}
function comparisonBuildGeneral(
array $raw,
array $city,
array $nation,
int $year,
int $month,
bool $defender = false,
): General {
$aux = [];
if (array_key_exists('inheritBuff', $raw)) {
$aux['inheritBuff'] = $raw['inheritBuff'];
}
$raw['aux'] = Json::encode($aux);
$raw['owner'] = 0;
return new General(
$raw,
comparisonExtractRankVar($raw),
null,
$city,
$nation,
$year,
$month,
$defender,
);
}
function comparisonRunBattle(array $fixture): array
{
$seed = (string)($fixture['seed'] ?? 'battle-differential');
$year = (int)$fixture['year'];
$month = (int)$fixture['month'];
$startYear = (int)($fixture['startYear'] ?? 180);
$rawAttacker = $fixture['attackerGeneral'];
$rawAttackerCity = $fixture['attackerCity'];
$rawAttackerNation = $fixture['attackerNation'];
$rawDefenderCity = $fixture['defenderCity'];
$rawDefenderNation = $fixture['defenderNation'];
// 비교 fixture에 명시된 경우에만 ref runtime의 scenario action chain을
// 활성화합니다. 일반 비교 fixture의 기존 동작은 그대로 유지합니다.
$scenarioEffect = $fixture['scenarioEffect'] ?? null;
if ($scenarioEffect === '' || $scenarioEffect === 'None') {
$scenarioEffect = null;
}
if (
$scenarioEffect !== null
&& (!is_string($scenarioEffect) || getScenarioEffectClass($scenarioEffect) === null)
) {
throw new \InvalidArgumentException('scenarioEffect is invalid');
}
GameConst::$scenarioEffect = $scenarioEffect;
$tracingRng = new ComparisonTracingRNG(new LiteHashDRBG($seed));
$warRng = new RandUtil($tracingRng);
$attacker = new WarUnitGeneral(
$warRng,
comparisonBuildGeneral($rawAttacker, $rawAttackerCity, $rawAttackerNation, $year, $month),
$rawAttackerNation,
true,
);
$city = new WarUnitCity($warRng, $rawDefenderCity, $rawDefenderNation, $year, $month, $startYear);
$defenderList = [];
foreach ($fixture['defenderGenerals'] as $rawDefender) {
$defenderList[] = new WarUnitGeneral(
$warRng,
comparisonBuildGeneral($rawDefender, $rawDefenderCity, $rawDefenderNation, $year, $month, true),
$rawDefenderNation,
false,
);
}
if (count($defenderList) && extractBattleOrder($city, $attacker) > 0) {
$defenderList[] = $city;
}
usort(
$defenderList,
fn(WarUnit $lhs, WarUnit $rhs): int =>
-(extractBattleOrder($lhs, $attacker) <=> extractBattleOrder($rhs, $attacker)),
);
$iterDefender = new \ArrayIterator($defenderList);
$iterDefender->rewind();
$finishedDefenders = [];
$getNextDefender = function (?WarUnit $previous, bool $requestNext) use (
$iterDefender,
$attacker,
&$finishedDefenders,
): ?WarUnit {
if ($previous !== null) {
$finishedDefenders[] = buildWarTraceUnitSnapshot($previous);
}
if (!$requestNext || !$iterDefender->valid()) {
return null;
}
$next = $iterDefender->current();
if (extractBattleOrder($next, $attacker) <= 0) {
return null;
}
$iterDefender->next();
return $next;
};
$events = [];
$conquered = processWar_NG(
$seed,
$attacker,
$getNextDefender,
$city,
static function (array $event) use (&$events): void {
$events[] = $event;
},
);
$attackerLogs = $attacker->getLogger()->rollback();
$defenderLogs = [];
foreach ($defenderList as $defenderUnit) {
if (!($defenderUnit instanceof WarUnitGeneral)) {
continue;
}
$snapshot = buildWarTraceUnitSnapshot($defenderUnit);
$defenderLogs[(string)$snapshot['id']] = $defenderUnit->getLogger()->rollback();
}
$cityLogs = $city->getLogger()->rollback();
return [
'engine' => 'ref',
'seed' => $seed,
'conquered' => $conquered,
'attacker' => buildWarTraceUnitSnapshot($attacker),
'city' => buildWarTraceUnitSnapshot($city),
'finishedDefenders' => $finishedDefenders,
'events' => $events,
'rng' => $tracingRng->calls,
'logs' => [
'attacker' => $attackerLogs,
'defenders' => $defenderLogs,
'city' => $cityLogs,
],
];
}
$fixturePath = $argv[1] ?? null;
if ($fixturePath === null || ($fixturePath !== '-' && !is_file($fixturePath))) {
fwrite(STDERR, "usage: php compare/battle_trace.php <fixture.json|->\n");
exit(2);
}
try {
$fixtureJson = $fixturePath === '-' ? stream_get_contents(STDIN) : file_get_contents($fixturePath);
$fixture = json_decode((string)$fixtureJson, true, flags: JSON_THROW_ON_ERROR);
echo json_encode(
comparisonRunBattle($fixture),
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
), PHP_EOL;
} catch (\Throwable $throwable) {
fwrite(STDERR, $throwable::class . ': ' . $throwable->getMessage() . PHP_EOL);
exit(1);
}
@@ -1,23 +0,0 @@
{
"action": "AddGlobalBetray",
"args": [2, 1],
"setup": {
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2, 3],
"general": [
{ "id": 1, "values": { "name": "배신0", "betray": 0 } },
{ "id": 2, "values": { "name": "배신1", "betray": 1 } },
{ "id": 3, "values": { "name": "배신2", "betray": 2 } }
]
},
"environment": {
"year": 194,
"month": 1,
"startyear": 190
},
"observe": {
"generalIds": [1, 2, 3]
},
"compactOutput": true
}
@@ -1,79 +0,0 @@
{
"action": "AssignGeneralSpeciality",
"setup": {
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2, 3],
"general": [
{
"id": 1,
"values": {
"name": "내정대상",
"nation": 1,
"age": 30,
"leadership": 40,
"strength": 45,
"intel": 80,
"special": "None",
"specage": 30,
"special2": "che_신산",
"specage2": 99,
"aux": {
"prev_types_special": ["che_경작"]
}
}
},
{
"id": 2,
"values": {
"name": "전투대상",
"nation": 1,
"age": 30,
"leadership": 80,
"strength": 75,
"intel": 40,
"special": "che_인덕",
"specage": 99,
"special2": "None",
"specage2": 30,
"dex1": 200,
"dex2": 10,
"dex3": 10,
"dex4": 10,
"dex5": 10,
"aux": {
"prev_types_special2": ["che_돌격"]
}
}
},
{
"id": 3,
"values": {
"name": "계승대상",
"nation": 2,
"age": 30,
"leadership": 50,
"strength": 50,
"intel": 50,
"special": "che_경작",
"specage": 99,
"special2": "None",
"specage2": 30,
"aux": {
"inheritSpecificSpecialWar": "che_의술",
"marker": 3
}
}
}
]
},
"environment": {
"year": 200,
"month": 1,
"startyear": 190
},
"observe": {
"generalIds": [1, 2, 3]
},
"compactOutput": true
}
@@ -1,54 +0,0 @@
{
"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]
}
}
@@ -1,24 +0,0 @@
{
"action": "BlockScoutAction",
"args": [true],
"setup": {
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1, 2],
"nation": [
{ "id": 1, "values": { "name": "위", "level": 2, "scout": 0 } },
{ "id": 2, "values": { "name": "촉", "level": 2, "scout": 0 } }
],
"gameEnvironment": {
"block_change_scout": false
}
},
"environment": {
"year": 200,
"month": 1,
"startyear": 190
},
"observe": {
"nationIds": [1, 2]
}
}
@@ -1,226 +0,0 @@
{
"action": "MonthlyBoundary",
"compactOutput": true,
"environment": {
"year": 190,
"month": 12,
"startyear": 190,
"turnterm": 10,
"starttime": "2026-07-25 00:00:00",
"turntime": "2026-07-25 02:00:00"
},
"setup": {
"syncEnvironment": true,
"world": {
"hiddenSeed": "monthly-boundary-core-catalog"
},
"gameEnvironment": {
"isunited": 1,
"refreshLimit": 3,
"block_change_scout": false
},
"resetCities": true,
"deleteOtherCities": true,
"keepCityIds": [1, 2],
"city": [
{
"id": 1,
"values": {
"name": "경계도시",
"level": 1,
"nation": 1,
"pop": 10000,
"pop_max": 20000,
"agri": 1000,
"agri_max": 2000,
"comm": 1000,
"comm_max": 2000,
"secu": 1000,
"secu_max": 2000,
"trust": 50,
"trade": 100,
"def": 1000,
"def_max": 2000,
"wall": 1000,
"wall_max": 2000,
"officer_set": 1,
"state": 31,
"term": 1,
"supply": 1,
"front": 2,
"conflict": {},
"dead": 0
}
},
{
"id": 2,
"values": {
"name": "중립도시",
"level": 8,
"nation": 0,
"pop": 12000,
"pop_max": 22000,
"agri": 1200,
"agri_max": 2200,
"comm": 1200,
"comm_max": 2200,
"secu": 1200,
"secu_max": 2200,
"trust": 55,
"trade": 100,
"def": 1200,
"def_max": 2200,
"wall": 1200,
"wall_max": 2200,
"officer_set": 1,
"state": 0,
"term": 0,
"supply": 0,
"front": 0,
"conflict": {},
"dead": 0
}
}
],
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2],
"general": [
{
"id": 1,
"values": {
"name": "경계군주",
"nation": 1,
"city": 1,
"officer_level": 12,
"npc": 0,
"age": 30,
"belong": 3,
"makelimit": 2,
"gold": 1000,
"rice": 1000,
"crew": 100,
"train": 80,
"atmos": 70,
"leadership": 50,
"strength": 50,
"intel": 50,
"experience": 0,
"dedication": 0,
"dex1": 0,
"dex2": 0,
"dex3": 0,
"dex4": 0,
"dex5": 0,
"personal": "None",
"special": "None",
"specage": 0,
"special2": "None",
"specage2": 0,
"betray": 0,
"bornyear": 160,
"deadyear": 250
}
},
{
"id": 2,
"values": {
"name": "경계재야",
"nation": 0,
"city": 2,
"officer_level": 0,
"npc": 0,
"age": 25,
"belong": 4,
"makelimit": 0,
"gold": 500,
"rice": 500,
"crew": 50,
"train": 60,
"atmos": 50,
"leadership": 40,
"strength": 40,
"intel": 40,
"experience": 0,
"dedication": 0,
"dex1": 0,
"dex2": 0,
"dex3": 0,
"dex4": 0,
"dex5": 0,
"personal": "None",
"special": "None",
"specage": 0,
"special2": "None",
"specage2": 0,
"betray": 0,
"bornyear": 165,
"deadyear": 250
}
}
],
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1],
"nation": [
{
"id": 1,
"values": {
"name": "경계국",
"color": "#123456",
"capital": 1,
"gennum": 1,
"gold": 10000,
"rice": 20000,
"tech": 100,
"power": 10,
"level": 2,
"type": "che_유가",
"bill": 100,
"rate": 20,
"rate_tmp": 10,
"scout": 1,
"chief_set": 1,
"strategic_cmd_limit": 2,
"surlimit": 1,
"spy": "{}"
}
}
],
"resetDiplomacy": true,
"resetEvents": true,
"event": [
{
"id": 991001,
"target": "PRE_MONTH",
"priority": 1000,
"condition": true,
"action": [
["NoticeToHistoryLog", "aggregate-pre", 6]
]
},
{
"id": 991002,
"target": "MONTH",
"priority": 1000,
"condition": true,
"action": [
["ProcessIncome", "gold"],
["NewYear"],
["ResetOfficerLock"],
["RandomizeCityTradeRate"],
["ChangeCity", "all", {"pop": "+10", "trust": 60}],
["NoticeToHistoryLog", "aggregate-month", 6],
["DeleteEvent"]
]
}
]
},
"observe": {
"generalIds": [1, 2],
"nationIds": [1],
"cityIds": [1, 2],
"eventIds": [991001, 991002],
"accessGeneralIds": [1, 2]
}
}
@@ -1,34 +0,0 @@
{
"action": "NewYear",
"environment": {
"year": 191,
"month": 1,
"startyear": 190
},
"setup": {
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2],
"general": [
{
"id": 1,
"values": {
"nation": 1,
"age": 30,
"belong": 3
}
},
{
"id": 2,
"values": {
"nation": 0,
"age": 30,
"belong": 4
}
}
]
},
"observe": {
"generalIds": [1, 2]
}
}
@@ -1,11 +0,0 @@
{
"action": "NoticeToHistoryLog",
"args": ["<S>새해 알림</>", 6],
"environment": {
"year": 191,
"month": 1,
"startyear": 190
},
"setup": {},
"observe": {}
}
@@ -1,75 +0,0 @@
{
"action": "ProcessIncome",
"args": ["gold"],
"environment": {
"year": 190,
"month": 7,
"startyear": 190
},
"setup": {
"resetCities": true,
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1],
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1],
"general": [
{
"id": 1,
"values": {
"nation": 1,
"city": 1,
"officer_level": 5,
"npc": 0,
"gold": 1000,
"rice": 1000,
"dedication": 100
}
}
],
"nation": [
{
"id": 1,
"values": {
"name": "갑국",
"capital": 1,
"gold": 10000,
"rice": 20000,
"level": 1,
"rate_tmp": 20
}
}
],
"city": [
{
"id": 1,
"values": {
"name": "갑성",
"nation": 1,
"level": 4,
"supply": 1,
"pop": 10000,
"pop_max": 20000,
"agri": 1000,
"agri_max": 2000,
"comm": 1000,
"comm_max": 2000,
"secu": 1000,
"secu_max": 2000,
"trust": 80,
"def": 500,
"def_max": 1000,
"wall": 500,
"wall_max": 1000
}
}
]
},
"observe": {
"generalIds": [1],
"nationIds": [1],
"cityIds": [1],
"nationEnvironmentKeys": ["prev_income_gold", "prev_income_rice"]
}
}
@@ -1,33 +0,0 @@
{
"action": "ResetOfficerLock",
"environment": {
"year": 191,
"month": 1,
"startyear": 190
},
"setup": {
"resetCities": true,
"resetNations": true,
"city": [
{
"id": 1,
"values": {
"nation": 1,
"officer_set": 1
}
}
],
"nation": [
{
"id": 1,
"values": {
"chief_set": 1
}
}
]
},
"observe": {
"cityIds": [1],
"nationIds": [1]
}
}
@@ -1,9 +0,0 @@
{
"action": "CreateAdminNPC",
"environment": {
"year": 193,
"month": 12,
"startyear": 190
},
"observe": {}
}
@@ -1,15 +0,0 @@
{
"action": "CreateManyNPC",
"args": [2, 0],
"environment": {
"year": 193,
"month": 5,
"startyear": 190,
"turnterm": 10,
"turntime": "0193-05-01 00:00:00"
},
"setup": {
"resetGenerals": true
},
"observe": {}
}
@@ -1,69 +0,0 @@
{
"action": "FinishNationBetting",
"args": [1],
"setup": {
"clearBetting": true,
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1, 2],
"nation": [
{ "id": 1, "values": { "name": "위", "gennum": 1, "power": 100, "level": 2 } },
{ "id": 2, "values": { "name": "촉", "gennum": 1, "power": 300, "level": 0 } }
],
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2],
"general": [
{ "id": 1, "values": { "name": "장수1", "nation": 1, "owner": 1, "npc": 0 } },
{ "id": 2, "values": { "name": "장수2", "nation": 2, "owner": 2, "npc": 0 } }
],
"bettingInfo": [
{
"id": 1,
"type": "bettingNation",
"name": "천통국 예상",
"finished": false,
"selectCnt": 1,
"isExclusive": null,
"reqInheritancePoint": true,
"openYearMonth": 2400,
"closeYearMonth": 2424,
"candidates": [
{
"title": "위",
"info": "국력: 100<br>장수 수: 1<br>도시 수: 1",
"isHtml": true,
"aux": { "nation": 1 }
},
{
"title": "촉",
"info": "국력: 300<br>장수 수: 1<br>도시 수: 1",
"isHtml": true,
"aux": { "nation": 2 }
}
],
"winner": null
}
],
"bet": [
{ "betting_id": 1, "general_id": 0, "user_id": null, "betting_type": "[-1]", "amount": 100 },
{ "betting_id": 1, "general_id": 1, "user_id": 1, "betting_type": "[0]", "amount": 100 },
{ "betting_id": 1, "general_id": 2, "user_id": 2, "betting_type": "[1]", "amount": 100 }
],
"inheritancePrevious": {
"1": 900,
"2": 900
},
"syncEnvironment": true
},
"environment": {
"year": 202,
"month": 1,
"startyear": 190
},
"observe": {
"generalIds": [1, 2],
"nationIds": [1, 2],
"ownerIds": [1, 2]
}
}
@@ -1,40 +0,0 @@
{
"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": {}
}
@@ -1,108 +0,0 @@
{
"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": {}
}
@@ -1,42 +0,0 @@
{
"action": "LostUniqueItem",
"args": [0.2],
"environment": {
"year": 210,
"month": 1,
"startyear": 180
},
"setup": {
"syncEnvironment": true,
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2],
"general": [
{
"id": 1,
"values": {
"name": "갑",
"npc": 0,
"horse": "che_명마_12_옥란백용구",
"weapon": "che_무기_15_의천검",
"book": "che_서적_01_효경전",
"item": "che_저지_삼황내문"
}
},
{
"id": 2,
"values": {
"name": "을",
"npc": 1,
"horse": "che_명마_12_사륜거",
"weapon": "che_무기_01_단도",
"book": "None",
"item": "che_부적_태현청생부"
}
}
]
},
"observe": {
"generalIds": [1, 2]
}
}
@@ -1,74 +0,0 @@
{
"action": "MergeInheritPointRank",
"environment": {
"year": 210,
"month": 1,
"startyear": 180
},
"setup": {
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1],
"clearInheritanceOwnerIds": [100],
"gameEnvironment": {
"isunited": 0
},
"general": [
{
"id": 1,
"values": {
"name": "갑",
"nation": 1,
"owner": 100,
"npc": 0,
"belong": 3,
"dex1": 1000,
"dex2": 0,
"dex3": 0,
"dex4": 0,
"dex5": 0
}
}
],
"inheritancePoints": [
{"ownerId": 100, "key": "previous", "value": 999},
{"ownerId": 100, "key": "lived_month", "value": 2},
{"ownerId": 100, "key": "max_domestic_critical", "value": 4},
{"ownerId": 100, "key": "active_action", "value": 15},
{"ownerId": 100, "key": "unifier", "value": 6},
{"ownerId": 100, "key": "tournament", "value": 7}
],
"rankData": [
{"generalId": 1, "type": "warnum", "value": 2},
{"generalId": 1, "type": "firenum", "value": 1},
{"generalId": 1, "type": "betwin", "value": 2},
{"generalId": 1, "type": "betgold", "value": 4000},
{"generalId": 1, "type": "betwingold", "value": 2000},
{"generalId": 1, "type": "inherit_earned_dyn", "value": 999},
{"generalId": 1, "type": "inherit_earned_act", "value": 11},
{"generalId": 1, "type": "inherit_earned", "value": 999},
{"generalId": 1, "type": "inherit_spent_dyn", "value": 9},
{"generalId": 1, "type": "inherit_spent", "value": 999}
]
},
"observe": {
"generalIds": [1],
"ownerIds": [100],
"inheritanceKeys": [
"previous",
"lived_month",
"max_domestic_critical",
"active_action",
"unifier",
"tournament"
],
"rankGeneralIds": [1],
"rankTypes": [
"inherit_earned",
"inherit_earned_act",
"inherit_earned_dyn",
"inherit_spent",
"inherit_spent_dyn"
]
}
}
@@ -1,75 +0,0 @@
{
"action": "NationBettingLifecycle",
"args": [1, 100],
"bettorGeneralIds": [1, 2],
"bettorOwnerIds": [1, 2],
"setup": {
"clearBetting": true,
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1, 2],
"nation": [
{
"id": 1,
"values": {
"name": "위",
"color": "#111111",
"capital": 1,
"gennum": 1,
"gold": 1000,
"rice": 2000,
"tech": 100,
"power": 100,
"level": 2,
"type": "che_유가"
}
},
{
"id": 2,
"values": {
"name": "촉",
"color": "#222222",
"capital": 2,
"gennum": 1,
"gold": 3000,
"rice": 4000,
"tech": 200,
"power": 300,
"level": 2,
"type": "che_병가"
}
}
],
"resetCities": true,
"deleteOtherCities": true,
"keepCityIds": [1, 2],
"city": [
{ "id": 1, "values": { "name": "허창", "nation": 1, "level": 3 } },
{ "id": 2, "values": { "name": "성도", "nation": 2, "level": 3 } }
],
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2],
"general": [
{ "id": 1, "values": { "name": "장수1", "nation": 1, "city": 1, "owner": 1, "npc": 0 } },
{ "id": 2, "values": { "name": "장수2", "nation": 2, "city": 2, "owner": 2, "npc": 0 } }
],
"inheritancePrevious": {
"1": 1000,
"2": 1000
},
"rankData": [
{ "generalId": 1, "type": "inherit_spent_dyn", "value": 0 },
{ "generalId": 1, "type": "inherit_earned_act", "value": 0 },
{ "generalId": 2, "type": "inherit_spent_dyn", "value": 0 },
{ "generalId": 2, "type": "inherit_earned_act", "value": 0 }
],
"syncEnvironment": true
},
"environment": {
"year": 200,
"month": 1,
"startyear": 190
},
"observe": {}
}
@@ -1,77 +0,0 @@
{
"action": "NpcNationLifecycle",
"setup": {
"resetCities": true,
"resetGenerals": true,
"resetNations": true,
"city": [
{
"id": 1,
"values": {
"name": "점유도시",
"level": 5,
"nation": 1,
"pop": 10001,
"pop_max": 50001,
"agri": 1001,
"agri_max": 5001,
"comm": 2001,
"comm_max": 6001,
"secu": 3001,
"secu_max": 7001,
"def": 4001,
"def_max": 8001,
"wall": 5001,
"wall_max": 9001
}
},
{
"id": 48,
"values": {
"name": "건국도시",
"level": 5,
"nation": 0,
"pop": 10048,
"pop_max": 50048,
"agri": 1048,
"agri_max": 5048,
"comm": 2048,
"comm_max": 6048,
"secu": 3048,
"secu_max": 7048,
"def": 4048,
"def_max": 8048,
"wall": 5048,
"wall_max": 9048
}
}
],
"nation": [
{
"id": 1,
"values": {
"name": "기준국",
"capital": 1,
"gennum": 0,
"tech": 120,
"level": 2,
"type": "che_유가"
}
}
],
"gameEnvironment": {
"lastNPCTroopLeaderID": 40
},
"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": {}
}
@@ -1,65 +0,0 @@
{
"action": "OpenNationBetting",
"args": [1, 500],
"setup": {
"clearBetting": true,
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1, 2],
"nation": [
{
"id": 1,
"values": {
"name": "위",
"color": "#111111",
"capital": 1,
"gennum": 1,
"gold": 1000,
"rice": 2000,
"tech": 100,
"power": 100,
"level": 2,
"type": "che_유가"
}
},
{
"id": 2,
"values": {
"name": "촉",
"color": "#222222",
"capital": 2,
"gennum": 1,
"gold": 3000,
"rice": 4000,
"tech": 200,
"power": 300,
"level": 2,
"type": "che_병가"
}
}
],
"resetCities": true,
"city": [
{ "id": 1, "values": { "nation": 1, "level": 3 } },
{ "id": 2, "values": { "nation": 2, "level": 3 } }
],
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2],
"general": [
{ "id": 1, "values": { "name": "장수1", "nation": 1, "city": 1, "owner": 1, "npc": 0 } },
{ "id": 2, "values": { "name": "장수2", "nation": 2, "city": 2, "owner": 2, "npc": 0 } }
],
"syncEnvironment": true
},
"environment": {
"year": 200,
"month": 1,
"startyear": 190
},
"observe": {
"generalIds": [1, 2],
"cityIds": [1, 2],
"nationIds": [1, 2]
}
}
@@ -1,178 +0,0 @@
{
"action": "PostUpdateMonthly",
"environment": {
"year": 193,
"month": 2,
"startyear": 193,
"seedYear": 193,
"seedMonth": 1
},
"setup": {
"world": {
"hiddenSeed": "monthly-post-diplomacy-fixture"
},
"syncEnvironment": true,
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2, 3, 4],
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1, 2, 3, 4],
"general": [
{
"id": 1,
"values": {
"name": "갑장",
"nation": 1,
"npc": 2,
"crew": 0,
"gold": 0,
"rice": 0,
"leadership": 10,
"strength": 10,
"intel": 10,
"experience": 0,
"dedication": 0
}
},
{
"id": 2,
"values": {
"name": "을장",
"nation": 2,
"npc": 2,
"crew": 0,
"gold": 0,
"rice": 0,
"leadership": 10,
"strength": 10,
"intel": 10,
"experience": 0,
"dedication": 0
}
},
{
"id": 3,
"values": {
"name": "병장",
"nation": 3,
"npc": 2,
"crew": 0,
"gold": 0,
"rice": 0,
"leadership": 10,
"strength": 10,
"intel": 10,
"experience": 0,
"dedication": 0
}
},
{
"id": 4,
"values": {
"name": "정장",
"nation": 4,
"npc": 2,
"crew": 0,
"gold": 0,
"rice": 0,
"leadership": 10,
"strength": 10,
"intel": 10,
"experience": 0,
"dedication": 0
}
}
],
"nation": [
{
"id": 1,
"values": {
"name": "갑국",
"gennum": 2,
"level": 1
}
},
{
"id": 2,
"values": {
"name": "을국",
"gennum": 1,
"level": 1
}
},
{
"id": 3,
"values": {
"name": "병국",
"gennum": 1,
"level": 1
}
},
{
"id": 4,
"values": {
"name": "정국",
"gennum": 1,
"level": 1
}
}
],
"resetDiplomacy": true,
"diplomacy": [
{
"fromNationId": 1,
"toNationId": 2,
"state": 1,
"term": 1,
"dead": 777
},
{
"fromNationId": 2,
"toNationId": 1,
"state": 1,
"term": 1,
"dead": 888
},
{
"fromNationId": 1,
"toNationId": 3,
"state": 0,
"term": 5,
"dead": 250
},
{
"fromNationId": 3,
"toNationId": 1,
"state": 0,
"term": 5,
"dead": 50
},
{
"fromNationId": 3,
"toNationId": 4,
"state": 0,
"term": 1,
"dead": 0
},
{
"fromNationId": 4,
"toNationId": 3,
"state": 0,
"term": 1,
"dead": 0
},
{
"fromNationId": 2,
"toNationId": 4,
"state": 7,
"term": 1,
"dead": 999
}
]
},
"observe": {
"generalIds": [1, 2, 3, 4],
"nationIds": [1, 2, 3, 4]
}
}
@@ -1,162 +0,0 @@
{
"action": "PostUpdateMonthly",
"environment": {
"year": 193,
"month": 2,
"startyear": 193,
"seedYear": 193,
"seedMonth": 1
},
"setup": {
"world": {
"hiddenSeed": "monthly-post-nation-stats-fixture"
},
"syncEnvironment": true,
"resetCities": true,
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2],
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1, 2],
"resetDiplomacy": true,
"general": [
{
"id": 1,
"values": {
"nation": 1,
"npc": 0,
"gold": 1000,
"rice": 2000,
"crew": 100,
"leadership": 50,
"strength": 40,
"intel": 30,
"experience": 100,
"dedication": 200,
"dex1": 1000,
"dex2": 0,
"dex3": 0,
"dex4": 0,
"dex5": 0
}
},
{
"id": 2,
"values": {
"nation": 2,
"npc": 2,
"gold": 3000,
"rice": 4000,
"crew": 200,
"leadership": 60,
"strength": 50,
"intel": 40,
"experience": 300,
"dedication": 400,
"dex1": 2000,
"dex2": 1000,
"dex3": 0,
"dex4": 0,
"dex5": 0
}
}
],
"rankData": [
{"generalId": 1, "type": "killcrew_person", "value": 100},
{"generalId": 1, "type": "deathcrew_person", "value": 0},
{"generalId": 2, "type": "killcrew_person", "value": 0},
{"generalId": 2, "type": "deathcrew_person", "value": 100}
],
"nation": [
{
"id": 1,
"values": {
"name": "갑국",
"gold": 10000,
"rice": 20000,
"tech": 100,
"level": 2,
"gennum": 9,
"power": 7
}
},
{
"id": 2,
"values": {
"name": "을국",
"gold": 2000,
"rice": 3000,
"tech": 50,
"level": 1,
"gennum": 8,
"power": 8
}
}
],
"city": [
{
"id": 1,
"values": {
"name": "갑성",
"nation": 1,
"level": 4,
"supply": 1,
"pop": 1000,
"pop_max": 2000,
"agri": 100,
"agri_max": 200,
"comm": 100,
"comm_max": 200,
"secu": 100,
"secu_max": 200,
"def": 100,
"def_max": 200,
"wall": 100,
"wall_max": 200
}
},
{
"id": 2,
"values": {
"name": "을성",
"nation": 2,
"level": 4,
"supply": 1,
"pop": 2000,
"pop_max": 4000,
"agri": 200,
"agri_max": 400,
"comm": 200,
"comm_max": 400,
"secu": 200,
"secu_max": 400,
"def": 200,
"def_max": 400,
"wall": 200,
"wall_max": 400
}
}
],
"nationEnvironment": [
{
"nationId": 1,
"key": "max_power",
"value": {
"maxPower": 999,
"maxCrew": 50,
"maxCities": ["옛도시", "옛도시2"]
}
},
{"nationId": 1, "key": "available_war_setting_cnt", "value": 1}
]
},
"observe": {
"generalIds": [1, 2],
"nationIds": [1, 2],
"cityIds": [1, 2],
"rankGeneralIds": [1, 2],
"rankTypes": ["killcrew_person", "deathcrew_person"],
"nationEnvironmentKeys": ["max_power", "available_war_setting_cnt"]
}
}
@@ -1,128 +0,0 @@
{
"action": "PostUpdateMonthly",
"environment": {
"year": 193,
"month": 2,
"startyear": 193,
"seedYear": 193,
"seedMonth": 1
},
"setup": {
"world": {
"hiddenSeed": "monthly-post-tail-2"
},
"syncEnvironment": true,
"clearNeutralAuctions": true,
"resetCities": true,
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2],
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1, 2],
"resetDiplomacy": true,
"gameEnvironment": {
"tournament": 0,
"tnmt_trig": true,
"tnmt_pattern": [0, 1, 2, 3],
"prev_winner": null
},
"general": [
{
"id": 1,
"values": {
"name": "갑장",
"nation": 1,
"city": 1,
"officer_level": 12,
"npc": 0,
"gold": 5000,
"rice": 7000,
"crew": 100,
"leadership": 50,
"strength": 40,
"intel": 30,
"experience": 100,
"dedication": 200
}
},
{
"id": 2,
"values": {
"name": "을장",
"nation": 2,
"city": 2,
"officer_level": 12,
"npc": 0,
"gold": 6000,
"rice": 8000,
"crew": 200,
"leadership": 60,
"strength": 50,
"intel": 40,
"experience": 300,
"dedication": 400
}
}
],
"nation": [
{
"id": 1,
"values": {
"name": "갑국",
"capital": 1,
"gold": 10000,
"rice": 20000,
"tech": 100,
"level": 2,
"gennum": 1,
"power": 7
}
},
{
"id": 2,
"values": {
"name": "을국",
"capital": 2,
"gold": 2000,
"rice": 3000,
"tech": 50,
"level": 1,
"gennum": 1,
"power": 8
}
}
],
"city": [
{
"id": 1,
"values": {
"name": "갑성",
"nation": 1,
"level": 4,
"supply": 1,
"front": 3,
"pop": 1000,
"pop_max": 2000
}
},
{
"id": 2,
"values": {
"name": "을성",
"nation": 2,
"level": 4,
"supply": 1,
"front": 3,
"pop": 2000,
"pop_max": 4000
}
}
]
},
"observe": {
"generalIds": [1, 2],
"nationIds": [1, 2],
"cityIds": [1, 2]
}
}
@@ -1,143 +0,0 @@
{
"action": "PostUpdateMonthly",
"environment": {
"year": 195,
"month": 2,
"startyear": 193,
"seedYear": 195,
"seedMonth": 1
},
"setup": {
"world": {
"hiddenSeed": "monthly-post-wander-fixture"
},
"syncEnvironment": true,
"resetCities": true,
"city": [
{
"id": 1,
"values": {
"name": "방랑성",
"nation": 4,
"level": 4,
"front": 1
}
},
{
"id": 2,
"values": {
"name": "존속성",
"nation": 1,
"level": 4,
"front": 1
}
}
],
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2, 3],
"general": [
{
"id": 1,
"values": {
"name": "방랑주",
"nation": 4,
"city": 1,
"officer_level": 12,
"npc": 2,
"gold": 2000,
"rice": 3000,
"crew": 100,
"makelimit": 0,
"belong": 7,
"leadership": 50,
"strength": 40,
"intel": 30,
"experience": 100,
"dedication": 200
}
},
{
"id": 2,
"values": {
"name": "방랑객",
"nation": 4,
"city": 1,
"officer_level": 1,
"npc": 0,
"gold": 1500,
"rice": 4000,
"crew": 200,
"makelimit": 0,
"belong": 5,
"leadership": 60,
"strength": 50,
"intel": 40,
"experience": 300,
"dedication": 400
}
},
{
"id": 3,
"values": {
"name": "존속장",
"nation": 1,
"city": 2,
"officer_level": 12,
"npc": 2,
"gold": 500,
"rice": 500,
"crew": 50,
"leadership": 50,
"strength": 50,
"intel": 50,
"experience": 0,
"dedication": 0
}
}
],
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1, 4],
"nation": [
{
"id": 1,
"values": {
"name": "존속국",
"gennum": 1,
"level": 1
}
},
{
"id": 4,
"values": {
"name": "방랑국",
"gennum": 2,
"level": 0
}
}
],
"resetDiplomacy": true,
"diplomacy": [
{
"fromNationId": 1,
"toNationId": 4,
"state": 2,
"term": 0,
"dead": 0
},
{
"fromNationId": 4,
"toNationId": 1,
"state": 2,
"term": 0,
"dead": 0
}
]
},
"observe": {
"generalIds": [1, 2, 3],
"nationIds": [1, 4],
"cityIds": [1, 2]
}
}
@@ -1,49 +0,0 @@
{
"action": "PreUpdateMonthly",
"environment": {
"year": 200,
"month": 12,
"startyear": 190
},
"setup": {
"syncEnvironment": true,
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1, 2],
"general": [
{"id": 1, "values": {"makelimit": 2}},
{"id": 2, "values": {"makelimit": 0}}
],
"generalAccessLog": [
{"generalId": 1, "refreshScoreTotal": 101},
{"generalId": 2, "refreshScoreTotal": 1}
],
"nation": [
{
"id": 1,
"values": {
"rate": 35,
"rate_tmp": 10,
"strategic_cmd_limit": 2,
"surlimit": 1,
"spy": "{\"1\":1,\"2\":2}"
}
}
],
"city": [
{"id": 1, "values": {"state": 31, "term": 1, "conflict": {"1": 10}}},
{"id": 2, "values": {"state": 32, "term": 2, "conflict": {"1": 20}}},
{"id": 3, "values": {"state": 33, "term": 0, "conflict": {"1": 30}}},
{"id": 4, "values": {"state": 34, "term": 3, "conflict": {"1": 40}}},
{"id": 5, "values": {"state": 41, "term": 1, "conflict": {"1": 50}}},
{"id": 6, "values": {"state": 42, "term": 2, "conflict": {"1": 60}}},
{"id": 7, "values": {"state": 43, "term": 3, "conflict": {"1": 70}}}
]
},
"observe": {
"generalIds": [1, 2],
"nationIds": [1],
"cityIds": [1, 2, 3, 4, 5, 6, 7],
"accessGeneralIds": [1, 2]
}
}
@@ -1,76 +0,0 @@
{
"action": "ProcessIncome",
"args": ["gold"],
"environment": {
"year": 190,
"month": 7,
"startyear": 190
},
"setup": {
"resetCities": true,
"resetGenerals": true,
"deleteOtherGenerals": true,
"keepGeneralIds": [1],
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1],
"general": [
{
"id": 1,
"values": {
"nation": 1,
"city": 1,
"officer_level": 5,
"npc": 0,
"gold": 1000,
"rice": 1000,
"dedication": 100
}
}
],
"nation": [
{
"id": 1,
"values": {
"name": "갑국",
"capital": 1,
"gold": 10000,
"rice": 20000,
"level": 1,
"rate": 35,
"rate_tmp": 10
}
}
],
"city": [
{
"id": 1,
"values": {
"name": "갑성",
"nation": 1,
"level": 4,
"supply": 1,
"pop": 10000,
"pop_max": 20000,
"agri": 1000,
"agri_max": 2000,
"comm": 1000,
"comm_max": 2000,
"secu": 1000,
"secu_max": 2000,
"trust": 80,
"def": 500,
"def_max": 1000,
"wall": 500,
"wall_max": 1000
}
}
]
},
"observe": {
"generalIds": [1],
"nationIds": [1],
"cityIds": [1],
"nationEnvironmentKeys": ["prev_income_gold", "prev_income_rice"]
}
}
@@ -1,106 +0,0 @@
{
"action": "ProcessSemiAnnual",
"resource": "gold",
"environment": {
"year": 193,
"month": 1,
"startyear": 190
},
"setup": {
"resetCities": true,
"resetGenerals": true,
"nation": [
{
"id": 1,
"values": {
"name": "반기검증국",
"gold": 100001,
"rice": 7777,
"rate_tmp": 20,
"type": "che_도가"
}
}
],
"city": [
{
"id": 1,
"values": {
"nation": 1,
"supply": 1,
"pop": 10000,
"pop_max": 50000,
"agri": 1001,
"agri_max": 5000,
"comm": 1001,
"comm_max": 5000,
"secu": 1001,
"secu_max": 2000,
"trust": 55,
"def": 1001,
"def_max": 5000,
"wall": 1001,
"wall_max": 5000,
"dead": 123
}
},
{
"id": 2,
"values": {
"nation": 1,
"supply": 0,
"pop": 10000,
"pop_max": 50000,
"agri": 1001,
"agri_max": 5000,
"comm": 1001,
"comm_max": 5000,
"secu": 1001,
"secu_max": 2000,
"trust": 55,
"def": 1001,
"def_max": 5000,
"wall": 1001,
"wall_max": 5000,
"dead": 123
}
},
{
"id": 3,
"values": {
"nation": 0,
"supply": 1,
"pop": 10000,
"pop_max": 50000,
"agri": 1001,
"agri_max": 5000,
"comm": 1001,
"comm_max": 5000,
"secu": 1001,
"secu_max": 2000,
"trust": 55,
"def": 1001,
"def_max": 5000,
"wall": 1001,
"wall_max": 5000,
"dead": 123
}
}
],
"general": [
{
"id": 1,
"values": {
"nation": 1,
"city": 1,
"gold": 10001,
"rice": 8888
}
}
]
},
"observe": {
"generalIds": [1],
"cityIds": [1, 2, 3],
"nationIds": [1]
}
}
@@ -1,65 +0,0 @@
{
"action": "ProcessSemiAnnual",
"resource": "gold",
"environment": {
"year": 193,
"month": 1,
"startyear": 190
},
"setup": {
"resetCities": true,
"resetGenerals": true,
"nation": [
{
"id": 1,
"values": {
"name": "반기검증국",
"gold": 100001,
"rice": 7777,
"rate": 50,
"rate_tmp": 20,
"type": "che_중립"
}
}
],
"city": [
{
"id": 1,
"values": {
"nation": 1,
"supply": 1,
"pop": 10000,
"pop_max": 50000,
"agri": 1001,
"agri_max": 5000,
"comm": 1001,
"comm_max": 5000,
"secu": 1001,
"secu_max": 2000,
"trust": 55,
"def": 1001,
"def_max": 5000,
"wall": 1001,
"wall_max": 5000,
"dead": 123
}
}
],
"general": [
{
"id": 1,
"values": {
"nation": 1,
"city": 1,
"gold": 10001,
"rice": 8888
}
}
]
},
"observe": {
"generalIds": [1],
"cityIds": [1],
"nationIds": [1]
}
}
@@ -1,96 +0,0 @@
{
"action": "ProcessWarIncome",
"environment": {
"year": 193,
"month": 1,
"startyear": 190
},
"setup": {
"resetCities": true,
"nation": [
{
"id": 1,
"values": {
"name": "전쟁수입국",
"gold": 1000,
"level": 1,
"type": "che_중립"
}
},
{
"id": 2,
"values": {
"name": "비활성국",
"gold": 2000,
"level": 0,
"type": "che_중립"
}
},
{
"id": 3,
"values": {
"name": "법가국",
"gold": 3000,
"level": 1,
"type": "che_법가"
}
}
],
"city": [
{
"id": 1,
"values": {
"nation": 1,
"supply": 1,
"pop": 1000,
"pop_max": 2000,
"dead": 101
}
},
{
"id": 2,
"values": {
"nation": 1,
"supply": 0,
"pop": 1000,
"pop_max": 2000,
"dead": 999
}
},
{
"id": 3,
"values": {
"nation": 2,
"supply": 1,
"pop": 1000,
"pop_max": 2000,
"dead": 1000
}
},
{
"id": 4,
"values": {
"nation": 3,
"supply": 1,
"pop": 1000,
"pop_max": 2000,
"dead": 105
}
},
{
"id": 5,
"values": {
"nation": 0,
"supply": 1,
"pop": 999,
"pop_max": 1000,
"dead": 10
}
}
]
},
"observe": {
"cityIds": [1, 2, 3, 4, 5],
"nationIds": [1, 2, 3]
}
}
@@ -1,105 +0,0 @@
{
"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]
}
}
@@ -1,77 +0,0 @@
{
"action": "RaiseNPCNation",
"setup": {
"resetCities": true,
"resetGenerals": true,
"resetNations": true,
"city": [
{
"id": 1,
"values": {
"name": "점유도시",
"level": 5,
"nation": 1,
"pop": 10001,
"pop_max": 50001,
"agri": 1001,
"agri_max": 5001,
"comm": 2001,
"comm_max": 6001,
"secu": 3001,
"secu_max": 7001,
"def": 4001,
"def_max": 8001,
"wall": 5001,
"wall_max": 9001
}
},
{
"id": 48,
"values": {
"name": "건국도시",
"level": 5,
"nation": 0,
"pop": 10048,
"pop_max": 50048,
"agri": 1048,
"agri_max": 5048,
"comm": 2048,
"comm_max": 6048,
"secu": 3048,
"secu_max": 7048,
"def": 4048,
"def_max": 8048,
"wall": 5048,
"wall_max": 9048
}
}
],
"nation": [
{
"id": 1,
"values": {
"name": "기준국",
"capital": 1,
"gennum": 1,
"tech": 120,
"level": 2,
"type": "che_유가"
}
}
],
"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": {
"cityIds": [1, 48],
"nationIds": [1]
}
}
@@ -1,35 +0,0 @@
{
"action": "RegNeutralNPC",
"args": [
0,
"등록재야",
null,
0,
33,
45,
55,
65,
180,
245,
null,
"무쌍",
""
],
"setup": {
"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": [],
"cityIds": [33],
"nationIds": [1]
}
}
-36
View File
@@ -1,36 +0,0 @@
{
"action": "RegNPC",
"args": [
0,
"등록장수",
null,
0,
33,
60,
50,
40,
7,
186,
240,
null,
"인덕",
"등록 대사"
],
"setup": {
"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": [],
"cityIds": [33],
"nationIds": [1]
}
}
@@ -1,24 +0,0 @@
{
"action": "UnblockScoutAction",
"args": [false],
"setup": {
"resetNations": true,
"deleteOtherNations": true,
"keepNationIds": [1, 2],
"nation": [
{ "id": 1, "values": { "name": "위", "level": 2, "scout": 1 } },
{ "id": 2, "values": { "name": "촉", "level": 2, "scout": 1 } }
],
"gameEnvironment": {
"block_change_scout": true
}
},
"environment": {
"year": 200,
"month": 1,
"startyear": 190
},
"observe": {
"nationIds": [1, 2]
}
}
@@ -1,140 +0,0 @@
{
"action": "UpdateCitySupply",
"environment": {
"year": 193,
"month": 1,
"startyear": 190
},
"setup": {
"nation": [
{
"id": 99,
"values": {
"name": "보급검증국",
"color": "#777777",
"capital": 1,
"level": 1,
"type": "che_중립"
}
},
{
"id": 98,
"values": {
"name": "불일치수도국",
"color": "#888888",
"capital": 2,
"level": 1,
"type": "che_중립"
}
}
],
"city": [
{
"id": 1,
"values": {
"nation": 99,
"supply": 0,
"front": 2,
"pop": 1001,
"agri": 501,
"comm": 499,
"secu": 99,
"trust": 50,
"def": 101,
"wall": 50,
"officer_set": 7,
"term": 2,
"conflict": {"98": 3}
}
},
{
"id": 9,
"values": {
"nation": 99,
"supply": 0,
"front": 2,
"pop": 1001,
"agri": 501,
"comm": 499,
"secu": 99,
"trust": 50,
"def": 101,
"wall": 50,
"officer_set": 7,
"term": 2,
"conflict": {"98": 3}
}
},
{
"id": 2,
"values": {
"nation": 99,
"supply": 1,
"front": 2,
"pop": 1001,
"agri": 501,
"comm": 499,
"secu": 99,
"trust": 33,
"def": 101,
"wall": 50,
"officer_set": 7,
"term": 2,
"conflict": {"98": 3}
}
},
{
"id": 3,
"values": {
"nation": 0,
"supply": 0,
"front": 0,
"trust": 50
}
}
],
"general": [
{
"id": 1,
"values": {
"nation": 99,
"city": 2,
"officer_level": 4,
"officer_city": 2,
"crew": 101,
"train": 51,
"atmos": 99
}
},
{
"id": 2,
"values": {
"nation": 99,
"city": 1,
"officer_level": 4,
"officer_city": 2,
"crew": 101,
"train": 51,
"atmos": 99
}
},
{
"id": 3,
"values": {
"nation": 98,
"city": 2,
"officer_level": 3,
"officer_city": 1,
"crew": 101,
"train": 51,
"atmos": 99
}
}
]
},
"observe": {
"generalIds": [1, 2, 3],
"cityIds": [1, 2, 3, 9],
"nationIds": [98, 99]
}
}
@@ -1,85 +0,0 @@
{
"action": "UpdateNationLevel",
"environment": {
"year": 193,
"month": 2,
"startyear": 190,
"killturn": 1000,
"turnterm": 10
},
"setup": {
"resetCities": true,
"resetGenerals": true,
"resetUniqueOccupancy": true,
"syncEnvironment": true,
"clearNationTurnIds": [1],
"clearInheritanceOwnerIds": [990061],
"nation": [
{
"id": 1,
"values": {
"name": "작위검증국",
"color": "#777777",
"capital": 1,
"gold": 10000,
"rice": 20000,
"level": 0,
"type": "che_중립",
"aux": {}
}
}
],
"city": [
{
"id": 1,
"values": {
"nation": 1,
"level": 4
}
},
{
"id": 2,
"values": {
"nation": 1,
"level": 4
}
}
],
"general": [
{
"id": 1,
"values": {
"name": "조조",
"nation": 1,
"city": 1,
"officer_level": 12,
"owner": 990061,
"npc": 0,
"killturn": 1000,
"belong": 10,
"horse": "None",
"weapon": "None",
"book": "None",
"item": "None",
"aux": {}
}
},
{
"id": 2,
"values": {
"nation": 0,
"city": 2,
"officer_level": 1,
"npc": 0,
"killturn": 0
}
}
]
},
"observe": {
"generalIds": [1],
"cityIds": [1, 2],
"nationIds": [1],
"ownerIds": [990061]
}
}
-40
View File
@@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
namespace sammo;
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit;
}
chdir(dirname(__DIR__));
require_once 'lib.php';
require_once 'func.php';
$input = stream_get_contents(STDIN);
$keys = Json::decode($input);
if (!is_array($keys)) {
fwrite(STDERR, "Expected a JSON array of item keys.\n");
exit(2);
}
$result = [];
foreach ($keys as $key) {
if (!is_string($key)) {
continue;
}
$item = buildItemClass($key);
$result[$key] = [
'rawName' => $item->getRawName(),
'name' => $item->getName(),
'info' => $item->getInfo(),
'cost' => $item->getCost(),
'buyable' => $item->isBuyable(),
'consumable' => $item->isConsumable(),
'reqSecu' => $item->getReqSecu(),
];
}
echo Json::encode($result);
-1579
View File
@@ -1,1579 +0,0 @@
<?php
declare(strict_types=1);
namespace sammo;
if (PHP_SAPI !== 'cli' || getenv('TURN_DIFFERENTIAL_ENABLED') !== '1') {
http_response_code(404);
exit;
}
chdir(dirname(__DIR__));
$_SERVER['REMOTE_ADDR'] ??= '127.0.0.1';
error_reporting(E_ALL & ~E_DEPRECATED);
require_once 'lib.php';
require_once 'func.php';
require_once __DIR__ . '/turn_state_snapshot.php';
/** @param array<string, mixed> $values */
function comparisonMonthlyPatch(string $table, int $id, array $values): void
{
$definitions = [
'city' => [
'idColumn' => 'city',
'allowed' => [
'name', 'level', 'nation', 'supply', 'front', 'pop', 'pop_max',
'agri', 'agri_max', 'comm', 'comm_max', 'secu', 'secu_max',
'trust', 'trade', 'def', 'def_max', 'wall', 'wall_max',
'officer_set', 'state', 'region', 'term', 'conflict', 'dead',
],
],
'nation' => [
'idColumn' => 'nation',
'allowed' => [
'name', 'color', 'capital', 'gennum', 'gold', 'rice', 'tech',
'power', 'level', 'type', 'bill', 'rate_tmp', 'scout', 'chief_set', 'aux',
'rate', 'strategic_cmd_limit', 'surlimit', 'spy',
],
],
'general' => [
'idColumn' => 'no',
'allowed' => [
'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',
'injury',
'dex1', 'dex2', 'dex3', 'dex4', 'dex5', 'personal', 'special',
'specage', 'special2', 'specage2', 'betray', 'age',
'bornyear', 'deadyear', 'turntime', 'makelimit',
],
],
];
$definition = $definitions[$table] ?? null;
if ($definition === null) {
throw new \InvalidArgumentException("unsupported setup table: {$table}");
}
$patch = [];
foreach ($values as $key => $value) {
if (!is_string($key) || !in_array($key, $definition['allowed'], true)) {
throw new \InvalidArgumentException("unsupported {$table} setup field");
}
if (in_array($key, ['conflict', 'aux'], true) && is_array($value)) {
$value = json_encode($value, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
}
$patch[$key] = $value;
}
if ($table === 'nation') {
$patch += [
'nation' => $id,
'name' => "fixture-nation-{$id}",
'color' => '#777777',
'capital' => 0,
'gold' => 0,
'rice' => 0,
'level' => 1,
'type' => 'che_중립',
'aux' => '{}',
];
DB::db()->insertUpdate('nation', $patch, $patch);
} elseif ($patch !== []) {
DB::db()->update($table, $patch, "`{$definition['idColumn']}` = %i", $id);
}
}
/** @param array<string, mixed> $observe */
function comparisonMonthlyDetails(array $observe, int $worldHistoryAfterId): array
{
$db = DB::db();
$generals = array_map(
static fn(array $row): array => comparisonPickRow(
$row,
[
'id' => 'no',
'nationId' => 'nation',
'cityId' => 'city',
'officerLevel' => 'officer_level',
'officerCityId' => 'officer_city',
'crew' => 'crew',
'train' => 'train',
'atmos' => 'atmos',
'gold' => 'gold',
'rice' => 'rice',
'injury' => 'injury',
'leadership' => 'leadership',
'strength' => 'strength',
'intelligence' => 'intel',
'age' => 'age',
'specialDomestic' => 'special',
'specAge' => 'specage',
'specialWar' => 'special2',
'specAge2' => 'specage2',
'betray' => 'betray',
'makeLimit' => 'makelimit',
'horse' => 'horse',
'weapon' => 'weapon',
'book' => 'book',
'item' => 'item',
'dex1' => 'dex1',
'dex2' => 'dex2',
'dex3' => 'dex3',
'dex4' => 'dex4',
'dex5' => 'dex5',
'aux' => 'aux',
],
['aux'],
),
comparisonRowsById('general', 'no', comparisonIntegerList($observe['generalIds'] ?? [], 'generalIds')),
);
$cities = array_map(
static fn(array $row): array => comparisonPickRow(
$row,
[
'id' => 'city',
'nationId' => 'nation',
'state' => 'state',
'supplyState' => 'supply',
'frontState' => 'front',
'officerSet' => 'officer_set',
'term' => 'term',
'dead' => 'dead',
'population' => 'pop',
'populationMax' => 'pop_max',
'agriculture' => 'agri',
'agricultureMax' => 'agri_max',
'commerce' => 'comm',
'commerceMax' => 'comm_max',
'security' => 'secu',
'securityMax' => 'secu_max',
'trust' => 'trust',
'trade' => 'trade',
'defence' => 'def',
'defenceMax' => 'def_max',
'wall' => 'wall',
'wallMax' => 'wall_max',
'conflict' => 'conflict',
],
['conflict'],
),
comparisonRowsById('city', 'city', comparisonIntegerList($observe['cityIds'] ?? [], 'cityIds')),
);
$nations = array_map(
static fn(array $row): array => comparisonPickRow(
$row,
[
'id' => 'nation',
'gold' => 'gold',
'rice' => 'rice',
'rate' => 'rate_tmp',
'baseRate' => 'rate',
'power' => 'power',
'generalCount' => 'gennum',
'tech' => 'tech',
'typeCode' => 'type',
'scout' => 'scout',
'strategicCommandLimit' => 'strategic_cmd_limit',
'surrenderLimit' => 'surlimit',
'spy' => 'spy',
'chiefSet' => 'chief_set',
],
['spy'],
),
comparisonRowsById('nation', 'nation', comparisonIntegerList($observe['nationIds'] ?? [], 'nationIds')),
);
$worldHistory = array_map(
static fn(array $row): array => comparisonPickRow(
$row,
[
'id' => 'id',
'nationId' => 'nation_id',
'year' => 'year',
'month' => 'month',
'text' => 'text',
],
),
$db->query(
'SELECT id, nation_id, year, month, text FROM world_history WHERE id > %i ORDER BY id',
$worldHistoryAfterId,
),
);
$inheritanceKeys = $observe['inheritanceKeys'] ?? ['unifier'];
if (!is_array($inheritanceKeys) || array_filter($inheritanceKeys, static fn($key): bool => !is_string($key))) {
throw new \InvalidArgumentException('inheritanceKeys must be an array of strings');
}
$inheritancePoints = [];
foreach (comparisonIntegerList($observe['ownerIds'] ?? [], 'ownerIds') as $ownerId) {
$entry = ['ownerId' => $ownerId];
foreach ($inheritanceKeys as $key) {
$value = $db->queryFirstField(
'SELECT value FROM storage WHERE namespace = %s AND `key` = %s',
"inheritance_{$ownerId}",
$key,
);
$entry[$key] = $value === null ? 0 : (comparisonJsonValue($value)[0] ?? 0);
}
$inheritancePoints[] = $entry;
}
$rankGeneralIds = comparisonIntegerList($observe['rankGeneralIds'] ?? [], 'rankGeneralIds');
$rankTypes = $observe['rankTypes'] ?? [];
if (!is_array($rankTypes) || array_filter($rankTypes, static fn($type): bool => !is_string($type))) {
throw new \InvalidArgumentException('rankTypes must be an array of strings');
}
$rankData = (!$rankGeneralIds || !$rankTypes)
? []
: iterator_to_array($db->query(
'SELECT general_id AS generalId, nation_id AS nationId, `type`, `value` FROM rank_data WHERE general_id IN %li AND `type` IN %ls ORDER BY general_id, `type`',
$rankGeneralIds,
$rankTypes,
));
$generalAccessLogs = [];
$accessGeneralIds = comparisonIntegerList($observe['accessGeneralIds'] ?? [], 'accessGeneralIds');
if ($accessGeneralIds) {
$generalAccessLogs = iterator_to_array($db->query(
'SELECT general_id AS generalId, refresh_score_total AS refreshScoreTotal FROM general_access_log WHERE general_id IN %li ORDER BY general_id',
$accessGeneralIds,
));
}
$nationEnvironment = [];
$nationEnvironmentKeys = $observe['nationEnvironmentKeys'] ?? [];
if (
!is_array($nationEnvironmentKeys)
|| array_filter($nationEnvironmentKeys, static fn($key): bool => !is_string($key))
) {
throw new \InvalidArgumentException('nationEnvironmentKeys must be an array of strings');
}
foreach (comparisonIntegerList($observe['nationIds'] ?? [], 'nationIds') as $nationId) {
$entry = ['nationId' => $nationId];
$storage = KVStorage::getStorage($db, $nationId, 'nation_env');
foreach ($nationEnvironmentKeys as $key) {
$entry[$key] = $storage->getValue($key);
}
$nationEnvironment[] = $entry;
}
return [
'generals' => $generals,
'cities' => $cities,
'nations' => $nations,
'worldHistory' => $worldHistory,
'inheritancePoints' => $inheritancePoints,
'rankData' => $rankData,
'generalAccessLogs' => $generalAccessLogs,
'nationEnvironment' => $nationEnvironment,
];
}
/**
* 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,
],
];
}
/**
* Exercise NPC nation creation and the same-month troop leader follow-up.
*
* @param array<string, mixed> $environment
* @return array<string, mixed>
*/
function comparisonNpcNationLifecycleTrace(
array $environment,
int $generalIdBeforeAction,
int $nationIdBeforeAction,
int $worldHistoryIdBeforeAction,
): array {
$db = DB::db();
$actionEnvironment = [
'year' => $environment['year'],
'month' => $environment['month'],
'startyear' => $environment['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],
];
$gameStorage = KVStorage::getStorage($db, 'game_env');
$leaderCounterBefore = (int)($gameStorage->lastNPCTroopLeaderID ?? 0);
(new \sammo\Event\Action\RaiseNPCNation())->run($actionEnvironment);
$createdNationIds = array_map(
'intval',
$db->queryFirstColumn('SELECT nation FROM nation WHERE nation > %i ORDER BY nation', $nationIdBeforeAction),
);
if ($createdNationIds === []) {
throw new \RuntimeException('NpcNationLifecycle failed to create a nation');
}
$createdNationGeneralCounts = array_map(
static fn(int $nationId): int => (int)DB::db()->queryFirstField(
'SELECT count(*) FROM general WHERE nation = %i',
$nationId,
),
$createdNationIds,
);
$afterRaise = [
'createdNationCount' => count($createdNationIds),
'createdGeneralCount' => (int)$db->queryFirstField(
'SELECT count(*) FROM general WHERE no > %i',
$generalIdBeforeAction,
),
'generalCountsPerCreatedNation' => array_values(array_unique($createdNationGeneralCounts)),
'historyLogs' => array_values($db->queryFirstColumn(
'SELECT text FROM world_history WHERE id > %i AND nation_id = 0 ORDER BY id',
$worldHistoryIdBeforeAction,
)),
];
(new \sammo\Event\Action\ProvideNPCTroopLeader())->run($actionEnvironment);
$leaderIds = array_map(
'intval',
$db->queryFirstColumn('SELECT no FROM general WHERE no > %i AND npc = 5 ORDER BY no', $generalIdBeforeAction),
);
if ($leaderIds === []) {
throw new \RuntimeException('NpcNationLifecycle failed to create troop leaders');
}
$leaderNationCounts = array_map(
static fn(int $nationId): int => (int)DB::db()->queryFirstField(
'SELECT count(*) FROM general WHERE nation = %i AND npc = 5',
$nationId,
),
array_map(
'intval',
$db->queryFirstColumn('SELECT nation FROM nation WHERE level > 0 ORDER BY nation'),
),
);
$gatherTurnCounts = array_map(
static fn(int $generalId): int => (int)DB::db()->queryFirstField(
'SELECT count(*) FROM general_turn WHERE general_id = %i AND action = %s',
$generalId,
'che_집합',
),
$leaderIds,
);
$gameStorage->resetCache();
$afterProvide = [
'createdLeaderCount' => count($leaderIds),
'leaderCountsPerActiveNation' => array_values(array_unique($leaderNationCounts)),
'troopCount' => (int)$db->queryFirstField(
'SELECT count(*) FROM troop WHERE troop_leader IN %li',
$leaderIds,
),
'gatherTurnCounts' => array_values(array_unique($gatherTurnCounts)),
'leaderCounterDelta' => (int)$gameStorage->lastNPCTroopLeaderID - $leaderCounterBefore,
];
return [
'schemaVersion' => 1,
'engine' => 'ref',
'action' => 'NpcNationLifecycle',
'phases' => [
'afterRaise' => $afterRaise,
'afterProvide' => $afterProvide,
],
];
}
/**
* Exercise nation betting from monthly open through real user bets and settlement.
*
* @param array<string, mixed> $request
* @param array<string, mixed> $environment
* @return array<string, mixed>
*/
function comparisonNationBettingLifecycleTrace(
array $request,
array $environment,
int $eventIdBeforeAction,
int $worldHistoryIdBeforeAction,
): array {
$db = DB::db();
$args = $request['args'] ?? [];
if (!is_array($args) || count($args) > 2) {
throw new \InvalidArgumentException('NationBettingLifecycle args must have at most two entries');
}
$bettorGeneralIds = comparisonIntegerList(
$request['bettorGeneralIds'] ?? [],
'bettorGeneralIds',
);
$bettorOwnerIds = comparisonIntegerList(
$request['bettorOwnerIds'] ?? [],
'bettorOwnerIds',
);
if (count($bettorGeneralIds) !== 2 || count($bettorOwnerIds) !== 2) {
throw new \InvalidArgumentException(
'NationBettingLifecycle requires exactly two bettorGeneralIds and bettorOwnerIds',
);
}
$year = $environment['year'];
$month = $environment['month'];
$startYear = $environment['startyear'];
(new \sammo\Event\Action\OpenNationBetting(...$args))->run([
'year' => $year,
'month' => $month,
'startyear' => $startYear,
]);
$gameStorage = KVStorage::getStorage($db, 'game_env');
$gameStorage->resetCache();
$bettingId = (int)$gameStorage->last_betting_id;
$betting = new Betting($bettingId);
$bettingInfo = $betting->getInfo();
$candidateNationIds = array_map(
static fn(\sammo\DTO\SelectItem $candidate): int => (int)($candidate->aux['nation'] ?? 0),
$bettingInfo->candidates,
);
if (count($candidateNationIds) !== 2) {
throw new \RuntimeException('NationBettingLifecycle requires exactly two candidates');
}
$finishEventCount = (int)$db->queryFirstField(
'SELECT count(*) FROM event WHERE id > %i AND action LIKE %s',
$eventIdBeforeAction,
"%FinishNationBetting%{$bettingId}%",
);
$afterOpen = [
'bettingId' => $bettingId,
'candidateNationIds' => $candidateNationIds,
'systemBonus' => (int)($db->queryFirstField(
'SELECT amount FROM ng_betting WHERE betting_id = %i AND general_id = 0',
$bettingId,
) ?? 0),
'finishEventCount' => $finishEventCount,
'historyLogs' => array_values($db->queryFirstColumn(
'SELECT text FROM world_history WHERE id > %i AND nation_id = 0 ORDER BY id',
$worldHistoryIdBeforeAction,
)),
];
$betting->bet($bettorGeneralIds[0], $bettorOwnerIds[0], [1], 100);
$betting->bet($bettorGeneralIds[1], $bettorOwnerIds[1], [0], 100);
$afterBets = [
'bets' => array_map(
static fn(array $row): array => [
'generalId' => (int)$row['general_id'],
'ownerId' => $row['user_id'] === null ? null : (int)$row['user_id'],
'selectionKey' => $row['betting_type'],
'amount' => (int)$row['amount'],
],
iterator_to_array($db->query(
'SELECT general_id, user_id, betting_type, amount FROM ng_betting WHERE betting_id = %i ORDER BY general_id',
$bettingId,
)),
),
'inheritancePrevious' => array_map(
static function (int $ownerId): int {
$value = KVStorage::getStorage(DB::db(), "inheritance_{$ownerId}")->getValue('previous');
return is_array($value) ? (int)($value[0] ?? 0) : 0;
},
$bettorOwnerIds,
),
'spentRank' => array_map(
static fn(int $generalId): int => (int)(DB::db()->queryFirstField(
'SELECT value FROM rank_data WHERE general_id = %i AND type = %s',
$generalId,
\sammo\Enums\RankColumn::inherit_point_spent_dynamic->value,
) ?? 0),
$bettorGeneralIds,
),
];
$db->update('nation', ['level' => 0], 'nation = %i', $candidateNationIds[1]);
$finishResult = (new \sammo\Event\Action\FinishNationBetting($bettingId))->run([
'year' => $year,
'month' => $month + 1,
'startyear' => $startYear,
]);
$settledInfo = (new Betting($bettingId))->getInfo();
$afterFinish = [
'result' => $finishResult[1] ?? null,
'finished' => $settledInfo->finished,
'winner' => $settledInfo->winner,
'inheritancePrevious' => array_map(
static function (int $ownerId): int {
$value = KVStorage::getStorage(DB::db(), "inheritance_{$ownerId}")->getValue('previous');
return is_array($value) ? (int)($value[0] ?? 0) : 0;
},
$bettorOwnerIds,
),
'earnedRank' => array_map(
static fn(int $generalId): int => (int)(DB::db()->queryFirstField(
'SELECT value FROM rank_data WHERE general_id = %i AND type = %s',
$generalId,
\sammo\Enums\RankColumn::inherit_point_earned_by_action->value,
) ?? 0),
$bettorGeneralIds,
),
'historyLogs' => array_values($db->queryFirstColumn(
'SELECT text FROM world_history WHERE id > %i AND nation_id = 0 ORDER BY id',
$worldHistoryIdBeforeAction,
)),
];
return [
'schemaVersion' => 1,
'engine' => 'ref',
'action' => 'NationBettingLifecycle',
'phases' => [
'afterOpen' => $afterOpen,
'afterBets' => $afterBets,
'afterFinish' => $afterFinish,
],
];
}
function comparisonMonthlyEventTraceMain(): void
{
try {
$input = stream_get_contents(STDIN);
$request = json_decode($input === '' ? '{}' : $input, true, flags: JSON_THROW_ON_ERROR);
if (!is_array($request)) {
throw new \InvalidArgumentException('request must be an object');
}
$actionName = $request['action'] ?? null;
$supportedActions = [
'UpdateCitySupply',
'UpdateNationLevel',
'RaiseDisaster',
'ProcessSemiAnnual',
'ProcessWarIncome',
'CreateAdminNPC',
'CreateManyNPC',
'RegNPC',
'RegNeutralNPC',
'RaiseNPCNation',
'NpcNationLifecycle',
'RaiseInvader',
'InvaderLifecycle',
'AutoDeleteInvader',
'InvaderEnding',
'ChangeCity',
'ProvideNPCTroopLeader',
'NationBettingLifecycle',
'OpenNationBetting',
'FinishNationBetting',
'BlockScoutAction',
'UnblockScoutAction',
'AssignGeneralSpeciality',
'AddGlobalBetray',
'LostUniqueItem',
'MergeInheritPointRank',
'ProcessIncome',
'NoticeToHistoryLog',
'NewYear',
'ResetOfficerLock',
'PreUpdateMonthly',
'PostUpdateMonthly',
'MonthlyBoundary',
];
if (!in_array($actionName, $supportedActions, true)) {
throw new \InvalidArgumentException('unsupported monthly action');
}
$setup = $request['setup'] ?? [];
if (!is_array($setup)) {
throw new \InvalidArgumentException('setup must be an object');
}
foreach (['city', 'nation', 'general'] as $table) {
$rows = $setup[$table] ?? [];
if (!is_array($rows)) {
throw new \InvalidArgumentException("setup.{$table} must be an array");
}
foreach ($rows as $row) {
if (!is_array($row) || !is_int($row['id'] ?? null) || !is_array($row['values'] ?? null)) {
throw new \InvalidArgumentException("invalid setup.{$table} row");
}
comparisonMonthlyPatch($table, $row['id'], $row['values']);
}
}
if (($setup['resetCities'] ?? false) === true) {
DB::db()->query('UPDATE city SET nation = 0, level = 1');
foreach ($setup['city'] ?? [] as $row) {
comparisonMonthlyPatch('city', $row['id'], $row['values']);
}
}
if (($setup['deleteOtherCities'] ?? false) === true) {
$keepCityIds = comparisonIntegerList($setup['keepCityIds'] ?? [], 'keepCityIds');
if ($keepCityIds === []) {
throw new \InvalidArgumentException('deleteOtherCities requires keepCityIds');
}
DB::db()->query('DELETE FROM city WHERE city NOT IN %li', $keepCityIds);
}
if (($setup['resetGenerals'] ?? false) === true) {
DB::db()->query(
'UPDATE general SET nation = 0, officer_level = 1, npc = 2, killturn = 0, horse = %s, weapon = %s, book = %s, item = %s',
'None',
'None',
'None',
'None',
);
foreach ($setup['general'] ?? [] as $row) {
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\\_%');
}
if (($setup['clearNeutralAuctions'] ?? false) === true) {
DB::db()->delete(
'ng_auction',
'`type` IN %ls AND host_general_id = 0',
['buyRice', 'sellRice'],
);
}
foreach (comparisonIntegerList($setup['clearNationTurnIds'] ?? [], 'clearNationTurnIds') as $nationId) {
DB::db()->delete('nation_turn', 'nation_id = %i', $nationId);
}
foreach (comparisonIntegerList($setup['clearInheritanceOwnerIds'] ?? [], 'clearInheritanceOwnerIds') as $ownerId) {
DB::db()->delete('storage', 'namespace = %s', "inheritance_{$ownerId}");
}
if (($setup['clearBetting'] ?? false) === true) {
DB::db()->query('DELETE FROM ng_betting');
DB::db()->query('DELETE FROM storage WHERE namespace = %s', 'betting');
$gameStorage = KVStorage::getStorage(DB::db(), 'game_env');
$gameStorage->setValue('last_betting_id', 0);
}
foreach ($setup['bettingInfo'] ?? [] as $rawBettingInfo) {
if (!is_array($rawBettingInfo) || !is_int($rawBettingInfo['id'] ?? null)) {
throw new \InvalidArgumentException('invalid setup.bettingInfo row');
}
KVStorage::getStorage(DB::db(), 'betting')->setValue(
"id_{$rawBettingInfo['id']}",
$rawBettingInfo,
);
}
foreach ($setup['bet'] ?? [] as $row) {
if (
!is_array($row)
|| !is_int($row['betting_id'] ?? null)
|| !is_int($row['general_id'] ?? null)
|| !is_string($row['betting_type'] ?? null)
|| !is_int($row['amount'] ?? null)
) {
throw new \InvalidArgumentException('invalid setup.bet row');
}
DB::db()->insert('ng_betting', [
'betting_id' => $row['betting_id'],
'general_id' => $row['general_id'],
'user_id' => $row['user_id'] ?? null,
'betting_type' => $row['betting_type'],
'amount' => $row['amount'],
]);
}
foreach ($setup['inheritancePrevious'] ?? [] as $ownerId => $value) {
if (!is_numeric($ownerId) || !is_int($value)) {
throw new \InvalidArgumentException('invalid setup.inheritancePrevious entry');
}
KVStorage::getStorage(DB::db(), "inheritance_{$ownerId}")->setValue('previous', [$value, null]);
}
foreach ($setup['inheritancePoints'] ?? [] as $row) {
if (
!is_array($row)
|| !is_int($row['ownerId'] ?? null)
|| !is_string($row['key'] ?? null)
|| !is_numeric($row['value'] ?? null)
) {
throw new \InvalidArgumentException('invalid setup.inheritancePoints row');
}
KVStorage::getStorage(DB::db(), "inheritance_{$row['ownerId']}")->setValue(
$row['key'],
[$row['value'], null],
);
}
foreach ($setup['rankData'] ?? [] as $row) {
if (
!is_array($row)
|| !is_int($row['generalId'] ?? null)
|| !is_string($row['type'] ?? null)
|| !is_int($row['value'] ?? null)
) {
throw new \InvalidArgumentException('invalid setup.rankData row');
}
$nationId = (int)(DB::db()->queryFirstField(
'SELECT nation FROM general WHERE no = %i',
$row['generalId'],
) ?? 0);
DB::db()->insertUpdate('rank_data', [
'general_id' => $row['generalId'],
'nation_id' => $nationId,
'type' => $row['type'],
'value' => $row['value'],
], [
'nation_id' => $nationId,
'value' => $row['value'],
]);
}
foreach ($setup['generalAccessLog'] ?? [] as $row) {
if (
!is_array($row)
|| !is_int($row['generalId'] ?? null)
|| !is_int($row['refreshScoreTotal'] ?? null)
) {
throw new \InvalidArgumentException('invalid setup.generalAccessLog row');
}
DB::db()->insertUpdate('general_access_log', [
'general_id' => $row['generalId'],
'refresh_score_total' => $row['refreshScoreTotal'],
], [
'refresh_score_total' => $row['refreshScoreTotal'],
]);
}
foreach ($setup['nationEnvironment'] ?? [] as $row) {
if (
!is_array($row)
|| !is_int($row['nationId'] ?? null)
|| !is_string($row['key'] ?? null)
|| !array_key_exists('value', $row)
) {
throw new \InvalidArgumentException('invalid setup.nationEnvironment row');
}
KVStorage::getStorage(DB::db(), $row['nationId'], 'nation_env')->setValue(
$row['key'],
$row['value'],
);
}
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'],
'dead' => $row['dead'] ?? 0,
];
DB::db()->insertUpdate('diplomacy', $diplomacyPatch, $diplomacyPatch);
}
if (($setup['resetEvents'] ?? false) === true) {
DB::db()->query('DELETE FROM event');
}
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');
$worldPatch = $setup['world'] ?? [];
if (!is_array($worldPatch)) {
throw new \InvalidArgumentException('setup.world must be an object');
}
if (array_key_exists('hiddenSeed', $worldPatch)) {
if (!is_string($worldPatch['hiddenSeed']) || $worldPatch['hiddenSeed'] === '') {
throw new \InvalidArgumentException('setup.world.hiddenSeed must be a non-empty string');
}
UniqueConst::$hiddenSeed = $worldPatch['hiddenSeed'];
}
foreach ([
'isunited',
'refreshLimit',
'block_change_scout',
'lastNPCTroopLeaderID',
'tournament',
'tnmt_trig',
'tnmt_pattern',
'prev_winner',
] 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);
$auctionIdBeforeAction =
(int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM ng_auction') ?? 0);
$snapshotRequest = ['observe' => $request['observe'] ?? []];
$snapshotRequest['observe']['logAfterId'] =
(int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM general_record') ?? 0);
$worldHistoryAfterId =
(int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM world_history') ?? 0);
$before = comparisonTurnStateSnapshot($snapshotRequest);
$beforeDetails = comparisonMonthlyDetails($snapshotRequest['observe'], $worldHistoryAfterId);
$environment = $request['environment'] ?? [];
if (!is_array($environment)) {
throw new \InvalidArgumentException('environment must be an object');
}
$year = $environment['year'] ?? null;
$month = $environment['month'] ?? null;
$startYear = $environment['startyear'] ?? null;
if (!is_int($year) || !is_int($month) || !is_int($startYear)) {
throw new \InvalidArgumentException('environment year, month, and startyear must be integers');
}
if (($setup['syncEnvironment'] ?? false) === true) {
$gameStorage = KVStorage::getStorage(DB::db(), 'game_env');
$environmentValues = [
'year' => $year,
'month' => $month,
'startyear' => $startYear,
'killturn' => $environment['killturn'] ?? $gameStorage->killturn,
'turnterm' => $environment['turnterm'] ?? $gameStorage->turnterm,
];
if (isset($environment['starttime'])) {
if (!is_string($environment['starttime']) || $environment['starttime'] === '') {
throw new \InvalidArgumentException('environment.starttime must be a non-empty string');
}
$environmentValues['starttime'] = $environment['starttime'];
}
foreach ($environmentValues as $key => $value) {
$gameStorage->setValue($key, $value);
}
if ($actionName === 'MonthlyBoundary') {
$before = comparisonTurnStateSnapshot($snapshotRequest);
$beforeDetails = comparisonMonthlyDetails($snapshotRequest['observe'], $worldHistoryAfterId);
}
}
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;
}
if ($actionName === 'NpcNationLifecycle') {
foreach (['turnterm', 'turntime'] as $requiredEnvironmentKey) {
if (!array_key_exists($requiredEnvironmentKey, $environment)) {
throw new \InvalidArgumentException(
"NpcNationLifecycle requires environment.{$requiredEnvironmentKey}",
);
}
}
echo json_encode(
comparisonNpcNationLifecycleTrace(
$environment,
$generalIdBeforeAction,
$nationIdBeforeAction,
$worldHistoryAfterId,
),
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
), PHP_EOL;
return;
}
if ($actionName === 'NationBettingLifecycle') {
echo json_encode(
comparisonNationBettingLifecycleTrace(
$request,
$environment,
$eventIdBeforeAction,
$worldHistoryAfterId,
),
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
), PHP_EOL;
return;
}
$actionClass = "\\sammo\\Event\\Action\\{$actionName}";
if (in_array($actionName, ['PreUpdateMonthly', 'PostUpdateMonthly', 'MonthlyBoundary'], true)) {
$action = null;
} elseif ($actionName === 'ProcessSemiAnnual') {
$resource = $request['resource'] ?? null;
if (!is_string($resource)) {
throw new \InvalidArgumentException('ProcessSemiAnnual requires resource');
}
$action = new $actionClass($resource);
} elseif (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC', 'RaiseInvader', 'AutoDeleteInvader', 'ChangeCity', 'OpenNationBetting', 'FinishNationBetting', 'BlockScoutAction', 'UnblockScoutAction', 'AddGlobalBetray', 'LostUniqueItem', 'ProcessIncome', 'NoticeToHistoryLog'], true)) {
$args = $request['args'] ?? [];
if (!is_array($args)) {
throw new \InvalidArgumentException("{$actionName} args must be an array");
}
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');
}
if ($actionName === 'ChangeCity' && count($args) !== 2) {
throw new \InvalidArgumentException('ChangeCity args must have exactly two entries');
}
if ($actionName === 'OpenNationBetting' && count($args) > 2) {
throw new \InvalidArgumentException('OpenNationBetting args must have at most two entries');
}
if ($actionName === 'FinishNationBetting' && count($args) !== 1) {
throw new \InvalidArgumentException('FinishNationBetting args must have exactly one entry');
}
if (in_array($actionName, ['BlockScoutAction', 'UnblockScoutAction'], true) && count($args) > 1) {
throw new \InvalidArgumentException("{$actionName} args must have at most one entry");
}
if ($actionName === 'AddGlobalBetray' && count($args) > 2) {
throw new \InvalidArgumentException('AddGlobalBetray args must have at most two entries');
}
if ($actionName === 'LostUniqueItem' && count($args) > 1) {
throw new \InvalidArgumentException('LostUniqueItem args must have at most one entry');
}
if ($actionName === 'ProcessIncome' && count($args) !== 1) {
throw new \InvalidArgumentException('ProcessIncome args must have exactly one entry');
}
if ($actionName === 'NoticeToHistoryLog' && (count($args) < 1 || count($args) > 2)) {
throw new \InvalidArgumentException('NoticeToHistoryLog args must have one or two entries');
}
$action = new $actionClass(...$args);
} else {
$action = new $actionClass();
}
$actionEnvironment = [
'year' => $year,
'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;
if (!is_int($killturn) || !is_int($turnterm) || $turnterm <= 0) {
throw new \InvalidArgumentException('UpdateNationLevel requires integer killturn and positive turnterm');
}
$actionEnvironment['killturn'] = $killturn;
$actionEnvironment['turnterm'] = $turnterm;
}
if (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC', 'RaiseNPCNation', 'RaiseInvader', 'ProvideNPCTroopLeader'], true)) {
$turnterm = $environment['turnterm'] ?? null;
$turntime = $environment['turntime'] ?? null;
$showImgLevel = $environment['show_img_level'] ?? 3;
$fiction = $environment['fiction'] ?? [0];
if (
!is_int($turnterm)
|| $turnterm <= 0
|| !is_string($turntime)
|| !is_int($showImgLevel)
|| !is_array($fiction)
) {
throw new \InvalidArgumentException(
"{$actionName} requires positive turnterm, string turntime, integer show_img_level, and array fiction",
);
}
$actionEnvironment += [
'turnterm' => $turnterm,
'turntime' => $turntime,
'show_img_level' => $showImgLevel,
'stored_icons' => $environment['stored_icons'] ?? [],
'icon_path' => $environment['icon_path'] ?? '.',
'fiction' => $fiction,
];
}
if ($actionName === 'PreUpdateMonthly') {
if (!preUpdateMonthly()) {
throw new \RuntimeException('preUpdateMonthly failed');
}
} elseif ($actionName === 'PostUpdateMonthly') {
$seedYear = $environment['seedYear'] ?? null;
$seedMonth = $environment['seedMonth'] ?? null;
if (!is_int($seedYear) || !is_int($seedMonth)) {
throw new \InvalidArgumentException('PostUpdateMonthly requires seedYear and seedMonth');
}
postUpdateMonthly(new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'monthly',
$seedYear,
$seedMonth,
))));
} elseif ($actionName === 'MonthlyBoundary') {
$turntime = $environment['turntime'] ?? null;
if (!is_string($turntime) || $turntime === '') {
throw new \InvalidArgumentException('MonthlyBoundary requires turntime');
}
$gameStorage = KVStorage::getStorage($db, 'game_env');
$seedYear = (int)$gameStorage->year;
$seedMonth = (int)$gameStorage->month;
TurnExecutionHelper::runEventHandler($db, $gameStorage, Enums\EventTarget::PreMonth);
if (!preUpdateMonthly()) {
throw new \RuntimeException('preUpdateMonthly failed');
}
turnDate($turntime);
$gameStorage->resetCache();
if ((int)$gameStorage->month === 1) {
checkStatistic();
}
TurnExecutionHelper::runEventHandler($db, $gameStorage, Enums\EventTarget::Month);
postUpdateMonthly(new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'monthly',
$seedYear,
$seedMonth,
))));
} else {
$action->run($actionEnvironment);
}
unset($action);
gc_collect_cycles();
$after = comparisonTurnStateSnapshot($snapshotRequest);
$afterDetails = comparisonMonthlyDetails($snapshotRequest['observe'], $worldHistoryAfterId);
$createdGenerals = [];
if (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC', 'RaiseNPCNation', 'RaiseInvader', 'ProvideNPCTroopLeader'], true)) {
foreach ($db->query('SELECT * FROM general WHERE no > %i ORDER BY no', $generalIdBeforeAction) as $row) {
$generalId = (int)$row['no'];
$turnRows = $db->query(
'SELECT turn_idx, action, arg FROM general_turn WHERE general_id = %i ORDER BY turn_idx',
$generalId,
);
$createdGenerals[] = comparisonPickRow(
$row,
[
'id' => 'no',
'name' => 'name',
'nationId' => 'nation',
'cityId' => 'city',
'leadership' => 'leadership',
'strength' => 'strength',
'intelligence' => 'intel',
'experience' => 'experience',
'dedication' => 'dedication',
'officerLevel' => 'officer_level',
'gold' => 'gold',
'rice' => 'rice',
'crew' => 'crew',
'crewTypeId' => 'crewtype',
'train' => 'train',
'atmos' => 'atmos',
'turnTime' => 'turntime',
'killturn' => 'killturn',
'age' => 'age',
'npcState' => 'npc',
'npcOriginalState' => 'npc_org',
'affinity' => 'affinity',
'personality' => 'personal',
'specialDomestic' => 'special',
'specialWar' => 'special2',
'specAge' => 'specage',
'specAge2' => 'specage2',
'bornYear' => 'bornyear',
'deadYear' => 'deadyear',
'picture' => 'picture',
'dex1' => 'dex1',
'dex2' => 'dex2',
'dex3' => 'dex3',
'dex4' => 'dex4',
'dex5' => 'dex5',
],
) + [
'turnCount' => count($turnRows),
'turnActions' => array_values(array_unique(array_column($turnRows, 'action'))),
'rankCount' => (int)$db->queryFirstField(
'SELECT count(*) FROM rank_data WHERE general_id = %i',
$generalId,
),
'nonZeroRankCount' => (int)$db->queryFirstField(
'SELECT count(*) FROM rank_data WHERE general_id = %i AND value != 0',
$generalId,
),
'troop' => $db->queryFirstRow(
'SELECT troop_leader AS id, nation AS nationId, name FROM troop WHERE troop_leader = %i',
$generalId,
) ?: null,
];
}
}
$createdNations = [];
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(
$row,
[
'id' => 'nation',
'name' => 'name',
'color' => 'color',
'capitalCityId' => 'capital',
'generalCount' => 'gennum',
'gold' => 'gold',
'rice' => 'rice',
'tech' => 'tech',
'level' => 'level',
'typeCode' => 'type',
'bill' => 'bill',
'rate' => 'rate',
'scout' => 'scout',
'war' => 'war',
'strategicCommandLimit' => 'strategic_cmd_limit',
'surrenderLimit' => 'surlimit',
'aux' => 'aux',
],
['aux'],
) + [
'nationTurnCount' => (int)$db->queryFirstField(
'SELECT count(*) FROM nation_turn WHERE nation_id = %i',
$nationId,
),
'diplomacyCount' => (int)$db->queryFirstField(
'SELECT count(*) FROM diplomacy WHERE me = %i OR you = %i',
$nationId,
$nationId,
),
];
}
}
$response = [
'schemaVersion' => 1,
'engine' => 'ref',
'action' => $actionName,
'before' => $before,
'beforeDetails' => $beforeDetails,
'after' => $after,
'afterDetails' => $afterDetails,
];
if (in_array($actionName, ['CreateManyNPC', 'RegNPC', 'RegNeutralNPC'], true)) {
$response['createdGenerals'] = $createdGenerals;
}
if (in_array($actionName, ['RaiseNPCNation', 'RaiseInvader'], true)) {
$response['createdGenerals'] = $createdGenerals;
$response['createdNations'] = $createdNations;
}
if ($actionName === 'ProvideNPCTroopLeader') {
$response['createdGenerals'] = $createdGenerals;
$response['gameEnvironment'] = KVStorage::getStorage(DB::db(), 'game_env')->getValues([
'lastNPCTroopLeaderID',
]);
}
if ($actionName === 'PreUpdateMonthly') {
$response['gameEnvironment'] = KVStorage::getStorage(DB::db(), 'game_env')->getValues([
'develcost',
]);
}
if ($actionName === 'PostUpdateMonthly') {
$response['gameEnvironment'] = KVStorage::getStorage(DB::db(), 'game_env')->getValues([
'tournament',
'tnmt_trig',
'tnmt_pattern',
'tnmt_type',
'tnmt_auto',
'phase',
]);
$response['createdNeutralAuctions'] = array_map(
static fn(array $row): array => [
'id' => (int)$row['id'],
'type' => $row['type'],
'target' => $row['target'],
'detail' => Json::decode($row['detail']),
],
iterator_to_array(
$db->query(
'SELECT id, `type`, target, detail FROM ng_auction WHERE id > %i AND host_general_id = 0 ORDER BY id',
$auctionIdBeforeAction,
),
),
);
}
if (in_array($actionName, ['OpenNationBetting', 'FinishNationBetting'], true)) {
$bettingStorage = KVStorage::getStorage(DB::db(), 'betting');
$response['betting'] = $bettingStorage->getAll();
$response['bets'] = iterator_to_array(
$db->query(
'SELECT betting_id AS bettingId, general_id AS generalId, user_id AS userId, betting_type AS selectionKey, amount FROM ng_betting ORDER BY id',
),
);
$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['gameEnvironment'] = KVStorage::getStorage(DB::db(), 'game_env')->getValues([
'last_betting_id',
]);
$response['inheritancePrevious'] = [];
foreach (comparisonIntegerList($request['observe']['ownerIds'] ?? [], 'ownerIds') as $ownerId) {
$value = KVStorage::getStorage(DB::db(), "inheritance_{$ownerId}")->getValue('previous');
$response['inheritancePrevious'][(string)$ownerId] = is_array($value) ? ($value[0] ?? 0) : 0;
}
}
if (in_array($actionName, ['BlockScoutAction', 'UnblockScoutAction'], true)) {
$response['gameEnvironment'] = KVStorage::getStorage(DB::db(), 'game_env')->getValues([
'block_change_scout',
]);
}
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'] ?? [],
'logs' => $after['logs'],
'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,
), PHP_EOL;
} catch (\Throwable $throwable) {
fwrite(STDERR, $throwable::class . ': ' . $throwable->getMessage() . PHP_EOL);
exit(1);
}
}
comparisonMonthlyEventTraceMain();
-40
View File
@@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
namespace sammo;
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit;
}
chdir(dirname(__DIR__));
require_once 'lib.php';
require_once 'func.php';
$groups = [
'nation' => ['ActionNationType', 'ActionNationType'],
'domestic' => ['ActionSpecialDomestic', 'ActionSpecialDomestic'],
'war' => ['ActionSpecialWar', 'ActionSpecialWar'],
'personality' => ['ActionPersonality', 'ActionPersonality'],
];
$result = [];
foreach ($groups as $kind => [$directory, $namespace]) {
foreach (glob(__DIR__ . "/../sammo/{$directory}/*.php") ?: [] as $path) {
$key = basename($path, '.php');
if ($key === 'None' || str_starts_with($key, 'che_event_') || $key === 'che_거상') {
continue;
}
$className = "\\sammo\\{$namespace}\\{$key}";
$trait = new $className();
$result[$kind][$key] = [
'name' => $trait->getName(),
'info' => $trait->getInfo(),
];
}
ksort($result[$kind]);
}
echo Json::encode($result);
-804
View File
@@ -1,804 +0,0 @@
<?php
declare(strict_types=1);
namespace sammo;
use sammo\Enums\RankColumn;
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit;
}
chdir(dirname(__DIR__));
$_SERVER['REMOTE_ADDR'] ??= '127.0.0.1';
error_reporting(E_ALL & ~E_DEPRECATED);
require_once 'lib.php';
require_once 'func.php';
require_once __DIR__ . '/turn_state_snapshot.php';
final class TurnComparisonTracingRNG implements RNG
{
private int $sequence = 0;
/** @var list<array<string, mixed>> */
public array $calls = [];
public function __construct(private readonly RNG $inner)
{
}
public static function getMaxInt(): int
{
return LiteHashDRBG::getMaxInt();
}
public function nextBytes(int $bytes): string
{
$value = $this->inner->nextBytes($bytes);
$this->record('nextBytes', ['bytes' => $bytes], bin2hex($value));
return $value;
}
public function nextBits(int $bits): string
{
$value = $this->inner->nextBits($bits);
$this->record('nextBits', ['bits' => $bits], bin2hex($value));
return $value;
}
public function nextInt(?int $max = null): int
{
$value = $this->inner->nextInt($max);
$this->record('nextInt', ['maxInclusive' => $max], $value);
return $value;
}
public function nextFloat1(): float
{
$value = $this->inner->nextFloat1();
$this->record('nextFloat1', [], $value);
return $value;
}
private function record(string $operation, array $arguments, mixed $result): void
{
$this->calls[] = [
'seq' => $this->sequence++,
'operation' => $operation,
'arguments' => $arguments === [] ? (object)[] : $arguments,
'result' => $result,
];
}
}
/** @return array<string, mixed> */
function comparisonMappedPatch(array $row, array $mapping): array
{
$patch = [];
foreach ($mapping as $canonical => $legacy) {
if (array_key_exists($canonical, $row)) {
$patch[$legacy] = $row[$canonical];
}
}
return $patch;
}
function comparisonApplyTurnFixtureSetup(mixed $setup): void
{
if ($setup === null) {
return;
}
if (!is_array($setup)) {
throw new \InvalidArgumentException('setup must be an object');
}
$db = DB::db();
$world = $setup['world'] ?? null;
if ($world !== null) {
if (!is_array($world)) {
throw new \InvalidArgumentException('setup.world must be an object');
}
$game = KVStorage::getStorage($db, 'game_env');
foreach (['year', 'month', 'startyear', 'init_year', 'init_month'] as $key) {
$fixtureKey = match ($key) {
'startyear' => 'startYear',
'init_year' => 'initYear',
'init_month' => 'initMonth',
default => $key,
};
if (!array_key_exists($fixtureKey, $world)) {
continue;
}
$value = $world[$fixtureKey];
if (
!is_int($value)
|| $value < 1
|| (in_array($key, ['month', 'init_month'], true) && $value > 12)
) {
throw new \InvalidArgumentException("setup.world.{$fixtureKey} is invalid");
}
$game->setValue($key, $value);
}
$game->resetCache();
if (array_key_exists('hiddenSeed', $world)) {
$hiddenSeed = $world['hiddenSeed'];
if (!is_string($hiddenSeed) || $hiddenSeed === '' || strlen($hiddenSeed) > 256) {
throw new \InvalidArgumentException('setup.world.hiddenSeed is invalid');
}
UniqueConst::$hiddenSeed = $hiddenSeed;
}
// 비교 fixture는 전역 상태에 의존하지 않도록 effect 기본값을 null로 고정하고,
// 명시된 경우에만 해당 effect를 활성화합니다.
GameConst::$scenarioEffect = null;
if (array_key_exists('scenarioEffect', $world)) {
$scenarioEffect = $world['scenarioEffect'];
if ($scenarioEffect === '' || $scenarioEffect === 'None') {
$scenarioEffect = null;
}
if ($scenarioEffect !== null) {
if (!is_string($scenarioEffect) || getScenarioEffectClass($scenarioEffect) === null) {
throw new \InvalidArgumentException('setup.world.scenarioEffect is invalid');
}
}
GameConst::$scenarioEffect = $scenarioEffect;
}
if (array_key_exists('staticEventHandlers', $world)) {
$handlersByEvent = $world['staticEventHandlers'];
if (!is_array($handlersByEvent)) {
throw new \InvalidArgumentException('setup.world.staticEventHandlers must be an object');
}
foreach ($handlersByEvent as $eventType => $handlers) {
if (!is_string($eventType) || $eventType === '' || !is_array($handlers)) {
throw new \InvalidArgumentException('setup.world.staticEventHandlers is invalid');
}
foreach ($handlers as $handler) {
if (!is_string($handler) || $handler === '') {
throw new \InvalidArgumentException('setup.world.staticEventHandlers is invalid');
}
}
}
GameConst::$staticEventHandlers = $handlersByEvent;
}
}
foreach (['nations', 'cities', 'generals', 'troops', 'diplomacy'] as $collection) {
if (isset($setup[$collection]) && !is_array($setup[$collection])) {
throw new \InvalidArgumentException("setup.{$collection} must be an array");
}
}
$generalCooldowns = comparisonGeneralCooldownSelectors(
$setup['generalCooldowns'] ?? [],
'setup.generalCooldowns',
);
if ($generalCooldowns !== []) {
$nextExecuteStorage = KVStorage::getStorage($db, 'next_execute');
foreach ($setup['generalCooldowns'] as $entry) {
$nextAvailableTurn = $entry['nextAvailableTurn'] ?? null;
if (!is_int($nextAvailableTurn) || $nextAvailableTurn < 0) {
throw new \InvalidArgumentException(
'setup.generalCooldowns entries require a non-negative nextAvailableTurn',
);
}
$key = "next_execute_{$entry['generalId']}_{$entry['actionName']}";
$nextExecuteStorage->setValue($key, $nextAvailableTurn);
}
$nextExecuteStorage->resetCache();
}
if (isset($setup['randomFoundingCandidateCityIds'])) {
$candidateCityIds = comparisonIntegerList(
$setup['randomFoundingCandidateCityIds'],
'randomFoundingCandidateCityIds',
);
$db->update('city', ['level' => 4], '1 = 1');
if ($candidateCityIds !== []) {
$db->update('city', ['level' => 5], 'city IN %li', $candidateCityIds);
}
}
if (($setup['isolateWorld'] ?? false) === true) {
$generalIds = array_values(array_map(
static fn(array $row): int => (int)$row['id'],
$setup['generals'] ?? [],
));
$nationIds = array_values(array_map(
static fn(array $row): int => (int)$row['id'],
$setup['nations'] ?? [],
));
if ($generalIds === [] || $nationIds === []) {
throw new \InvalidArgumentException('isolateWorld requires generals and nations');
}
$generalTemplate = $db->queryFirstRow(
'SELECT * FROM general WHERE no IN %li ORDER BY no LIMIT 1',
$generalIds,
);
if ($generalTemplate === null) {
throw new \InvalidArgumentException('isolateWorld requires at least one existing fixture general');
}
$generalTemplateId = (int)$generalTemplate['no'];
$rankDataTemplate = $db->query(
'SELECT `type`, `value` FROM rank_data WHERE general_id = %i',
$generalTemplateId,
);
$db->delete('general_turn', 'general_id NOT IN %li', $generalIds);
$db->delete('rank_data', 'general_id NOT IN %li', $generalIds);
$db->delete('nation_turn', 'nation_id NOT IN %li', $nationIds);
$db->delete('troop', '1 = 1');
$db->delete('diplomacy', '1 = 1');
$db->delete('general', 'no NOT IN %li', $generalIds);
foreach ($generalIds as $generalId) {
if ($db->queryFirstField('SELECT no FROM general WHERE no = %i', $generalId) !== null) {
continue;
}
$newGeneral = $generalTemplate;
$newGeneral['no'] = $generalId;
$newGeneral['owner'] = 0;
$newGeneral['name'] = "fixture-general-{$generalId}";
$db->insert('general', $newGeneral);
}
foreach ($generalIds as $generalId) {
foreach ($rankDataTemplate as $rankRow) {
$rankData = [
'general_id' => $generalId,
'type' => $rankRow['type'],
'value' => $rankRow['value'],
];
$db->insertUpdate('rank_data', $rankData, $rankData);
}
}
$db->delete('nation', 'nation NOT IN %li', $nationIds);
$db->update('city', [
'nation' => 0,
'supply' => 0,
'front' => 0,
'state' => 0,
'term' => 0,
'conflict' => '{}',
'officer_set' => 0,
], '1 = 1');
}
foreach ($setup['nations'] ?? [] as $row) {
if (!is_array($row) || !is_int($row['id'] ?? null) || $row['id'] < 1) {
throw new \InvalidArgumentException('setup.nations requires positive integer ids');
}
$data = comparisonMappedPatch($row, [
'id' => 'nation',
'name' => 'name',
'color' => 'color',
'capitalCityId' => 'capital',
'gold' => 'gold',
'rice' => 'rice',
'tech' => 'tech',
'level' => 'level',
'typeCode' => 'type',
'war' => 'war',
'diplomacyLimit' => 'surlimit',
'generalCount' => 'gennum',
'power' => 'power',
'capitalRevision' => 'capset',
'strategicCommandLimit' => 'strategic_cmd_limit',
]);
if (isset($row['meta'])) {
$data['aux'] = Json::encode($row['meta']);
}
$data += [
'name' => "fixture-nation-{$row['id']}",
'color' => '#777777',
'capital' => 0,
'gold' => 0,
'rice' => 0,
'level' => 1,
'type' => 'che_중립',
'aux' => '{}',
];
$db->insertUpdate('nation', $data, $data);
$nationStor = KVStorage::getStorage($db, $row['id'], 'nation_env');
if (($setup['isolateWorld'] ?? false) === true) {
$nationStor->deleteValue('recv_assist');
$nationStor->deleteValue('resp_assist');
}
if (array_key_exists('nationEnv', $row)) {
if (!is_array($row['nationEnv'])) {
throw new \InvalidArgumentException('setup.nations.nationEnv must be an object');
}
foreach ($row['nationEnv'] as $key => $value) {
if (!is_string($key) || $key === '') {
throw new \InvalidArgumentException('setup.nations.nationEnv keys must be non-empty strings');
}
$nationStor->setValue($key, $value);
}
$nationStor->resetCache();
}
if (array_key_exists('turnLastByOfficerLevel', $row)) {
$turnLastByOfficerLevel = $row['turnLastByOfficerLevel'];
if (!is_array($turnLastByOfficerLevel)) {
throw new \InvalidArgumentException('setup.nations.turnLastByOfficerLevel must be an object');
}
foreach ($turnLastByOfficerLevel as $officerLevel => $lastTurn) {
$officerLevel = filter_var($officerLevel, FILTER_VALIDATE_INT);
if (
$officerLevel === false
|| $officerLevel < 5
|| $officerLevel > 12
|| !is_array($lastTurn)
) {
throw new \InvalidArgumentException(
'setup.nations.turnLastByOfficerLevel requires officer levels 5 through 12'
);
}
$nationStor->setValue("turn_last_{$officerLevel}", $lastTurn);
}
$nationStor->resetCache();
}
}
if (($setup['isolateWorld'] ?? false) === true && ($setup['nations'] ?? []) !== []) {
$maxNationId = max(array_map(
static fn(array $row): int => (int)$row['id'],
$setup['nations'],
));
$db->query('ALTER TABLE nation AUTO_INCREMENT = %i', $maxNationId + 1);
}
foreach ($setup['cities'] ?? [] as $row) {
if (!is_array($row) || !is_int($row['id'] ?? null) || $row['id'] < 1) {
throw new \InvalidArgumentException('setup.cities requires positive integer ids');
}
$patch = comparisonMappedPatch($row, [
'nationId' => 'nation',
'level' => 'level',
'population' => 'pop',
'populationMax' => 'pop_max',
'agriculture' => 'agri',
'agricultureMax' => 'agri_max',
'commerce' => 'comm',
'commerceMax' => 'comm_max',
'security' => 'secu',
'securityMax' => 'secu_max',
'supplyState' => 'supply',
'frontState' => 'front',
'defence' => 'def',
'defenceMax' => 'def_max',
'wall' => 'wall',
'wallMax' => 'wall_max',
'state' => 'state',
'term' => 'term',
'trust' => 'trust',
'trade' => 'trade',
'officerSet' => 'officer_set',
]);
if (array_key_exists('conflictEntries', $row)) {
if (!is_array($row['conflictEntries'])) {
throw new \InvalidArgumentException('setup.cities.conflictEntries must be an array');
}
$orderedConflict = [];
foreach ($row['conflictEntries'] as $entry) {
if (
!is_array($entry)
|| count($entry) !== 2
|| !is_int($entry[0] ?? null)
|| $entry[0] < 1
|| !is_numeric($entry[1] ?? null)
) {
throw new \InvalidArgumentException(
'setup.cities.conflictEntries requires [positive nation id, numeric value] pairs'
);
}
$orderedConflict[$entry[0]] = $entry[1];
}
$patch['conflict'] = Json::encode($orderedConflict);
} elseif (array_key_exists('conflict', $row)) {
if (!is_array($row['conflict'])) {
throw new \InvalidArgumentException('setup.cities.conflict must be an object');
}
$patch['conflict'] = Json::encode($row['conflict']);
}
if ($patch !== []) {
$db->update('city', $patch, 'city = %i', $row['id']);
}
}
foreach ($setup['generals'] ?? [] as $row) {
if (!is_array($row) || !is_int($row['id'] ?? null) || $row['id'] < 1) {
throw new \InvalidArgumentException('setup.generals requires positive integer ids');
}
$patch = comparisonMappedPatch($row, [
'name' => 'name',
'nationId' => 'nation',
'cityId' => 'city',
'troopId' => 'troop',
'leadership' => 'leadership',
'strength' => 'strength',
'intelligence' => 'intel',
'leadershipExp' => 'leadership_exp',
'strengthExp' => 'strength_exp',
'intelExp' => 'intel_exp',
'experience' => 'experience',
'dedication' => 'dedication',
'expLevel' => 'explevel',
'officerLevel' => 'officer_level',
'officerCityId' => 'officer_city',
'belong' => 'belong',
'permission' => 'permission',
'betray' => 'betray',
'makeLimit' => 'makelimit',
'injury' => 'injury',
'age' => 'age',
'gold' => 'gold',
'rice' => 'rice',
'crew' => 'crew',
'crewTypeId' => 'crewtype',
'train' => 'train',
'atmos' => 'atmos',
'dex1' => 'dex1',
'dex2' => 'dex2',
'dex3' => 'dex3',
'dex4' => 'dex4',
'dex5' => 'dex5',
'specAge' => 'specage',
'specAge2' => 'specage2',
'killTurn' => 'killturn',
'npcState' => 'npc',
'blockState' => 'block',
'specialDomestic' => 'special',
'specialWar' => 'special2',
'personality' => 'personal',
'itemHorse' => 'horse',
'itemWeapon' => 'weapon',
'itemBook' => 'book',
'itemExtra' => 'item',
]);
if (isset($row['meta'])) {
$patch['aux'] = Json::encode($row['meta']);
}
if (array_key_exists('penalty', $row)) {
if (!is_array($row['penalty'])) {
throw new \InvalidArgumentException('setup.generals.penalty must be an object');
}
$patch['penalty'] = Json::encode($row['penalty']);
}
if (isset($row['lastTurn']) && is_array($row['lastTurn'])) {
$patch['last_turn'] = Json::encode($row['lastTurn']);
}
if ($patch !== []) {
$db->update('general', $patch, 'no = %i', $row['id']);
}
if (array_key_exists('nationId', $row)) {
$db->update('rank_data', [
'nation_id' => $row['nationId'],
], 'general_id = %i', $row['id']);
}
}
foreach ($setup['rankData'] ?? [] as $row) {
if (
!is_array($row)
|| !is_int($row['generalId'] ?? null)
|| !is_string($row['type'] ?? null)
|| RankColumn::tryFrom($row['type']) === null
|| !is_int($row['value'] ?? null)
) {
throw new \InvalidArgumentException('invalid setup.rankData row');
}
$nationId = (int)($db->queryFirstField(
'SELECT nation FROM general WHERE no = %i',
$row['generalId'],
) ?? 0);
$db->insertUpdate('rank_data', [
'general_id' => $row['generalId'],
'nation_id' => $nationId,
'type' => $row['type'],
'value' => $row['value'],
], [
'nation_id' => $nationId,
'value' => $row['value'],
]);
}
if (($setup['isolateWorld'] ?? false) === true && ($setup['generals'] ?? []) !== []) {
$maxGeneralId = max(array_map(
static fn(array $row): int => (int)$row['id'],
$setup['generals'],
));
$db->query('ALTER TABLE general AUTO_INCREMENT = %i', $maxGeneralId + 1);
}
foreach ($setup['troops'] ?? [] as $row) {
if (
!is_array($row)
|| !is_int($row['id'] ?? null)
|| !is_int($row['nationId'] ?? null)
|| !is_string($row['name'] ?? null)
|| $row['id'] < 1
|| $row['nationId'] < 1
|| $row['name'] === ''
) {
throw new \InvalidArgumentException('setup.troops requires positive id/nationId and non-empty name');
}
$data = [
'troop_leader' => $row['id'],
'nation' => $row['nationId'],
'name' => $row['name'],
];
$db->insertUpdate('troop', $data, $data);
}
foreach ($setup['diplomacy'] ?? [] as $row) {
if (
!is_array($row) ||
!is_int($row['fromNationId'] ?? null) ||
!is_int($row['toNationId'] ?? null) ||
$row['fromNationId'] < 1 ||
$row['toNationId'] < 1
) {
throw new \InvalidArgumentException('setup.diplomacy requires positive integer nation ids');
}
$data = comparisonMappedPatch($row, [
'fromNationId' => 'me',
'toNationId' => 'you',
'state' => 'state',
'term' => 'term',
'dead' => 'dead',
]);
$data += ['state' => 3, 'term' => 0, 'dead' => 0];
$db->insertUpdate('diplomacy', $data, $data);
}
}
function comparisonRunTurnCommand(array $request): array
{
if (getenv('TURN_DIFFERENTIAL_ENABLED') !== '1') {
throw new \RuntimeException('TURN_DIFFERENTIAL_ENABLED=1 is required');
}
$kind = $request['kind'] ?? null;
if (!in_array($kind, ['general', 'nation', 'instantNation', 'troopJoinStaticEvent'], true)) {
throw new \InvalidArgumentException(
'kind must be general, nation, instantNation, or troopJoinStaticEvent',
);
}
$actorGeneralId = $request['actorGeneralId'] ?? null;
$action = $request['action'] ?? null;
$args = $request['args'] ?? null;
$includeLifecycle = $request['includeLifecycle'] ?? false;
if (!is_int($actorGeneralId) || $actorGeneralId < 1) {
throw new \InvalidArgumentException('actorGeneralId must be a positive integer');
}
if (!is_string($action) || $action === '') {
throw new \InvalidArgumentException('action must be a non-empty string');
}
if (!is_bool($includeLifecycle)) {
throw new \InvalidArgumentException('includeLifecycle must be a boolean');
}
if ($includeLifecycle && $kind !== 'general') {
throw new \InvalidArgumentException('includeLifecycle currently supports general commands only');
}
comparisonApplyTurnFixtureSetup($request['setup'] ?? null);
if ($includeLifecycle) {
DB::db()->insertUpdate('general_turn', [
'general_id' => $actorGeneralId,
'turn_idx' => 0,
'action' => $action,
'arg' => Json::encode($args),
'brief' => $action,
], [
'action' => $action,
'arg' => Json::encode($args),
'brief' => $action,
]);
}
$snapshotRequest = ['observe' => $request['observe'] ?? []];
$before = comparisonTurnStateSnapshot($snapshotRequest);
$db = DB::db();
$gameStorage = KVStorage::getStorage($db, 'game_env');
$gameStorage->resetCache();
$environment = $gameStorage->getAll();
if ($kind === 'troopJoinStaticEvent') {
if ($action !== \sammo\API\Troop\JoinTroop::class) {
throw new \InvalidArgumentException('troopJoinStaticEvent requires the JoinTroop API action');
}
$troopId = is_array($args) ? ($args['troopID'] ?? null) : null;
if (!is_int($troopId) || $troopId < 1) {
throw new \InvalidArgumentException('troopJoinStaticEvent requires a positive integer troopID');
}
$general = GeneralLite::createObjFromDB(
$actorGeneralId,
['troop'],
Enums\GeneralLiteQueryMode::Lite,
);
if ($general === null) {
throw new \RuntimeException('장수 정보를 불러올 수 없습니다.');
}
if ($general->getVar('troop') != 0) {
throw new \RuntimeException('이미 부대에 소속되어 있습니다.');
}
$nationId = $general->getNationID();
if ($nationId == 0) {
throw new \RuntimeException('국가에 소속되어 있지 않습니다.');
}
$troopExists = $db->queryFirstField(
'SELECT `troop_leader` FROM `troop` WHERE `troop_leader` = %i AND `nation` = %i',
$troopId,
$nationId,
);
if (!$troopExists) {
throw new \RuntimeException('부대가 올바르지 않습니다.');
}
GameConst::$staticEventHandlers = [
\sammo\API\Troop\JoinTroop::class => ['event_부대탑승즉시이동'],
];
$general->setVar('troop', $troopId);
StaticEventHandler::handleEvent($general, null, \sammo\API\Troop\JoinTroop::class, [], $args);
$general->applyDB($db);
$after = comparisonTurnStateSnapshot($snapshotRequest);
return [
'schemaVersion' => 1,
'engine' => 'ref',
'execution' => [
'kind' => $kind,
'actorGeneralId' => $actorGeneralId,
'action' => $action,
'args' => $args,
'seedDomain' => 'none',
'outcome' => ['completed' => true],
],
'before' => $before,
'after' => $after,
'rng' => [],
];
}
$general = General::createObjFromDB($actorGeneralId);
if ($kind === 'instantNation') {
$command = buildNationCommandClass($action, $general, $environment, new LastTurn(), $args);
if (!$command->hasFullConditionMet()) {
throw new \RuntimeException($command->getFailString());
}
$completed = $command->run(NoRNG::rngInstance());
$command->setNextAvailable();
$general->getLogger()->flush();
$after = comparisonTurnStateSnapshot($snapshotRequest);
return [
'schemaVersion' => 1,
'engine' => 'ref',
'execution' => [
'kind' => $kind,
'actorGeneralId' => $actorGeneralId,
'action' => $action,
'args' => $args,
'seedDomain' => 'none',
'outcome' => [
'lastTurn' => (object)[],
'commandName' => $command->getName(),
'completed' => $completed,
],
],
'before' => $before,
'after' => $after,
'rng' => [],
];
}
$turn = new TurnExecutionHelper($general);
$general->increaseInheritancePoint(Enums\InheritanceKey::lived_month, 1);
$preprocessRng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'preprocess',
(int)$environment['year'],
(int)$environment['month'],
$actorGeneralId,
)));
$turn->preprocessCommand($preprocessRng, $environment);
$seedDomain = $kind === 'general' ? 'generalCommand' : 'nationCommand';
$seed = Util::simpleSerialize(
UniqueConst::$hiddenSeed,
$seedDomain,
(int)$environment['year'],
(int)$environment['month'],
$actorGeneralId,
$action,
);
$tracingRng = new TurnComparisonTracingRNG(new LiteHashDRBG($seed));
$rng = new RandUtil($tracingRng);
if ($kind === 'general' && $action === 'che_견문') {
\sammo\TextDecoration\SightseeingMessage::setComparisonRng($rng);
}
if ($kind === 'general') {
$previousLastTurn = $general->getLastTurn();
$previousLastTurnRaw = $previousLastTurn->toRaw();
$command = buildGeneralCommandClass($action, $general, $environment, $args);
$commandFullConditionMet = $command->hasFullConditionMet();
$resultTurn = $turn->processCommand($rng, $command, false);
} else {
$nationStor = KVStorage::getStorage($db, $general->getNationID(), 'nation_env');
$lastNationTurnKey = "turn_last_{$general->getVar('officer_level')}";
$previousLastTurn = LastTurn::fromRaw($nationStor->getValue($lastNationTurnKey));
$previousLastTurnRaw = $previousLastTurn->toRaw();
$command = buildNationCommandClass($action, $general, $environment, $previousLastTurn, $args);
$commandFullConditionMet = $command->hasFullConditionMet();
$resultTurn = $turn->processNationCommand($rng, $command);
$nationStor->setValue($lastNationTurnKey, $resultTurn->toRaw());
}
if ($includeLifecycle) {
pullGeneralCommand($general->getID());
$general->increaseVarWithLimit(
'myset',
GameConst::$incDefSettingChange,
null,
GameConst::$maxDefSettingChange,
);
$turn->updateTurnTime();
}
$general->getLogger()->flush();
$turn->applyDB();
unset($turn);
$after = comparisonTurnStateSnapshot($snapshotRequest);
$resultTurnRaw = $resultTurn->toRaw();
$resultTerm = (int)($resultTurnRaw['term'] ?? 0);
$preReqTurn = $command->getPreReqTurn();
$acceptScoutCompleted = (
$action === 'che_등용수락'
&& is_array($args)
&& is_int($args['destNationID'] ?? null)
&& $general->getNationID() === $args['destNationID']
);
$foundNationCompleted = (
in_array($action, ['che_건국', 'cr_건국', 'che_무작위건국'], true)
&& is_array($args)
&& is_string($args['nationName'] ?? null)
&& DB::db()->queryFirstField(
'SELECT name FROM nation WHERE nation = %i',
$general->getNationID(),
) === $args['nationName']
);
$completedByState = (
$action === 'che_접경귀환'
&& $tracingRng->calls !== []
) || $acceptScoutCompleted || $foundNationCompleted || (($resultTurnRaw['command'] ?? null) === $command->getName()
&& (
($preReqTurn === 0 && $resultTerm === 0)
|| (
$preReqTurn > 0
&& ($previousLastTurnRaw['command'] ?? null) === $command->getName()
&& ($previousLastTurnRaw['arg'] ?? null) === $command->getArg()
&& (int)($previousLastTurnRaw['term'] ?? 0) === $preReqTurn
)
));
$completed = $commandFullConditionMet && $completedByState;
return [
'schemaVersion' => 1,
'engine' => 'ref',
'execution' => [
'kind' => $kind,
'actorGeneralId' => $actorGeneralId,
'action' => $action,
'args' => $args,
'seedDomain' => $seedDomain,
'outcome' => [
'lastTurn' => $resultTurnRaw,
'commandName' => $command->getName(),
'completed' => $completed,
],
],
'before' => $before,
'after' => $after,
'rng' => $tracingRng->calls,
];
}
try {
$input = stream_get_contents(STDIN);
$request = json_decode($input === '' ? '{}' : $input, true, flags: JSON_THROW_ON_ERROR);
if (!is_array($request)) {
throw new \InvalidArgumentException('request must be an object');
}
echo json_encode(
comparisonRunTurnCommand($request),
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
), PHP_EOL;
} catch (\Throwable $throwable) {
fwrite(STDERR, $throwable::class . ': ' . $throwable->getMessage() . PHP_EOL);
exit(1);
}
-643
View File
@@ -1,643 +0,0 @@
<?php
declare(strict_types=1);
namespace sammo;
use sammo\Enums\RankColumn;
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit;
}
chdir(dirname(__DIR__));
require_once 'lib.php';
require_once 'func.php';
/** @return list<int> */
function comparisonIntegerList(mixed $value, string $label): array
{
if ($value === null) {
return [];
}
if (!is_array($value)) {
throw new \InvalidArgumentException("{$label} must be an array");
}
$result = [];
foreach ($value as $entry) {
if (!is_int($entry) || $entry < 0) {
throw new \InvalidArgumentException("{$label} entries must be non-negative integers");
}
$result[$entry] = $entry;
}
ksort($result, SORT_NUMERIC);
return array_values($result);
}
function comparisonJsonValue(mixed $value): mixed
{
if (!is_string($value) || $value === '') {
return $value;
}
try {
return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
} catch (\JsonException) {
return $value;
}
}
function comparisonOptionalCode(mixed $value): ?string
{
if (!is_string($value) || $value === '' || $value === 'None') {
return null;
}
return $value;
}
/** @return list<array{generalId: int, actionName: string}> */
function comparisonGeneralCooldownSelectors(mixed $value, string $label): array
{
if ($value === null) {
return [];
}
if (!is_array($value)) {
throw new \InvalidArgumentException("{$label} must be an array");
}
$result = [];
foreach ($value as $entry) {
if (!is_array($entry)) {
throw new \InvalidArgumentException("{$label} entries must be objects");
}
$generalId = $entry['generalId'] ?? null;
$actionName = $entry['actionName'] ?? null;
if (!is_int($generalId) || $generalId < 1 || !is_string($actionName) || $actionName === '') {
throw new \InvalidArgumentException(
"{$label} entries require a positive generalId and non-empty actionName",
);
}
$result["{$generalId}:{$actionName}"] = [
'generalId' => $generalId,
'actionName' => $actionName,
];
}
ksort($result, SORT_STRING);
return array_values($result);
}
/** @return list<array{nationId: int, actionName: string}> */
function comparisonNationCooldownSelectors(mixed $value, string $label): array
{
if ($value === null) {
return [];
}
if (!is_array($value)) {
throw new \InvalidArgumentException("{$label} must be an array");
}
$result = [];
foreach ($value as $entry) {
if (!is_array($entry)) {
throw new \InvalidArgumentException("{$label} entries must be objects");
}
$nationId = $entry['nationId'] ?? null;
$actionName = $entry['actionName'] ?? null;
if (!is_int($nationId) || $nationId < 1 || !is_string($actionName) || $actionName === '') {
throw new \InvalidArgumentException(
"{$label} entries require a positive nationId and non-empty actionName",
);
}
$result["{$nationId}:{$actionName}"] = [
'nationId' => $nationId,
'actionName' => $actionName,
];
}
ksort($result, SORT_STRING);
return array_values($result);
}
/** @return list<array{fromNationId: int, toNationId: int}> */
function comparisonDiplomacyPairSelectors(mixed $value, string $label): array
{
if ($value === null) {
return [];
}
if (!is_array($value)) {
throw new \InvalidArgumentException("{$label} must be an array");
}
$result = [];
foreach ($value as $entry) {
if (!is_array($entry)) {
throw new \InvalidArgumentException("{$label} entries must be objects");
}
$fromNationId = $entry['fromNationId'] ?? null;
$toNationId = $entry['toNationId'] ?? null;
if (!is_int($fromNationId) || $fromNationId < 1 || !is_int($toNationId) || $toNationId < 1) {
throw new \InvalidArgumentException(
"{$label} entries require positive fromNationId and toNationId",
);
}
$result["{$fromNationId}:{$toNationId}"] = [
'fromNationId' => $fromNationId,
'toNationId' => $toNationId,
];
}
ksort($result, SORT_STRING);
return array_values($result);
}
/** @param array<string, mixed> $row */
function comparisonPickRow(array $row, array $mapping, array $jsonKeys = []): array
{
$result = [];
foreach ($mapping as $canonical => $legacy) {
if (!array_key_exists($legacy, $row)) {
continue;
}
$value = $row[$legacy];
if (in_array($legacy, $jsonKeys, true)) {
$value = comparisonJsonValue($value);
}
$result[$canonical] = $value;
}
return $result;
}
/** @param list<int> $ids */
function comparisonRowsById(string $table, string $idColumn, array $ids): array
{
$db = DB::db();
$rows = [];
foreach ($ids as $id) {
$row = $db->queryFirstRow(
"SELECT * FROM `{$table}` WHERE `{$idColumn}` = %i",
$id,
);
if ($row !== null) {
$rows[] = $row;
}
}
return $rows;
}
function comparisonTurnStateSnapshot(array $request): array
{
$observe = $request['observe'] ?? [];
if (!is_array($observe)) {
throw new \InvalidArgumentException('observe must be an object');
}
$generalIds = comparisonIntegerList($observe['generalIds'] ?? [], 'generalIds');
$cityIds = comparisonIntegerList($observe['cityIds'] ?? [], 'cityIds');
$nationIds = comparisonIntegerList($observe['nationIds'] ?? [], 'nationIds');
$logAfterId = $observe['logAfterId'] ?? 0;
$messageAfterId = $observe['messageAfterId'] ?? 0;
$includeNationHistoryLogs = $observe['includeNationHistoryLogs'] ?? false;
$includeGlobalHistoryLogs = $observe['includeGlobalHistoryLogs'] ?? false;
$generalCooldownSelectors = comparisonGeneralCooldownSelectors(
$observe['generalCooldowns'] ?? [],
'generalCooldowns',
);
$nationCooldownSelectors = comparisonNationCooldownSelectors(
$observe['nationCooldowns'] ?? [],
'nationCooldowns',
);
$diplomacyPairSelectors = comparisonDiplomacyPairSelectors(
$observe['diplomacyPairs'] ?? [],
'diplomacyPairs',
);
if (
!is_int($logAfterId)
|| $logAfterId < 0
|| !is_int($messageAfterId)
|| $messageAfterId < 0
) {
throw new \InvalidArgumentException('logAfterId and messageAfterId must be non-negative integers');
}
if (!is_bool($includeNationHistoryLogs)) {
throw new \InvalidArgumentException('includeNationHistoryLogs must be a boolean');
}
if (!is_bool($includeGlobalHistoryLogs)) {
throw new \InvalidArgumentException('includeGlobalHistoryLogs must be a boolean');
}
$db = DB::db();
$game = KVStorage::getStorage($db, 'game_env');
$game->resetCache();
$worldValues = $game->getValues([
'year',
'month',
'turnterm',
'turntime',
'isunited',
'scenario',
'init_year',
'init_month',
'develcost',
'killturn',
]);
$nextExecuteStorage = KVStorage::getStorage($db, 'next_execute');
$nextExecuteStorage->resetCache();
$generalCooldowns = array_map(
static function (array $selector) use ($nextExecuteStorage): array {
$key = "next_execute_{$selector['generalId']}_{$selector['actionName']}";
$value = $nextExecuteStorage->getValue($key);
return [
...$selector,
'nextAvailableTurn' => is_int($value) ? $value : null,
];
},
$generalCooldownSelectors,
);
$nationCooldowns = array_map(
static function (array $selector) use ($db): array {
$nationStorage = KVStorage::getStorage($db, $selector['nationId'], 'nation_env');
$nationStorage->resetCache();
$value = $nationStorage->getValue("next_execute_{$selector['actionName']}");
return [
...$selector,
'nextAvailableTurn' => is_int($value) ? $value : null,
];
},
$nationCooldownSelectors,
);
$generals = array_map(
static function (array $row): array {
$result = comparisonPickRow(
$row,
[
'id' => 'no',
'name' => 'name',
'nationId' => 'nation',
'cityId' => 'city',
'troopId' => 'troop',
'leadership' => 'leadership',
'strength' => 'strength',
'intelligence' => 'intel',
'experience' => 'experience',
'dedication' => 'dedication',
'expLevel' => 'explevel',
'officerLevel' => 'officer_level',
'officerCityId' => 'officer_city',
'belong' => 'belong',
'permission' => 'permission',
'betray' => 'betray',
'makeLimit' => 'makelimit',
'injury' => 'injury',
'gold' => 'gold',
'rice' => 'rice',
'crew' => 'crew',
'crewTypeId' => 'crewtype',
'train' => 'train',
'atmos' => 'atmos',
'age' => 'age',
'npcState' => 'npc',
'turnTime' => 'turntime',
'recentWarTime' => 'recent_war',
'lastTurn' => 'last_turn',
'meta' => 'aux',
'penalty' => 'penalty',
'leadershipExp' => 'leadership_exp',
'strengthExp' => 'strength_exp',
'intelExp' => 'intel_exp',
'dex1' => 'dex1',
'dex2' => 'dex2',
'dex3' => 'dex3',
'dex4' => 'dex4',
'dex5' => 'dex5',
'specAge' => 'specage',
'specAge2' => 'specage2',
'killTurn' => 'killturn',
'mySet' => 'myset',
'specialDomestic' => 'special',
'specialWar' => 'special2',
'personality' => 'personal',
'itemHorse' => 'horse',
'itemWeapon' => 'weapon',
'itemBook' => 'book',
'itemExtra' => 'item',
],
['last_turn', 'aux', 'penalty'],
);
foreach ([
'specialDomestic',
'specialWar',
'personality',
'itemHorse',
'itemWeapon',
'itemBook',
'itemExtra',
] as $key) {
$result[$key] = comparisonOptionalCode($result[$key] ?? null);
}
$result['maxBelong'] = (int)($result['meta']['max_belong'] ?? 0);
$ownerId = (int)($row['owner'] ?? 0);
$activeActionPoints = 0;
if ($ownerId > 0) {
$inheritanceStorage = KVStorage::getStorage(DB::db(), "inheritance_{$ownerId}");
$inheritanceStorage->resetCache();
$stored = $inheritanceStorage->getValue('active_action');
if (is_array($stored) && (is_int($stored[0] ?? null) || is_float($stored[0] ?? null))) {
$activeActionPoints = $stored[0];
}
}
$result['hasOwner'] = $ownerId > 0;
$result['inheritActiveActionPoints'] = $activeActionPoints;
return $result;
},
comparisonRowsById('general', 'no', $generalIds),
);
$rankTypes = array_map(
static fn(RankColumn $column): string => $column->value,
RankColumn::cases(),
);
$rankData = $generalIds === []
? []
: iterator_to_array($db->query(
'SELECT general_id AS generalId, nation_id AS nationId, `type`, `value`'
. ' FROM rank_data WHERE general_id IN %li AND `type` IN %ls'
. ' ORDER BY general_id, `type`',
$generalIds,
$rankTypes,
));
$cities = array_map(
static fn(array $row): array => comparisonPickRow(
$row,
[
'id' => 'city',
'name' => 'name',
'nationId' => 'nation',
'level' => 'level',
'population' => 'pop',
'populationMax' => 'pop_max',
'agriculture' => 'agri',
'agricultureMax' => 'agri_max',
'commerce' => 'comm',
'commerceMax' => 'comm_max',
'security' => 'secu',
'securityMax' => 'secu_max',
'supplyState' => 'supply',
'frontState' => 'front',
'defence' => 'def',
'defenceMax' => 'def_max',
'wall' => 'wall',
'wallMax' => 'wall_max',
'state' => 'state',
'term' => 'term',
'trust' => 'trust',
'trade' => 'trade',
'conflict' => 'conflict',
'officerSet' => 'officer_set',
],
['conflict'],
),
comparisonRowsById('city', 'city', $cityIds),
);
$nations = array_map(
static function (array $row) use ($db): array {
$projected = comparisonPickRow(
$row,
[
'id' => 'nation',
'name' => 'name',
'color' => 'color',
'capitalCityId' => 'capital',
'gold' => 'gold',
'rice' => 'rice',
'tech' => 'tech',
'level' => 'level',
'typeCode' => 'type',
'generalCount' => 'gennum',
'power' => 'power',
'war' => 'war',
'diplomacyLimit' => 'surlimit',
'capitalRevision' => 'capset',
'strategicCommandLimit' => 'strategic_cmd_limit',
'meta' => 'aux',
],
['aux'],
);
$nationStor = KVStorage::getStorage($db, (int)$row['nation'], 'nation_env');
$receivedAssist = $nationStor->getValue('recv_assist');
if (is_array($receivedAssist) && $receivedAssist !== []) {
$projected['meta']['recv_assist'] = $receivedAssist;
}
$respondedAssist = $nationStor->getValue('resp_assist');
if (is_array($respondedAssist) && $respondedAssist !== []) {
$projected['meta']['resp_assist'] = $respondedAssist;
}
return $projected;
},
comparisonRowsById('nation', 'nation', $nationIds),
);
$diplomacyPairs = [];
foreach ($nationIds as $fromNationId) {
foreach ($nationIds as $toNationId) {
if ($fromNationId === $toNationId) {
continue;
}
$diplomacyPairs["{$fromNationId}:{$toNationId}"] = [
'fromNationId' => $fromNationId,
'toNationId' => $toNationId,
];
}
}
foreach ($diplomacyPairSelectors as $pair) {
$diplomacyPairs["{$pair['fromNationId']}:{$pair['toNationId']}"] = $pair;
}
ksort($diplomacyPairs, SORT_STRING);
$diplomacy = [];
foreach ($diplomacyPairs as $pair) {
$row = $db->queryFirstRow(
'SELECT me, you, state, term, dead FROM diplomacy WHERE me = %i AND you = %i',
$pair['fromNationId'],
$pair['toNationId'],
);
if ($row !== null) {
$diplomacy[] = comparisonPickRow($row, [
'fromNationId' => 'me',
'toNationId' => 'you',
'state' => 'state',
'term' => 'term',
'dead' => 'dead',
]);
}
}
$generalTurns = [];
foreach ($generalIds as $generalId) {
foreach ($db->query(
'SELECT general_id, turn_idx, action, arg FROM general_turn WHERE general_id = %i ORDER BY turn_idx',
$generalId,
) as $row) {
$generalTurns[] = comparisonPickRow(
$row,
['generalId' => 'general_id', 'turnIndex' => 'turn_idx', 'action' => 'action', 'args' => 'arg'],
['arg'],
);
}
}
$nationTurns = [];
foreach ($nationIds as $nationId) {
foreach ($db->query(
'SELECT nation_id, officer_level, turn_idx, action, arg FROM nation_turn WHERE nation_id = %i ORDER BY officer_level, turn_idx',
$nationId,
) as $row) {
$nationTurns[] = comparisonPickRow(
$row,
[
'nationId' => 'nation_id',
'officerLevel' => 'officer_level',
'turnIndex' => 'turn_idx',
'action' => 'action',
'args' => 'arg',
],
['arg'],
);
}
}
$logs = [];
if ($generalIds !== []) {
foreach ($db->query(
'SELECT id, general_id, log_type, year, month, text FROM general_record WHERE id > %i ORDER BY id',
$logAfterId,
) as $row) {
if ((int)$row['general_id'] !== 0 && !in_array((int)$row['general_id'], $generalIds, true)) {
continue;
}
$generalId = (int)$row['general_id'];
$category = (string)$row['log_type'];
if ($category === 'battle') {
$category = 'battle_detail';
} elseif ($generalId === 0) {
$category = 'summary';
}
$logs[] = comparisonPickRow($row, [
'id' => 'id',
'generalId' => 'general_id',
'year' => 'year',
'month' => 'month',
'text' => 'text',
]) + [
'scope' => $generalId === 0 ? 'system' : 'general',
'category' => $category,
'nationId' => null,
];
}
}
if ($includeNationHistoryLogs && $nationIds !== []) {
foreach ($db->query(
'SELECT id, nation_id, year, month, text FROM world_history WHERE nation_id IN %li ORDER BY id',
$nationIds,
) as $row) {
$logs[] = comparisonPickRow($row, [
'id' => 'id',
'nationId' => 'nation_id',
'year' => 'year',
'month' => 'month',
'text' => 'text',
]) + [
'scope' => 'nation',
'category' => 'history',
'generalId' => null,
];
}
}
if ($includeGlobalHistoryLogs) {
foreach ($db->query(
'SELECT id, nation_id, year, month, text FROM world_history WHERE nation_id = 0 ORDER BY id',
) as $row) {
$logs[] = comparisonPickRow($row, [
'id' => 'id',
'nationId' => 'nation_id',
'year' => 'year',
'month' => 'month',
'text' => 'text',
]) + [
'scope' => 'system',
'category' => 'history',
'generalId' => null,
];
}
}
$messages = array_map(
static fn(array $row): array => comparisonPickRow(
$row,
[
'id' => 'id',
'mailbox' => 'mailbox',
'type' => 'type',
'sourceId' => 'src',
'destinationId' => 'dest',
'createdAt' => 'time',
'payload' => 'message',
],
['message'],
),
$db->query(
'SELECT id, mailbox, type, src, dest, time, message FROM message WHERE id > %i ORDER BY id',
$messageAfterId,
),
);
return [
'schemaVersion' => 1,
'engine' => 'ref',
'world' => [
'year' => (int)$worldValues['year'],
'month' => (int)$worldValues['month'],
'tickMinutes' => (int)$worldValues['turnterm'],
'turnTime' => (string)$worldValues['turntime'],
'isUnited' => (int)$worldValues['isunited'],
'scenarioId' => (int)$worldValues['scenario'],
'initYear' => (int)$worldValues['init_year'],
'initMonth' => (int)$worldValues['init_month'],
'develCost' => (int)$worldValues['develcost'],
'killTurn' => (int)$worldValues['killturn'],
'generalCooldowns' => $generalCooldowns,
'nationCooldowns' => $nationCooldowns,
],
'generals' => $generals,
'rankData' => $rankData,
'cities' => $cities,
'nations' => $nations,
'diplomacy' => $diplomacy,
'generalTurns' => $generalTurns,
'nationTurns' => $nationTurns,
'logs' => $logs,
'messages' => $messages,
'watermarks' => [
'logId' => (int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM general_record') ?? 0),
'historyLogId' => (int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM world_history') ?? 0),
'messageId' => (int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM message') ?? 0),
],
];
}
function comparisonTurnStateSnapshotMain(): void
{
try {
$input = stream_get_contents(STDIN);
$request = json_decode($input === '' ? '{}' : $input, true, flags: JSON_THROW_ON_ERROR);
if (!is_array($request)) {
throw new \InvalidArgumentException('request must be an object');
}
echo json_encode(
comparisonTurnStateSnapshot($request),
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
), PHP_EOL;
} catch (\Throwable $throwable) {
fwrite(STDERR, $throwable::class . ': ' . $throwable->getMessage() . PHP_EOL);
exit(1);
}
}
if (realpath((string)($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
comparisonTurnStateSnapshotMain();
}
-16
View File
@@ -51,22 +51,6 @@
display: none; display: none;
} }
.picture_choice {
margin: 8px auto;
}
.picture_choice label,
.event_picture label {
display: inline-flex;
align-items: center;
gap: 4px;
margin: 4px 8px;
}
.picture_choice img {
object-fit: cover;
}
.custom_picture { .custom_picture {
display: none; display: none;
} }
+10 -15
View File
@@ -9,7 +9,6 @@ use sammo\Enums\GeneralAccessLogColumn;
use sammo\Enums\GeneralColumn; use sammo\Enums\GeneralColumn;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\Enums\InheritanceKey; use sammo\Enums\InheritanceKey;
use sammo\Enums\PenaltyKey;
use sammo\Enums\RankColumn; use sammo\Enums\RankColumn;
require_once 'process_war.php'; require_once 'process_war.php';
@@ -112,7 +111,7 @@ function GetImageURL($imgsvr, $filepath = '')
function checkLimit($refreshScore = null) function checkLimit($refreshScore = null)
{ {
$session = Session::getInstance(); $session = Session::getInstance();
if ($session->userGrade >= 6) { if ($session->userGrade >= 4) {
return 0; return 0;
} }
@@ -377,11 +376,11 @@ function myNationInfo(General $generalObj)
function checkSecretMaxPermission($penalty) function checkSecretMaxPermission($penalty)
{ {
$secretMax = 4; $secretMax = 4;
if ($penalty[PenaltyKey::NoTopSecret->value] ?? false) { if ($penalty['noTopSecret'] ?? false) {
$secretMax = 1; $secretMax = 1;
} else if ($penalty[PenaltyKey::NoChief->value] ?? false) { } else if ($penalty['noChief'] ?? false) {
$secretMax = 1; $secretMax = 1;
} else if ($penalty[PenaltyKey::NoAmbassador->value] ?? false) { } else if ($penalty['noAmbassador'] ?? false) {
$secretMax = 2; $secretMax = 2;
} }
return $secretMax; return $secretMax;
@@ -404,7 +403,7 @@ function checkSecretPermission(array $me, $checkSecretLimit = true)
} }
if ($penalty[PenaltyKey::NoChief->value] ?? false) { if ($penalty['noSecret'] ?? false) {
return 0; return 0;
} }
@@ -1009,7 +1008,7 @@ function increaseRefresh($type = "", $cnt = 1)
$isunited = $gameStor->isunited; $isunited = $gameStor->isunited;
$opentime = $gameStor->opentime; $opentime = $gameStor->opentime;
if ($userGrade >= 6) { if ($userGrade == 6) {
return; return;
} }
if ($isunited == 2) { if ($isunited == 2) {
@@ -1047,7 +1046,7 @@ function increaseRefresh($type = "", $cnt = 1)
$logDB = FileDB::db($logPath, $serverPath . '/../f_install/sql/api_log.sql'); $logDB = FileDB::db($logPath, $serverPath . '/../f_install/sql/api_log.sql');
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'local'; $ip = $_SERVER['REMOTE_ADDR'] ?? 'local';
$date = date('Y-m-d H:i:s'); $date = date('Y-m-d H:i:s');
$logDB->insert('api_log', [ $logDB->insert('api_log', [
@@ -1107,7 +1106,7 @@ function CheckOverhead()
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
[$turnterm, $refreshLimit] = $gameStor->getValuesAsArray(['turnterm', 'refreshLimit']); [$turnterm, $refreshLimit] = $gameStor->getValuesAsArray(['turnterm', 'refreshLimit']);
$nextRefreshLimit = Util::round(pow($turnterm, 0.6) * 3) * GameConst::$refreshLimitCoef; $nextRefreshLimit = Util::round(pow($turnterm, 0.6) * 3) * 10;
if ($nextRefreshLimit != $refreshLimit) { if ($nextRefreshLimit != $refreshLimit) {
@@ -1403,9 +1402,6 @@ function CheckHall($no)
if ($valueType === 'natural') { if ($valueType === 'natural') {
$value = $generalObj->getVar($typeName); $value = $generalObj->getVar($typeName);
if (in_array($typeName, ['dex1', 'dex2', 'dex3', 'dex4', 'dex5'], true)) {
$value = CentennialAllStarGrowthService::recordableValue($generalObj, $typeName);
}
} else if ($valueType === 'rank') { } else if ($valueType === 'rank') {
$value = $generalObj->getRankVar(RankColumn::from($typeName)); $value = $generalObj->getRankVar(RankColumn::from($typeName));
} else if ($valueType === 'calc') { } else if ($valueType === 'calc') {
@@ -1672,6 +1668,8 @@ function tryUniqueItemLottery(RandUtil $rng, General $general, string $acquireTy
$prob = 1 / ($genCount * $itemTypeCnt * 0.7 / 3); // 투표율 70%, 설문조사 한번에 2~3개 등장 $prob = 1 / ($genCount * $itemTypeCnt * 0.7 / 3); // 투표율 70%, 설문조사 한번에 2~3개 등장
} else if ($acquireType == '랜덤 임관') { } else if ($acquireType == '랜덤 임관') {
$prob = 1 / ($genCount * $itemTypeCnt / 10 / 2); // 랜임시 2개(10%) 등장(200명중 20명 랜임시도?) $prob = 1 / ($genCount * $itemTypeCnt / 10 / 2); // 랜임시 2개(10%) 등장(200명중 20명 랜임시도?)
} else if ($acquireType == '건국') {
$prob = 1 / ($genCount * $itemTypeCnt / 10 / 4); // 건국시 4개(20%) 등장(200명시 20국 정도 됨)
} }
$prob *= GameConst::$uniqueTrialCoef; $prob *= GameConst::$uniqueTrialCoef;
@@ -1685,9 +1683,6 @@ function tryUniqueItemLottery(RandUtil $rng, General $general, string $acquireTy
//포인트로 랜덤 유니크 획득 //포인트로 랜덤 유니크 획득
$prob = 1; $prob = 1;
} }
else if ($acquireType == '건국') {
$prob = 1; // 건국시 100%
}
foreach (Util::range($maxCnt) as $_idx) { foreach (Util::range($maxCnt) as $_idx) {
if ($rng->nextBool($prob)) { if ($rng->nextBool($prob)) {
-15
View File
@@ -3,7 +3,6 @@ namespace sammo;
use sammo\Command\GeneralCommand; use sammo\Command\GeneralCommand;
use sammo\Command\NationCommand; use sammo\Command\NationCommand;
use sammo\Enums\PenaltyKey;
function getGeneralTurnBrief(General $generalObj, array $turnList) { function getGeneralTurnBrief(General $generalObj, array $turnList) {
$result = []; $result = [];
@@ -435,13 +434,6 @@ function setNationCommand(int $generalID, array $turnList, string $command, ?arr
]; ];
} }
if($general->hasPenalty(PenaltyKey::NoChiefTurnInput)){
return [
'result'=>false,
'reason'=>'수뇌 턴 입력 불가능'
];
}
try{ try{
$commandObj = buildNationCommandClass($command, $general, $env, $general->getLastTurn(), $arg); $commandObj = buildNationCommandClass($command, $general, $env, $general->getLastTurn(), $arg);
} }
@@ -481,13 +473,6 @@ function setNationCommand(int $generalID, array $turnList, string $command, ?arr
} }
_setNationCommand($commandObj, $turnList); _setNationCommand($commandObj, $turnList);
if($general->getNPCType() < 2){
//유저장인 경우에는 턴 리필
$general->updateVarWithLimit('killturn', $env['killturn'], $general->getVar('killturn'));
$general->applyDB($db);
}
return [ return [
'result'=>true, 'result'=>true,
'brief'=>$commandObj->getBrief(), 'brief'=>$commandObj->getBrief(),
+1 -24
View File
@@ -457,29 +457,6 @@ function buildWarUnitTriggerClass(?string $type, WarUnit $unit, ?array $args = n
return $class->newInstanceArgs(array_merge([$unit], $args)); return $class->newInstanceArgs(array_merge([$unit], $args));
} }
function getStaticEventClass(string $type){
static $basePath = __NAMESPACE__.'\\StaticEvent\\';
$classPath = ($basePath.$type);
if(class_exists($classPath)){
return $classPath;
}
throw new \InvalidArgumentException("{$type}은 StaticEvent가 아님");
}
function buildStaticEventClass(?string $type):BaseStaticEvent{
static $cache = [];
if(key_exists($type, $cache)){
return $cache[$type];
}
$class = getStaticEventClass($type);
$obj = new $class();
$cache[$type] = $obj;
return $obj;
}
function getGeneralPoolClass(string $type){ function getGeneralPoolClass(string $type){
static $basePath = __NAMESPACE__.'\\GeneralPool\\'; static $basePath = __NAMESPACE__.'\\GeneralPool\\';
$classPath = ($basePath.$type); $classPath = ($basePath.$type);
@@ -686,7 +663,7 @@ function TechLimit($startYear, $year, $tech) : bool {
$relYear = $year - $startYear; $relYear = $year - $startYear;
$relMaxTech = Util::valueFit( $relMaxTech = Util::valueFit(
floor($relYear / GameConst::$techLevelIncYear) + GameConst::$initialAllowedTechLevel, floor($relYear / 5) + 1,
1, 1,
GameConst::$maxTechLevel GameConst::$maxTechLevel
); );
+3 -3
View File
@@ -372,7 +372,7 @@ function postUpdateMonthly(RandUtil $rng)
continue; continue;
} }
//양측 기간 모두 0이 되는 상황이면 //양측 기간 모두 0이 되는 상황이면
$nation1 = getNationStaticInfo($me); $nation1 = getNationStaticInfo($me);
$name1 = $nation1['name']; $name1 = $nation1['name'];
$nation2 = getNationStaticInfo($you); $nation2 = getNationStaticInfo($you);
@@ -381,7 +381,7 @@ function postUpdateMonthly(RandUtil $rng)
$josaWa = JosaUtil::pick($name1, '와'); $josaWa = JosaUtil::pick($name1, '와');
$josaYi = JosaUtil::pick($name2, '이'); $josaYi = JosaUtil::pick($name2, '이');
$globalLogger->pushGlobalHistoryLog("<R><b>【전】</b></><D><b>$name1</b></>{$josaWa} <D><b>$name2</b></>{$josaYi} <S>전</>합니다."); $globalLogger->pushGlobalHistoryLog("<R><b>【전】</b></><D><b>$name1</b></>{$josaWa} <D><b>$name2</b></>{$josaYi} <S>전</>합니다.");
$db->update('diplomacy', [ $db->update('diplomacy', [
'state' => 2, 'state' => 2,
'term' => 0, 'term' => 0,
@@ -559,7 +559,7 @@ function checkStatistic()
$nationHist = ''; $nationHist = '';
foreach (GameConst::$availableNationType as $nationType) { foreach (GameConst::$availableNationType as $nationType) {
if (!($nationHists[$nationType] ?? null)) { if (!Util::array_get($nationHists[$nationType])) {
$nationHists[$nationType] = '-'; $nationHists[$nationType] = '-';
} }
$nationHist .= getNationType($nationType) . "({$nationHists[$nationType]}), "; $nationHist .= getNationType($nationType) . "({$nationHists[$nationType]}), ";
+2 -2
View File
@@ -138,7 +138,7 @@ function JSCitiesBasedOnDistance(int $cityNo, int $maxDistance = 1): array{
$distanceList = searchDistance($cityNo, $maxDistance, true); $distanceList = searchDistance($cityNo, $maxDistance, true);
$result = []; $result = [];
for ($dist = 1; $dist <= $maxDistance; $dist++) { for ($dist = 1; $dist <= $maxDistance; $dist++) {
$result[$dist] = $distanceList[$dist] ?? []; $result[$dist] = Util::array_get($distanceList[$dist], []);
} }
return $result; return $result;
} }
@@ -152,7 +152,7 @@ function printCitiesBasedOnDistance(int $cityNo, int $maxDistance = 1): string
for ($dist = 1; $dist <= $maxDistance; $dist++) { for ($dist = 1; $dist <= $maxDistance; $dist++) {
$cityList = array_map(function ($cityID) { $cityList = array_map(function ($cityID) {
return CityConst::byID($cityID)->name; return CityConst::byID($cityID)->name;
}, $distanceList[$dist] ?? []); }, Util::array_get($distanceList[$dist], []));
$cityStr = join(', ', $cityList); $cityStr = join(', ', $cityList);
+3 -7
View File
@@ -1153,25 +1153,21 @@ function fight($tnmt_type, $tnmt, $phs, $group, $g1, $g2, $type)
]; ];
//막판 분노 //막판 분노
$factor1 = 1.0;
$factor2 = 1.0;
$ratio = rand() % 300; $ratio = rand() % 300;
if ($e1 / 5 > $energy1 && $damage1 > $damage2 && $gen1[$tp] >= $ratio) { if ($e1 / 5 > $energy1 && $damage1 > $damage2 && $gen1[$tp] >= $ratio) {
$factor2 = Util::round((rand() % 301 + 200) / 100); // 200 ~ 500% $damage2 *= Util::round((rand() % 301 + 200) / 100); // 200 ~ 500%
$critical1 = 1; $critical1 = 1;
$str = Util::choiceRandom($crticialSkillMap[$tnmt_type]); $str = Util::choiceRandom($crticialSkillMap[$tnmt_type]);
$log[] = "<S>●</> <Y>{$gen1['name']}</>의 분노의 <M>{$str}</> 공격!"; $log[] = "<S>●</> <Y>{$gen1['name']}</>의 분노의 <M>{$str}</> 공격!";
} }
$ratio = rand() % 300; $ratio = rand() % 300;
if ($e2 / 5 > $energy2 && $damage2 > $damage1 && $gen2[$tp] >= $ratio) { if ($e2 / 5 > $energy2 && $damage2 > $damage1 && $gen2[$tp] >= $ratio) {
$factor1 = Util::round((rand() % 301 + 200) / 100); // 200 ~ 500% $damage1 *= Util::round((rand() % 301 + 200) / 100); // 200 ~ 500%
$critical2 = 1; $critical2 = 1;
$str = Util::choiceRandom($crticialSkillMap[$tnmt_type]); $str = Util::choiceRandom($crticialSkillMap[$tnmt_type]);
$log[] = "<S>●</> <Y>{$gen2['name']}</>의 분노의 <M>{$str}</> 공격!"; $log[] = "<S>●</> <Y>{$gen2['name']}</>의 분노의 <M>{$str}</> 공격!";
} }
$damage1 *= $factor1;
$damage2 *= $factor2;
//1합 승부 //1합 승부
if ($phase == 1) { if ($phase == 1) {
+2 -2
View File
@@ -7,10 +7,10 @@ include "func.php";
$session = Session::requireLogin(); $session = Session::requireLogin();
if ($session->userGrade < 6) { if ($session->userGrade < 5) {
die('관리자 아님'); die('관리자 아님');
} }
if ($session->userGrade == 6) { if ($session->userGrade == 5) {
die('DB 리셋 권한을 가지고 있지 않습니다.'); die('DB 리셋 권한을 가지고 있지 않습니다.');
} }
+1 -1
View File
@@ -18,7 +18,7 @@ else{
$availableNextCall = $session->availableNextCallGetCityList??'2000-01-01 00:00:00'; $availableNextCall = $session->availableNextCallGetCityList??'2000-01-01 00:00:00';
$now = new \DateTimeImmutable(); $now = new \DateTimeImmutable();
if($now <= new \DateTimeImmutable($availableNextCall) && $session->userGrade < 6){ if($now <= new \DateTimeImmutable($availableNextCall) && $session->userGrade < 5){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
'reason'=>"도시 목록은 10초에 한번 갱신 가능합니다.\n다음 시간 : ".$availableNextCall 'reason'=>"도시 목록은 10초에 한번 갱신 가능합니다.\n다음 시간 : ".$availableNextCall
+4 -22
View File
@@ -10,23 +10,7 @@ function sortTokens(&$tokens){
}); });
} }
function putInfoText(&$info, ?array $currentTargetEnv){ function putInfoText(&$info){
if (($info['event100Growth'] ?? false) === true) {
if ($currentTargetEnv === null) {
$displayStats = CentennialAllStarGrowthService::calculateUserInitialStats($info);
$info['selectionStatLabel'] = '시작 능력치';
} else {
$displayStats = CentennialAllStarGrowthService::calculateUserCurrentTargetStats(
$info,
$currentTargetEnv
);
$info['selectionStatLabel'] = '현재 변경 기준 능력치';
}
$info['selectionLeadership'] = $displayStats['leadership'];
$info['selectionStrength'] = $displayStats['strength'];
$info['selectionIntel'] = $displayStats['intel'];
}
if(key_exists('specialDomestic', $info)){ if(key_exists('specialDomestic', $info)){
$class = buildGeneralSpecialDomesticClass($info['specialDomestic']); $class = buildGeneralSpecialDomesticClass($info['specialDomestic']);
$info['specialDomesticName'] = $class->getName(); $info['specialDomesticName'] = $class->getName();
@@ -50,8 +34,7 @@ $now = $oNow->format('Y-m-d H:i:s');
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$eventEnv = $gameStor->getValues(['npcmode', 'startyear', 'year', 'month']); $npcmode = $gameStor->getValue('npcmode');
$npcmode = $eventEnv['npcmode'];
if($npcmode!=2){ if($npcmode!=2){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
@@ -60,7 +43,6 @@ if($npcmode!=2){
} }
$rawGeneral = $db->queryFirstRow('SELECT no, aux FROM general WHERE `owner` = %i', $userID); $rawGeneral = $db->queryFirstRow('SELECT no, aux FROM general WHERE `owner` = %i', $userID);
$currentTargetEnv = $rawGeneral ? $eventEnv : null;
if($rawGeneral){ if($rawGeneral){
$generalAux = Json::decode($rawGeneral['aux']); $generalAux = Json::decode($rawGeneral['aux']);
if(key_exists('next_change', $generalAux)&& $generalAux['next_change'] > $now){ if(key_exists('next_change', $generalAux)&& $generalAux['next_change'] > $now){
@@ -80,7 +62,7 @@ if($tokens){
foreach($tokens as $token){ foreach($tokens as $token){
$valid_until = $token['reserved_until']; $valid_until = $token['reserved_until'];
$info = Json::decode($token['info']); $info = Json::decode($token['info']);
putInfoText($info, $currentTargetEnv); putInfoText($info);
$info['uniqueName'] = $token['unique_name']; $info['uniqueName'] = $token['unique_name'];
$pick[] = $info; $pick[] = $info;
} }
@@ -101,7 +83,7 @@ $valid_until = null;
foreach(pickGeneralFromPool($db, $rng, $userID, 14) as $pickObj){ foreach(pickGeneralFromPool($db, $rng, $userID, 14) as $pickObj){
$valid_until = $pickObj->getValidUntil(); $valid_until = $pickObj->getValidUntil();
$info = $pickObj->getInfo(); $info = $pickObj->getInfo();
putInfoText($info, $currentTargetEnv); putInfoText($info);
$pick[] = $info; $pick[] = $info;
} }
sortTokens($pick);//좀 무식하지만.. sortTokens($pick);//좀 무식하지만..
+1 -9
View File
@@ -19,15 +19,7 @@ if(!prepareDir('data/file_cache')){
]); ]);
} }
$absolutePath = realpath('data/file_cache'); $storage = new \Nette\Caching\Storages\FileStorage('data/file_cache');
if(!$absolutePath){
Json::die([
'result'=>false,
'reason'=>'cache 불가'
]);
}
$storage = new \Nette\Caching\Storages\FileStorage($absolutePath);
$cache = new Cache($storage); $cache = new Cache($storage);
$serverID = UniqueConst::$serverID; $serverID = UniqueConst::$serverID;
+97 -128
View File
@@ -1,12 +1,9 @@
<?php <?php
namespace sammo; namespace sammo;
use sammo\Enums\GeneralColumn;
use sammo\Enums\GeneralLiteQueryMode; use sammo\Enums\GeneralLiteQueryMode;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\Enums\MessageType; use sammo\Enums\MessageType;
use sammo\Enums\PenaltyKey;
include "lib.php"; include "lib.php";
include "func.php"; include "func.php";
@@ -24,99 +21,92 @@ $userID = Session::getUserID();
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['startyear', 'year', 'month', 'scenario', 'killturn']); $gameStor->cacheValues(['startyear','year','month','scenario']);
$me = $db->queryFirstRow('SELECT no,npc,nation,officer_level,penalty from general where owner=%i', $userID); $me = $db->queryFirstRow('SELECT no,nation,officer_level from general where owner=%i', $userID);
$myOfficerLevel = $me['officer_level']; $myOfficerLevel = $me['officer_level'];
$nationID = $me['nation']; $nationID = $me['nation'];
$myPenalty = Json::decode($me['penalty'] ?? '{}');
//수뇌가 아니면 아무것도 할 수 없음 //수뇌가 아니면 아무것도 할 수 없음
if ($myOfficerLevel < 5) { if($myOfficerLevel < 5){
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => '수뇌가 아닙니다.' 'reason'=>'수뇌가 아닙니다.'
]); ]);
} }
if ($action === '추방' && $destGeneralID == 0) { if($action === '추방' && $destGeneralID==0){
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => '장수가 지정되지 않았습니다.' 'reason'=>'장수가 지정되지 않았습니다.'
]); ]);
} }
if ($destGeneralID == 0) { if($destGeneralID==0){
$general = new DummyGeneral(true); $general = new DummyGeneral(true);
$general->setVar('nation', $nationID); $general->setVar('nation', $nationID);
} else { }
else{
$general = General::createObjFromDB($destGeneralID); $general = General::createObjFromDB($destGeneralID);
if ($general instanceof DummyGeneral) { if($general instanceof DummyGeneral){
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => '올바르지 않은 장수입니다.' 'reason'=>'올바르지 않은 장수입니다.'
]); ]);
} }
if ($nationID != $general->getNationID()) { if($nationID != $general->getNationID()){
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => '아국 장수가 아닙니다' 'reason'=>'아국 장수가 아닙니다'
]); ]);
} }
} }
if ($officerLevel == 12) { if($officerLevel == 12){
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => '군주를 대상으로 할 수 없습니다.' 'reason'=>'군주를 대상으로 할 수 없습니다.'
]); ]);
} }
function do수뇌임명(General $general, int $targetOfficerLevel): ?string function do수뇌임명(General $general, int $targetOfficerLevel):?string{
{
global $myPenalty;
if ($myPenalty[PenaltyKey::NoChiefChange->value] ?? false) {
return '수뇌를 임명할 수 없는 상태입니다.';
}
$generalID = $general->getID(); $generalID = $general->getID();
$nationID = $general->getNationID(); $nationID = $general->getNationID();
if($general->hasPenalty(PenaltyKey::NoChief)){
return '수뇌가 될 수 없는 상태입니다.';
}
$db = DB::db(); $db = DB::db();
[$chiefSet, $nationLevel] = $db->queryFirstList('SELECT chief_set,level FROM nation WHERE nation = %i', $nationID); [$chiefSet, $nationLevel] = $db->queryFirstList('SELECT chief_set,level FROM nation WHERE nation = %i',$nationID);
// 임명가능 레벨 // 임명가능 레벨
$lv = getNationChiefLevel($nationLevel); $lv = getNationChiefLevel($nationLevel);
if ($targetOfficerLevel < $lv) { if($targetOfficerLevel < $lv){
return '임명불가능한 관직입니다.'; return '임명불가능한 관직입니다.';
} }
if (isOfficerSet($chiefSet, $targetOfficerLevel)) { if(isOfficerSet($chiefSet, $targetOfficerLevel)){
return '지금은 임명할 수 없습니다.'; return '지금은 임명할 수 없습니다.';
} }
//기존 장수 일반으로 //기존 장수 일반으로
$db->update('general', [ $db->update('general', [
'officer_level' => 1, 'officer_level'=>1,
'officer_city' => 0, 'officer_city'=>0,
], 'nation=%i AND officer_level=%i AND no!=%i', $nationID, $targetOfficerLevel, $generalID); ], 'nation=%i AND officer_level=%i AND no!=%i', $nationID, $targetOfficerLevel, $generalID);
if ($targetOfficerLevel === 11) { if($targetOfficerLevel === 11){
} else if ($targetOfficerLevel % 2 == 0) {
if ($general->getVar('strength') < GameConst::$chiefStatMin) { }
else if($targetOfficerLevel % 2 == 0){
if($general->getVar('strength') < GameConst::$chiefStatMin){
return '무력이 부족합니다.'; return '무력이 부족합니다.';
} }
} else {
if ($general->getVar('intel') < GameConst::$chiefStatMin) { }
else{
if($general->getVar('intel') < GameConst::$chiefStatMin){
return '지력이 부족합니다.'; return '지력이 부족합니다.';
} }
} }
@@ -125,59 +115,52 @@ function do수뇌임명(General $general, int $targetOfficerLevel): ?string
$general->setVar('officer_level', $targetOfficerLevel); $general->setVar('officer_level', $targetOfficerLevel);
$general->setVar('officer_city', 0); $general->setVar('officer_city', 0);
$db->update('nation', [ $db->update('nation', [
'chief_set' => $db->sqleval('chief_set | %i', doOfficerSet(0, $targetOfficerLevel)), 'chief_set'=> $db->sqleval('chief_set | %i', doOfficerSet(0, $targetOfficerLevel)),
], 'nation=%i', $nationID); ], 'nation=%i', $nationID);
$general->applyDB($db); $general->applyDB($db);
return null; return null;
} }
function do도시임명(General $general, int $cityID, int $targetOfficerLevel): ?string function do도시임명(General $general, int $cityID, int $targetOfficerLevel):?string{
{
global $myPenalty;
$nationID = $general->getNationID(); $nationID = $general->getNationID();
$db = DB::db(); $db = DB::db();
if (CityConst::byID($cityID) === null) { if(CityConst::byID($cityID) === null){
return '올바르지 않은 도시입니다'; return '올바르지 않은 도시입니다';
} }
if ($general->getVar(GeneralColumn::officer_level) >= 4 && ($myPenalty[PenaltyKey::NoChiefChange->value] ?? false)) {
return '수뇌인 장수를 변경할 수 없는 상태입니다.';
}
$officerSet = $db->queryFirstField('SELECT officer_set FROM city WHERE nation=%i AND city=%i', $nationID, $cityID); $officerSet = $db->queryFirstField('SELECT officer_set FROM city WHERE nation=%i AND city=%i', $nationID, $cityID);
if ($officerSet === null) { if($officerSet === null){
return '아국 도시가 아닙니다'; return '아국 도시가 아닙니다';
} }
if (isOfficerSet($officerSet, $targetOfficerLevel)) { if(isOfficerSet($officerSet, $targetOfficerLevel)){
return '이미 다른 장수가 임명되어있습니다'; return '이미 다른 장수가 임명되어있습니다';
} }
//기존 장수 일반으로 //기존 장수 일반으로
$db->update('general', [ $db->update('general', [
'officer_level' => 1, 'officer_level'=>1,
'officer_city' => 0, 'officer_city'=>0,
], 'officer_level=%i AND officer_city=%i AND no!=%i', $targetOfficerLevel, $cityID, $general->getID()); ], 'officer_level=%i AND officer_city=%i AND no!=%i', $targetOfficerLevel, $cityID, $general->getID());
if ($general instanceof DummyGeneral) { if($general instanceof DummyGeneral){
return null; return null;
} }
if ($targetOfficerLevel === 4 && $general->getVar('strength') < GameConst::$chiefStatMin) { if($targetOfficerLevel === 4 && $general->getVar('strength') < GameConst::$chiefStatMin){
return '무력이 부족합니다.'; return '무력이 부족합니다.';
} }
if ($targetOfficerLevel === 3 && $general->getVar('intel') < GameConst::$chiefStatMin) { if($targetOfficerLevel === 3 && $general->getVar('intel') < GameConst::$chiefStatMin){
return '지력이 부족합니다.'; return '지력이 부족합니다.';
} }
//신임 장수 //신임 장수
$db->update('city', [ $db->update('city', [
'officer_set' => $db->sqleval('officer_set | %i', doOfficerSet(0, $targetOfficerLevel)), 'officer_set'=>$db->sqleval('officer_set | %i', doOfficerSet(0, $targetOfficerLevel)),
], 'city=%i AND nation=%i', $cityID, $nationID); ], 'city=%i AND nation=%i', $cityID, $nationID);
$general->setVar('officer_level', $targetOfficerLevel); $general->setVar('officer_level', $targetOfficerLevel);
$general->setVar('officer_city', $cityID); $general->setVar('officer_city', $cityID);
@@ -186,25 +169,22 @@ function do도시임명(General $general, int $cityID, int $targetOfficerLevel):
return null; return null;
} }
function do추방(General $general, int $myOfficerLevel): ?string function do추방(General $general, int $myOfficerLevel):?string{
{
$generalID = $general->getID(); $generalID = $general->getID();
$generalName = $general->getVar('name'); $generalName = $general->getVar('name');
$nationID = $general->getNationID(); $nationID = $general->getNationID();
if ($myPenalty[PenaltyKey::NoBanGeneral->value] ?? false) {
return '추방할 수 없는 상태입니다.';
}
//추방할사람이 외교권자이면 불가 //추방할사람이 외교권자이면 불가
$permission = checkSecretPermission($general->getRaw()); $permission = checkSecretPermission($general->getRaw());
if ($permission == 4) { if($permission == 4){
return '외교권자는 추방할 수 없습니다.'; return '외교권자는 추방할 수 없습니다.';
} }
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$env = $gameStor->getValues(['startyear', 'year', 'month', 'scenario']); $env = $gameStor->getValues(['startyear','year','month','scenario']);
$nation = $db->queryFirstRow('SELECT name,chief_set,color FROM nation WHERE nation=%i', $nationID); $nation = $db->queryFirstRow('SELECT name,chief_set,color FROM nation WHERE nation=%i', $nationID);
$nationName = $nation['name']; $nationName = $nation['name'];
@@ -212,17 +192,17 @@ function do추방(General $general, int $myOfficerLevel): ?string
$logger = $general->getLogger(); $logger = $general->getLogger();
//이미 지정했다면 무시 //이미 지정했다면 무시
if (isOfficerSet($nation['chief_set'], $myOfficerLevel) || ($myOfficerLevel == 0 && $myOfficerLevel == 12)) { if(isOfficerSet($nation['chief_set'], $myOfficerLevel) || ($myOfficerLevel == 0 && $myOfficerLevel == 12)) {
return '이미 추방 권한을 사용했습니다.'; return '이미 추방 권한을 사용했습니다.';
} }
$gold = 0; $gold = 0;
$rice = 0; $rice = 0;
// 금쌀1000이상은 남김 // 금쌀1000이상은 남김
if ($general->getVar('gold') > GameConst::$defaultGold) { if($general->getVar('gold') > GameConst::$defaultGold) {
$gold = $general->getVar('gold') - GameConst::$defaultGold; $gold = $general->getVar('gold') - GameConst::$defaultGold;
$general->setVar('gold', GameConst::$defaultGold); $general->setVar('gold', GameConst::$defaultGold);
} }
if ($general->getVar('rice') > GameConst::$defaultRice) { if($general->getVar('rice') > GameConst::$defaultRice) {
$rice = $general->getVar('rice') - GameConst::$defaultRice; $rice = $general->getVar('rice') - GameConst::$defaultRice;
$general->setVar('rice', GameConst::$defaultRice); $general->setVar('rice', GameConst::$defaultRice);
} }
@@ -240,26 +220,27 @@ function do추방(General $general, int $myOfficerLevel): ?string
$logger->pushGeneralActionLog("<D><b>{$nationName}</b></>에서 <R>추방</>당했습니다.", ActionLogger::PLAIN); $logger->pushGeneralActionLog("<D><b>{$nationName}</b></>에서 <R>추방</>당했습니다.", ActionLogger::PLAIN);
// 명성/공헌 N*10%감소 // 명성/공헌 N*10%감소
if ($env['year'] <= $env['startyear'] && $general->getNPCType() < 2) { if($env['year'] <= $env['startyear'] && $general->getNPCType() < 2){
$general->setVar('makelimit', $oldMakeLimit); $general->setVar('makelimit', $oldMakeLimit);
} else { }
else{
$betrayCnt = $general->getVar('betray'); $betrayCnt = $general->getVar('betray');
$general->addExperience(-$general->getVar('experience') * 0.15 * $betrayCnt); $general->addExperience(-$general->getVar('experience')*0.15*$betrayCnt);
$general->addDedication(-$general->getVar('dedication') * 0.15 * $betrayCnt); $general->addDedication(-$general->getVar('dedication')*0.15*$betrayCnt);
$general->increaseVarWithLimit('betray', 1, null, GameConst::$maxBetrayCnt); $general->increaseVarWithLimit('betray', 1, null, GameConst::$maxBetrayCnt);
} }
//부대장일 경우 //부대장일 경우
if ($general->getVar('troop') == $generalID) { if($general->getVar('troop') == $generalID){
// 모두 탈퇴 // 모두 탈퇴
$db->update('general', [ $db->update('general', [
'troop' => 0, 'troop'=>0,
], 'troop = %i', $generalID); ], 'troop = %i', $generalID);
$db->delete('troop', 'troop_leader=%i', $generalID); $db->delete('troop', 'troop_leader=%i', $generalID);
} }
$general->setVar('troop', 0); $general->setVar('troop', 0);
if ($general->getNPCType() >= 2) { if($general->getNPCType() >= 2){
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize( $rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed, UniqueConst::$hiddenSeed,
'BanNPC', 'BanNPC',
@@ -298,24 +279,24 @@ function do추방(General $general, int $myOfficerLevel): ?string
} }
} }
if ($env['year'] < $env['startyear'] + 3) { if($env['year'] < $env['startyear']+3) {
//초반엔 군주 부상 증가(엔장 임관지양) //초반엔 군주 부상 증가(엔장 임관지양)
$db->update('general', [ $db->update('general', [
'injury' => $db->sqleval('least(injury + 1, %i)', 80), 'injury'=>$db->sqleval('least(injury + 1, %i)', 80),
], 'nation=%i AND officer_level=12', $nationID); ], 'nation=%i AND officer_level=12', $nationID);
$db->update('nation', [ $db->update('nation', [
'gennum' => $db->sqleval('gennum - %i', $general->getNPCType() != 5 ? 1 : 0), 'gennum'=>$db->sqleval('gennum - %i', $general->getNPCType()!=5?1:0),
'gold' => $db->sqleval('gold + %i', $gold), 'gold'=>$db->sqleval('gold + %i', $gold),
'rice' => $db->sqleval('rice + %i', $rice), 'rice'=>$db->sqleval('rice + %i', $rice),
], 'nation = %i', $nationID); ], 'nation = %i', $nationID);
} else { } else {
//이번분기는 추방불가(초반 제외) //이번분기는 추방불가(초반 제외)
$db->update('nation', [ $db->update('nation', [
'chief_set' => $db->sqleval('chief_set | %i', doOfficerSet(0, $myOfficerLevel)), 'chief_set'=>$db->sqleval('chief_set | %i', doOfficerSet(0, $myOfficerLevel)),
'gennum' => $db->sqleval('gennum - %i', $general->getNPCType() != 5 ? 1 : 0), 'gennum'=>$db->sqleval('gennum - %i', $general->getNPCType()!=5?1:0),
'gold' => $db->sqleval('gold + %i', $gold), 'gold'=>$db->sqleval('gold + %i', $gold),
'rice' => $db->sqleval('rice + %i', $rice), 'rice'=>$db->sqleval('rice + %i', $rice),
], 'nation = %i', $nationID); ], 'nation = %i', $nationID);
} }
@@ -327,74 +308,62 @@ function do추방(General $general, int $myOfficerLevel): ?string
if ($action == "임명") { if($action == "임명") {
if (2 <= $officerLevel && $officerLevel <= 4) { if(2 <= $officerLevel && $officerLevel <= 4){
if (!$destCityID) { if(!$destCityID){
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => '도시가 지정되지 않았습니다.' 'reason'=>'도시가 지정되지 않았습니다.'
]); ]);
} }
$result = do도시임명($general, $destCityID, $officerLevel); $result = do도시임명($general, $destCityID, $officerLevel);
if ($result !== null) { if($result !== null){
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => $result 'reason'=>$result
]); ]);
} }
//수뇌의 삭턴 리필
$db->update('general', [
'killturn' => $db->sqleval('GREATEST(%b, %i)', 'killturn', $gameStor->getValue('killturn')),
], 'no=%i', $me['no']);
Json::die([ Json::die([
'result' => true, 'result'=>true,
'reason' => 'success' 'reason'=>'success'
]); ]);
} }
if (5 <= $officerLevel && $officerLevel < 12) { if(5 <= $officerLevel && $officerLevel < 12){
$result = do수뇌임명($general, $officerLevel); $result = do수뇌임명($general, $officerLevel);
if ($result !== null) { if($result !== null){
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => $result 'reason'=>$result
]); ]);
} }
//수뇌의 삭턴 리필
$db->update('general', [
'killturn' => $db->sqleval('GREATEST(%b, %i)', 'killturn', $gameStor->getValue('killturn')),
], 'no=%i', $me['no']);
Json::die([ Json::die([
'result' => true, 'result'=>true,
'reason' => 'success' 'reason'=>'success'
]); ]);
} }
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => '올바르지 않은 지정입니다.' 'reason'=>'올바르지 않은 지정입니다.'
]); ]);
} }
if ($action == "추방") { if($action == "추방") {
$result = do추방($general, $myOfficerLevel); $result = do추방($general, $myOfficerLevel);
if ($result !== null) { if($result !== null){
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => $result 'reason'=>$result
]); ]);
} }
//수뇌의 삭턴 리필
$db->update('general', [
'killturn' => $db->sqleval('GREATEST(%b, %i)', 'killturn', $gameStor->getValue('killturn')),
], 'no=%i', $me['no']);
Json::die([ Json::die([
'result' => true, 'result'=>true,
'reason' => 'success' 'reason'=>'success'
]); ]);
} }
Json::die([ Json::die([
'result' => false, 'result'=>false,
'reason' => '올바르지 않은 명령입니다.' 'reason'=>'올바르지 않은 명령입니다.'
]); ]);
+1 -1
View File
@@ -8,7 +8,7 @@ include('func.php');
$session = Session::requireLogin([])->setReadOnly(); $session = Session::requireLogin([])->setReadOnly();
if(Session::getInstance()->userGrade < 6){ if(Session::getInstance()->userGrade < 5){
Json::die([ Json::die([
'reason'=>'권한이 부족합니다.' 'reason'=>'권한이 부족합니다.'
]); ]);
+1 -1
View File
@@ -41,7 +41,7 @@ $penaltyInfo = array_merge($memberPenalty['any'] ?? [], $memberPenalty[DB::prefi
$penalty = []; $penalty = [];
foreach($penaltyInfo as $penaltyKey => $penaltyValue){ foreach($penaltyInfo as $penaltyKey => $penaltyValue){
if(($penaltyValue['expire'] ?? 0) > TimeUtil::now()){ if($penaltyValue['expire'] ?? 0 > TimeUtil::now()){
$penalty[$penaltyKey] = $penaltyValue['value']; $penalty[$penaltyKey] = $penaltyValue['value'];
} }
} }
+7 -79
View File
@@ -8,20 +8,10 @@ WebUtil::requireAJAX();
$pick = Util::getPost('pick'); $pick = Util::getPost('pick');
$leadership = Util::getPost('leadership', 'int', GameConst::$defaultStatMin); $leadership = Util::getPost('leadership', 'int', GameConst::$defaultStatMin);
$isCentennialAllStar = CentennialAllStarGrowthService::isActive(); $strength = Util::getPost('leadership', 'int', GameConst::$defaultStatMin);
$strength = Util::getPost( $intel = Util::getPost('leadership', 'int', GameConst::$defaultStatMin);
$isCentennialAllStar ? 'strength' : 'leadership',
'int',
GameConst::$defaultStatMin
);
$intel = Util::getPost(
$isCentennialAllStar ? 'intel' : 'leadership',
'int',
GameConst::$defaultStatMin
);
$personal = Util::getPost('personal', 'string', null); $personal = Util::getPost('personal', 'string', null);
$use_own_picture = Util::getPost('use_own_picture', 'bool', false); $use_own_picture = Util::getPost('use_own_picture', 'bool', false);
$pictureSource = Util::getPost('picture_source', 'string', 'selected');
if(!$pick){ if(!$pick){
@@ -70,36 +60,13 @@ if(!$selectInfo){
} }
$selectInfo = Json::decode($selectInfo); $selectInfo = Json::decode($selectInfo);
$ownerInfo = RootDB::db()->queryFirstRow( $ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID);
'SELECT `name`,`picture`,`imgsvr`,`grade` FROM member WHERE `NO`=%i',
$userID
);
if(!$ownerInfo){ if(!$ownerInfo){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
'reason'=>'멤버 정보를 가져오지 못했습니다.' 'reason'=>'멤버 정보를 가져오지 못했습니다.'
]); ]);
} }
if ($isCentennialAllStar) {
if (!in_array($pictureSource, ['selected', 'own'], true)) {
Json::die([
'result' => false,
'reason' => '올바르지 않은 전콘 선택입니다.',
]);
}
if ($pictureSource === 'own') {
$canUseOwnPicture = in_array('picture', GameConst::$generalPoolAllowOption, true)
&& $env['show_img_level'] >= 1
&& $ownerInfo['grade'] >= 1
&& $ownerInfo['picture'] !== '';
if (!$canUseOwnPicture) {
Json::die([
'result' => false,
'reason' => '사용할 수 있는 내 전콘이 없습니다.',
]);
}
}
}
$gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2'); $gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2');
@@ -113,25 +80,12 @@ if ($gencount >= $maxgeneral) {
$poolClass = getGeneralPoolClass(GameConst::$targetGeneralPool); $poolClass = getGeneralPoolClass(GameConst::$targetGeneralPool);
/** @var AbsGeneralPool */ /** @var AbsGeneralPool */
if ($isCentennialAllStar) { $pickedGeneral = new $poolClass($db, $selectInfo, $now);
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'selectPickedGeneral',
$userID,
$pick
)));
$pickedGeneral = new $poolClass($db, $rng, $selectInfo, $now);
} else {
$pickedGeneral = new $poolClass($db, $selectInfo, $now);
}
$builder = $pickedGeneral->getGeneralBuilder(); $builder = $pickedGeneral->getGeneralBuilder();
if ($isCentennialAllStar) {
CentennialAllStarGrowthService::prepareInitialUser($builder, $selectInfo);
}
foreach(GameConst::$generalPoolAllowOption as $allowOption){ foreach(GameConst::$generalPoolAllowOption as $allowOption){
if($allowOption == 'stat' && !$isCentennialAllStar){ if($allowOption == 'stat'){
$leadership = Util::valueFit($leadership, GameConst::$defaultStatMin, GameConst::$defaultStatMax); $leadership = Util::valueFit($leadership, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
$strength = Util::valueFit($strength, GameConst::$defaultStatMin, GameConst::$defaultStatMax); $strength = Util::valueFit($strength, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
$intel = Util::valueFit($intel, GameConst::$defaultStatMin, GameConst::$defaultStatMax); $intel = Util::valueFit($intel, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
@@ -144,23 +98,14 @@ foreach(GameConst::$generalPoolAllowOption as $allowOption){
} }
$builder->setStat($leadership, $strength, $intel); $builder->setStat($leadership, $strength, $intel);
} }
else if( else if($allowOption == 'picture' && $use_own_picture){
$allowOption == 'picture'
&& (
(!$isCentennialAllStar && $use_own_picture)
|| ($isCentennialAllStar && $pictureSource === 'own')
)
){
$builder->setPicture($ownerInfo['imgsvr'], $ownerInfo['picture']); $builder->setPicture($ownerInfo['imgsvr'], $ownerInfo['picture']);
} }
else if($allowOption == 'ego'){ else if($allowOption == 'ego'){
if(!$personal || $personal == 'Random'){ if(!$personal || $personal == 'Random'){
$personal = Util::choiceRandom(GameConst::$availablePersonality); $personal = Util::choiceRandom(GameConst::$availablePersonality);
} }
$invalidPersonal = $isCentennialAllStar if(!array_search($personal, GameConst::$availablePersonality)){
? !in_array($personal, GameConst::$availablePersonality, true)
: !array_search($personal, GameConst::$availablePersonality);
if($invalidPersonal){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
'reason'=>'올바르지 않은 성격입니다.' 'reason'=>'올바르지 않은 성격입니다.'
@@ -178,23 +123,6 @@ $builder->setKillturn(5);
$builder->setNPCType(0); $builder->setNPCType(0);
$builder->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $env['turnterm'])); $builder->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $env['turnterm']));
$builder->fillRemainSpecAsZero($env); $builder->fillRemainSpecAsZero($env);
if ($isCentennialAllStar) {
$candidateCities = $db->queryFirstColumn(
'SELECT city FROM city WHERE level >= 5 AND level <= 6 AND nation = 0'
);
if (!$candidateCities) {
$candidateCities = $db->queryFirstColumn(
'SELECT city FROM city WHERE level >= 5 AND level <= 6'
);
}
if (!$candidateCities) {
Json::die([
'result' => false,
'reason' => '장수를 생성할 소·중성이 없습니다.',
]);
}
$builder->setCityID($rng->choice($candidateCities));
}
$builder->build($env); $builder->build($env);
$generalID = $builder->getGeneralID(); $generalID = $builder->getGeneralID();
if(!$generalID){ if(!$generalID){
+14 -36
View File
@@ -1,5 +1,4 @@
<?php <?php
namespace sammo; namespace sammo;
include "lib.php"; include "lib.php";
@@ -25,13 +24,14 @@ if ($defence_train <= 40) {
$defence_train = 40; $defence_train = 40;
} }
if ($defence_train <= 90) { if($defence_train <= 90){
$defence_train = Util::round($defence_train, -1); $defence_train = Util::round($defence_train, -1);
} else { }
else{
$defence_train = 999; $defence_train = 999;
} }
if ($tnmt < 0 || $tnmt > 1) { if($tnmt < 0 || $tnmt > 1){
$tnmt = 1; $tnmt = 1;
} }
@@ -39,8 +39,8 @@ $db = DB::db();
$me = General::createObjFromDB($generalID); $me = General::createObjFromDB($generalID);
if ($defence_train !== $me->getVar('defence_train')) { if($defence_train !== $me->getVar('defence_train')){
if ($defence_train == 999) { if($defence_train == 999){
$me->increaseVar('myset', -1); $me->increaseVar('myset', -1);
$me->setVar('defence_train', $defence_train); $me->setVar('defence_train', $defence_train);
$affectedTrain = -3; $affectedTrain = -3;
@@ -49,7 +49,8 @@ if ($defence_train !== $me->getVar('defence_train')) {
$affectedAtmos = $me->onCalcDomestic('changeDefenceTrain', "atmos{$defence_train}", $affectedAtmos); $affectedAtmos = $me->onCalcDomestic('changeDefenceTrain', "atmos{$defence_train}", $affectedAtmos);
$me->increaseVarWithLimit('train', $affectedTrain, 20, GameConst::$maxTrainByWar); $me->increaseVarWithLimit('train', $affectedTrain, 20, GameConst::$maxTrainByWar);
$me->increaseVarWithLimit('atmos', $affectedAtmos, 20, GameConst::$maxAtmosByWar); $me->increaseVarWithLimit('atmos', $affectedAtmos, 20, GameConst::$maxAtmosByWar);
} else { }
else{
$me->increaseVar('myset', -1); $me->increaseVar('myset', -1);
$me->setVar('defence_train', $defence_train); $me->setVar('defence_train', $defence_train);
} }
@@ -59,44 +60,21 @@ $me->setAuxVar('use_treatment', Util::valueFit($use_treatment, 10, 100));
$me->setAuxVar('use_auto_nation_turn', $use_auto_nation_turn); $me->setAuxVar('use_auto_nation_turn', $use_auto_nation_turn);
$me->setVar('tnmt', $tnmt); $me->setVar('tnmt', $tnmt);
if ($me->getNPCType() == 1 && $detachNPC) { if($me->getNPCType() == 1 && $detachNPC){
$turnterm = $gameStor->turnterm; $turnterm = $gameStor->turnterm;
if ($turnterm < 10) { if($turnterm < 10){
$targetKillTurn = 30 / $turnterm; $targetKillTurn = 30 / $turnterm;
} else { }
else{
$targetKillTurn = 60 / $turnterm; $targetKillTurn = 60 / $turnterm;
} }
$me->setVar('killturn', $targetKillTurn); $me->setVar('killturn', $targetKillTurn);
} }
$rootDB = RootDB::db();
//회원 테이블에서 정보확인
$member = $rootDB->queryFirstRow('SELECT `no`, id, picture, grade, `name`, imgsvr, `penalty` FROM member WHERE no=%i', $userID);
if (!$member) {
//이런 경우가 있나?
//TODO: 로그 남겨둘 것
Json::die([
'result' => false,
'reason' => '회원 정보를 찾을 수 없습니다.'
]);
}
$memberPenalty = Json::decode($member['penalty'] ?? "{}");
$penaltyInfo = array_merge($memberPenalty['any'] ?? [], $memberPenalty[DB::prefix()] ?? []);
$penalty = [];
foreach ($penaltyInfo as $penaltyKey => $penaltyValue) {
if (($penaltyValue['expire'] ?? 0) > TimeUtil::now()) {
$penalty[$penaltyKey] = $penaltyValue['value'];
}
}
$me->setVar('penalty', Json::encode($penalty));
$me->applyDB($db); $me->applyDB($db);
Json::die([ Json::die([
'result' => true, 'result'=>true,
'reason' => 'success' 'reason'=>'success'
]); ]);
+24 -76
View File
@@ -7,7 +7,6 @@ include "func.php";
WebUtil::requireAJAX(); WebUtil::requireAJAX();
$pick = Util::getPost('pick'); $pick = Util::getPost('pick');
$pictureSource = Util::getPost('picture_source', 'string', 'selected');
if(!$pick){ if(!$pick){
Json::die([ Json::die([
@@ -36,20 +35,10 @@ if(!$generalID){
list( list(
$year, $year,
$month, $month,
$startYear,
$maxgeneral, $maxgeneral,
$npcmode, $npcmode,
$turnterm, $turnterm
$showImgLevel ) = $gameStor->getValuesAsArray(['year', 'month', 'maxgeneral', 'npcmode', 'turnterm']);
) = $gameStor->getValuesAsArray([
'year',
'month',
'startyear',
'maxgeneral',
'npcmode',
'turnterm',
'show_img_level',
]);
if($npcmode!=2){ if($npcmode!=2){
Json::die([ Json::die([
@@ -66,10 +55,7 @@ if(!$info){
]); ]);
} }
$ownerInfo = RootDB::db()->queryFirstRow( $ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID);
'SELECT `name`,`picture`,`imgsvr`,`grade` FROM member WHERE `NO`=%i',
$userID
);
if(!$ownerInfo){ if(!$ownerInfo){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
@@ -78,27 +64,6 @@ if(!$ownerInfo){
} }
$info = Json::decode($info); $info = Json::decode($info);
$isCentennialAllStar = CentennialAllStarGrowthService::isActive();
if ($isCentennialAllStar) {
if (!in_array($pictureSource, ['current', 'own', 'selected'], true)) {
Json::die([
'result' => false,
'reason' => '올바르지 않은 전콘 선택입니다.',
]);
}
if ($pictureSource === 'own') {
$canUseOwnPicture = in_array('picture', GameConst::$generalPoolAllowOption, true)
&& $showImgLevel >= 1
&& $ownerInfo['grade'] >= 1
&& $ownerInfo['picture'] !== '';
if (!$canUseOwnPicture) {
Json::die([
'result' => false,
'reason' => '사용할 수 있는 내 전콘이 없습니다.',
]);
}
}
}
$generalObj = General::createObjFromDB($generalID); $generalObj = General::createObjFromDB($generalID);
@@ -136,51 +101,34 @@ $db->update('select_pool',[
'reserved_until'=>null, 'reserved_until'=>null,
], '(owner=%i or reserved_until < %s) AND general_id is NULL', $userID, $now); ], '(owner=%i or reserved_until < %s) AND general_id is NULL', $userID, $now);
if ($isCentennialAllStar) { if(key_exists('leadership', $info)){
CentennialAllStarGrowthService::prepareLegacyUserReselection($generalObj); $generalObj->updateVar('leadership', $info['leadership']);
CentennialAllStarGrowthService::applyTarget($generalObj, $info, [ $generalObj->updateVar('strength', $info['strength']);
'startyear' => $startYear, $generalObj->updateVar('intel', $info['intel']);
'year' => $year,
'month' => $month,
]);
} else {
if(key_exists('leadership', $info)){
$generalObj->updateVar('leadership', $info['leadership']);
$generalObj->updateVar('strength', $info['strength']);
$generalObj->updateVar('intel', $info['intel']);
}
if(key_exists('dex', $info)){
$generalObj->updateVar('dex1', $info['dex'][0]);
$generalObj->updateVar('dex2', $info['dex'][1]);
$generalObj->updateVar('dex3', $info['dex'][2]);
$generalObj->updateVar('dex4', $info['dex'][3]);
$generalObj->updateVar('dex5', $info['dex'][4]);
}
if(key_exists('ego', $info)){
$generalObj->updateVar('personal', $info['ego']);
}
if(key_exists('specialDomestic', $info)){
$generalObj->updateVar('special', $info['specialDomestic']);
}
if(key_exists('specialWar', $info)){
$generalObj->updateVar('special2', $info['specialWar']);
}
} }
if ($isCentennialAllStar) { if(key_exists('picture', $info)){
if ($pictureSource === 'own') {
$generalObj->updateVar('imgsvr', $ownerInfo['imgsvr']);
$generalObj->updateVar('picture', $ownerInfo['picture']);
} elseif ($pictureSource === 'selected' && key_exists('picture', $info)) {
$generalObj->updateVar('imgsvr', $info['imgsvr']);
$generalObj->updateVar('picture', $info['picture']);
}
} elseif(key_exists('picture', $info)){
$generalObj->updateVar('imgsvr', $info['imgsvr']); $generalObj->updateVar('imgsvr', $info['imgsvr']);
$generalObj->updateVar('picture', $info['picture']); $generalObj->updateVar('picture', $info['picture']);
} }
if(key_exists('generalName', $info)){ if(key_exists('generalName', $info)){
$generalObj->updateVar('name', $info['generalName']); $generalObj->updateVar('name', $info['generalName']);
} }
if(key_exists('dex', $info)){
$generalObj->updateVar('dex1', $info['dex'][0]);
$generalObj->updateVar('dex2', $info['dex'][1]);
$generalObj->updateVar('dex3', $info['dex'][2]);
$generalObj->updateVar('dex4', $info['dex'][3]);
$generalObj->updateVar('dex5', $info['dex'][4]);
}
if(key_exists('ego', $info)){
$generalObj->updateVar('personal', $info['ego']);
}
if(key_exists('specialDomestic', $info)){
$generalObj->updateVar('special', $info['specialDomestic']);
}
if(key_exists('specialWar', $info)){
$generalObj->updateVar('special2', $info['specialWar']);
}
$generalObj->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $turnterm)); $generalObj->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $turnterm));
$userNick = $ownerInfo['name']; $userNick = $ownerInfo['name'];
+2 -3
View File
@@ -51,9 +51,8 @@ function MessageBox($str)
function LogText($prefix, $variable) function LogText($prefix, $variable)
{ {
return; $text = sprintf('%s : %s'."\r\n", $prefix, TVarDumper::dump($variable));
//$text = sprintf('%s : %s'."\r\n", $prefix, TVarDumper::dump($variable)); file_put_contents(ROOT.'/d_log/'.UniqueConst::$serverName.'_dbg_logs.txt', $text, FILE_APPEND);
//file_put_contents(ROOT.'/d_log/'.UniqueConst::$serverName.'_dbg_logs.txt', $text, FILE_APPEND);
} }
function prepareDir(string $dirPath, bool $forceCreate=true):bool{ function prepareDir(string $dirPath, bool $forceCreate=true):bool{
+430
View File
@@ -0,0 +1,430 @@
<?php
namespace sammo;
use sammo\DTO\VoteInfo;
use sammo\Enums\GeneralQueryMode;
include "lib.php";
include "func.php";
$session = Session::requireLogin()->loginGame()->setReadOnly();
$userID = Session::getUserID();
increaseRefresh("메인", 1);
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
if (!$userID) {
header('Location:..');
die();
}
//턴 실행.
TurnExecutionHelper::executeAllCommand();
if (!$session->isGameLoggedIn()) {
header('Location:..');
die();
}
$me = $db->queryFirstRow(
'SELECT no,refresh_score,turntime,newmsg,newvote,`officer_level` FROM `general`
LEFT JOIN general_access_log AS l ON `general`.no = l.general_id WHERE owner = %i',
$userID
);
//그새 사망이면
if ($me === null) {
$session->logoutGame();
header('Location: ../');
die();
}
$gameStor->cacheAll(true);
if ($me['newmsg'] == 1 || $me['newvote'] == 1) {
$db->update('general', [
'newmsg' => 0,
'newvote' => 0
], 'owner=%i', $userID);
}
$plock = boolval($db->queryFirstField('SELECT plock FROM plock WHERE `type`="GAME" LIMIT 1'));
$limitState = checkLimit($me['refresh_score']);
if ($limitState >= 2) {
printLimitMsg($me['turntime']);
exit();
}
$generalObj = General::createObjFromDB($me['no'], null, GeneralQueryMode::FullWithAccessLog);
$generalObj->setRawCity($db->queryFirstRow('SELECT * FROM city WHERE city = %i', $generalObj->getCityID()));
$scenario = $gameStor->scenario_text;
$nationID = $generalObj->getNationID();
if ($nationID) {
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
$nationStor->cacheAll();
}
if ($gameStor->extended_general == 0) {
$extend = "표준";
} else {
$extend = "확장";
}
if ($gameStor->fiction == 0) {
$fiction = "사실";
} else {
$fiction = "가상";
}
if ($gameStor->npcmode == 0) {
$npcmode = "불가능";
} else if ($gameStor->npcmode == 1) {
$npcmode = "가능";
} else {
$npcmode = "선택 생성";
}
$color = "cyan";
$serverName = UniqueConst::$serverName;
$serverCnt = $gameStor->server_cnt;
$auctionCount = $db->queryFirstField('SELECT count(*) FROM ng_auction WHERE finished = 0');
$isTournamentActive = $gameStor->tournament > 0;
$isTournamentApplicationOpen = $gameStor->tournament == 1;
$isBettingActive = $gameStor->tournament == 6;
$myNationStatic = getNationStaticInfo($generalObj->getNationID());
$nationColorType = substr($myNationStatic['color'] ?? '#000000', 1);
$autorunUser = ($gameStor->autorun_user) ?? [];
$otherTextInfo = [];
if ($gameStor->join_mode == 'onlyRandom') {
$otherTextInfo[] = '랜덤 임관 전용';
}
if ($autorunUser['limit_minutes'] ?? false) {
$otherTextInfo[] = getAutorunInfo($autorunUser);
}
if (!$otherTextInfo) {
$otherTextInfo = '표준';
} else {
$otherTextInfo = join(', ', $otherTextInfo);
}
$lastVoteID = $gameStor->lastVote;
$lastVote = null;
if ($lastVoteID) {
$voteStor = KVStorage::getStorage($db, 'vote');
$lastVote = VoteInfo::fromArray($voteStor->getValue("vote_{$lastVoteID}"));
if ($lastVote->endDate && $lastVote->endDate < TimeUtil::now()) {
$lastVote = null;
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title><?= $serverName ?>: 메인</title>
<meta charset="UTF-8">
<meta name="color-scheme" content="dark">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=500" />
<?= WebUtil::printJS('../d_shared/common_path.js') ?>
<?= WebUtil::printJS('d_shared/base_map.js') ?>
<?= WebUtil::printCSS('../d_shared/common.css') ?>
<?= WebUtil::printDist('vue', 'v_main', true) ?>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css" />
<?= WebUtil::printCSS('css/map.css') ?>
<?= WebUtil::printStaticValues([
//deprecated, staticValues를 이용할 것
'serverName' => $serverName,
'serverCnt' => $serverCnt,
'serverNick' => DB::prefix(),
'serverID' => UniqueConst::$serverID,
'maxTurn' => GameConst::$maxTurn,
'maxPushTurn' => 12,
'serverNow' => TimeUtil::now(false),
'lastExecuted' => $gameStor->turntime,
'isLocked' => $plock,
'isTournamentActive' => $isTournamentActive,
'isBettingActive' => $isBettingActive,
'staticValues' => [
'serverName' => $serverName,
'serverCnt' => $serverCnt,
'serverNick' => DB::prefix(),
'serverID' => UniqueConst::$serverID,
'maxTurn' => GameConst::$maxTurn,
'maxPushTurn' => 12,
'serverNow' => TimeUtil::now(false),
'lastExecuted' => $gameStor->turntime,
'isLocked' => $plock,
'isTournamentActive' => $isTournamentActive,
'isBettingActive' => $isBettingActive,
'mapName' => GameConst::$mapName,
'unitSet' => GameConst::$unitSet,
]
]) ?>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
</head>
<body class="img_back sam-color-<?= $nationColorType ?>">
<div id="container" class="bg0">
<div class="row toolbars gx-0">
<div class="buttonPlate"><?= allButton($gameStor->npcmode == 1) ?></button></div>
</div>
<div class="tb_layout row gx-0">
<div id="server_title" class="row py-2 center">
<h3>삼국지 모의전투 HiDCHe <?= $serverName . $serverCnt ?>기 <span class="avoid-wrap">(<font color=cyan><?= $scenario ?></font>)</span></h3>
</div>
<div class="row center gx-0">
<div class="s-border-t col py-2 col-8 col-lg-4" style="color:<?= $color ?>;">
<?= $scenario ?>
</div>
<div class="s-border-t col py-2 col-4 col-lg-2" style="color:<?= $color ?>;">
NPC 수, 상성 : <?= $extend ?>,<?= $fiction ?>
</div>
<div class="s-border-t col py-2 col-4 col-lg-2" style="color:<?= $color ?>;">
NPC선택 : <?= $npcmode ?>
</div>
<div class="s-border-t col py-2 col-4 col-lg-2" style="color:<?= $color ?>;">
토너먼트 : <?= getTournamentTermText($gameStor->turnterm) ?>
</div>
<div class="s-border-t col py-2 col-4 col-lg-2" style="color:<?= $color ?>;">
기타 설정: <?= $otherTextInfo ?>
</div>
<div class="s-border-t col py-2 col-8 col-lg-4"><?= info(2) ?></div>
<div class="s-border-t col py-2 col-4 col-lg-2">전체 접속자 수 : <?= $gameStor->online_user_cnt ?> 명</div>
<div class="s-border-t col py-2 col-4 col-lg-2">턴당 갱신횟수 : <?= $gameStor->refreshLimit ?>회</div>
<div class="s-border-t col py-2 col-8 col-lg-4"><?= info(3) ?></div>
<div class="s-border-t py-2 col col-6 col-lg-4">
<?php if ($isTournamentActive) : ?>
↑<span style='color:cyan'><?= (['전력전', '통솔전', '일기토', '설전',])[$gameStor->tnmt_type] ?? '' ?> <?= getTournament($gameStor->tournament) ?> <?= getTournamentTime() ?></span>↑
<?php else : ?>
<span style='color:magenta'>현재 토너먼트 경기 없음</span>
<?php endif; ?>
</div>
<div class="s-border-t py-2 col col-6 col-lg-2">
<div style="display:inline-block;"><?= $plock ? ("<span style='color:magenta;'>동작 시각: " . substr($gameStor->turntime, 5, 14) . "</span>") : ("<span style='color:cyan;'>동작 시각: " . substr($gameStor->turntime, 5, 14) . "</span>") ?></div>
</div>
<div class="s-border-t py-2 col col-6 col-lg-2">
<?php if ($auctionCount > 0) : ?>
<span style='color:cyan'><?= $auctionCount ?>건</span> 거래 진행중
<?php else : ?>
<span style='color:magenta'>진행중 거래 없음</span>
<?php endif; ?>
</div>
<div class="s-border-t py-2 col col-6 col-lg-4 vote-cell">
<?php
?>
<?php if ($lastVote === null) : ?>
<span style='color:magenta'>진행중 설문 없음</span>
<?php else : ?>
<span style='color:cyan'>설문 진행중</span> : <span><?= $lastVote->title ?></span>
<?php endif; ?>
</div>
</div>
<div class="row gx-0">
<div class="col s-border-t px-2 py-2">접속중인 국가: <?= $gameStor->online_nation ?></div>
</div>
<div class="row gx-0">
<div class="col s-border-t px-2 py-2">운영자 메세지 : <span style='color:yellow;'><?= $gameStor->msg ?></span></div>
</div>
<div class="row gx-0">
<div class="col s-border-t py-2" id="nation-msg-position">
<div class="px-2">【 국가방침 】</div>
<div id='nation-msg-box'>
<div id='nation-msg'><?= nationMsg($generalObj) ?></div>
</div>
</div>
</div>
<div class="row gx-0">
<div class="col s-border-t px-2 py-2">
【 접속자 】<?= onlinegen($generalObj) ?>
</div>
</div>
<?php if ($session->userGrade >= 5) : ?>
<div class="row">
<div class="col">
<a href='_admin1.php' target='_blank'><button type='button'>게임관리</button></a>
<a href='_admin2.php' target='_blank'><button type='button'>회원관리</button></a>
<a href='_admin4.php' target='_blank'><button type='button'>멀티관리</button></a>
<a href='_admin5.php' target='_blank'><button type='button'>일제정보</button></a>
<a href='_admin7.php' target='_blank'><button type='button'>로그정보</button></a>
<a href='_admin8.php' target='_blank'><button type='button'>외교정보</button></a>
<a href='_119.php' target='_blank'><button type='button'>119</button></a>
</div>
</div>
<?php elseif ($session->userGrade == 4) : ?>
<div class="row">
<div class="col">
<a href='_119.php' target='_blank'><button type='button'>119</button></a>
</div>
</div>
<?php endif; ?>
<div id="map_view" class="gx-0">
<div id="mapZone" class="view-item"><?= getMapHtml() ?></div>
<div class="view-item" id="reservedCommandZone">
<div id="reservedCommandList"></div>
<div id="actionMiniPlate" class="gx-0 row">
<div class="col">
<div class="gx-1 row">
<div class="col-8 d-grid"><button type='button' class='btn btn-sammo-base2 refreshPage'>갱 신</button></div>
<div class="col-4 d-grid"><button type='button' class='btn btn-sammo-base2' onclick="location.replace('../')">로비로</button></div>
</div>
</div>
</div>
</div>
<div id="cityInfo" class="view-item" style="border:none;text-align:center;"><?= cityInfo($generalObj) ?></div>
<div id="nation-position"><?php myNationInfo($generalObj); ?></div>
<div id="general-position"><?php generalInfo($generalObj); ?></div>
<div id="generalCommandButton" class="row gx-0">
<div class="buttonPlate bg2">
?></div>
</div>
</div>
</div>
<div id="actionMiniPlateSub" class="gx-0 row">
<div class="col">
<div class="gx-1 row">
<div class="col-3 d-grid"><button type='button' class="btn btn-dark" onclick="scrollHardTo('reservedCommandList')">명령으로</button></div>
<div class="col-5 d-grid"><button type='button' class='btn btn-sammo-base2 refreshPage'>갱 신</button></div>
<div class="col-4 d-grid"><button type='button' class='btn btn-sammo-base2' onclick="location.replace('../')">로비로</button></div>
</div>
</div>
</div>
<div class="row gx-0">
<div class="col-lg-6" id="general_public_record-position">
<div class="bg1 center s-border-tb"><b>장수 동향</b></div>
<div id="general_public_record" style="text-align:left;"><?= formatHistoryToHTML(getGlobalActionLogRecent(15)) ?></div>
</div>
<div class="col-lg-6" id="general_log-position">
<div class="bg1 center s-border-tb"><b>개인 기록</b></div>
<div id="general_log" style="text-align:left;"><?= formatHistoryToHTML(getGeneralActionLogRecent($me['no'], 15)) ?></div>
</div>
<div class="col-12" id="world_history-position">
<div class="bg1 center s-border-tb"><b>중원 정세</b></div>
<div id="world_history" colspan=2 style="text-align:left;"><?= formatHistoryToHTML(getGlobalHistoryLogRecent(15)) ?></div>
</div>
</div>
<div class="row toolbars gx-0">
<div class="buttonPlate"><?= allButton($gameStor->npcmode == 1) ?></div>
<div id="message_board" class="row gx-0">
<div class="message_input_form bg0 gx-0 row">
<div id="mailbox_list-col" class="col-6 col-lg-2 d-grid">
<select id="mailbox_list" size="1" class="form-control bg-dark text-white">
</select>
</div>
<div id="msg_input-col" class="col-12 col-lg-8 d-grid">
<input type="text" id="msg_input" maxlength="99" class="form-control">
</div>
<div id="msg_submit-col" class="col-6 col-lg-2 d-grid"><button id="msg_submit" class="btn btn-primary">서신전달&amp;갱신</button></div>
</div>
<div class="col-lg-6 board_side bg0"><a id="public_talk_position"></a>
<div class="board_header bg0">전체 메시지(최고99자)</div>
<section class="public_message">
<div class="d-grid"><button type="button" class="load_old_message btn btn-secondary" data-msg_type="public">이전 메시지 불러오기</button></div>
</section>
</div>
<div class="col-lg-6 board_side bg0"><a id="national_talk_position"></a>
<div class="board_header bg0">국가 메시지(최고99자)</div>
<section class="national_message">
<div class="d-grid"><button type="button" class="load_old_message btn btn-secondary" data-msg_type="national">이전 메시지 불러오기</button></div>
</section>
</div>
<div class="col-lg-6 board_side bg0"><a id="private_talk_position"></a>
<div class="board_header bg0">개인 메시지(최고99자)</div>
<section class="private_message">
<div class="d-grid"><button type="button" class="load_old_message btn btn-secondary" data-msg_type="private">이전 메시지 불러오기</button></div>
</section>
</div>
<div class="col-lg-6 board_side bg0"><a id="diplomacy_talk_position"></a>
<div class="board_header bg0">외교 메시지(최고99자)</div>
<section class="diplomacy_message">
<div class="d-grid"><button type="button" class="load_old_message btn btn-secondary" data-msg_type="diplomacy">이전 메시지 불러오기</button></div>
</section>
</div>
</div>
<div class="row toolbars gx-0">
<div class="buttonPlate"><?= allButton($gameStor->npcmode == 1) ?></div><?= banner() ?>
</div>
</div>
<?php
if ($limitState == 1) {
MessageBox("접속제한이 얼마 남지 않았습니다!");
}
if ($me['newmsg'] == 1) {
MessageBox("새로운 서신이 도착했습니다!");
}
if ($me['newvote'] == 1) {
$develcost = $gameStor->develcost * 5;
MessageBox("설문조사에 참여하시면 금{$develcost}과 유니크템을 드립니다! (우측 상단 설문조사 메뉴)");
}
?>
</div>
<nav class="navbar navbar-expand fixed-bottom navbar-dark bg-dark d-sm-block d-lg-none p-0" id="navbar500">
<div class="container-fluid px-0">
<div class="collapse navbar-collapse">
<ul class="navbar-nav me-auto mx-auto">
<li class="nav-item dropup">
<div class="dropdown-toggle text-white btn btn-sammo-base2" id="navbarGlobal" role="button" data-bs-toggle="dropdown" aria-expanded="false">
외부 메뉴
</div>
<ul class="dropdown-menu dropdown-menu-start" aria-labelledby="navbarGlobal" id="navbarGlobalItems">
<?= allButton($gameStor->npcmode == 1, ['btnBegin' => '<li>', 'btnEnd' => '</li>', 'btnClass' => 'dropdown-item']) ?>
</ul>
</li>
<li class="nav-item dropup">
<div class="dropdown-toggle btn btn-sammo-nation" id="navbarNation" role="button" data-bs-toggle="dropdown" aria-expanded="false">
국가 메뉴
</div>
<ul class="dropdown-menu" aria-labelledby="navbarNation" id="navbarNationItems">
</ul>
</li>
<li class="nav-item dropup">
<div class="dropdown-toggle text-white btn btn-dark" id="navbarQuick" role="button" data-bs-toggle="dropdown" aria-expanded="false">
빠른 이동
</div>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown" id="navbarQuickItems">
<li><a class="dropdown-item disabled">국가 정보</a></button></li>
<hr class="dropdown-divider">
</hr>
<li><button type="button" onclick="scrollHardTo('nation-msg-position')" class="dropdown-item">방침</button></li>
<li><button type="button" onclick="scrollHardTo('reservedCommandList')" class="dropdown-item">명령</button></li>
<li><button type="button" onclick="scrollHardTo('nation-position')" class="dropdown-item">국가</button></li>
<li><button type="button" onclick="scrollHardTo('general-position')" class="dropdown-item">장수</button></li>
<li><button type="button" onclick="scrollHardTo('cityInfo')" class="dropdown-item">도시</button></li>
<li><a class="dropdown-item disabled">동향 정보</a></button></li>
<hr class="dropdown-divider">
</hr>
<li><button type="button" onclick="scrollHardTo('mapZone')" class="dropdown-item">지도</button></li>
<li><button type="button" onclick="scrollHardTo('general_public_record-position')" class="dropdown-item">동향</button></li>
<li><button type="button" onclick="scrollHardTo('general_log-position')" class="dropdown-item">개인</button></li>
<li><button type="button" onclick="scrollHardTo('world_history-position')" class="dropdown-item">정세</button></li>
<li><span>&nbsp;</span></li>
<li><a class="dropdown-item disabled">메시지</a></button></li>
<hr class="dropdown-divider">
</hr>
<li><button type="button" onclick="scrollHardTo('public_talk_position')" class="dropdown-item">전체</button></li>
<li><button type="button" onclick="scrollHardTo('national_talk_position')" class="dropdown-item">국가</button></li>
<li><button type="button" onclick="scrollHardTo('private_talk_position')" class="dropdown-item">개인</button></li>
<li><button type="button" onclick="scrollHardTo('diplomacy_talk_position')" class="dropdown-item">외교</button></li>
<li><button type='button' class='btn btn-sammo-base2' onclick="location.replace('../')">로비로</button></li>
</ul>
</li>
<li class="nav-item">
<a class="refreshPage btn btn-sammo-base2 text-white" role="button">갱신</a>
</li>
</ul>
</div>
</div>
</nav>
</body>
</html>
+1 -9
View File
@@ -8,12 +8,4 @@ $session = Session::getInstance()->setReadOnly();
$db = DB::db(); $db = DB::db();
$updated = false; TurnExecutionHelper::executeAllCommand();
$locked = false;
$lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked);
Json::die([
'result' => true,
'updated' => $updated,
'locked' => $locked,
'lastExecuted' => $lastExecuted,
]);
+10 -113
View File
@@ -230,7 +230,6 @@ function processWar_NG(
WarUnitGeneral $attacker, WarUnitGeneral $attacker,
callable $getNextDefender, callable $getNextDefender,
WarUnitCity $city, WarUnitCity $city,
?callable $trace = null,
): bool { ): bool {
$templates = new \League\Plates\Engine(__DIR__ . '/templates'); $templates = new \League\Plates\Engine(__DIR__ . '/templates');
@@ -246,26 +245,6 @@ function processWar_NG(
/** @var WarUnit */ /** @var WarUnit */
$defender = ($getNextDefender)(null, true); $defender = ($getNextDefender)(null, true);
$conquerCity = false; $conquerCity = false;
$traceSeq = 0;
$emitTrace = function (string $event, ?WarUnit $currentDefender, array $details = []) use (
$trace,
&$traceSeq,
$attacker,
$city
): void {
if ($trace === null) {
return;
}
$trace([
'seq' => $traceSeq++,
'event' => $event,
'attacker' => buildWarTraceUnitSnapshot($attacker),
'defender' => $currentDefender === null ? null : buildWarTraceUnitSnapshot($currentDefender),
'city' => buildWarTraceUnitSnapshot($city),
'details' => $details,
]);
};
$emitTrace('battle_start', $defender, ['seed' => $warSeed]);
$josaRo = JosaUtil::pick($city->getName(), '로'); $josaRo = JosaUtil::pick($city->getName(), '로');
$josaYi = JosaUtil::pick($attacker->getName(), '이'); $josaYi = JosaUtil::pick($attacker->getName(), '이');
@@ -298,7 +277,6 @@ function processWar_NG(
$logger->pushGlobalHistoryLog("<M><b>【패퇴】</b></><D><b>{$defender->getNationVar('name')}</b></>{$josaYi} 병량 부족으로 <G><b>{$defender->getName()}</b></>{$josaUl} 뺏기고 말았습니다."); $logger->pushGlobalHistoryLog("<M><b>【패퇴】</b></><D><b>{$defender->getNationVar('name')}</b></>{$josaYi} 병량 부족으로 <G><b>{$defender->getName()}</b></>{$josaUl} 뺏기고 말았습니다.");
$conquerCity = true; $conquerCity = true;
$emitTrace('supply_retreat', $defender);
break; break;
} }
} }
@@ -354,34 +332,18 @@ function processWar_NG(
$initCaller->merge($defender->getGeneral()->getBattleInitSkillTriggerList($defender)); $initCaller->merge($defender->getGeneral()->getBattleInitSkillTriggerList($defender));
$initCaller->fire($attacker->rng, [], [$attacker, $defender]); $initCaller->fire($attacker->rng, [], [$attacker, $defender]);
$emitTrace('opponent_initialized', $defender);
} }
$attacker->beginPhase(); $attacker->beginPhase();
$defender->beginPhase(); $defender->beginPhase();
if ($trace !== null) {
$emitTrace('phase_power', $defender, [
'attackerAttack' => $attacker->getComputedAttack(),
'attackerDefence' => $attacker->getComputedDefence(),
'attackerTrain' => $attacker->getComputedTrain(),
'attackerAtmos' => $attacker->getComputedAtmos(),
'defenderAttack' => $defender->getComputedAttack(),
'defenderDefence' => $defender->getComputedDefence(),
'defenderTrain' => $defender->getComputedTrain(),
'defenderAtmos' => $defender->getComputedAtmos(),
]);
}
$battleCaller = $attacker->getGeneral()->getBattlePhaseSkillTriggerList($attacker); $battleCaller = $attacker->getGeneral()->getBattlePhaseSkillTriggerList($attacker);
$battleCaller->merge($defender->getGeneral()->getBattlePhaseSkillTriggerList($defender)); $battleCaller->merge($defender->getGeneral()->getBattlePhaseSkillTriggerList($defender));
$battleCaller->fire($attacker->rng, [], [$attacker, $defender]); $battleCaller->fire($attacker->rng, [], [$attacker, $defender]);
$emitTrace('phase_triggered', $defender);
$deadDefender = $attacker->calcDamage(); $deadDefender = $attacker->calcDamage();
$deadAttacker = $defender->calcDamage(); $deadAttacker = $defender->calcDamage();
$rawDeadAttacker = $deadAttacker;
$rawDeadDefender = $deadDefender;
$attackerHP = $attacker->getHP(); $attackerHP = $attacker->getHP();
$defenderHP = $defender->getHP(); $defenderHP = $defender->getHP();
@@ -409,14 +371,6 @@ function processWar_NG(
$attacker->increaseKilled($deadDefender); $attacker->increaseKilled($deadDefender);
$defender->increaseKilled($deadAttacker); $defender->increaseKilled($deadAttacker);
$emitTrace('phase_damage', $defender, [
'rawDeadAttacker' => $rawDeadAttacker,
'rawDeadDefender' => $rawDeadDefender,
'deadAttacker' => $deadAttacker,
'deadDefender' => $deadDefender,
'attackerHpBefore' => $attackerHP,
'defenderHpBefore' => $defenderHP,
]);
if($defender->getPhase() < 0){ if($defender->getPhase() < 0){
$phaseNickname = '先'; $phaseNickname = '先';
@@ -440,10 +394,8 @@ function processWar_NG(
$attacker->addPhase(); $attacker->addPhase();
$defender->addPhase(); $defender->addPhase();
$emitTrace('phase_end', $defender);
if (!$attacker->continueWar($noRice)) { if (!$attacker->continueWar($noRice)) {
$emitTrace('attacker_stopped', $defender, ['noRice' => (bool)$noRice]);
$logWritten = true; $logWritten = true;
$attacker->logBattleResult(); $attacker->logBattleResult();
@@ -468,7 +420,6 @@ function processWar_NG(
} }
if (!$defender->continueWar($noRice)) { if (!$defender->continueWar($noRice)) {
$emitTrace('defender_stopped', $defender, ['noRice' => (bool)$noRice]);
$logWritten = true; $logWritten = true;
$attacker->logBattleResult(); $attacker->logBattleResult();
@@ -511,7 +462,6 @@ function processWar_NG(
$defender->finishBattle(); $defender->finishBattle();
$defender = ($getNextDefender)($defender, true); $defender = ($getNextDefender)($defender, true);
$emitTrace('opponent_switched', $defender);
if ($defender !== null && !($defender instanceof WarUnitGeneral)) { if ($defender !== null && !($defender instanceof WarUnitGeneral)) {
throw new \RuntimeException('다음 수비자를 받아오는데 실패'); throw new \RuntimeException('다음 수비자를 받아오는데 실패');
@@ -547,60 +497,10 @@ function processWar_NG(
} }
($getNextDefender)($defender, false); ($getNextDefender)($defender, false);
$emitTrace('battle_end', $defender, ['conquered' => $conquerCity]);
return $conquerCity; return $conquerCity;
} }
function buildWarTraceUnitSnapshot(WarUnit $unit): array
{
$snapshot = [
'kind' => $unit instanceof WarUnitGeneral ? 'general' : 'city',
'id' => $unit instanceof WarUnitGeneral
? $unit->getGeneral()->getID()
: $unit->getVar('city'),
'name' => $unit->getName(),
'isAttacker' => $unit->isAttacker(),
'crewTypeId' => $unit->getCrewType()->id,
'phase' => $unit->getPhase(),
'realPhase' => $unit->getRealPhase(),
'maxPhase' => $unit->getMaxPhase(),
'hp' => $unit->getHP(),
'rawWarPower' => $unit->getRawWarPower(),
'warPower' => $unit->getWarPower(),
'warPowerMultiplier' => $unit->getWarPowerMultiply(),
'killed' => $unit->getKilled(),
'dead' => $unit->getDead(),
'activatedSkills' => $unit->getActivatedSkillLog(),
];
if ($unit instanceof WarUnitGeneral) {
$general = $unit->getGeneral();
$snapshot['general'] = [
'crew' => $general->getVar('crew'),
'rice' => $general->getVar('rice'),
'train' => $general->getVar('train'),
'atmos' => $general->getVar('atmos'),
'injury' => $general->getVar('injury'),
'experience' => $general->getVar('experience'),
'dedication' => $general->getVar('dedication'),
'dex1' => $general->getVar('dex1'),
'dex2' => $general->getVar('dex2'),
'dex3' => $general->getVar('dex3'),
'dex4' => $general->getVar('dex4'),
'dex5' => $general->getVar('dex5'),
];
} else {
$snapshot['cityState'] = [
'defence' => $unit->getVar('def'),
'wall' => $unit->getVar('wall'),
'population' => $unit->getVar('pop'),
];
}
return $snapshot;
}
function DeleteConflict($nation) function DeleteConflict($nation)
{ {
$db = DB::db(); $db = DB::db();
@@ -626,7 +526,7 @@ function DeleteConflict($nation)
function getConquerNation($city): int function getConquerNation($city): int
{ {
$conflict = Json::decode($city['conflict']); $conflict = Json::decode($city['conflict']);
return array_key_first($conflict); return Util::array_first_key($conflict);
} }
function ConquerCity(array $admin, General $general, array $city, array $defenderCityGeneralList) function ConquerCity(array $admin, General $general, array $city, array $defenderCityGeneralList)
@@ -818,17 +718,14 @@ function ConquerCity(array $admin, General $general, array $city, array $defende
$attackerLogger->pushGlobalHistoryLog("<M><b>【긴급천도】</b></><D><b>{$defenderNationName}</b></>{$josaYi} 수도가 함락되어 <G><b>$minCityName</b></>{$josaRo} 긴급천도하였습니다."); $attackerLogger->pushGlobalHistoryLog("<M><b>【긴급천도】</b></><D><b>{$defenderNationName}</b></>{$josaYi} 수도가 함락되어 <G><b>$minCityName</b></>{$josaRo} 긴급천도하였습니다.");
$moveLog = "수도가 함락되어 <G><b>$minCityName</b></>{$josaRo} <M>긴급천도</>합니다."; $moveLog = "수도가 함락되어 <G><b>$minCityName</b></>{$josaRo} <M>긴급천도</>합니다.";
foreach ($db->queryAllLists( //아국 수뇌부에게 로그 전달
'SELECT no, officer_level FROM general WHERE nation=%i', foreach ($db->queryFirstColumn(
'SELECT no FROM general WHERE nation=%i AND officer_level>=5',
$defenderNationID $defenderNationID
) as [$defenderGeneralID, $defenderGeneralChiefLevel]) { ) as $defenderChiefID) {
$defenderLogger = new ActionLogger($defenderGeneralID, $defenderNationID, $year, $month); $chiefLogger = new ActionLogger($defenderChiefID, $defenderNationID, $year, $month);
$defenderLogger->pushGeneralActionLog($moveLog, ActionLogger::PLAIN); $chiefLogger->pushGeneralActionLog($moveLog, ActionLogger::PLAIN);
if($defenderGeneralChiefLevel >= 5){ $chiefLogger->flush();
$defenderLogger->pushGeneralActionLog("수뇌는 <G><b>{$minCityName}</b></>{$josaRo} 집합되었습니다.");
}
$defenderLogger->flush();
} }
//천도 //천도
@@ -885,8 +782,8 @@ function ConquerCity(array $admin, General $general, array $city, array $defende
'officer_set' => 0, 'officer_set' => 0,
]; ];
if ($city['level'] > 3) { if ($city['level'] > 3) {
$query['def'] = GameConst::$defaultCityWall; $query['def'] = 1000;
$query['wall'] = GameConst::$defaultCityWall; $query['wall'] = 1000;
} else { } else {
$query['def'] = $db->sqleval('def_max/2'); $query['def'] = $db->sqleval('def_max/2');
$query['wall'] = $db->sqleval('wall_max/2'); $query['wall'] = $db->sqleval('wall_max/2');
+2 -2
View File
@@ -99,7 +99,7 @@ class Join extends \sammo\BaseAPI
} }
$gameID = UniqueConst::$serverID; $gameID = UniqueConst::$serverID;
$alreadyJoined = $db->queryFirstField('SELECT COUNT(*) FROM `inheritance_result` WHERE `server_id` = %s AND `owner` = %i', $gameID, $userID); $alreadyJoined = $db->queryFirstField('SELECT count(*) FROM `storage` WHERE `namespace` = %s AND `key` LIKE %s', 'inheritance_result', "{$gameID}_{$userID}_%");
if ($alreadyJoined) { if ($alreadyJoined) {
//이미 받았음 //이미 받았음
return 0; return 0;
@@ -157,7 +157,7 @@ class Join extends \sammo\BaseAPI
$penalty = []; $penalty = [];
foreach($penaltyInfo as $penaltyKey => $penaltyValue){ foreach($penaltyInfo as $penaltyKey => $penaltyValue){
if(($penaltyValue['expire'] ?? 0) > TimeUtil::now()){ if($penaltyValue['expire'] ?? 0 > TimeUtil::now()){
$penalty[$penaltyKey] = $penaltyValue['value']; $penalty[$penaltyKey] = $penaltyValue['value'];
} }
} }
+1 -1
View File
@@ -54,7 +54,7 @@ class GeneralList extends \sammo\BaseAPI
$availableNextCall = $session->availableNextCallGetGeneralList ?? '2000-01-01 00:00:00'; $availableNextCall = $session->availableNextCallGetGeneralList ?? '2000-01-01 00:00:00';
$now = new \DateTimeImmutable(); $now = new \DateTimeImmutable();
if ($now <= new \DateTimeImmutable($availableNextCall) && $session->userGrade < 6) { if ($now <= new \DateTimeImmutable($availableNextCall) && $session->userGrade < 5) {
return "장수 리스트는 10초에 한번 갱신 가능합니다.\n다음 시간 : " . $availableNextCall; return "장수 리스트는 10초에 한번 갱신 가능합니다.\n다음 시간 : " . $availableNextCall;
} }
+1 -5
View File
@@ -55,11 +55,7 @@ class GetCachedMap extends \sammo\BaseAPI
return 'cache 불가'; return 'cache 불가';
} }
$absolutePath = realpath('data/file_cache'); $storage = new \Nette\Caching\Storages\FileStorage('data/file_cache');
if (!$absolutePath) {
return 'cache 불가';
}
$storage = new \Nette\Caching\Storages\FileStorage($absolutePath);
$cache = new Cache($storage); $cache = new Cache($storage);
$now = TimeUtil::nowDateTimeImmutable(); $now = TimeUtil::nowDateTimeImmutable();
-4
View File
@@ -288,10 +288,6 @@ class GetConst extends \sammo\BaseAPI
if (!prepareDir($cacheDir)) { if (!prepareDir($cacheDir)) {
throw new RuntimeException('cache 폴더 없음'); throw new RuntimeException('cache 폴더 없음');
} }
$absolutePath = realpath($cacheDir);
if (!$absolutePath) {
throw new RuntimeException('cache 폴더 없음');
}
$storage = new \Nette\Caching\Storages\FileStorage($cacheDir); $storage = new \Nette\Caching\Storages\FileStorage($cacheDir);
$cache = new Cache($storage); $cache = new Cache($storage);
-154
View File
@@ -1,154 +0,0 @@
<?php
namespace sammo\API\InheritAction;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\Enums\APIRecoveryType;
use sammo\Enums\MessageType;
use sammo\Enums\RankColumn;
use sammo\GameConst;
use sammo\General;
use sammo\KVStorage;
use sammo\Message;
use sammo\MessageTarget;
use sammo\RootDB;
use sammo\TimeUtil;
use sammo\UserLogger;
use sammo\Validator;
use function sammo\GetImageURL;
use function sammo\getNationStaticInfo;
/**
*
* 유산 포인트 1000 포인트를 사용하면 지정한 상대의 본래 유저명을 확인 가능.
* 개인 메시지로 전달되며, 이 기능이 사용되었음을 상대에게도 알림.
*/
class CheckOwner extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', [
'destGeneralID',
])
->rule('int', 'destGeneralID')
->rule('min', 'destGeneralID', 1);
if (!$v->validate()) {
return $v->errorStr();
}
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null|string|array|APIRecoveryType
{
$userID = $session->userID;
$generalID = $session->generalID;
$destGeneralID = $this->args['destGeneralID'];
if ($generalID == $destGeneralID) {
return '자신의 정보는 확인할 수 없습니다.';
}
$general = General::createObjFromDB($generalID);
if ($userID != $general->getVar('owner')) {
return '로그인 상태가 이상합니다. 다시 로그인해 주세요.';
}
$db = DB::db();
$destRawGeneral = $db->queryFirstRow('SELECT no,name,nation,owner,owner_name,imgsvr,picture FROM general WHERE no = %i', $destGeneralID);
if (!$destRawGeneral) {
return '대상 장수가 존재하지 않습니다.';
}
if (!$destRawGeneral['owner']) {
return '대상 장수는 NPC입니다.';
}
$gameStor = KVStorage::getStorage($db, 'game_env');
if ($gameStor->isunited) {
return '이미 천하가 통일되었습니다.';
}
$reqPoint = GameConst::$inheritCheckOwnerPoint;
$inheritStor = KVStorage::getStorage($db, "inheritance_{$userID}");
$previousPoint = ($inheritStor->getValue('previous') ?? [0, 0])[0];
if ($previousPoint < $reqPoint) {
return '충분한 유산 포인트를 가지고 있지 않습니다.';
}
$userLogger = new UserLogger($userID);
$userLogger->push("{$reqPoint} 포인트로 장수 소유자 확인", "inheritPoint");
$userLogger->flush();
$destGeneralName = $destRawGeneral['name'];
$destGeneralOwnerName = $destRawGeneral['owner_name'];
if (!$destGeneralOwnerName) {
$rootDB = RootDB::db();
$destGeneralOwnerName = $rootDB->queryFirstField('SELECT name FROM member WHERE no = %i', $destRawGeneral['owner']) ?? '알수없음';
}
$src = new MessageTarget(0, '', 0, 'System', '#000000');
if (true) {
$staticNation = $general->getStaticNation();
$dest = new MessageTarget(
$generalID,
$general->getName(),
$general->getNationID(),
$staticNation['name'],
$staticNation['color'],
GetImageURL($general->getVar('imgsvr'), $general->getVar('picture'))
);
$msg = new Message(
MessageType::private,
$src,
$dest,
"{$destGeneralName}의 소유자는 {$destGeneralOwnerName} 입니다.",
new \DateTime(),
new \DateTime('9999-12-31'),
[]
);
$msg->send(true);
}
$inheritStor->setValue('previous', [$previousPoint - $reqPoint, null]);
$general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $reqPoint);
$general->applyDB($db);
if(true){
$destStaticNation = getNationStaticInfo($destRawGeneral['nation']);
$dest = new MessageTarget(
$destGeneralID,
$destGeneralName,
$destRawGeneral['nation'],
$destStaticNation['name'],
$destStaticNation['color'],
GetImageURL($destRawGeneral['imgsvr'], $destRawGeneral['picture'])
);
$msg = new Message(
MessageType::private,
$src,
$dest,
"소유자명이 누군가에 의해 확인되었습니다.",
new \DateTime(),
new \DateTime('9999-12-31'),
[]
);
$msg->send(true);
}
return null;
}
}
-188
View File
@@ -1,188 +0,0 @@
<?php
namespace sammo\API\InheritAction;
use sammo\Session;
use DateTimeInterface;
use sammo\CentennialAllStarGrowthService;
use sammo\DB;
use sammo\Enums\APIRecoveryType;
use sammo\Enums\RankColumn;
use sammo\GameConst;
use sammo\General;
use sammo\KVStorage;
use sammo\LiteHashDRBG;
use sammo\RandUtil;
use sammo\UniqueConst;
use sammo\UserLogger;
use sammo\Util;
use sammo\Validator;
class ResetStat extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', [
'leadership',
'strength',
'intel',
])
->rule('int', [
'leadership',
'strength',
'intel',
])
->rule('min', [
'leadership',
'strength',
'intel'
], GameConst::$defaultStatMin)
->rule('max', [
'leadership',
'strength',
'intel'
], GameConst::$defaultStatMax)
->rule('integerArray', 'inheritBonusStat');
if (!$v->validate()) {
return $v->errorStr();
}
return null;
}
public function getRequiredSessionMode(): int
{
//General.aux 쓰므로 lock;
return static::REQ_GAME_LOGIN;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{
$userID = $session->userID;
$generalID = $session->generalID;
$leadership = $this->args['leadership'];
$strength = $this->args['strength'];
$intel = $this->args['intel'];
$inheritBonusStat = $this->args['inheritBonusStat'] ?? null;
if ($leadership + $strength + $intel != GameConst::$defaultStatTotal) {
return "능력치 총합이 " . GameConst::$defaultStatTotal . "이 아닙니다. 다시 입력해주세요!";
}
if ($inheritBonusStat) {
if (count($inheritBonusStat) != 3) {
return "보너스 능력치가 잘못 지정되었습니다. 다시 입력해주세요!";
}
foreach ($inheritBonusStat as $stat) {
if ($stat < 0) {
return "보너스 능력치가 음수입니다. 다시 입력해주세요!";
}
}
$sum = array_sum($inheritBonusStat);
if ($sum == 0) {
$inheritBonusStat = null;
} else if ($sum < 3 || $sum > 5) {
return "보너스 능력치 합이 잘못 지정되었습니다. 다시 입력해주세요!";
}
}
$general = General::createObjFromDB($generalID);
if ($userID != $general->getVar('owner')) {
return '로그인 상태가 이상합니다. 다시 로그인해 주세요.';
}
$userLogger = new UserLogger($userID);
if($general->getNPCType() != 0){
return 'NPC는 능력치 초기화를 할 수 없습니다.';
}
if (!CentennialAllStarGrowthService::isStatResetAllowed()) {
return '100기 올스타 장수는 능력치 초기화를 사용할 수 없습니다.';
}
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues([
'isunited',
'season',
]);
if ($gameStor->isunited) {
return '이미 천하가 통일되었습니다.';
}
$userStor = KVStorage::getStorage($db, "user_{$userID}");
$lastUserStatReset = $userStor->getValue('last_stat_reset') ?? [];
$gameSeason = $gameStor->getValue('season');
if (array_search($gameSeason, $lastUserStatReset) !== false) {
return '이번 시즌에 이미 능력치를 초기화하셨습니다.';
}
$inheritStor = KVStorage::getStorage($db, "inheritance_{$userID}");
$previousPoint = ($inheritStor->getValue('previous') ?? [0, 0])[0];
$reqAmount = 0;
if ($inheritBonusStat !== null) {
$reqAmount += GameConst::$inheritBornStatPoint;
}
if ($previousPoint < $reqAmount) {
return '충분한 유산 포인트를 가지고 있지 않습니다.';
}
$userLogger->push("통솔 {$leadership}, 무력 {$strength}, 지력 {$intel} 스탯 재설정", "inheritPoint");
if ($inheritBonusStat) {
$pleadership = $inheritBonusStat[0] ?? 0;
$pstrength = $inheritBonusStat[1] ?? 0;
$pintel = $inheritBonusStat[2] ?? 0;
$userLogger->push("{$reqAmount}로 통솔 {$pleadership}, 무력 {$pstrength}, 지력 {$pintel} 보너스 능력치 적용", "inheritPoint");
} else {
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'ResetStat',
$userID,
)));
$pleadership = 0;
$pstrength = 0;
$pintel = 0;
foreach (Util::range($rng->nextRangeInt(3, 5)) as $statIdx) {
switch ($rng->choiceUsingWeight([$leadership, $strength, $intel])) {
case 0:
$pleadership++;
break;
case 1:
$pstrength++;
break;
case 2:
$pintel++;
break;
}
}
$userLogger->push("통솔 {$pleadership}, 무력 {$pstrength}, 지력 {$pintel} 보너스 능력치 적용", "inheritPoint");
}
$leadership += $pleadership;
$strength += $pstrength;
$intel += $pintel;
$lastUserStatReset[] = $gameSeason;
$general->setVar('leadership', $leadership);
$general->setVar('strength', $strength);
$general->setVar('intel', $intel);
$userLogger->flush();
$inheritStor->setValue('previous', [$previousPoint - $reqAmount, null]);
$userStor->setValue('last_stat_reset', $lastUserStatReset);
$general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $reqAmount);
$general->applyDB($db);
return null;
}
}
+1 -9
View File
@@ -7,7 +7,6 @@ use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\MessageType; use sammo\Enums\MessageType;
use sammo\Enums\PenaltyKey;
use sammo\Json; use sammo\Json;
use sammo\Message; use sammo\Message;
use sammo\MessageTarget; use sammo\MessageTarget;
@@ -192,9 +191,6 @@ class SendMessage extends \sammo\BaseAPI
// 전체 메세지 // 전체 메세지
if ($mailbox === Message::MAILBOX_PUBLIC) { if ($mailbox === Message::MAILBOX_PUBLIC) {
if($penalty[PenaltyKey::NoSendPublicMsg->value] ?? 0) {
return '공개 메세지를 보낼 수 없습니다.';
}
$msgID = $this->genPublicMessage($src, $text)->send(); $msgID = $this->genPublicMessage($src, $text)->send();
return [ return [
'msgType' => 'public', 'msgType' => 'public',
@@ -235,11 +231,7 @@ class SendMessage extends \sammo\BaseAPI
$lastMsg = new \DateTime($session->lastMsg ?? '0000-00-00'); $lastMsg = new \DateTime($session->lastMsg ?? '0000-00-00');
$msg_interval = $now->getTimestamp() - $lastMsg->getTimestamp(); $msg_interval = $now->getTimestamp() - $lastMsg->getTimestamp();
if($penalty[PenaltyKey::NoSendPrivateMsg->value] ?? 0) { $msg_min_interval = $penalty['sendPrivateMsgDelay'] ?? 2;
return '개인 메세지를 보낼 수 없습니다.';
}
$msg_min_interval = $penalty[PenaltyKey::SendPrivateMsgDelay->value] ?? 2;
if ($msg_interval < $msg_min_interval) { if ($msg_interval < $msg_min_interval) {
return "개인메세지는 {$msg_min_interval}초당 1건만 보낼 수 있습니다!"; return "개인메세지는 {$msg_min_interval}초당 1건만 보낼 수 있습니다!";
} }
+1 -1
View File
@@ -50,7 +50,7 @@ class UploadImage extends \sammo\BaseAPI
} }
$extension = ltrim($contentType, 'image/'); $extension = ltrim($contentType, 'image/');
$validExtensions = ['png', 'jpeg', 'jpg', 'gif', 'webp', 'avif']; $validExtensions = ['png', 'jpeg', 'jpg', 'gif', 'webp'];
if (!in_array(strtolower($extension), $validExtensions)) { if (!in_array(strtolower($extension), $validExtensions)) {
return '지원하지 않는 이미지 파일입니다: ' . $contentType; return '지원하지 않는 이미지 파일입니다: ' . $contentType;
} }
+5 -20
View File
@@ -6,9 +6,6 @@ use sammo\Session;
use DateTimeInterface; use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralLiteQueryMode;
use sammo\GeneralLite;
use sammo\StaticEventHandler;
class ExitTroop extends \sammo\BaseAPI class ExitTroop extends \sammo\BaseAPI
{ {
@@ -27,35 +24,23 @@ class ExitTroop extends \sammo\BaseAPI
$generalID = $session->generalID; $generalID = $session->generalID;
$db = DB::db(); $db = DB::db();
$me = GeneralLite::createObjFromDB($generalID, ['troop'], GeneralLiteQueryMode::Lite); $troopID = $db->queryFirstField('SELECT troop FROM general WHERE no = %i', $generalID);
if (!$me) {
return '장수 정보를 불러올 수 없습니다.';
}
$troopID = $me->getVar('troop');
if($troopID == 0){ if($troopID == 0){
return '부대에 소속되어 있지 않습니다.'; return '부대에 소속되어 있지 않습니다.';
} }
if($troopID != $generalID){ if($generalID != $troopID){
StaticEventHandler::handleEvent($me, null, $this::class, [ $db->update('general', [
"isTroopLeader" => false, 'troop' => 0,
], $this->args); ], '`no` = %i', $generalID);
$me->setVar('troop', 0);
$me->applyDB($db);
return null; return null;
} }
//부대장이다. //부대장이다.
StaticEventHandler::handleEvent($me, null, $this::class, [
"isTroopLeader" => true,
], $this->args);
$db->update('general', [ $db->update('general', [
'troop' => 0, 'troop' => 0,
], '`troop` = %i', $troopID); ], '`troop` = %i', $troopID);
$db->delete('troop', 'troop_leader = %i', $troopID); $db->delete('troop', 'troop_leader = %i', $troopID);
$me->setVar('troop', 0);
$me->applyDB($db);
return null; return null;
} }
+9 -17
View File
@@ -6,9 +6,6 @@ use sammo\Session;
use DateTimeInterface; use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralLiteQueryMode;
use sammo\GeneralLite;
use sammo\StaticEventHandler;
use sammo\StringUtil; use sammo\StringUtil;
use sammo\Validator; use sammo\Validator;
@@ -35,22 +32,16 @@ class JoinTroop extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{ {
$generalID = $session->generalID; $userID = $session->userID;
$troopID = $this->args['troopID']; $troopID = $this->args['troopID'];
$db = DB::db(); $db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,nation,`troop`,`officer_level`,permission,penalty FROM general WHERE `owner`=%i', $userID);
$me = GeneralLite::createObjFromDB($generalID, ['troop'], GeneralLiteQueryMode::Lite); if ($me['troop'] != 0) {
if(!$me){
return '장수 정보를 불러올 수 없습니다.';
}
if($me->getVar('troop') != 0){
return '이미 부대에 소속되어 있습니다.'; return '이미 부대에 소속되어 있습니다.';
} }
$nationID = $me['nation'];
$nationID = $me->getNationID(); if ($nationID == 0) {
if($nationID == 0){
return '국가에 소속되어 있지 않습니다.'; return '국가에 소속되어 있지 않습니다.';
} }
@@ -58,10 +49,11 @@ class JoinTroop extends \sammo\BaseAPI
if (!$troopExists) { if (!$troopExists) {
return '부대가 올바르지 않습니다.'; return '부대가 올바르지 않습니다.';
} }
$generalID = $me['no'];
$me->setVar('troop', $troopID); $db->update('general', [
StaticEventHandler::handleEvent($me, null, $this::class, [], $this->args); 'troop' => $troopID,
$me->applyDB($db); ], '`no` = %i AND `troop` = %i', $generalID, 0);
return null; return null;
} }
+4 -9
View File
@@ -6,9 +6,6 @@ use sammo\Session;
use DateTimeInterface; use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralLiteQueryMode;
use sammo\GeneralLite;
use sammo\StaticEventHandler;
use sammo\Validator; use sammo\Validator;
class KickFromTroop extends \sammo\BaseAPI class KickFromTroop extends \sammo\BaseAPI
@@ -40,8 +37,7 @@ class KickFromTroop extends \sammo\BaseAPI
$generalID = $this->args['generalID']; $generalID = $this->args['generalID'];
$db = DB::db(); $db = DB::db();
$destGeneral = GeneralLite::createObjFromDB($generalID, ['troop'], GeneralLiteQueryMode::Lite); $troopID = $db->queryFirstField('SELECT troop FROM general WHERE no = %i', $generalID);
$troopID = $destGeneral->getVar('troop');
if($troopID == 0){ if($troopID == 0){
return '부대에 소속되어 있지 않습니다.'; return '부대에 소속되어 있지 않습니다.';
} }
@@ -54,10 +50,9 @@ class KickFromTroop extends \sammo\BaseAPI
return '부대장을 추방할 수 없습니다.'; return '부대장을 추방할 수 없습니다.';
} }
StaticEventHandler::handleEvent($destGeneral, null, $this::class, [], $this->args); $db->update('general', [
$destGeneral->setVar('troop', 0); 'troop' => 0,
$destGeneral->applyDB($db); ], '`no` = %i AND `troop` = %i', $generalID, $troopID);
return null; return null;
} }
} }
+9 -11
View File
@@ -6,9 +6,6 @@ use sammo\Session;
use DateTimeInterface; use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralLiteQueryMode;
use sammo\GeneralLite;
use sammo\StaticEventHandler;
use sammo\StringUtil; use sammo\StringUtil;
use sammo\Validator; use sammo\Validator;
@@ -35,23 +32,24 @@ class NewTroop extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{ {
$generalID = $session->generalID; $userID = $session->userID;
$troopName = StringUtil::neutralize($this->args['troopName']); $troopName = StringUtil::neutralize($this->args['troopName']);
if(!$troopName){ if(!$troopName){
return '부대 이름이 없습니다.'; return '부대 이름이 없습니다.';
} }
$db = DB::db(); $db = DB::db();
$me = GeneralLite::createObjFromDB($generalID, ['troop'], GeneralLiteQueryMode::Lite); $me = $db->queryFirstRow('SELECT `no`,nation,`troop`,`officer_level`,permission,penalty FROM general WHERE `owner`=%i', $userID);
$troopID = $me->getVar('troop'); if($me['troop'] != 0){
if($troopID != 0){
return '이미 부대에 소속되어 있습니다.'; return '이미 부대에 소속되어 있습니다.';
} }
$nationID = $me->getNationID(); $nationID = $me['nation'];
if($nationID == 0){ if($nationID == 0){
return '국가에 소속되어 있지 않습니다.'; return '국가에 소속되어 있지 않습니다.';
} }
$generalID = $me['no'];
$db->insert('troop', [ $db->insert('troop', [
'name'=>$troopName, 'name'=>$troopName,
'troop_leader'=>$generalID, 'troop_leader'=>$generalID,
@@ -62,9 +60,9 @@ class NewTroop extends \sammo\BaseAPI
return '부대가 생성되지 않았습니다. 버그일 수 있습니다.'; return '부대가 생성되지 않았습니다. 버그일 수 있습니다.';
} }
$me->setVar('troop', $generalID); $db->update('general', [
StaticEventHandler::handleEvent($me, null, $this::class, [], $this->args); 'troop'=>$generalID
$me->applyDB($db); ], '`no` = %i', $generalID);
return null; return null;
} }
+5 -8
View File
@@ -6,9 +6,6 @@ use sammo\Session;
use DateTimeInterface; use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralLiteQueryMode;
use sammo\GeneralLite;
use sammo\StaticEventHandler;
use sammo\StringUtil; use sammo\StringUtil;
use sammo\Validator; use sammo\Validator;
@@ -39,12 +36,13 @@ class SetTroopName extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{ {
$generalID = $session->generalID; $userID = $session->userID;
$db = DB::db(); $db = DB::db();
$me = GeneralLite::createObjFromDB($generalID, ['troop', 'permission', 'penalty'], GeneralLiteQueryMode::Lite); $me = $db->queryFirstRow('SELECT `no`,nation,`officer_level`,permission,penalty FROM general WHERE `owner`=%i', $userID);
$permission = checkSecretPermission($me->getRaw(), false); $permission = checkSecretPermission($me, false);
$troopID = $this->args['troopID']; $troopID = $this->args['troopID'];
$generalID = $me['no'];
if($generalID != $troopID && $permission < 4){ if($generalID != $troopID && $permission < 4){
return "권한이 부족합니다."; return "권한이 부족합니다.";
} }
@@ -54,7 +52,7 @@ class SetTroopName extends \sammo\BaseAPI
return '부대 이름이 없습니다.'; return '부대 이름이 없습니다.';
} }
$nationID = $me->getNationID(); $nationID = $me['nation'];
$db->update('troop', [ $db->update('troop', [
'name'=>$troopName 'name'=>$troopName
], 'troop_leader=%i AND `nation`=%i',$troopID, $nationID); ], 'troop_leader=%i AND `nation`=%i',$troopID, $nationID);
@@ -62,7 +60,6 @@ class SetTroopName extends \sammo\BaseAPI
if($db->affectedRows() == 0){ if($db->affectedRows() == 0){
return '부대가 없습니다.'; return '부대가 없습니다.';
} }
StaticEventHandler::handleEvent($me, null, $this::class, [], $this->args);
return null; return null;
} }
-5
View File
@@ -7,7 +7,6 @@ use sammo\DB;
use sammo\DTO\VoteInfo; use sammo\DTO\VoteInfo;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\RootDB;
use sammo\Session; use sammo\Session;
use sammo\TimeUtil; use sammo\TimeUtil;
use sammo\Util; use sammo\Util;
@@ -94,8 +93,6 @@ class NewVote extends \sammo\BaseAPI
} }
} }
$userName = $session->userName;
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
@@ -111,11 +108,9 @@ class NewVote extends \sammo\BaseAPI
$multipleOptions = Util::valueFit($multipleOptions, 0, count($options)); $multipleOptions = Util::valueFit($multipleOptions, 0, count($options));
$voteInfo = new VoteInfo( $voteInfo = new VoteInfo(
id: $voteID, id: $voteID,
title: $title, title: $title,
opener: $userName ?? '[SYSTEM]',
multipleOptions: $multipleOptions, multipleOptions: $multipleOptions,
startDate: $now, startDate: $now,
endDate: $endDate, endDate: $endDate,
+2 -6
View File
@@ -8,11 +8,6 @@ use sammo\Util;
abstract class AbsFromUserPool extends AbsGeneralPool{ abstract class AbsFromUserPool extends AbsGeneralPool{
protected static function getCandidateWeight(array $info, int $owner): int|float
{
return array_sum($info['dex'] ?? []);
}
public function occupyGeneralName(): bool public function occupyGeneralName(): bool
{ {
$generalID = $this->getGeneralBuilder()->getGeneralID(); $generalID = $this->getGeneralBuilder()->getGeneralID();
@@ -41,7 +36,8 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
$pool = []; $pool = [];
foreach($db->query('SELECT id, unique_name, info FROM select_pool WHERE reserved_until IS NULL AND general_id IS NULL', $pickCnt) as $cand){ foreach($db->query('SELECT id, unique_name, info FROM select_pool WHERE reserved_until IS NULL AND general_id IS NULL', $pickCnt) as $cand){
$cand['info'] = Json::decode($cand['info']); $cand['info'] = Json::decode($cand['info']);
$pool[] = [$cand, static::getCandidateWeight($cand['info'], $owner)]; $dexTotal = array_sum($cand['info']['dex']);
$pool[] = [$cand, $dexTotal];
} }
if(count($pool) < $pickCnt){ if(count($pool) < $pickCnt){
@@ -19,14 +19,10 @@ class che_저지_삼황내문 extends \sammo\BaseItem{
if($unit->isAttacker()){ if($unit->isAttacker()){
return null; return null;
} }
if($unit->getPhase() >= 2){
if($unit->getPhase() > 0){
return null; return null;
} }
if($unit->hasActivatedSkillOnLog('저지') >= 2){ if($unit->getPhase() == 1 && $unit->rng->nextBool(0.5)){
return null;
}
if($unit->hasActivatedSkillOnLog('저지') == 1 && $unit->getPhase() == 0 && $unit->rng->nextBool(0.5)){
return null; return null;
} }
@@ -15,7 +15,7 @@ class che_척사_오악진형도 extends \sammo\BaseItem{
public function getWarPowerMultiplier(WarUnit $unit):array{ public function getWarPowerMultiplier(WarUnit $unit):array{
$opposeCrewType = $unit->getOppose()->getCrewType(); $opposeCrewType = $unit->getOppose()->getCrewType();
if($opposeCrewType->reqCities() || $opposeCrewType->reqRegions()){ if($opposeCrewType->reqCities || $opposeCrewType->reqRegions){
return [1.15, 0.85]; return [1.15, 0.85];
} }
return [1, 1]; return [1, 1];
@@ -33,7 +33,7 @@ class event_전투특기_공성 extends \sammo\BaseItem{
} }
public function onCalcStat(General $general, string $statName, $value, $aux=null){ public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(str_starts_with($statName, 'dex')){ if(\sammo\Util::starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_SIEGE; $myArmType = 'dex'.GameUnitConst::T_SIEGE;
$opposeArmType = 'dex'.$aux['opposeType']->armType;; $opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){ if($aux['isAttacker'] && $opposeArmType === $statName){

Some files were not shown because too many files have changed in this diff Show More