Compare commits

...
Author SHA1 Message Date
Hide_D a9813baab6 fix: reuse message ticks across mailboxes 2026-08-04 07:03:17 +00:00
Hide_D ed8b343e7c fix: keep scenario war specials in war slot 2026-08-04 01:56:48 +00:00
Hide_D 68a82c066b fix: support mixed wall and game clock servers 2026-08-04 01:09:18 +00:00
Hide_D 41ec80c96b fix: complete logical clock wall-time isolation 2026-08-03 16:09:35 +00:00
Hide_D ed3c84aa8e feat: replace game schedules with logical ticks 2026-08-03 05:13:47 +00:00
Hide_D f4a1e2258d merge: preserve S100 generated NPC stat floor 2026-07-31 05:06:38 +00:00
Hide_D 4ffd7b5bc0 fix: preserve S100 generated NPC stat floor 2026-07-31 05:06:15 +00:00
Hide_D e7c773709d merge: apply S100 cooldown and growth fixes 2026-07-31 01:24:41 +00:00
Hide_D 1e30659bdd test: verify S100 final-growth reselection 2026-07-31 01:19:23 +00:00
Hide_D 162f198f08 fix: stabilize S100 cooldowns and generated NPC growth 2026-07-31 01:11:47 +00:00
Hide_D 4aa09a294e perf: reduce MariaDB turn queue round trips 2026-07-30 17:12:22 +00:00
Hide_D 191abf83c0 test: isolate S100 inheritance browser logs 2026-07-29 08:51:51 +00:00
Hide_D edfeae5f0f fix: block S100 inheritance stat resets 2026-07-29 08:42:38 +00:00
Hide_D 424184a51a fix: define mixed S100 dex conversion ownership 2026-07-29 07:29:43 +00:00
Hide_D 03a321f95d fix: prevent S100 dex conversion refills 2026-07-29 06:36:33 +00:00
Hide_D e3615fa6bb fix: cap S100 NPC dex growth at forty percent 2026-07-29 05:20:53 +00:00
Hide_D 32991f736d fix: expose S100 event traits to frontend 2026-07-29 05:11:19 +00:00
Hide_D 8b2748d3d4 feat: 재선택 현재 능력치 기준 표시 2026-07-28 16:56:02 +00:00
Hide_D 883080e275 feat: 100기 NPC 동조 및 후보 추첨 기능 개선과 초기 능력치 계산 로직 추가 2026-07-28 16:28:13 +00:00
Hide_D ce807e2564 유니크 획득 확률 조금 더 증가 2026-07-28 16:11:24 +00:00
Hide_D 9a2ac82373 시나리오 문제점 수정 2026-07-28 16:07:21 +00:00
Hide_D 3c35539c16 CreateManyNPC 추가 2026-07-28 16:04:50 +00:00
Hide_D 5ab08403c6 feat: 100기 NPC 동조와 후보 추첨 보완 2026-07-28 16:02:14 +00:00
Hide_D c1ce478d22 feat: exclude prior event seasons from all-star pool 2026-07-28 00:43:46 +00:00
Hide_D d8bf8ba922 feat: expand 100th-season pool through phase 99 2026-07-28 00:29:25 +00:00
Hide_D 95f66e4c39 feat: add 100th-season all-star growth event 2026-07-25 10:46:23 +00:00
Hide_D 4de7ebec17 요양 깃수 추가 2026-03-04 14:16:08 +00:00
Hide_D d43dff35fb fix: 디버그 코드 복구 2026-02-18 18:54:46 +00:00
Hide_D 5b13f8bd30 ip 기록 2026-02-17 22:31:08 +09:00
Hide_D e51bc6f711 fix: 턴 데몬에 cookie jar 적용 2026-02-17 20:35:15 +09:00
Hide_D 1b08aa0c58 wip: 디버그 코드 추가 2026-02-17 19:12:28 +09:00
Hide_D f2547fe207 dbg 2026-02-17 18:32:04 +09:00
류화영 cec7726731 fix: 방랑군이 빈번하게 해산하는 문제 해결 시도 2026-01-19 22:50:11 +09:00
Hide_D d679a6fed8 fix: InheritancePointManager 버그 수정 2025-11-01 17:38:02 +00:00
152 changed files with 123152 additions and 1009 deletions
+5 -2
View File
@@ -13,6 +13,9 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
[$turntime, $tnmt_time] = $gameStor->getValuesAsArray(['turntime','tnmt_time']); [$turntime, $tnmt_time] = $gameStor->getValuesAsArray(['turntime','tnmt_time']);
$clock = GameClock::fromStorage($gameStor);
$turntimeDisplay = $clock->formatTick(Util::toInt($turntime), true);
$tnmtTimeDisplay = $tnmt_time === null ? '-' : $clock->formatTick(Util::toInt($tnmt_time), true);
$plock = $db->queryFirstField('SELECT plock FROM plock WHERE `type` ="GAME"'); $plock = $db->queryFirstField('SELECT plock FROM plock WHERE `type` ="GAME"');
?> ?>
@@ -30,8 +33,8 @@ $plock = $db->queryFirstField('SELECT plock FROM plock WHERE `type` ="GAME"');
</head> </head>
<body> <body>
<form action=_119_b.php method=post> <form action=_119_b.php method=post>
시간조정 : <input type=text size=3 name=minute><input type=submit name=btn value='분당김'><input type=submit name=btn value='분지연'> 최종갱신 : <?=$turntime?><br> 시간조정 : <input type=text size=3 name=minute><input type=submit name=btn value='분당김'><input type=submit name=btn value='분지연'> 최종갱신 : <?=$turntimeDisplay?> (tick <?=$turntime?>)<br>
시간조정 : <input type=text size=3 name=minute2><input type=submit name=btn value='토너분당김'><input type=submit name=btn value='토너분지연'> 토너먼트 : <?=$tnmt_time?><br> 시간조정 : <input type=text size=3 name=minute2><input type=submit name=btn value='토너분당김'><input type=submit name=btn value='토너분지연'> 토너먼트 : <?=$tnmtTimeDisplay?> (tick <?=$tnmt_time?>)<br>
봉급지급 : <input type=submit name=btn value='금지급'><input type=submit name=btn value='쌀지급'><br> 봉급지급 : <input type=submit name=btn value='금지급'><input type=submit name=btn value='쌀지급'><br>
락 풀 기 : <input type=submit name=btn value='락걸기'><input type=submit name=btn value='락풀기'> 현재 : <?=$plock>0?"동결중":"가동중"?><br> 락 풀 기 : <input type=submit name=btn value='락걸기'><input type=submit name=btn value='락풀기'> 현재 : <?=$plock>0?"동결중":"가동중"?><br>
</form> </form>
+9 -36
View File
@@ -38,21 +38,9 @@ switch ($btn) {
usleep(500000); usleep(500000);
} }
$gameStor->cacheValues(['turntime', 'starttime', 'tnmt_time']); $clock = GameClock::fromStorage($gameStor);
$turntime = (new \DateTimeImmutable($gameStor->turntime))->sub(new \DateInterval("PT{$minute}M")); // 스케줄 전체를 벽시계에서 빼지 않고 논리 현재 tick만 앞으로 이동합니다.
$starttime = (new \DateTimeImmutable($gameStor->starttime))->sub(new \DateInterval("PT{$minute}M")); $clock->advance($gameStor, $clock->ticksFromMinutes($minute));
$tnmt_time = (new \DateTimeImmutable($gameStor->tnmt_time))->sub(new \DateInterval("PT{$minute}M"));
$gameStor->turntime = $turntime->format('Y-m-d H:i:s.u');
$gameStor->starttime = $starttime->format('Y-m-d H:i:s');
$gameStor->tnmt_time = $tnmt_time->format('Y-m-d H:i:s');
$db->update('general', [
'turntime' => $db->sqleval('DATE_SUB(turntime, INTERVAL %i MINUTE)', $minute)
], true);
$db->update('ng_auction', [
'close_date' => $db->sqleval('DATE_SUB(close_date, INTERVAL %i MINUTE)', $minute)
], 'finished = 0');
if ($locked) { if ($locked) {
unlock(); unlock();
} }
@@ -66,34 +54,19 @@ switch ($btn) {
} }
usleep(500000); usleep(500000);
} }
$gameStor->cacheValues(['turntime', 'starttime', 'tnmt_time']); $clock = GameClock::fromStorage($gameStor);
$turntime = (new \DateTimeImmutable($gameStor->turntime))->add(new \DateInterval("PT{$minute}M")); $clock->advance($gameStor, -$clock->ticksFromMinutes($minute));
$starttime = (new \DateTimeImmutable($gameStor->starttime))->add(new \DateInterval("PT{$minute}M"));
$tnmt_time = (new \DateTimeImmutable($gameStor->tnmt_time))->add(new \DateInterval("PT{$minute}M"));
$gameStor->turntime = $turntime->format('Y-m-d H:i:s.u');
$gameStor->starttime = $starttime->format('Y-m-d H:i:s');
$gameStor->tnmt_time = $tnmt_time->format('Y-m-d H:i:s');
$db->update('general', [
'turntime' => $db->sqleval('DATE_ADD(turntime, INTERVAL %i MINUTE)', $minute)
], true);
$db->update('ng_auction', [
'close_date' => $db->sqleval('DATE_ADD(close_date, INTERVAL %i MINUTE)', $minute)
], 'finished = 0');
if ($locked) { if ($locked) {
unlock(); unlock();
} }
break; break;
case "토너분당김": case "토너분당김":
$tnmt_time = new \DateTime($gameStor->tnmt_time); $clock = GameClock::fromStorage($gameStor);
$tnmt_time->sub(new \DateInterval("PT{$minute2}M")); $gameStor->tnmt_time = Util::toInt($gameStor->tnmt_time) - $clock->ticksFromMinutes($minute2);
$gameStor->tnmt_time = $tnmt_time->format('Y-m-d H:i:s');
break; break;
case "토너분지연": case "토너분지연":
$tnmt_time = new \DateTimeImmutable($gameStor->tnmt_time); $clock = GameClock::fromStorage($gameStor);
$tnmt_time->add(new \DateInterval("PT{$minute2}M")); $gameStor->tnmt_time = Util::toInt($gameStor->tnmt_time) + $clock->ticksFromMinutes($minute2);
$gameStor->tnmt_time = $tnmt_time->format('Y-m-d H:i:s');
break; break;
case "금지급": case "금지급":
processGoldIncome(); processGoldIncome();
+2 -2
View File
@@ -40,7 +40,7 @@ $admin = getAdmin();
</tr> </tr>
<tr> <tr>
<td width=110 align=right>시작시간변경</td> <td width=110 align=right>시작시간변경</td>
<td width=285><input type=text size=20 maxlength=20 style=color:white;background-color:black;text-align:right; name=starttime value='<?=substr($admin['starttime'], 0, 19)?>'><input type=submit name=btn value=변경1></td> <td width=285><input type=text size=20 maxlength=20 style=color:white;background-color:black;text-align:right; name=starttime value='<?=substr($admin['starttime_display'], 0, 19)?>'><input type=submit name=btn value=변경1></td>
</tr> </tr>
<tr> <tr>
<td width=110 align=right>최대 장수</td> <td width=110 align=right>최대 장수</td>
@@ -52,7 +52,7 @@ $admin = getAdmin();
<td width=110 align=right>시작 년도</td> <td width=110 align=right>시작 년도</td>
<td width=285><input type=text size=3 maxlength=3 style=color:white;background-color:black;text-align:right; name=startyear value='<?=$admin['startyear']?>'><input type=submit name=btn value=변경4></td> <td width=285><input type=text size=3 maxlength=3 style=color:white;background-color:black;text-align:right; name=startyear value='<?=$admin['startyear']?>'><input type=submit name=btn value=변경4></td>
<td width=110 align=right>최근 갱신 시간</td> <td width=110 align=right>최근 갱신 시간</td>
<td width=285>&nbsp;<?=$admin['turntime']?></td> <td width=285>&nbsp;<?=$admin['turntime_display']?> (tick <?=$admin['turntime']?>)</td>
</tr> </tr>
<tr> <tr>
<td width=110 align=right>턴시간</td> <td width=110 align=right>턴시간</td>
+8 -2
View File
@@ -25,13 +25,15 @@ if (!$v->validate()) {
$msg = Util::getPost('msg'); $msg = Util::getPost('msg');
$btn = Util::getPost('btn'); $btn = Util::getPost('btn');
$log = Util::getPost('log'); $log = Util::getPost('log');
$starttime = Util::getPost('starttime', 'string', (new \DateTime())->format('Y-m-d H:i:s')); $starttime = Util::getPost('starttime', 'string', null);
$maxgeneral = Util::getPost('maxgeneral', 'int', GameConst::$defaultMaxGeneral); $maxgeneral = Util::getPost('maxgeneral', 'int', GameConst::$defaultMaxGeneral);
$maxnation = Util::getPost('maxnation', 'int', GameConst::$defaultMaxNation); $maxnation = Util::getPost('maxnation', 'int', GameConst::$defaultMaxNation);
$startyear = Util::getPost('startyear', 'int', GameConst::$defaultStartYear); $startyear = Util::getPost('startyear', 'int', GameConst::$defaultStartYear);
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$starttime ??= $clock->formatTick($clock->nowTick());
$admin = getAdmin(); $admin = getAdmin();
@@ -43,7 +45,11 @@ switch ($btn) {
pushGlobalHistoryLog(["<R>★</><S>{$log}</>"]); pushGlobalHistoryLog(["<R>★</><S>{$log}</>"]);
break; break;
case "변경1": case "변경1":
$gameStor->starttime = (new \DateTime($starttime))->format('Y-m-d H:i:s'); $gameStor->clock_base_time = TimeUtil::format(GameClock::baseTimeForProjection(
new \DateTimeImmutable($starttime),
Util::toInt($gameStor->starttime),
$clock->getTurnTermMinutes(),
), true);
break; break;
case "변경2": case "변경2":
$gameStor->maxgeneral = $maxgeneral; $gameStor->maxgeneral = $maxgeneral;
+9 -9
View File
@@ -99,7 +99,7 @@ switch ($btn) {
], '`no` IN %li', $genlist); ], '`no` IN %li', $genlist);
break; break;
case "강제 사망": case "강제 사망":
$date = TimeUtil::now(true); $date = GameClock::fromStorage($gameStor)->nowTick();
$db->update('general', [ $db->update('general', [
'killturn' => 0, 'killturn' => 0,
'turntime' => $date, 'turntime' => $date,
@@ -113,7 +113,7 @@ switch ($btn) {
case "경험치1000": case "경험치1000":
$text = $btn . " 지급!"; $text = $btn . " 지급!";
foreach ($genlist as $generalID) { foreach ($genlist as $generalID) {
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []); $msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, Message::gameNow(), new \DateTime('9999-12-31'), []);
$msg->send(true); $msg->send(true);
} }
$db->update('general', [ $db->update('general', [
@@ -124,7 +124,7 @@ switch ($btn) {
case "공헌치1000": case "공헌치1000":
$text = $btn . " 지급!"; $text = $btn . " 지급!";
foreach ($genlist as $generalID) { foreach ($genlist as $generalID) {
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []); $msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, Message::gameNow(), new \DateTime('9999-12-31'), []);
$msg->send(true); $msg->send(true);
} }
$db->update('general', [ $db->update('general', [
@@ -135,7 +135,7 @@ switch ($btn) {
case "보숙10000": case "보숙10000":
$text = "보병숙련도+10000 지급!"; $text = "보병숙련도+10000 지급!";
foreach ($genlist as $generalID) { foreach ($genlist as $generalID) {
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []); $msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, Message::gameNow(), new \DateTime('9999-12-31'), []);
$msg->send(true); $msg->send(true);
} }
$db->update('general', [ $db->update('general', [
@@ -145,7 +145,7 @@ switch ($btn) {
case "궁숙10000": case "궁숙10000":
$text = "궁병숙련도+10000 지급!"; $text = "궁병숙련도+10000 지급!";
foreach ($genlist as $generalID) { foreach ($genlist as $generalID) {
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []); $msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, Message::gameNow(), new \DateTime('9999-12-31'), []);
$msg->send(true); $msg->send(true);
} }
$db->update('general', [ $db->update('general', [
@@ -156,7 +156,7 @@ switch ($btn) {
$src = MessageTarget::buildQuick($session->generalID); $src = MessageTarget::buildQuick($session->generalID);
$text = "기병숙련도+10000 지급!"; $text = "기병숙련도+10000 지급!";
foreach ($genlist as $generalID) { foreach ($genlist as $generalID) {
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []); $msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, Message::gameNow(), new \DateTime('9999-12-31'), []);
$msg->send(true); $msg->send(true);
} }
$db->update('general', [ $db->update('general', [
@@ -167,7 +167,7 @@ switch ($btn) {
$src = MessageTarget::buildQuick($session->generalID); $src = MessageTarget::buildQuick($session->generalID);
$text = "귀병숙련도+10000 지급!"; $text = "귀병숙련도+10000 지급!";
foreach ($genlist as $generalID) { foreach ($genlist as $generalID) {
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []); $msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, Message::gameNow(), new \DateTime('9999-12-31'), []);
$msg->send(true); $msg->send(true);
} }
$db->update('general', [ $db->update('general', [
@@ -178,7 +178,7 @@ switch ($btn) {
$src = MessageTarget::buildQuick($session->generalID); $src = MessageTarget::buildQuick($session->generalID);
$text = "차병숙련도+10000 지급!"; $text = "차병숙련도+10000 지급!";
foreach ($genlist as $generalID) { foreach ($genlist as $generalID) {
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []); $msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, Message::gameNow(), new \DateTime('9999-12-31'), []);
$msg->send(true); $msg->send(true);
} }
$db->update('general', [ $db->update('general', [
@@ -198,7 +198,7 @@ switch ($btn) {
case "메세지 전달": case "메세지 전달":
$text = $msg ?? ''; $text = $msg ?? '';
foreach ($genlist as $generalID) { foreach ($genlist as $generalID) {
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []); $msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, Message::gameNow(), new \DateTime('9999-12-31'), []);
$msg->send(true); $msg->send(true);
} }
break; break;
+2 -1
View File
@@ -42,6 +42,7 @@ if ($session->userGrade < 6) {
} }
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
if ($btn == '정렬하기') { if ($btn == '정렬하기') {
$gen = 0; $gen = 0;
@@ -111,7 +112,7 @@ $generalObj = General::createObjFromDB($gen, null, GeneralQueryMode::FullWithAcc
대상장수 : 대상장수 :
<select name=gen size=1> <select name=gen size=1>
<?php foreach ($generalBasicList as $general) : ?> <?php foreach ($generalBasicList as $general) : ?>
<option <?= $gen == $general['no'] ? 'selected' : '' ?> value='<?= $general['no'] ?>'><?= $general['name'] ?> (<?= substr($general['turntime'], 14, 5) ?>)</option> <option <?= $gen == $general['no'] ? 'selected' : '' ?> value='<?= $general['no'] ?>'><?= $general['name'] ?> (<?= substr($clock->formatTick(Util::toInt($general['turntime'])), 14, 5) ?>)</option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<input type=submit name=btn value='조회하기'> <input type=submit name=btn value='조회하기'>
-2
View File
@@ -100,8 +100,6 @@ $sel[$type] = "selected";
throw new \Exception("알 수 없는 외교 상태: {$dip['state']}"); throw new \Exception("알 수 없는 외교 상태: {$dip['state']}");
} }
$date = TimeUtil::now();
echo " echo "
<tr> <tr>
<td align=center style=color:" . newColor($nationColor[$me]) . ";background-color:{$nationColor[$me]};>$nationName[$me]</td> <td align=center style=color:" . newColor($nationColor[$me]) . ";background-color:{$nationColor[$me]};>$nationName[$me]</td>
+4 -1
View File
@@ -243,11 +243,14 @@ $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 horse, weapon, book, item, aux
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 -1
View File
@@ -9,6 +9,7 @@ include "func.php";
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
increaseRefresh("갱신정보", 1); increaseRefresh("갱신정보", 1);
@@ -23,7 +24,7 @@ $recentTraffic[] = [
'month' => $admin['month'], 'month' => $admin['month'],
'refresh' => $admin['refresh'], 'refresh' => $admin['refresh'],
'online' => $curonline, 'online' => $curonline,
'date' => TimeUtil::now() 'date' => $clock->formatNow()
]; ];
if ($admin['maxrefresh'] == 0) { if ($admin['maxrefresh'] == 0) {
+1 -1
View File
@@ -16,7 +16,7 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
increaseRefresh("현재도시", 1); increaseRefresh("현재도시", 1);
$lastExecute = substr($gameStor->turntime, 5, 14); $lastExecute = substr(GameClock::fromStorage($gameStor)->formatTick(Util::toInt($gameStor->turntime)), 5, 14);
$me = $db->queryFirstRow('SELECT no,nation,officer_level,city from general where owner=%i', $userID); $me = $db->queryFirstRow('SELECT no,nation,officer_level,city from general where owner=%i', $userID);
$myNation = $db->queryFirstRow('SELECT nation,level,spy FROM nation WHERE nation=%i', $me['nation']) ?? [ $myNation = $db->queryFirstRow('SELECT nation,level,spy FROM nation WHERE nation=%i', $me['nation']) ?? [
+2
View File
@@ -129,6 +129,7 @@ $templates = new \League\Plates\Engine('templates');
); );
$generalTurnList = []; $generalTurnList = [];
$clock = GameClock::fromStorage($gameStor);
foreach ($db->queryAllLists( foreach ($db->queryAllLists(
'SELECT general_id, turn_idx, brief FROM general_turn WHERE general_id IN %li AND turn_idx < 5 ORDER BY general_id ASC, turn_idx ASC', 'SELECT general_id, turn_idx, brief FROM general_turn WHERE general_id IN %li AND turn_idx < 5 ORDER BY general_id ASC, turn_idx ASC',
@@ -142,6 +143,7 @@ $templates = new \League\Plates\Engine('templates');
$genCntEff = 0; $genCntEff = 0;
foreach ($generals as &$general) { foreach ($generals as &$general) {
$general['turntime'] = $clock->formatTick(Util::toInt($general['turntime']), true);
$general['cityText'] = CityConst::byID($general['city'])->name; $general['cityText'] = CityConst::byID($general['city'])->name;
$general['troopText'] = $troopName[$general['troop']] ?? '-'; $general['troopText'] = $troopName[$general['troop']] ?? '-';
+24 -6
View File
@@ -21,8 +21,8 @@ $generalID = $session->generalID;
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['turntime', 'opentime', 'autorun_user', 'npcmode']); $gameStor->cacheValues(['turntime', 'opentime', 'autorun_user', 'npcmode']);
$clock = GameClock::fromStorage($gameStor);
increaseRefresh("내정보", 1); $nowTick = $clock->nowTick();
$me = General::createObjFromDB($generalID, null, GeneralQueryMode::FullWithAccessLog); $me = General::createObjFromDB($generalID, null, GeneralQueryMode::FullWithAccessLog);
@@ -41,12 +41,30 @@ $lastRefresh = $db->queryFirstField(
$generalID $generalID
); );
$targetTime = addTurn($lastRefresh, $gameStor->turnterm, GameConst::$minTurnDieOnPrestart); $nextChange = $me->getAuxVar('next_change');
if (!is_int($nextChange)) {
$nextChange = null;
}
$nextChangeDisplay = $nextChange === null ? null : $clock->formatTick($nextChange);
increaseRefresh("내정보", 1);
if ($gameStor->turntime <= $gameStor->opentime) { if ($gameStor->turntime <= $gameStor->opentime) {
$targetTime = $me->getAuxVar('prestart_delete_after');
if (!is_int($targetTime)) {
$targetTime = addTurn(
$lastRefresh === null ? $nowTick : Util::toInt($lastRefresh),
$gameStor->turnterm,
GameConst::$minTurnDieOnPrestart
);
$me->setAuxVar('prestart_delete_after', $targetTime);
$me->applyDB($db);
}
$targetTimeDisplay = $clock->formatTick($targetTime);
//서버 가오픈시 할 수 있는 행동 //서버 가오픈시 할 수 있는 행동
if ($me->getNPCType() == 0 && $me->getNationID() == 0) { if ($me->getNPCType() == 0 && $me->getNationID() == 0) {
$showDieOnPrestartBtn = true; $showDieOnPrestartBtn = true;
if ($targetTime <= TimeUtil::now()) { if ($targetTime <= $nowTick) {
$availableDieOnPrestart = true; $availableDieOnPrestart = true;
} }
} }
@@ -159,7 +177,7 @@ $changeDefence999Atmos = $me->onCalcDomestic('changeDefenceTrain', "atmos999", $
<a href="b_myPage.php?detachNPC=1"><button type="button" style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>빙의 해체 요청</button></a>--> <a href="b_myPage.php?detachNPC=1"><button type="button" style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>빙의 해체 요청</button></a>-->
<?php if ($showDieOnPrestartBtn) : ?> <?php if ($showDieOnPrestartBtn) : ?>
가오픈 기간 내 장수 삭제 (<?= substr($targetTime, 0, 19) ?> 부터)<br> 가오픈 기간 내 장수 삭제 (<?= $targetTimeDisplay ?> 부터)<br>
<button type="button" id='dieOnPrestart' style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>장수 삭제</button><br><br> <button type="button" id='dieOnPrestart' style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>장수 삭제</button><br><br>
<?php endif; ?> <?php endif; ?>
@@ -174,7 +192,7 @@ $changeDefence999Atmos = $me->onCalcDomestic('changeDefenceTrain', "atmos999", $
<?php endif; ?> <?php endif; ?>
<?php if ($gameStor->npcmode == 2 && $me->getNPCType() == 0) : ?> <?php if ($gameStor->npcmode == 2 && $me->getNPCType() == 0) : ?>
다른 장수 선택 (<?= substr($me->getAuxVar('next_change') ?? TimeUtil::now(), 0, 19) ?> 부터)<br> 다른 장수 선택 (<?= $nextChangeDisplay ?? '지금' ?>부터)<br>
<a href="select_general_from_pool.php" id='select_general_from_pool'><button type="button" style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>다른 장수 선택</button></a><br><br> <a href="select_general_from_pool.php" id='select_general_from_pool'><button type="button" style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>다른 장수 선택</button></a><br><br>
<?php endif; ?> <?php endif; ?>
+2 -2
View File
@@ -228,10 +228,10 @@ if($btn == "자동개최설정") {
$phase = 0; $phase = 0;
startBetting($admin['tnmt_type'], 720); startBetting($admin['tnmt_type'], 720);
} elseif($btn == "베팅마감") { } elseif($btn == "베팅마감") {
$dt = date("Y-m-d H:i:s", time() + 60); $clock = GameClock::fromStorage($gameStor);
$gameStor->tournament=7; $gameStor->tournament=7;
$gameStor->phase=0; $gameStor->phase=0;
$gameStor->tnmt_time = $dt; $gameStor->tnmt_time = $clock->nowTick() + $clock->ticksFromSeconds(60);
} elseif($btn == "16강") { } elseif($btn == "16강") {
finalFight($admin['tnmt_type'], $admin['tournament'], $admin['phase'], 16); finalFight($admin['tnmt_type'], $admin['tournament'], $admin['phase'], 16);
} elseif($btn == "8강") { } elseif($btn == "8강") {
+16
View File
@@ -51,6 +51,22 @@
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;
} }
+77 -78
View File
@@ -630,7 +630,11 @@ function generalInfo(General $generalObj)
$injury = "건강"; $injury = "건강";
} }
$remaining = (new \DateTimeImmutable($generalObj->getTurnTime()))->diff(new \DateTimeImmutable())->i; $clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$remaining = max(0, intdiv(
$generalObj->getTurnTick() - $clock->nowTick(),
$clock->ticksFromMinutes(1),
));
if ($nation['color'] == "") { if ($nation['color'] == "") {
$nation['color'] = "#000000"; $nation['color'] = "#000000";
@@ -921,44 +925,36 @@ function banner()
); );
} }
function addTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true) function addTurn(int $tick, int $turnterm, int $turn = 1, bool $withFraction = true): int
{ {
$date = new \DateTime($date); return $tick + GameClock::TICKS_PER_TURN * $turn;
$target = $turnterm * $turn;
$date->add(new \DateInterval("PT{$target}M"));
if ($withFraction) {
return $date->format('Y-m-d H:i:s.u');
}
return $date->format('Y-m-d H:i:s');
} }
function subTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true) function subTurn(int $tick, int $turnterm, int $turn = 1, bool $withFraction = true): int
{ {
$date = new \DateTime($date); return $tick - GameClock::TICKS_PER_TURN * $turn;
$target = $turnterm * $turn;
$date->sub(new \DateInterval("PT{$target}M"));
if ($withFraction) {
return $date->format('Y-m-d H:i:s.u');
}
return $date->format('Y-m-d H:i:s');
} }
function cutTurn($date, int $turnterm, bool $withFraction = true) function cutTurn(int $tick, int $turnterm, bool $withFraction = true): int
{ {
$date = new \DateTime($date); $remainder = $tick % GameClock::TICKS_PER_TURN;
if ($remainder < 0) {
$remainder += GameClock::TICKS_PER_TURN;
}
return $tick - $remainder;
}
$baseDate = new \DateTime($date->format('Y-m-d')); /** 시나리오 초기화 입력인 벽시계를 기존 01:00 기준 월 경계로 정렬합니다. */
$baseDate->sub(new \DateInterval("P1D")); function cutTurnDateTime(string $date, int $turnterm, bool $withFraction = true): string
$baseDate->add(new \DateInterval("PT1H")); {
$dateObj = new \DateTime($date);
$diffMin = intdiv($date->getTimeStamp() - $baseDate->getTimeStamp(), 60); $baseDate = new \DateTime($dateObj->format('Y-m-d'));
$baseDate->sub(new \DateInterval('P1D'));
$baseDate->add(new \DateInterval('PT1H'));
$diffMin = intdiv($dateObj->getTimestamp() - $baseDate->getTimestamp(), 60);
$diffMin -= $diffMin % $turnterm; $diffMin -= $diffMin % $turnterm;
$baseDate->add(new \DateInterval("PT{$diffMin}M")); $baseDate->add(new \DateInterval("PT{$diffMin}M"));
if ($withFraction) { return $baseDate->format($withFraction ? 'Y-m-d H:i:s.u' : 'Y-m-d H:i:s');
return $baseDate->format('Y-m-d H:i:s.u');
}
return $baseDate->format('Y-m-d H:i:s');
} }
function cutDay($date, int $turnterm, bool $withFraction = true) function cutDay($date, int $turnterm, bool $withFraction = true)
@@ -1001,11 +997,9 @@ function increaseRefresh($type = "", $cnt = 1)
$generalID = $session->generalID; $generalID = $session->generalID;
$userGrade = $session->userGrade; $userGrade = $session->userGrade;
$dateObj = new \DateTimeImmutable();
$date = TimeUtil::format($dateObj, false);
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$date = GameClock::fromStorage($gameStor)->nowTick();
$isunited = $gameStor->isunited; $isunited = $gameStor->isunited;
$opentime = $gameStor->opentime; $opentime = $gameStor->opentime;
@@ -1047,7 +1041,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['REMOTE_ADDR'] ?? 'local'; $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_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', [
@@ -1093,7 +1087,7 @@ function updateTraffic()
'month' => $admin['month'], 'month' => $admin['month'],
'refresh' => $admin['refresh'], 'refresh' => $admin['refresh'],
'online' => $online, 'online' => $online,
'date' => TimeUtil::now(), 'date' => GameClock::fromStorage($gameStor)->formatNow(),
]; ];
$gameStor->recentTraffic = $recentTraffic; $gameStor->recentTraffic = $recentTraffic;
@@ -1151,12 +1145,13 @@ function timeover(): bool
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
list($turnterm, $turntime) = $gameStor->getValuesAsArray(['turnterm', 'turntime']); list($turnterm, $turntime) = $gameStor->getValuesAsArray(['turnterm', 'turntime']);
$diff = (new \DateTime())->getTimestamp() - (new \DateTime($turntime))->getTimestamp(); $clock = GameClock::fromStorage($gameStor);
$diff = $clock->nowTick() - Util::toInt($turntime);
$t = min($turnterm, 5); $t = min($turnterm, 5);
$term = $diff; $term = $clock->ticksFromSeconds($t);
if ($term >= $t || $term < 0) { if ($diff >= $term || $diff < 0) {
return true; return true;
} else { } else {
return false; return false;
@@ -1169,9 +1164,8 @@ function checkDelay()
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
//서버정보 //서버정보
$now = new \DateTimeImmutable(); $clock = GameClock::fromStorage($gameStor);
$turntime = new \DateTimeImmutable($gameStor->turntime); $timeMinDiff = intdiv($clock->nowTick() - Util::toInt($gameStor->turntime), $clock->ticksFromMinutes(1));
$timeMinDiff = intdiv($now->getTimestamp() - $turntime->getTimestamp(), 60);
// 1턴이상 갱신 없었으면 서버 지연 // 1턴이상 갱신 없었으면 서버 지연
$term = $gameStor->turnterm; $term = $gameStor->turnterm;
@@ -1186,18 +1180,15 @@ function checkDelay()
$iter = intdiv($timeMinDiff, $term); $iter = intdiv($timeMinDiff, $term);
if ($iter > $threshold) { if ($iter > $threshold) {
$minute = $iter * $term; $minute = $iter * $term;
$newTurntime = $turntime->add(new \DateInterval("PT{$minute}M")); $delayTick = $clock->ticksFromMinutes($minute);
$newNextTurntime = $turntime->add(new \DateInterval("PT{$term}M")); $gameStor->turntime = Util::toInt($gameStor->turntime) + $delayTick;
$gameStor->turntime = $newTurntime->format('Y-m-d H:i:s'); $gameStor->starttime = Util::toInt($gameStor->starttime) + $delayTick;
$gameStor->starttime = (new \DateTimeImmutable($gameStor->starttime))
->add(new \DateInterval("PT{$minute}M"))
->format('Y-m-d H:i:s');
$db->update('general', [ $db->update('general', [
'turntime' => $db->sqleval('DATE_ADD(turntime, INTERVAL %i MINUTE)', $minute) 'turntime' => $db->sqleval('turntime + %i', $delayTick)
], 'turntime<=DATE_ADD(turntime, INTERVAL %i MINUTE)', $term); ], true);
$db->update('ng_auction', [ $db->update('ng_auction', [
'close_date' => $db->sqleval('DATE_ADD(close_date, INTERVAL %i MINUTE)', $minute) 'close_tick' => $db->sqleval('close_tick + %i', $delayTick)
], 'finished = 0'); ], 'finished = 0');
} }
} }
@@ -1254,10 +1245,8 @@ function turnDate($curtime)
$admin = $gameStor->getValues(['startyear', 'starttime', 'turnterm', 'year', 'month']); $admin = $gameStor->getValues(['startyear', 'starttime', 'turnterm', 'year', 'month']);
$turn = $admin['starttime']; $turn = $admin['starttime'];
$curturn = cutTurn($curtime, $admin['turnterm']); $curturn = cutTurn(Util::toInt($curtime), $admin['turnterm']);
$term = $admin['turnterm']; $num = intdiv($curturn - Util::toInt($turn), GameClock::TICKS_PER_TURN);
$num = intdiv((strtotime($curturn) - strtotime($turn)), $term * 60);
$date = $admin['startyear'] * 12; $date = $admin['startyear'] * 12;
$date += $num; $date += $num;
@@ -1387,12 +1376,14 @@ function CheckHall($no)
return; return;
} }
$unitedDate = TimeUtil::now(); $clock = GameClock::fromStorage($gameStor);
$unitedDate = $clock->formatTick($clock->nowTick());
$nation = $generalObj->getStaticNation(); $nation = $generalObj->getStaticNation();
$serverCnt = $db->queryFirstField('SELECT count(*) FROM ng_games'); $serverCnt = $db->queryFirstField('SELECT count(*) FROM ng_games');
[$scenarioIdx, $scenarioName, $startTime] = $gameStor->getValuesAsArray(['scenario', 'scenario_text', 'starttime']); [$scenarioIdx, $scenarioName, $startTick] = $gameStor->getValuesAsArray(['scenario', 'scenario_text', 'starttime']);
$startTime = $clock->formatTick(Util::toInt($startTick));
$ownerName = $generalObj->getVar('owner_name'); $ownerName = $generalObj->getVar('owner_name');
if ($generalObj->getVar('owner')) { if ($generalObj->getVar('owner')) {
@@ -1403,6 +1394,9 @@ 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') {
@@ -1706,7 +1700,16 @@ function getAdmin()
{ {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
return $gameStor->getAll(); $admin = $gameStor->getAll();
$clock = GameClock::fromStorage($gameStor);
foreach (['turntime', 'starttime', 'opentime', 'tnmt_time'] as $key) {
if (($admin[$key] ?? null) !== null) {
$admin["{$key}_display"] = $clock->formatTick(Util::toInt($admin[$key]), true);
}
}
$admin['clock_now_tick'] = $clock->nowTick();
$admin['clock_now_display'] = $clock->formatTick($admin['clock_now_tick'], true);
return $admin;
} }
/** @return General[] */ /** @return General[] */
@@ -1786,9 +1789,11 @@ function deleteNation(General $lord, bool $applyDB): array
$db->delete('troop', 'nation=%i', $nationID); $db->delete('troop', 'nation=%i', $nationID);
// 국가 삭제 // 국가 삭제
$gameDate = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
$db->insert('ng_old_nations', [ $db->insert('ng_old_nations', [
'server_id' => UniqueConst::$serverID, 'server_id' => UniqueConst::$serverID,
'nation' => $nationID, 'nation' => $nationID,
'date' => $gameDate,
'data' => Json::encode($nation) 'data' => Json::encode($nation)
]); ]);
$db->delete('nation', 'nation=%i', $nationID); $db->delete('nation', 'nation=%i', $nationID);
@@ -2196,35 +2201,29 @@ function SabotageInjury(RandUtil $rng, array $cityGeneralList, string $reason):
return $injuryCount; return $injuryCount;
} }
function getRandTurn(RandUtil $rng, $term, ?\DateTimeInterface $baseDateTime = null) function getRandTurn(RandUtil $rng, int $term, ?int $baseTick = null): int
{ {
if ($baseDateTime === null) { $db = DB::db();
$baseDateTime = new \DateTimeImmutable(); $clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
} else if ($baseDateTime instanceof \DateTime) { $baseTick ??= $clock->nowTick();
$baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime);
} else if ($baseDateTime instanceof \DateTimeImmutable) {
//do Nothing
} else {
throw new MustNotBeReachedException();
}
$randSecond = $rng->nextRangeInt(0, 60 * $term - 1); $randSecond = $rng->nextRangeInt(0, 60 * $term - 1);
$randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수 $randMicrosecond = $rng->nextRangeInt(0, 999999); // 레거시 RNG 소비 6자리 유지
return TimeUtil::format($baseDateTime->add(TimeUtil::secondsToDateInterval($randSecond + $randFraction)), true); return $baseTick
+ $clock->ticksFromSeconds($randSecond)
+ intdiv($randMicrosecond * $clock->ticksPerSecond(), 1_000_000);
} }
function getRandTurn2(RandUtil $rng, $term, ?\DateTimeInterface $baseDateTime = null) function getRandTurn2(RandUtil $rng, int $term, ?int $baseTick = null): int
{ {
if ($baseDateTime === null) { $db = DB::db();
$baseDateTime = new \DateTimeImmutable(); $clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
} else if ($baseDateTime instanceof \DateTime) { $baseTick ??= $clock->nowTick();
$baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime);
} else {
throw new MustNotBeReachedException();
}
$randSecond = $rng->nextRangeInt(0, 60 * $term - 1); $randSecond = $rng->nextRangeInt(0, 60 * $term - 1);
$randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수 $randMicrosecond = $rng->nextRangeInt(0, 999999); // 레거시 RNG 소비 6자리 유지
return $baseDateTime->sub(TimeUtil::secondsToDateInterval($randSecond + $randFraction))->format('Y-m-d H:i:s.u'); return $baseTick
- $clock->ticksFromSeconds($randSecond)
- intdiv($randMicrosecond * $clock->ticksPerSecond(), 1_000_000);
} }
+4 -3
View File
@@ -62,11 +62,12 @@ function processAuction()
{ {
$db = DB::db(); $db = DB::db();
$now = TimeUtil::now(); $gameStor = KVStorage::getStorage($db, 'game_env');
$nowTick = GameClock::fromStorage($gameStor)->nowTick();
$auctionList = $db->queryAllLists( $auctionList = $db->queryAllLists(
'SELECT id, `type` FROM ng_auction WHERE `close_date` <= %s AND finished = 0', 'SELECT id, `type` FROM ng_auction WHERE `close_tick` <= %i AND finished = 0',
$now $nowTick
); );
if (!$auctionList) { if (!$auctionList) {
+64 -36
View File
@@ -42,15 +42,21 @@ function pushGeneralCommand(int $generalID, int $turnCnt=1){
$db = DB::db(); $db = DB::db();
$db->update('general_turn', [ $db->query(
'turn_idx'=>$db->sqleval('turn_idx + %i', $turnCnt) 'UPDATE general_turn AS dst
], 'general_id=%i ORDER BY turn_idx DESC', $generalID); LEFT JOIN general_turn AS src
$db->update('general_turn', [ ON src.general_id = dst.general_id
'turn_idx'=>$db->sqleval('turn_idx - %i', GameConst::$maxTurn), AND src.turn_idx = dst.turn_idx - %i
'action'=>'휴식', SET dst.action = IF(src.id IS NULL, %s, src.action),
'arg'=>'{}', dst.arg = IF(src.id IS NULL, %s, src.arg),
'brief'=>'휴식' dst.brief = IF(src.id IS NULL, %s, src.brief)
], 'general_id=%i AND turn_idx >= %i', $generalID, GameConst::$maxTurn); WHERE dst.general_id = %i',
$turnCnt,
'휴식',
'{}',
'휴식',
$generalID
);
} }
function pullGeneralCommand(int $generalID, int $turnCnt=1){ function pullGeneralCommand(int $generalID, int $turnCnt=1){
@@ -67,15 +73,21 @@ function pullGeneralCommand(int $generalID, int $turnCnt=1){
$db = DB::db(); $db = DB::db();
$db->update('general_turn', [ $db->query(
'turn_idx'=>$db->sqleval('turn_idx + %i', GameConst::$maxTurn), 'UPDATE general_turn AS dst
'action'=>'휴식', LEFT JOIN general_turn AS src
'arg'=>'{}', ON src.general_id = dst.general_id
'brief'=>'휴식' AND src.turn_idx = dst.turn_idx + %i
], 'general_id=%i AND turn_idx < %i', $generalID, $turnCnt); SET dst.action = IF(src.id IS NULL, %s, src.action),
$db->update('general_turn', [ dst.arg = IF(src.id IS NULL, %s, src.arg),
'turn_idx'=>$db->sqleval('turn_idx - %i', $turnCnt) dst.brief = IF(src.id IS NULL, %s, src.brief)
], 'general_id=%i ORDER BY turn_idx ASC', $generalID); WHERE dst.general_id = %i',
$turnCnt,
'휴식',
'{}',
'휴식',
$generalID
);
} }
function repeatGeneralCommand(int $generalId, int $turnCnt){ function repeatGeneralCommand(int $generalId, int $turnCnt){
@@ -126,15 +138,23 @@ function pushNationCommand(int $nationID, int $officerLevel, int $turnCnt=1){
$db = DB::db(); $db = DB::db();
$db->update('nation_turn', [ $db->query(
'turn_idx'=>$db->sqleval('turn_idx + %i', $turnCnt) 'UPDATE nation_turn AS dst
], 'nation_id=%i AND officer_level=%i ORDER BY turn_idx DESC', $nationID, $officerLevel); LEFT JOIN nation_turn AS src
$db->update('nation_turn', [ ON src.nation_id = dst.nation_id
'turn_idx'=>$db->sqleval('turn_idx - %i', GameConst::$maxChiefTurn), AND src.officer_level = dst.officer_level
'action'=>'휴식', AND src.turn_idx = dst.turn_idx - %i
'arg'=>'{}', SET dst.action = IF(src.id IS NULL, %s, src.action),
'brief'=>'휴식' dst.arg = IF(src.id IS NULL, %s, src.arg),
], 'nation_id=%i AND officer_level=%i AND turn_idx >= %i ORDER BY turn_idx ASC', $nationID, $officerLevel, GameConst::$maxChiefTurn); dst.brief = IF(src.id IS NULL, %s, src.brief)
WHERE dst.nation_id = %i AND dst.officer_level = %i',
$turnCnt,
'휴식',
'{}',
'휴식',
$nationID,
$officerLevel
);
} }
function pullNationCommand(int $nationID, int $officerLevel, int $turnCnt=1){ function pullNationCommand(int $nationID, int $officerLevel, int $turnCnt=1){
@@ -157,15 +177,23 @@ function pullNationCommand(int $nationID, int $officerLevel, int $turnCnt=1){
$db = DB::db(); $db = DB::db();
$db->update('nation_turn', [ $db->query(
'turn_idx'=>$db->sqleval('turn_idx + %i', GameConst::$maxChiefTurn), 'UPDATE nation_turn AS dst
'action'=>'휴식', LEFT JOIN nation_turn AS src
'arg'=>'{}', ON src.nation_id = dst.nation_id
'brief'=>'휴식', AND src.officer_level = dst.officer_level
], 'nation_id=%i AND officer_level=%i AND turn_idx < %i', $nationID, $officerLevel, $turnCnt); AND src.turn_idx = dst.turn_idx + %i
$db->update('nation_turn', [ SET dst.action = IF(src.id IS NULL, %s, src.action),
'turn_idx'=>$db->sqleval('turn_idx - %i', $turnCnt) dst.arg = IF(src.id IS NULL, %s, src.arg),
], 'nation_id=%i AND officer_level=%i ORDER BY turn_idx ASC', $nationID, $officerLevel); dst.brief = IF(src.id IS NULL, %s, src.brief)
WHERE dst.nation_id = %i AND dst.officer_level = %i',
$turnCnt,
'휴식',
'{}',
'휴식',
$nationID,
$officerLevel
);
} }
function repeatNationCommand(int $nationID, int $officerLevel, int $turnCnt){ function repeatNationCommand(int $nationID, int $officerLevel, int $turnCnt){
+13 -2
View File
@@ -654,13 +654,21 @@ function checkStatistic()
function convForOldGeneral(array $general, int $year, int $month) function convForOldGeneral(array $general, int $year, int $month)
{ {
$general['history'] = getGeneralHistoryLogAll($general['no']); $general['history'] = getGeneralHistoryLogAll($general['no']);
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$turnTimeDisplay = $clock->formatTick(Util::toInt($general['turntime']), true);
$general['turntime_tick'] = Util::toInt($general['turntime']);
$general['turntime'] = $turnTimeDisplay;
if ($general['recent_war'] !== null) {
$general['recent_war_tick'] = Util::toInt($general['recent_war']);
$general['recent_war'] = $clock->formatTick(Util::toInt($general['recent_war']), true);
}
return [ return [
'server_id' => UniqueConst::$serverID, 'server_id' => UniqueConst::$serverID,
'general_no' => $general['no'], 'general_no' => $general['no'],
'owner' => $general['owner'], 'owner' => $general['owner'],
'name' => $general['name'], 'name' => $general['name'],
'last_yearmonth' => $year * 100 + $month, 'last_yearmonth' => $year * 100 + $month,
'turntime' => $general['turntime'], 'turntime' => $turnTimeDisplay,
'data' => Json::encode($general) 'data' => Json::encode($general)
]; ];
} }
@@ -734,7 +742,7 @@ function checkEmperior()
/** @var int[] */ /** @var int[] */
$auctionList = $db->queryFirstColumn( $auctionList = $db->queryFirstColumn(
'SELECT `id` FROM `ng_auction` WHERE `type` = %s AND `finished` = 0 ORDER BY `close_date` ASC', 'SELECT `id` FROM `ng_auction` WHERE `type` = %s AND `finished` = 0 ORDER BY `close_tick` ASC',
AuctionType::UniqueItem->value AuctionType::UniqueItem->value
); );
foreach($auctionList as $auctionID){ foreach($auctionList as $auctionID){
@@ -832,10 +840,12 @@ function checkEmperior()
storeOldGenerals(0, $admin['year'], $admin['month']); storeOldGenerals(0, $admin['year'], $admin['month']);
storeOldGenerals($nation['nation'], $admin['year'], $admin['month']); storeOldGenerals($nation['nation'], $admin['year'], $admin['month']);
$gameDate = GameClock::fromStorage($gameStor)->formatNow();
$db->insert('ng_old_nations', [ $db->insert('ng_old_nations', [
'server_id' => UniqueConst::$serverID, 'server_id' => UniqueConst::$serverID,
'nation' => $nation['nation'], 'nation' => $nation['nation'],
'date' => $gameDate,
'data' => Json::encode($nation) 'data' => Json::encode($nation)
]); ]);
@@ -843,6 +853,7 @@ function checkEmperior()
$db->insert('ng_old_nations', [ $db->insert('ng_old_nations', [
'server_id' => UniqueConst::$serverID, 'server_id' => UniqueConst::$serverID,
'nation' => 0, 'nation' => 0,
'date' => $gameDate,
'data' => Json::encode([ 'data' => Json::encode([
'nation' => 0, 'nation' => 0,
'name' => '재야', 'name' => '재야',
+4
View File
@@ -4,6 +4,10 @@ namespace sammo;
function printLimitMsg($turntime) function printLimitMsg($turntime)
{ {
if (is_int($turntime) || (is_string($turntime) && ctype_digit(ltrim($turntime, '-')))) {
$gameStor = KVStorage::getStorage(DB::db(), 'game_env');
$turntime = GameClock::fromStorage($gameStor)->formatTick(Util::toInt($turntime), true);
}
//FIXME: template로 이동. //FIXME: template로 이동.
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
+4
View File
@@ -63,6 +63,10 @@ function chiefTurnTable()
function templateLimitMsg(string $turntime): string function templateLimitMsg(string $turntime): string
{ {
if (ctype_digit(ltrim($turntime, '-'))) {
$gameStor = KVStorage::getStorage(DB::db(), 'game_env');
$turntime = GameClock::fromStorage($gameStor)->formatTick(Util::toInt($turntime), true);
}
return "이미 너무 많은 접속을 하셨습니다. 다음 턴에 다시 시도해주세요. (턴시간: {$turntime})"; return "이미 너무 많은 접속을 하셨습니다. 다음 턴에 다시 시도해주세요. (턴시간: {$turntime})";
} }
+10 -7
View File
@@ -17,10 +17,10 @@ function processTournament()
{ {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$admin = $gameStor->getValues(['tournament', 'phase', 'tnmt_type', 'tnmt_auto', 'tnmt_time', 'turnterm', 'last_tournament_betting_id']); $admin = $gameStor->getValues(['tournament', 'phase', 'tnmt_type', 'tnmt_auto', 'tnmt_time', 'turnterm', 'last_tournament_betting_id']);
$now = new \DateTime(); $offset = intdiv($clock->nowTick() - Util::toInt($admin['tnmt_time']), $clock->ticksPerSecond());
$offset = $now->getTimestamp() - (new \DateTime($admin['tnmt_time']))->getTimestamp();
//수동일땐 무시 //수동일땐 무시
if (!$admin['tnmt_auto']) { if (!$admin['tnmt_auto']) {
@@ -122,10 +122,10 @@ function processTournament()
if ($tnmt == 6) { if ($tnmt == 6) {
$betTerm = Util::valueFit($unit * 60, null, 3600); $betTerm = Util::valueFit($unit * 60, null, 3600);
//처리 초 더한 날짜 //처리 초 더한 날짜
$dt = date("Y-m-d H:i:s", strtotime($admin['tnmt_time']) + $unit * $i + $betTerm);
$gameStor->tournament = $tnmt; $gameStor->tournament = $tnmt;
$gameStor->phase = $phase; $gameStor->phase = $phase;
$gameStor->tnmt_time = $dt; $gameStor->tnmt_time = Util::toInt($admin['tnmt_time'])
+ $clock->ticksFromSeconds($unit * $i + $betTerm);
return; return;
} }
} }
@@ -133,7 +133,7 @@ function processTournament()
$second = $unit * $iter; $second = $unit * $iter;
$gameStor->tournament = $tnmt; $gameStor->tournament = $tnmt;
$gameStor->phase = $phase; $gameStor->phase = $phase;
$gameStor->tnmt_time = (new \DateTimeImmutable($admin['tnmt_time']))->add(new \DateInterval("PT{$second}S"))->format('Y-m-d H:i:s'); $gameStor->tnmt_time = Util::toInt($admin['tnmt_time']) + $clock->ticksFromSeconds($second);
} }
function getTournamentTermText(int $turnTerm) function getTournamentTermText(int $turnTerm)
@@ -160,7 +160,8 @@ function getTournamentTime()
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
list($tnmt, $tnmt_time) = $gameStor->getValuesAsArray(['tournament', 'tnmt_time']); list($tnmt, $tnmt_time) = $gameStor->getValuesAsArray(['tournament', 'tnmt_time']);
$dt = substr($tnmt_time, 11, 5); $clock = GameClock::fromStorage($gameStor);
$dt = substr($clock->formatTick(Util::toInt($tnmt_time)), 11, 5);
switch ($tnmt) { switch ($tnmt) {
case 1: case 1:
$tnmt = "개막시간 {$dt}"; $tnmt = "개막시간 {$dt}";
@@ -284,9 +285,11 @@ function startTournament($type)
$admin = $gameStor->getValues(['year', 'month', 'turnterm']); $admin = $gameStor->getValues(['year', 'month', 'turnterm']);
$turnTerm = $admin['turnterm']; $turnTerm = $admin['turnterm'];
$unit = calcTournamentTerm($turnTerm); $unit = calcTournamentTerm($turnTerm);
$clock = GameClock::fromStorage($gameStor);
$gameStor->tnmt_auto = true; $gameStor->tnmt_auto = true;
$gameStor->tnmt_time = (new \DateTimeImmutable())->add(new \DateInterval("PT{$unit}M"))->format('Y-m-d H:i:s'); // 기존 startTournament은 unit을 분으로 더하므로 그 계약을 유지합니다.
$gameStor->tnmt_time = $clock->nowTick() + $clock->ticksFromMinutes($unit);
$gameStor->tournament = 1; $gameStor->tournament = 1;
$gameStor->tnmt_type = $type; $gameStor->tnmt_type = $type;
$gameStor->last_tournament_betting_id = 0; $gameStor->last_tournament_betting_id = 0;
+2 -1
View File
@@ -7,6 +7,7 @@ include "func.php";
Session::requireLogin()->loginGame()->setReadOnly(); Session::requireLogin()->loginGame()->setReadOnly();
$mapName = GameConst::$mapName; $mapName = GameConst::$mapName;
$frontClock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
@@ -28,7 +29,7 @@ $mapName = GameConst::$mapName;
'maxTurn' => GameConst::$maxTurn, 'maxTurn' => GameConst::$maxTurn,
'maxPushTurn' => 12, 'maxPushTurn' => 12,
'serverNow' => TimeUtil::now(false), 'serverNow' => $frontClock->formatTick($frontClock->nowTick()),
] ]
], false) ?> ], false) ?>
<?= WebUtil::printJS('../d_shared/common_path.js') ?> <?= WebUtil::printJS('../d_shared/common_path.js') ?>
+11 -8
View File
@@ -25,20 +25,23 @@ if(!$reserved){
]); ]);
} }
$reservedDate = new \DateTime($reserved['date']); $reservedDate = new \DateTimeImmutable($reserved['date']);
$now = new \DateTime(); $now = GameClock::readWallTime();
$status = 'not_yet'; $status = 'not_yet';
list($isUnited, $lastTurn) = $gameStor->getValuesAsArray(['isunited', 'turntime']); list($isUnited, $unitedWallAnchor) = $gameStor->getValuesAsArray(['isunited', 'autoreset_united_wall_anchor']);
if($isUnited === null || $lastTurn === null){ if($isUnited === null){
$isUnited = 2; $isUnited = 2;
$lastTurn = '2000-01-01';
} }
$lastTurn = null;
if($lastTurn !== null){ if($isUnited > 0){
$lastTurn = new \DateTime($lastTurn); if(!is_string($unitedWallAnchor) || $unitedWallAnchor === ''){
$unitedWallAnchor = TimeUtil::format($now, true);
$gameStor->autoreset_united_wall_anchor = $unitedWallAnchor;
}
$lastTurn = new \DateTimeImmutable($unitedWallAnchor);
} }
if($lastTurn === null){ if($lastTurn === null){
+2 -1
View File
@@ -10,6 +10,7 @@ $session = Session::requireGameLogin()->setReadOnly();
$userID = Session::getUserID(); $userID = Session::getUserID();
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$isSecretBoard = Util::getPost('isSecret', 'bool', false); $isSecretBoard = Util::getPost('isSecret', 'bool', false);
$title = Util::getPost('title'); $title = Util::getPost('title');
@@ -67,7 +68,7 @@ $icon = GetImageURL($me['imgsvr'], $me['picture']);
$db->insert('board', [ $db->insert('board', [
'nation_no'=>$me['nation'], 'nation_no'=>$me['nation'],
'is_secret'=>$isSecretBoard, 'is_secret'=>$isSecretBoard,
'date'=>TimeUtil::now(), 'date'=>$clock->formatNow(),
'general_no'=>$me['no'], 'general_no'=>$me['no'],
'author'=>$me['name'], 'author'=>$me['name'],
'author_icon'=>$icon, 'author_icon'=>$icon,
+2 -1
View File
@@ -10,6 +10,7 @@ $session = Session::requireGameLogin()->setReadOnly();
$userID = Session::getUserID(); $userID = Session::getUserID();
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$articleNo = Util::getPost('articleNo', 'int'); $articleNo = Util::getPost('articleNo', 'int');
$text = Util::getPost('text'); $text = Util::getPost('text');
@@ -73,7 +74,7 @@ else if ($isSecretBoard && $permission < 2) {
$db->insert('comment', [ $db->insert('comment', [
'nation_no'=>$me['nation'], 'nation_no'=>$me['nation'],
'is_secret'=>$isSecretBoard, 'is_secret'=>$isSecretBoard,
'date'=>TimeUtil::now(), 'date'=>$clock->formatNow(),
'document_no'=>$articleNo, 'document_no'=>$articleNo,
'general_no'=>$me['no'], 'general_no'=>$me['no'],
'author'=>$me['name'], 'author'=>$me['name'],
+1 -1
View File
@@ -83,7 +83,7 @@ else{
} }
$now = new \DateTime(); $now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31'); $unlimited = new \DateTime('9999-12-31');
if(in_array($stateOpt, ['try_destroy_src', 'try_destroy_dest'])){ if(in_array($stateOpt, ['try_destroy_src', 'try_destroy_dest'])){
+1 -1
View File
@@ -69,7 +69,7 @@ $destNation = getNationStaticInfo($letter['dest_nation_id']);
$src = new MessageTarget($me['no'], $me['name'], $destNation['nation'], $destNation['name'], $destNation['color'], $me['icon']); $src = new MessageTarget($me['no'], $me['name'], $destNation['nation'], $destNation['name'], $destNation['color'], $me['icon']);
$dest = new MessageTarget(0, '', $srcNation['nation'], $srcNation['name'], $srcNation['color']); $dest = new MessageTarget(0, '', $srcNation['nation'], $srcNation['name'], $srcNation['color']);
$now = new \DateTime(); $now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31'); $unlimited = new \DateTime('9999-12-31');
if($isAgree){ if($isAgree){
+1 -1
View File
@@ -63,7 +63,7 @@ $destNation = getNationStaticInfo($letter['dest_nation_id']);
$src = new MessageTarget($me['no'], $me['name'], $srcNation['nation'], $srcNation['name'], $srcNation['color'], $me['icon']); $src = new MessageTarget($me['no'], $me['name'], $srcNation['nation'], $srcNation['name'], $srcNation['color'], $me['icon']);
$dest = new MessageTarget(0, '', $destNation['nation'], $destNation['name'], $destNation['color']); $dest = new MessageTarget(0, '', $destNation['nation'], $destNation['name'], $destNation['color']);
$now = new \DateTime(); $now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31'); $unlimited = new \DateTime('9999-12-31');
$aux['reason'] = [ $aux['reason'] = [
+4 -2
View File
@@ -140,6 +140,8 @@ else{
$me['icon'] = GetImageURL($me['imgsvr'], $me['picture']); $me['icon'] = GetImageURL($me['imgsvr'], $me['picture']);
$clock = GameClock::fromStorage($gameStor);
$gameNow = $clock->nowDateTime();
$db->insert('ng_diplomacy', [ $db->insert('ng_diplomacy', [
'src_nation_id'=>$srcNation['nation'], 'src_nation_id'=>$srcNation['nation'],
'dest_nation_id'=>$destNation['nation'], 'dest_nation_id'=>$destNation['nation'],
@@ -147,7 +149,7 @@ $db->insert('ng_diplomacy', [
'state'=>'proposed', 'state'=>'proposed',
'text_brief'=>$textBrief, 'text_brief'=>$textBrief,
'text_detail'=>$textDetail, 'text_detail'=>$textDetail,
'date'=>TimeUtil::now(), 'date'=>TimeUtil::format($gameNow),
'src_signer'=>$me['no'], 'src_signer'=>$me['no'],
'dest_signer'=>null, 'dest_signer'=>null,
'aux'=>Json::encode([ 'aux'=>Json::encode([
@@ -168,7 +170,7 @@ $newLetterNo = $db->insertId();
$src = new MessageTarget($me['no'], $me['name'], $srcNation['nation'], $srcNation['name'], $srcNation['color'], $me['icon']); $src = new MessageTarget($me['no'], $me['name'], $srcNation['nation'], $srcNation['name'], $srcNation['color'], $me['icon']);
$dest = new MessageTarget(0, '', $destNation['nation'], $destNation['name'], $destNation['color']); $dest = new MessageTarget(0, '', $destNation['nation'], $destNation['name'], $destNation['color']);
$now = new \DateTime(); $now = \DateTime::createFromImmutable($gameNow);
$unlimited = new \DateTime('9999-12-31'); $unlimited = new \DateTime('9999-12-31');
$josaYi = JosaUtil::pick($newLetterNo, '이'); $josaYi = JosaUtil::pick($newLetterNo, '이');
+22 -22
View File
@@ -16,12 +16,10 @@ $session = Session::requireLogin([])->setReadOnly();
$userID = Session::getUserID(); $userID = Session::getUserID();
$oNow = new \DateTimeImmutable();
$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');
$clock = GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$oldGeneral = $db->queryFirstField('SELECT `no` FROM general WHERE `owner`=%i', $userID); $oldGeneral = $db->queryFirstField('SELECT `no` FROM general WHERE `owner`=%i', $userID);
if($oldGeneral !== null){ if($oldGeneral !== null){
@@ -44,15 +42,14 @@ if($npcmode!=1){
]); ]);
} }
$token = $db->queryFirstRow('SELECT * FROM select_npc_token WHERE `owner`=%i AND `valid_until`>=%s', $userID, $now); $token = $db->queryFirstRow('SELECT * FROM select_npc_token WHERE `owner`=%i AND `valid_until`>=%i', $userID, $now);
$pickResult = []; $pickResult = [];
if($token && $refresh){ if($token && $refresh){
$pickMoreFrom = (new \DateTime($token['pick_more_from']))->getTimestamp(); $pickMoreFrom = Util::toInt($token['pick_more_from']);
$nowT = $oNow->getTimestamp();
if($nowT >= $pickMoreFrom){ if($now >= $pickMoreFrom){
$oldPickResult = Json::decode($token['pick_result']); $oldPickResult = Json::decode($token['pick_result']);
foreach($keepResult as $keepId){ foreach($keepResult as $keepId){
@@ -75,15 +72,16 @@ if($token && $refresh){
} }
if($token && !$refresh){ if($token && !$refresh){
$pickMoreFrom = (new \DateTime($token['pick_more_from']))->getTimestamp(); $pickMoreFrom = Util::toInt($token['pick_more_from']);
$nowT = $oNow->getTimestamp();
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'pick'=>Json::decode($token['pick_result']), 'pick'=>Json::decode($token['pick_result']),
'pickMoreFrom'=>$token['pick_more_from'], 'pickMoreFrom'=>$clock->formatTick($pickMoreFrom),
'pickMoreSeconds'=>$pickMoreFrom-$nowT, 'pickMoreSeconds'=>intdiv($pickMoreFrom - $now, $clock->ticksPerSecond()),
'validUntil'=>$token['valid_until'] 'validUntil'=>$clock->formatTick(Util::toInt($token['valid_until'])),
'validForSeconds'=>max(0, intdiv(Util::toInt($token['valid_until']) - $now, $clock->ticksPerSecond())),
'clockMode'=>$clock->getMode(),
]); ]);
} }
@@ -100,7 +98,7 @@ foreach($db->query('SELECT `no`, `name`, leadership, strength, intel, nation, im
$weight[$general['no']] = pow($allStat, 1.5); $weight[$general['no']] = pow($allStat, 1.5);
} }
foreach($db->queryFirstColumn('SELECT pick_result FROM select_npc_token WHERE `owner`!=%i AND valid_until >=%s', $userID, $now) as $reserved){ foreach($db->queryFirstColumn('SELECT pick_result FROM select_npc_token WHERE `owner`!=%i AND valid_until >=%i', $userID, $now) as $reserved){
$reserved = Json::decode($reserved); $reserved = Json::decode($reserved);
foreach(array_keys($reserved) as $reservedNPC){ foreach(array_keys($reserved) as $reservedNPC){
if(key_exists($reservedNPC, $weight)){ if(key_exists($reservedNPC, $weight)){
@@ -131,8 +129,8 @@ $newNonce = random_int(0, 0xfffffff);
$validSecond = max(VALID_SECOND, $turnterm*40); $validSecond = max(VALID_SECOND, $turnterm*40);
$pickMoreSecond = max(PICK_MORE_SECOND, Util::round(pow($turnterm, 0.672)*8)); $pickMoreSecond = max(PICK_MORE_SECOND, Util::round(pow($turnterm, 0.672)*8));
$validUntil = $oNow->add(new \DateInterval(sprintf('PT%dS', $validSecond))); $validUntil = $now + $clock->ticksFromSeconds($validSecond);
$pickMoreFrom = $oNow->add(new \DateInterval(sprintf('PT%dS', $pickMoreSecond))); $pickMoreFrom = $now + $clock->ticksFromSeconds($pickMoreSecond);
$db->delete('select_npc_token', 'valid_until < %s', $now); $db->delete('select_npc_token', 'valid_until < %s', $now);
@@ -140,8 +138,8 @@ $inserted = 0;
if($token){ if($token){
$db->update('select_npc_token', [ $db->update('select_npc_token', [
'valid_until'=>$validUntil->format('Y-m-d H:i:s'), 'valid_until'=>$validUntil,
'pick_more_from'=>$pickMoreFrom->format('Y-m-d H:i:s'), 'pick_more_from'=>$pickMoreFrom,
'pick_result'=>Json::encode($pickResult), 'pick_result'=>Json::encode($pickResult),
'nonce'=>$newNonce 'nonce'=>$newNonce
], 'owner = %i AND nonce = %i', $userID, $token['nonce']); ], 'owner = %i AND nonce = %i', $userID, $token['nonce']);
@@ -152,8 +150,8 @@ if($token){
else{ else{
$db->insertIgnore('select_npc_token', [ $db->insertIgnore('select_npc_token', [
'owner'=>$userID, 'owner'=>$userID,
'valid_until'=>$validUntil->format('Y-m-d H:i:s'), 'valid_until'=>$validUntil,
'pick_more_from'=>'2000-01-01 01:00:00', 'pick_more_from'=>$now,
'pick_result'=>Json::encode($pickResult), 'pick_result'=>Json::encode($pickResult),
'nonce'=>$newNonce 'nonce'=>$newNonce
]); ]);
@@ -173,7 +171,9 @@ if($inserted === 0){
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'pick'=>$pickResult, 'pick'=>$pickResult,
'pickMoreFrom'=>($inserted===-1)?$pickMoreFrom->format('Y-m-d H:i:s'):'2000-01-01 01:00:00', 'pickMoreFrom'=>$clock->formatTick(($inserted===-1)?$pickMoreFrom:$now),
'pickMoreSeconds'=>($inserted===-1)?$pickMoreSecond:0, 'pickMoreSeconds'=>($inserted===-1)?$pickMoreSecond:0,
'validUntil'=>$validUntil->format('Y-m-d H:i:s') 'validUntil'=>$clock->formatTick($validUntil),
'validForSeconds'=>max(0, intdiv($validUntil - $now, $clock->ticksPerSecond())),
'clockMode'=>$clock->getMode(),
]); ]);
+30 -10
View File
@@ -10,7 +10,23 @@ function sortTokens(&$tokens){
}); });
} }
function putInfoText(&$info){ function putInfoText(&$info, ?array $currentTargetEnv){
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();
@@ -27,14 +43,13 @@ function putInfoText(&$info){
$session = Session::requireLogin([])->setReadOnly(); $session = Session::requireLogin([])->setReadOnly();
$userID = Session::getUserID(); $userID = Session::getUserID();
$oNow = new \DateTimeImmutable();
$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');
$clock = GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$npcmode = $gameStor->getValue('npcmode'); $eventEnv = $gameStor->getValues(['npcmode', 'startyear', 'year', 'month']);
$npcmode = $eventEnv['npcmode'];
if($npcmode!=2){ if($npcmode!=2){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
@@ -43,6 +58,7 @@ 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){
@@ -62,7 +78,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); putInfoText($info, $currentTargetEnv);
$info['uniqueName'] = $token['unique_name']; $info['uniqueName'] = $token['unique_name'];
$pick[] = $info; $pick[] = $info;
} }
@@ -70,7 +86,9 @@ if($tokens){
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'pick'=>$pick, 'pick'=>$pick,
'validUntil'=>$valid_until 'validUntil'=>$clock->formatTick(Util::toInt($valid_until)),
'validForSeconds'=>max(0, intdiv(Util::toInt($valid_until) - $now, $clock->ticksPerSecond())),
'clockMode'=>$clock->getMode(),
]); ]);
} }
@@ -83,12 +101,14 @@ $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); putInfoText($info, $currentTargetEnv);
$pick[] = $info; $pick[] = $info;
} }
sortTokens($pick);//좀 무식하지만.. sortTokens($pick);//좀 무식하지만..
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'pick'=>$pick, 'pick'=>$pick,
'validUntil'=>$valid_until 'validUntil'=>$valid_until === null ? null : $clock->formatTick(Util::toInt($valid_until)),
'validForSeconds'=>$valid_until === null ? 0 : max(0, intdiv(Util::toInt($valid_until) - $now, $clock->ticksPerSecond())),
'clockMode'=>$clock->getMode(),
]); ]);
+1 -1
View File
@@ -211,7 +211,7 @@ try{
$show_img_level, $show_img_level,
!!$tournament_trig, !!$tournament_trig,
$join_mode, $join_mode,
TimeUtil::now(), TimeUtil::format(GameClock::readWallTime(), false),
$autorun_user $autorun_user
)); ));
} }
+1 -1
View File
@@ -290,7 +290,7 @@ function do추방(General $general, int $myOfficerLevel): ?string
$src, $src,
$src, $src,
$str, $str,
new \DateTime(), Message::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
+2 -2
View File
@@ -24,8 +24,8 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$rootDB = RootDB::db(); $rootDB = RootDB::db();
$oNow = new \DateTimeImmutable(); $clock = GameClock::fromStorage($gameStor);
$now = $oNow->format('Y-m-d H:i:s'); $now = $clock->nowTick();
$member = $rootDB->queryFirstRow('SELECT `name`, `penalty` FROM member WHERE `NO`=%i', $userID); $member = $rootDB->queryFirstRow('SELECT `name`, `penalty` FROM member WHERE `NO`=%i', $userID);
if(!$member){ if(!$member){
+86 -10
View File
@@ -8,10 +8,20 @@ WebUtil::requireAJAX();
$pick = Util::getPost('pick'); $pick = Util::getPost('pick');
$leadership = Util::getPost('leadership', 'int', GameConst::$defaultStatMin); $leadership = Util::getPost('leadership', 'int', GameConst::$defaultStatMin);
$strength = Util::getPost('leadership', 'int', GameConst::$defaultStatMin); $isCentennialAllStar = CentennialAllStarGrowthService::isActive();
$intel = Util::getPost('leadership', 'int', GameConst::$defaultStatMin); $strength = Util::getPost(
$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){
@@ -27,8 +37,8 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$rootDB = RootDB::db(); $rootDB = RootDB::db();
$oNow = new \DateTimeImmutable(); $clock = GameClock::fromStorage($gameStor);
$now = $oNow->format('Y-m-d H:i:s'); $now = $clock->nowTick();
$hasGeneralID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID); $hasGeneralID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID);
if($hasGeneralID){ if($hasGeneralID){
@@ -60,13 +70,36 @@ if(!$selectInfo){
} }
$selectInfo = Json::decode($selectInfo); $selectInfo = Json::decode($selectInfo);
$ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID); $ownerInfo = RootDB::db()->queryFirstRow(
'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');
@@ -80,12 +113,25 @@ if ($gencount >= $maxgeneral) {
$poolClass = getGeneralPoolClass(GameConst::$targetGeneralPool); $poolClass = getGeneralPoolClass(GameConst::$targetGeneralPool);
/** @var AbsGeneralPool */ /** @var AbsGeneralPool */
$pickedGeneral = new $poolClass($db, $selectInfo, $now); if ($isCentennialAllStar) {
$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'){ if($allowOption == 'stat' && !$isCentennialAllStar){
$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);
@@ -98,14 +144,23 @@ foreach(GameConst::$generalPoolAllowOption as $allowOption){
} }
$builder->setStat($leadership, $strength, $intel); $builder->setStat($leadership, $strength, $intel);
} }
else if($allowOption == 'picture' && $use_own_picture){ else if(
$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);
} }
if(!array_search($personal, GameConst::$availablePersonality)){ $invalidPersonal = $isCentennialAllStar
? !in_array($personal, GameConst::$availablePersonality, true)
: !array_search($personal, GameConst::$availablePersonality);
if($invalidPersonal){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
'reason'=>'올바르지 않은 성격입니다.' 'reason'=>'올바르지 않은 성격입니다.'
@@ -121,8 +176,29 @@ $builder->setOwner($userID);
$builder->setOwnerName($userNick); $builder->setOwnerName($userNick);
$builder->setKillturn(5); $builder->setKillturn(5);
$builder->setNPCType(0); $builder->setNPCType(0);
$builder->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $env['turnterm'])); $builder->setAuxVar('next_change', $now + GameClock::TICKS_PER_TURN * 12);
$builder->setAuxVar(
'prestart_delete_after',
addTurn($now, $env['turnterm'], GameConst::$minTurnDieOnPrestart)
);
$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){
+4 -1
View File
@@ -19,6 +19,7 @@ if(!class_exists('\\sammo\\DB')){
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
if(file_exists(__DIR__.'/.htaccess')){ if(file_exists(__DIR__.'/.htaccess')){
$reserved = $db->queryFirstRow( $reserved = $db->queryFirstRow(
@@ -75,8 +76,10 @@ $admin['maxUserCnt'] = $admin['maxgeneral'];
$admin['npcMode'] = $admin['npcmode']; $admin['npcMode'] = $admin['npcmode'];
$admin['turnTerm'] = $admin['turnterm']; $admin['turnTerm'] = $admin['turnterm'];
$admin['isUnited'] = $admin['isunited']; $admin['isUnited'] = $admin['isunited'];
$admin['isOpen'] = $clock->nowTick() >= Util::toInt($admin['opentime']);
$admin['opentime'] = $clock->formatTick(Util::toInt($admin['opentime']));
$admin['starttime'] = substr($admin['opentime'], 5, 11); $admin['starttime'] = substr($admin['opentime'], 5, 11);
$admin['turntime'] = substr($admin['turntime'], 5, 11); $admin['turntime'] = substr($clock->formatTick(Util::toInt($admin['turntime'])), 5, 11);
unset($admin['npcmode']); unset($admin['npcmode']);
unset($admin['maxgeneral']); unset($admin['maxgeneral']);
unset($admin['turnterm']); unset($admin['turnterm']);
+6 -3
View File
@@ -64,6 +64,7 @@ if ($permission < 3) {
function applyNationPolicy($policy, $nationID, $generalName): ?string function applyNationPolicy($policy, $nationID, $generalName): ?string
{ {
$db = DB::db(); $db = DB::db();
$gameNow = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env'); $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
$defaultPolicy = AutorunNationPolicy::$defaultPolicy; $defaultPolicy = AutorunNationPolicy::$defaultPolicy;
@@ -139,7 +140,7 @@ function applyNationPolicy($policy, $nationID, $generalName): ?string
$nationPolicyRoot['values'] = $nationPolicy; $nationPolicyRoot['values'] = $nationPolicy;
$nationPolicyRoot['valueSetter'] = $generalName; $nationPolicyRoot['valueSetter'] = $generalName;
$nationPolicyRoot['valueSetTime'] = TimeUtil::now(); $nationPolicyRoot['valueSetTime'] = $gameNow;
$nationStor->npc_nation_policy = $nationPolicyRoot; $nationStor->npc_nation_policy = $nationPolicyRoot;
return null; return null;
} }
@@ -147,6 +148,7 @@ function applyNationPolicy($policy, $nationID, $generalName): ?string
function applyNationPriority($priority, $nationID, $generalName): ?string function applyNationPriority($priority, $nationID, $generalName): ?string
{ {
$db = DB::db(); $db = DB::db();
$gameNow = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env'); $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
$nationPolicyRoot = $nationStor->npc_nation_policy; $nationPolicyRoot = $nationStor->npc_nation_policy;
@@ -158,7 +160,7 @@ function applyNationPriority($priority, $nationID, $generalName): ?string
} }
$nationPolicyRoot['priority'] = $priority; $nationPolicyRoot['priority'] = $priority;
$nationPolicyRoot['prioritySetter'] = $generalName; $nationPolicyRoot['prioritySetter'] = $generalName;
$nationPolicyRoot['prioritySetTime'] = TimeUtil::now(); $nationPolicyRoot['prioritySetTime'] = $gameNow;
$nationStor->npc_nation_policy = $nationPolicyRoot; $nationStor->npc_nation_policy = $nationPolicyRoot;
return null; return null;
} }
@@ -166,6 +168,7 @@ function applyNationPriority($priority, $nationID, $generalName): ?string
function applyGeneralPriority($priority, $nationID, $generalName): ?string function applyGeneralPriority($priority, $nationID, $generalName): ?string
{ {
$db = DB::db(); $db = DB::db();
$gameNow = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env'); $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
$generalPolicyRoot = $nationStor->npc_general_policy; $generalPolicyRoot = $nationStor->npc_general_policy;
@@ -206,7 +209,7 @@ function applyGeneralPriority($priority, $nationID, $generalName): ?string
$generalPolicyRoot['priority'] = $priority; $generalPolicyRoot['priority'] = $priority;
$generalPolicyRoot['prioritySetter'] = $generalName; $generalPolicyRoot['prioritySetter'] = $generalName;
$generalPolicyRoot['prioritySetTime'] = TimeUtil::now(); $generalPolicyRoot['prioritySetTime'] = $gameNow;
$nationStor->npc_general_policy = $generalPolicyRoot; $nationStor->npc_general_policy = $generalPolicyRoot;
return null; return null;
} }
+2 -1
View File
@@ -77,7 +77,8 @@ $month = $query['month'];
$repeatCnt = $query['repeatCnt']; $repeatCnt = $query['repeatCnt'];
$rawAttacker = $query['attackerGeneral']; $rawAttacker = $query['attackerGeneral'];
$rawAttacker['turntime'] = TimeUtil::now(); $battleClock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$rawAttacker['turntime'] = $battleClock->formatTick($battleClock->nowTick());
$rawAttackerCity = $query['attackerCity']; $rawAttackerCity = $query['attackerCity'];
$rawAttackerNation = $query['attackerNation']; $rawAttackerNation = $query['attackerNation'];
+79 -27
View File
@@ -7,6 +7,7 @@ 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([
@@ -21,8 +22,8 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$rootDB = RootDB::db(); $rootDB = RootDB::db();
$oNow = new \DateTimeImmutable(); $clock = GameClock::fromStorage($gameStor);
$now = $oNow->format('Y-m-d H:i:s'); $now = $clock->nowTick();
$generalID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID); $generalID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID);
if(!$generalID){ if(!$generalID){
@@ -35,10 +36,20 @@ if(!$generalID){
list( list(
$year, $year,
$month, $month,
$startYear,
$maxgeneral, $maxgeneral,
$npcmode, $npcmode,
$turnterm $turnterm,
) = $gameStor->getValuesAsArray(['year', 'month', 'maxgeneral', 'npcmode', 'turnterm']); $showImgLevel
) = $gameStor->getValuesAsArray([
'year',
'month',
'startyear',
'maxgeneral',
'npcmode',
'turnterm',
'show_img_level',
]);
if($npcmode!=2){ if($npcmode!=2){
Json::die([ Json::die([
@@ -55,7 +66,10 @@ if(!$info){
]); ]);
} }
$ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID); $ownerInfo = RootDB::db()->queryFirstRow(
'SELECT `name`,`picture`,`imgsvr`,`grade` FROM member WHERE `NO`=%i',
$userID
);
if(!$ownerInfo){ if(!$ownerInfo){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
@@ -64,6 +78,27 @@ 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);
@@ -101,35 +136,52 @@ $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(key_exists('leadership', $info)){ if ($isCentennialAllStar) {
$generalObj->updateVar('leadership', $info['leadership']); CentennialAllStarGrowthService::prepareLegacyUserReselection($generalObj);
$generalObj->updateVar('strength', $info['strength']); CentennialAllStarGrowthService::applyTarget($generalObj, $info, [
$generalObj->updateVar('intel', $info['intel']); 'startyear' => $startYear,
'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(key_exists('picture', $info)){ if ($isCentennialAllStar) {
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->setAuxVar('next_change', $now + GameClock::TICKS_PER_TURN * 12);
$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));
$userNick = $ownerInfo['name']; $userNick = $ownerInfo['name'];
$generalObj->setVar('owner_name', $userNick); $generalObj->setVar('owner_name', $userNick);
+3 -1
View File
@@ -11,9 +11,11 @@ $db = DB::db();
$updated = false; $updated = false;
$locked = false; $locked = false;
$lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked); $lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked);
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
Json::die([ Json::die([
'result' => true, 'result' => true,
'updated' => $updated, 'updated' => $updated,
'locked' => $locked, 'locked' => $locked,
'lastExecuted' => $lastExecuted, 'lastExecutedTick' => $lastExecuted,
'lastExecuted' => $clock->formatTick($lastExecuted, true),
]); ]);
+2 -2
View File
@@ -21,7 +21,7 @@ class BidUniqueAuction extends \sammo\BaseAPI
]) ])
->rule('int', 'amount') ->rule('int', 'amount')
->rule('int', 'auctionID') ->rule('int', 'auctionID')
->rule('boolean', 'extendCloseDate'); ->rule('boolean', 'extendCloseTick');
if (!$v->validate()) { if (!$v->validate()) {
return $v->errorStr(); return $v->errorStr();
@@ -38,7 +38,7 @@ class BidUniqueAuction extends \sammo\BaseAPI
{ {
$auctionID = $this->args['auctionID']; $auctionID = $this->args['auctionID'];
$amount = $this->args['amount']; $amount = $this->args['amount'];
$tryExtendCloseDate = $this->args['extendCloseDate'] ?? false; $tryExtendCloseDate = $this->args['extendCloseTick'] ?? false;
$generalID = $session->generalID; $generalID = $session->generalID;
$general = General::createObjFromDB($generalID); $general = General::createObjFromDB($generalID);
@@ -15,6 +15,8 @@ use sammo\General;
use sammo\Json; use sammo\Json;
use sammo\TimeUtil; use sammo\TimeUtil;
use sammo\Util; use sammo\Util;
use sammo\GameClock;
use sammo\KVStorage;
use function sammo\getAuctionLogRecent; use function sammo\getAuctionLogRecent;
@@ -33,12 +35,13 @@ class GetActiveResourceAuctionList 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
{ {
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$buyRiceList = []; $buyRiceList = [];
$sellRiceList = []; $sellRiceList = [];
/** @var AuctionInfo[] */ /** @var AuctionInfo[] */
$auctions = array_map(fn ($raw) => AuctionInfo::fromArray($raw), $db->query( $auctions = array_map(fn ($raw) => AuctionInfo::fromArray($raw), $db->query(
'SELECT * FROM `ng_auction` WHERE `type` IN %ls AND `finished` = 0 ORDER BY `close_date` ASC', 'SELECT * FROM `ng_auction` WHERE `type` IN %ls AND `finished` = 0 ORDER BY `close_tick` ASC',
[ [
AuctionType::BuyRice->value, AuctionType::BuyRice->value,
AuctionType::SellRice->value, AuctionType::SellRice->value,
@@ -87,8 +90,8 @@ class GetActiveResourceAuctionList extends \sammo\BaseAPI
'type' => $auction->type->value, 'type' => $auction->type->value,
'hostGeneralID' => $auction->hostGeneralID, 'hostGeneralID' => $auction->hostGeneralID,
'hostName' => $auction->detail->hostName, 'hostName' => $auction->detail->hostName,
'openDate' => TimeUtil::format($auction->openDate, false), 'openDate' => $clock->formatTick($auction->openTick),
'closeDate' => TimeUtil::format($auction->closeDate, false), 'closeDate' => $clock->formatTick($auction->closeTick),
'amount' => $auction->detail->amount, 'amount' => $auction->detail->amount,
'startBidAmount' => $auction->detail->startBidAmount, 'startBidAmount' => $auction->detail->startBidAmount,
'finishBidAmount' => $auction->detail->finishBidAmount, 'finishBidAmount' => $auction->detail->finishBidAmount,
@@ -14,6 +14,8 @@ use sammo\Enums\GeneralQueryMode;
use sammo\Enums\InheritanceKey; use sammo\Enums\InheritanceKey;
use sammo\InheritancePointManager; use sammo\InheritancePointManager;
use sammo\TimeUtil; use sammo\TimeUtil;
use sammo\GameClock;
use sammo\KVStorage;
use sammo\Validator; use sammo\Validator;
use sammo\General; use sammo\General;
@@ -42,6 +44,7 @@ class GetUniqueItemAuctionDetail 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
{ {
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$generalID = $session->generalID; $generalID = $session->generalID;
$auctionID = $this->args['auctionID']; $auctionID = $this->args['auctionID'];
@@ -92,9 +95,11 @@ class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
'target' => $auction->target, 'target' => $auction->target,
'isCallerHost' => $auction->hostGeneralID === $generalID, 'isCallerHost' => $auction->hostGeneralID === $generalID,
'hostName' => $auction->detail->hostName, 'hostName' => $auction->detail->hostName,
'closeDate' => TimeUtil::format($auction->closeDate, false), 'closeDate' => $clock->formatTick($auction->closeTick),
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt, 'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
'availableLatestBidCloseDate' => TimeUtil::format($auction->detail->availableLatestBidCloseDate, false), 'availableLatestBidCloseDate' => $auction->detail->availableLatestBidCloseTick === null
? null
: $clock->formatTick($auction->detail->availableLatestBidCloseTick),
], ],
'bidList' => $responseBid, 'bidList' => $responseBid,
'obfuscatedName' => $obfuscatedName, 'obfuscatedName' => $obfuscatedName,
@@ -12,6 +12,8 @@ use sammo\Enums\APIRecoveryType;
use sammo\Enums\AuctionType; use sammo\Enums\AuctionType;
use sammo\TimeUtil; use sammo\TimeUtil;
use sammo\Util; use sammo\Util;
use sammo\GameClock;
use sammo\KVStorage;
class GetUniqueItemAuctionList extends \sammo\BaseAPI class GetUniqueItemAuctionList extends \sammo\BaseAPI
{ {
@@ -28,12 +30,13 @@ class GetUniqueItemAuctionList 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
{ {
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$generalID = $session->generalID; $generalID = $session->generalID;
/** @var AuctionInfo[] */ /** @var AuctionInfo[] */
$auctions = array_map(fn($raw)=>AuctionInfo::fromArray($raw), $db->query( $auctions = array_map(fn($raw)=>AuctionInfo::fromArray($raw), $db->query(
'SELECT * FROM `ng_auction` WHERE `type` = %s ORDER BY `close_date` ASC', 'SELECT * FROM `ng_auction` WHERE `type` = %s ORDER BY `close_tick` ASC',
AuctionType::UniqueItem->value AuctionType::UniqueItem->value
) ?? []); ) ?? []);
@@ -85,9 +88,11 @@ class GetUniqueItemAuctionList extends \sammo\BaseAPI
'target' => $auction->target, 'target' => $auction->target,
'isCallerHost' => $auction->hostGeneralID === $generalID, 'isCallerHost' => $auction->hostGeneralID === $generalID,
'hostName' => $auction->detail->hostName, 'hostName' => $auction->detail->hostName,
'closeDate' => TimeUtil::format($auction->closeDate, false), 'closeDate' => $clock->formatTick($auction->closeTick),
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt, 'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
'availableLatestBidCloseDate' => TimeUtil::format($auction->detail->availableLatestBidCloseDate, false), 'availableLatestBidCloseDate' => $auction->detail->availableLatestBidCloseTick === null
? null
: $clock->formatTick($auction->detail->availableLatestBidCloseTick),
'highestBid' => [ 'highestBid' => [
'generalName' => $highestBid->aux->generalName, 'generalName' => $highestBid->aux->generalName,
'amount' => $highestBid->amount, 'amount' => $highestBid->amount,
+7 -3
View File
@@ -7,9 +7,10 @@ use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\GameConst; use sammo\GameConst;
use sammo\GameClock;
use sammo\Json; use sammo\Json;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\TimeUtil; use sammo\Util;
use function sammo\cutTurn; use function sammo\cutTurn;
@@ -31,6 +32,7 @@ class GetReservedCommand extends \sammo\BaseAPI
$commandList = []; $commandList = [];
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$generalID = $session->generalID; $generalID = $session->generalID;
$invalidTurnList = 0; $invalidTurnList = 0;
@@ -82,11 +84,13 @@ class GetReservedCommand extends \sammo\BaseAPI
return [ return [
'result' => true, 'result' => true,
'turnTime' => $turnTime, 'turnTimeTick' => Util::toInt($turnTime),
'turnTime' => $clock->formatTick(Util::toInt($turnTime)),
'turnTerm' => $turnTerm, 'turnTerm' => $turnTerm,
'year' => $year, 'year' => $year,
'month' => $month, 'month' => $month,
'date' => TimeUtil::now(true), 'date' => $clock->formatTick($clock->nowTick(), true),
'clockMode' => $clock->getMode(),
'turn' => $commandList, 'turn' => $commandList,
'autorun_limit' => $generalAux['autorun_limit'] ?? null, 'autorun_limit' => $generalAux['autorun_limit'] ?? null,
]; ];
+24 -11
View File
@@ -11,7 +11,8 @@ use sammo\Session;
use sammo\General; use sammo\General;
use sammo\JosaUtil; use sammo\JosaUtil;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\TimeUtil; use sammo\GameClock;
use sammo\Util;
use function sammo\addTurn; use function sammo\addTurn;
use function sammo\increaseRefresh; use function sammo\increaseRefresh;
@@ -37,8 +38,14 @@ class DieOnPrestart extends \sammo\BaseAPI
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['turnterm', 'opentime', 'turntime', 'year', 'month']); $gameStor->cacheValues(['turnterm', 'opentime', 'turntime', 'year', 'month']);
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$general = $db->queryFirstRow('SELECT no,name,nation,owner_name,npc FROM general WHERE owner=%i AND npc = 0', $userID); $general = $db->queryFirstRow('SELECT no,name,nation,owner_name,npc FROM general WHERE owner=%i AND npc = 0', $userID);
if (!$general) {
return '장수가 없습니다';
}
$lastRefresh = $db->queryFirstField( $lastRefresh = $db->queryFirstField(
'SELECT %b FROM general_access_log WHERE %b = %i', 'SELECT %b FROM general_access_log WHERE %b = %i',
GeneralAccessLogColumn::lastRefresh->value, GeneralAccessLogColumn::lastRefresh->value,
@@ -46,8 +53,9 @@ class DieOnPrestart extends \sammo\BaseAPI
$general['no'] $general['no']
); );
if (!$general) { $generalObj = General::createObjFromDB($general['no']);
return '장수가 없습니다'; if ($generalObj instanceof DummyGeneral) {
trigger_error("올바르지 않은 삭제 프로세스 $userID", E_USER_WARNING);
} }
increaseRefresh("장수 삭제", 1); increaseRefresh("장수 삭제", 1);
@@ -61,16 +69,21 @@ class DieOnPrestart extends \sammo\BaseAPI
return '이미 국가에 소속되어있습니다.'; return '이미 국가에 소속되어있습니다.';
} }
//서버 가오픈시 할 수 있는 행동 $targetTime = $generalObj->getAuxVar('prestart_delete_after');
$targetTime = addTurn($lastRefresh, $gameStor->turnterm, GameConst::$minTurnDieOnPrestart); if (!is_int($targetTime)) {
if ($targetTime > TimeUtil::now()) { $targetTime = addTurn(
$targetTimeShort = substr($targetTime, 0, 19); $lastRefresh === null ? $nowTick : Util::toInt($lastRefresh),
return "아직 삭제할 수 없습니다. {$targetTimeShort} 부터 가능합니다."; $gameStor->turnterm,
GameConst::$minTurnDieOnPrestart
);
$generalObj->setAuxVar('prestart_delete_after', $targetTime);
$generalObj->applyDB($db);
} }
$generalObj = General::createObjFromDB($general['no']); //서버 가오픈시 할 수 있는 행동
if ($generalObj instanceof DummyGeneral) { if ($targetTime > $nowTick) {
trigger_error("올바르지 않은 삭제 프로세스 $userID", E_USER_WARNING); $targetTimeShort = $clock->formatTick($targetTime);
return "아직 삭제할 수 없습니다. {$targetTimeShort} 부터 가능합니다.";
} }
$generalName = $generalObj->getName(); $generalName = $generalObj->getName();
+18 -8
View File
@@ -14,13 +14,13 @@ use sammo\Enums\GeneralColumn;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\Enums\RankColumn; use sammo\Enums\RankColumn;
use sammo\GameConst; use sammo\GameConst;
use sammo\GameClock;
use sammo\General; use sammo\General;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\LastTurn; use sammo\LastTurn;
use sammo\Validator; use sammo\Validator;
use sammo\Session; use sammo\Session;
use sammo\TimeUtil;
use sammo\Util; use sammo\Util;
use function sammo\buildNationCommandClass; use function sammo\buildNationCommandClass;
@@ -105,6 +105,7 @@ class GetFrontInfo extends \sammo\BaseAPI
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$gameStor->cacheValues(['isunited', 'opentime', 'refresh']); $gameStor->cacheValues(['isunited', 'opentime', 'refresh']);
$lastHistoryID = $this->args['lastWorldHistoryID']; $lastHistoryID = $this->args['lastWorldHistoryID'];
@@ -158,6 +159,7 @@ class GetFrontInfo extends \sammo\BaseAPI
private function generateGlobalInfo(MeekroDB $db): array private function generateGlobalInfo(MeekroDB $db): array
{ {
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
[ [
$scenarioText, $extendedGeneral, $isFiction, $npcMode, $scenarioText, $extendedGeneral, $isFiction, $npcMode,
@@ -182,8 +184,9 @@ class GetFrontInfo extends \sammo\BaseAPI
$lastVote = null; $lastVote = null;
if ($lastVoteID) { if ($lastVoteID) {
$voteStor = KVStorage::getStorage($db, 'vote'); $voteStor = KVStorage::getStorage($db, 'vote');
$lastVote = VoteInfo::fromArray($voteStor->getValue("vote_{$lastVoteID}")); $rawLastVote = VoteInfo::normalizeGameStorage($voteStor->getValue("vote_{$lastVoteID}"), $clock);
if ($lastVote->endDate && $lastVote->endDate < TimeUtil::now()) { $lastVote = VoteInfo::fromGameStorage($rawLastVote, $clock);
if ($rawLastVote['endTick'] !== null && $rawLastVote['endTick'] < $clock->nowTick()) {
$lastVote = null; $lastVote = null;
} }
} }
@@ -210,7 +213,8 @@ class GetFrontInfo extends \sammo\BaseAPI
'month' => $month, 'month' => $month,
'autorunUser' => $autorunUser, 'autorunUser' => $autorunUser,
'turnterm' => $turnterm, 'turnterm' => $turnterm,
'lastExecuted' => $lastExecuted, 'lastExecutedTick' => $lastExecuted,
'lastExecuted' => $clock->formatTick(Util::toInt($lastExecuted), true),
'lastVoteID' => $lastVoteID, 'lastVoteID' => $lastVoteID,
'develCost' => $develCost, 'develCost' => $develCost,
'noticeMsg' => $noticeMsg, 'noticeMsg' => $noticeMsg,
@@ -224,7 +228,8 @@ class GetFrontInfo extends \sammo\BaseAPI
'isLocked' => $isLocked, 'isLocked' => $isLocked,
'tournamentType' => $tournamentType, 'tournamentType' => $tournamentType,
'tournamentState' => $tournamentState, 'tournamentState' => $tournamentState,
'tournamentTime' => $tournamentTime, 'tournamentTimeTick' => $tournamentTime,
'tournamentTime' => $tournamentTime === null ? null : $clock->formatTick(Util::toInt($tournamentTime)),
'genCount' => $globalGenCount, 'genCount' => $globalGenCount,
'generalCntLimit' => $generalCntLimit, 'generalCntLimit' => $generalCntLimit,
'serverCnt' => $serverCnt, 'serverCnt' => $serverCnt,
@@ -361,6 +366,7 @@ class GetFrontInfo extends \sammo\BaseAPI
public function generateGeneralInfo(MeekroDB $db, General $general, array $rawNation): array public function generateGeneralInfo(MeekroDB $db, General $general, array $rawNation): array
{ {
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$permission = checkSecretPermission($general->getRaw()); $permission = checkSecretPermission($general->getRaw());
@@ -425,8 +431,12 @@ class GetFrontInfo extends \sammo\BaseAPI
'crew' => $general->getVar(GeneralColumn::crew), // number; 'crew' => $general->getVar(GeneralColumn::crew), // number;
'train' => $general->getVar(GeneralColumn::train), // number; 'train' => $general->getVar(GeneralColumn::train), // number;
'atmos' => $general->getVar(GeneralColumn::atmos), // number; 'atmos' => $general->getVar(GeneralColumn::atmos), // number;
'turntime' => $general->getVar(GeneralColumn::turntime), // string; 'turntimeTick' => $general->getTurnTick(), // number;
'recent_war' => $general->getVar(GeneralColumn::recent_war), // string; 'turntime' => $general->getTurnTime(), // string;
'recent_war_tick' => $general->getVar(GeneralColumn::recent_war), // number|null;
'recent_war' => $general->getVar(GeneralColumn::recent_war) === null
? null
: $clock->formatTick(Util::toInt($general->getVar(GeneralColumn::recent_war)), true), // string|null;
'horse' => $general->getVar(GeneralColumn::horse), // GameObjClassKey; 'horse' => $general->getVar(GeneralColumn::horse), // GameObjClassKey;
'weapon' => $general->getVar(GeneralColumn::weapon), // GameObjClassKey; 'weapon' => $general->getVar(GeneralColumn::weapon), // GameObjClassKey;
'book' => $general->getVar(GeneralColumn::book), // GameObjClassKey; 'book' => $general->getVar(GeneralColumn::book), // GameObjClassKey;
@@ -544,7 +554,7 @@ class GetFrontInfo extends \sammo\BaseAPI
'result' => false, 'result' => false,
'reason' => '접속 제한중입니다.', 'reason' => '접속 제한중입니다.',
'recovery' => APIRecoveryType::GameQuota, 'recovery' => APIRecoveryType::GameQuota,
'recovery_arg' => $general->getVar('turntime'), 'recovery_arg' => $general->getTurnTime(),
]; ];
} }
+15 -9
View File
@@ -11,6 +11,7 @@ use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralAccessLogColumn; use sammo\Enums\GeneralAccessLogColumn;
use sammo\Enums\RankColumn; use sammo\Enums\RankColumn;
use sammo\GameConst; use sammo\GameConst;
use sammo\GameClock;
use sammo\GameUnitConst; use sammo\GameUnitConst;
use sammo\General; use sammo\General;
use sammo\InheritancePointManager; use sammo\InheritancePointManager;
@@ -165,6 +166,7 @@ class Join extends \sammo\BaseAPI
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['year', 'month', 'maxgeneral', 'scenario', 'show_img_level', 'block_general_create', 'turnterm', 'turntime', 'genius', 'npcmode']); $gameStor->cacheValues(['year', 'month', 'maxgeneral', 'scenario', 'show_img_level', 'block_general_create', 'turnterm', 'turntime', 'genius', 'npcmode']);
$clock = GameClock::fromStorage($gameStor);
########## 동일 정보 존재여부 확인. ########## ########## 동일 정보 존재여부 확인. ##########
$block_general_create = $gameStor->getValue('block_general_create'); $block_general_create = $gameStor->getValue('block_general_create');
@@ -222,7 +224,7 @@ class Join extends \sammo\BaseAPI
$userLogger = new UserLogger($userID, $admin['year'], $admin['month'], false); $userLogger = new UserLogger($userID, $admin['year'], $admin['month'], false);
$now = TimeUtil::now(false); $now = $clock->nowTick();
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize( $rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed, UniqueConst::$hiddenSeed,
'MakeGeneral', 'MakeGeneral',
@@ -360,17 +362,14 @@ class Join extends \sammo\BaseAPI
$userLogger->push(sprintf("턴 시간 %02d:%02d 로 지정", intdiv($inheritTurntime, 60), $inheritTurntime % 60), "inheritPoint"); $userLogger->push(sprintf("턴 시간 %02d:%02d 로 지정", intdiv($inheritTurntime, 60), $inheritTurntime % 60), "inheritPoint");
$inheritTurntime += $rng->nextRangeInt(0, 999999) / 1000000; $inheritTurnMicrosecond = $rng->nextRangeInt(0, 999999);
$turntime = cutTurn(Util::toInt($admin['turntime']), $admin['turnterm'])
$turntime = new \DateTimeImmutable(cutTurn($admin['turntime'], $admin['turnterm'])); + $clock->ticksFromSeconds($inheritTurntime)
$turntime = $turntime->add(TimeUtil::secondsToDateInterval($inheritTurntime)); + intdiv($inheritTurnMicrosecond * $clock->ticksPerSecond(), 1_000_000);
$turntime = TimeUtil::format($turntime, true);
} else { } else {
$turntime = getRandTurn($rng, $admin['turnterm'], new \DateTimeImmutable($admin['turntime'])); $turntime = getRandTurn($rng, $admin['turnterm'], Util::toInt($admin['turntime']));
} }
$now = TimeUtil::now(true);
if ($now >= $turntime) { if ($now >= $turntime) {
$turntime = addTurn($turntime, $admin['turnterm']); $turntime = addTurn($turntime, $admin['turnterm']);
} }
@@ -435,6 +434,13 @@ class Join extends \sammo\BaseAPI
'specage2' => $specage2, 'specage2' => $specage2,
'special2' => $special2, 'special2' => $special2,
'penalty' => Json::encode($penalty), 'penalty' => Json::encode($penalty),
'aux' => Json::encode([
'prestart_delete_after' => addTurn(
$now,
$admin['turnterm'],
GameConst::$minTurnDieOnPrestart
),
]),
]); ]);
$generalID = $db->insertId(); $generalID = $db->insertId();
$db->insert('general_access_log', [ $db->insert('general_access_log', [
+5 -1
View File
@@ -8,6 +8,8 @@ use DateTimeInterface;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\TurnExecutionHelper; use sammo\TurnExecutionHelper;
use sammo\UniqueConst; use sammo\UniqueConst;
use sammo\GameClock;
use sammo\KVStorage;
class ExecuteEngine extends \sammo\BaseAPI class ExecuteEngine extends \sammo\BaseAPI
{ {
@@ -35,11 +37,13 @@ class ExecuteEngine extends \sammo\BaseAPI
$updated = false; $updated = false;
$locked = false; $locked = false;
$lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked); $lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked);
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
return [ return [
'result' => true, 'result' => true,
'updated' => $updated, 'updated' => $updated,
'locked' => $locked, 'locked' => $locked,
'lastExecuted' => $lastExecuted, 'lastExecutedTick' => $lastExecuted,
'lastExecuted' => $clock->formatTick($lastExecuted, true),
]; ];
} }
} }
+3 -1
View File
@@ -3,6 +3,7 @@
namespace sammo\API\Global; namespace sammo\API\Global;
use sammo\DB; use sammo\DB;
use sammo\GameClock;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Json; use sammo\Json;
use sammo\KVStorage; use sammo\KVStorage;
@@ -145,7 +146,8 @@ class GeneralList extends \sammo\BaseAPI
if (static::$withToken) { if (static::$withToken) {
$now = (new \DateTimeImmutable())->format('Y-m-d H:i:s'); $gameStor = KVStorage::getStorage($db, 'game_env');
$now = GameClock::fromStorage($gameStor)->nowTick();
$tokens = []; $tokens = [];
foreach ($db->query('SELECT * FROM select_npc_token WHERE `valid_until`>=%s', $now) as $token) { foreach ($db->query('SELECT * FROM select_npc_token WHERE `valid_until`>=%s', $now) as $token) {
$validUntil = $token['valid_until']; $validUntil = $token['valid_until'];
@@ -10,7 +10,6 @@ use sammo\Enums\RankColumn;
use sammo\GameConst; use sammo\GameConst;
use sammo\General; use sammo\General;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\TimeUtil;
use sammo\UserLogger; use sammo\UserLogger;
class BuyRandomUnique extends \sammo\BaseAPI class BuyRandomUnique extends \sammo\BaseAPI
@@ -56,7 +55,7 @@ class BuyRandomUnique extends \sammo\BaseAPI
$userLogger->push("{$reqAmount} 포인트로 랜덤 유니크 구입", "inheritPoint"); $userLogger->push("{$reqAmount} 포인트로 랜덤 유니크 구입", "inheritPoint");
$userLogger->flush(); $userLogger->flush();
$general->setAuxVar('inheritRandomUnique', TimeUtil::now()); $general->setAuxVar('inheritRandomUnique', true);
$inheritStor->setValue('previous', [$previousPoint - $reqAmount, null]); $inheritStor->setValue('previous', [$previousPoint - $reqAmount, null]);
$general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $reqAmount); $general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $reqAmount);
$general->applyDB($db); $general->applyDB($db);
+2 -2
View File
@@ -116,7 +116,7 @@ class CheckOwner extends \sammo\BaseAPI
$src, $src,
$dest, $dest,
"{$destGeneralName}의 소유자는 {$destGeneralOwnerName} 입니다.", "{$destGeneralName}의 소유자는 {$destGeneralOwnerName} 입니다.",
new \DateTime(), Message::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
@@ -142,7 +142,7 @@ class CheckOwner extends \sammo\BaseAPI
$src, $src,
$dest, $dest,
"소유자명이 누군가에 의해 확인되었습니다.", "소유자명이 누군가에 의해 확인되었습니다.",
new \DateTime(), Message::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
@@ -4,6 +4,7 @@ namespace sammo\API\InheritAction;
use sammo\Session; use sammo\Session;
use DateTimeInterface; use DateTimeInterface;
use sammo\CentennialAllStarGrowthService;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\RankColumn; use sammo\Enums\RankColumn;
@@ -98,6 +99,10 @@ class ResetStat extends \sammo\BaseAPI
return 'NPC는 능력치 초기화를 할 수 없습니다.'; return 'NPC는 능력치 초기화를 할 수 없습니다.';
} }
if (!CentennialAllStarGrowthService::isStatResetAllowed()) {
return '100기 올스타 장수는 능력치 초기화를 사용할 수 없습니다.';
}
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
@@ -64,13 +64,11 @@ class ResetTurnTime extends \sammo\BaseAPI
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
$currTurnTime = new DateTimeImmutable($general->getTurnTime());
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize( $rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed, UniqueConst::$hiddenSeed,
'ResetTurnTime', 'ResetTurnTime',
$userID, $userID,
$general->getAuxVar('nextTurnTimeBase') ?? $general->getTurnTime() $general->getAuxVar('nextTurnTimeBase') ?? $general->getTurnTick()
))); )));
$afterTurn = $rng->nextFloat1() * $turnTerm * 60; $afterTurn = $rng->nextFloat1() * $turnTerm * 60;
+4 -4
View File
@@ -44,7 +44,7 @@ class SendMessage extends \sammo\BaseAPI
private function genPublicMessage(MessageTarget $src, string $text): Message private function genPublicMessage(MessageTarget $src, string $text): Message
{ {
$now = new \DateTime(); $now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31'); $unlimited = new \DateTime('9999-12-31');
$msg = new Message( $msg = new Message(
@@ -62,7 +62,7 @@ class SendMessage extends \sammo\BaseAPI
private function genNationalMessage(MessageTarget $src, string $text): Message private function genNationalMessage(MessageTarget $src, string $text): Message
{ {
$now = new \DateTime(); $now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31'); $unlimited = new \DateTime('9999-12-31');
$dest = new MessageTarget(0, '', $src->nationID, $src->nationName, $src->color); $dest = new MessageTarget(0, '', $src->nationID, $src->nationName, $src->color);
@@ -82,7 +82,7 @@ class SendMessage extends \sammo\BaseAPI
private function genDiplomacyMessage(MessageTarget $src, int $destNationID, string $text): Message|string private function genDiplomacyMessage(MessageTarget $src, int $destNationID, string $text): Message|string
{ {
$now = new \DateTime(); $now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31'); $unlimited = new \DateTime('9999-12-31');
$destNation = getNationStaticInfo($destNationID); $destNation = getNationStaticInfo($destNationID);
@@ -107,7 +107,7 @@ class SendMessage extends \sammo\BaseAPI
private function genPrivateMessage(MessageTarget $src, int $destGeneralID, int $permission, string $text): Message|string private function genPrivateMessage(MessageTarget $src, int $destGeneralID, int $permission, string $text): Message|string
{ {
$now = new \DateTime(); $now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31'); $unlimited = new \DateTime('9999-12-31');
$db = DB::db(); $db = DB::db();
+6 -2
View File
@@ -8,6 +8,7 @@ use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralLiteQueryMode; use sammo\Enums\GeneralLiteQueryMode;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\General; use sammo\General;
use sammo\GameClock;
use sammo\GeneralLite; use sammo\GeneralLite;
use sammo\Session; use sammo\Session;
use sammo\Util; use sammo\Util;
@@ -264,8 +265,11 @@ class GeneralList extends \sammo\BaseAPI
'honorText' => fn ($rawGeneral) => getHonor($rawGeneral['experience']), 'honorText' => fn ($rawGeneral) => getHonor($rawGeneral['experience']),
'dedLevelText' => fn ($rawGeneral) => getDedLevelText($rawGeneral['dedlevel']), 'dedLevelText' => fn ($rawGeneral) => getDedLevelText($rawGeneral['dedlevel']),
//'0000-00-00 11:23'; //'0000-00-00 11:23';
'turntime' => fn ($rawGeneral) => substr($rawGeneral['turntime'], 0, 19), 'turntime' => fn ($rawGeneral) => GameClock::fromStorage($gameStor)
'recent_war' => fn ($rawGeneral) => substr($rawGeneral['recent_war'], 0, 19), ->formatTick(Util::toInt($rawGeneral['turntime'])),
'recent_war' => fn ($rawGeneral) => $rawGeneral['recent_war'] === null
? null
: GameClock::fromStorage($gameStor)->formatTick(Util::toInt($rawGeneral['recent_war'])),
'bill' => fn ($rawGeneral) => getBillByLevel($rawGeneral['dedlevel']), 'bill' => fn ($rawGeneral) => getBillByLevel($rawGeneral['dedlevel']),
'reservedCommand' => fn ($rawGeneral) => $reservedCommand[$rawGeneral['no']] ?? null, 'reservedCommand' => fn ($rawGeneral) => $reservedCommand[$rawGeneral['no']] ?? null,
'autorun_limit' => fn ($rawGeneral) => ($rawGeneral['aux'] ?? [])['autorun_limit'] ?? 0, 'autorun_limit' => fn ($rawGeneral) => ($rawGeneral['aux'] ?? [])['autorun_limit'] ?? 0,
+3 -2
View File
@@ -6,8 +6,8 @@ use sammo\Session;
use DateTimeInterface; use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\GameClock;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\TimeUtil;
use sammo\Validator; use sammo\Validator;
use sammo\WebUtil; use sammo\WebUtil;
@@ -51,8 +51,9 @@ class SetNotice extends \sammo\BaseAPI
$nationID = $me['nation']; $nationID = $me['nation'];
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env'); $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
$gameNow = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
$nationStor->nationNotice = [ $nationStor->nationNotice = [
'date'=>TimeUtil::now(), 'date'=>$gameNow,
'msg'=>WebUtil::htmlPurify($msg), 'msg'=>WebUtil::htmlPurify($msg),
'author'=>$me['name'], 'author'=>$me['name'],
'authorID'=>$me['no'], 'authorID'=>$me['no'],
@@ -8,10 +8,10 @@ use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\GameConst; use sammo\GameConst;
use sammo\GameClock;
use sammo\General; use sammo\General;
use sammo\Json; use sammo\Json;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\TimeUtil;
use sammo\Util; use sammo\Util;
use function sammo\checkLimit; use function sammo\checkLimit;
@@ -39,6 +39,7 @@ class GetReservedCommand extends \sammo\BaseAPI
increaseRefresh("사령부", 1); increaseRefresh("사령부", 1);
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$userID = $session->userID; $userID = $session->userID;
$me = $db->queryFirstRow( $me = $db->queryFirstRow(
@@ -51,7 +52,8 @@ class GetReservedCommand extends \sammo\BaseAPI
$nationID = $me['nation']; $nationID = $me['nation'];
$limitState = checkLimit($me['refresh_score']); $limitState = checkLimit($me['refresh_score']);
if ($limitState >= 2) { if ($limitState >= 2) {
return "접속 제한중입니다. 1턴 이내에 너무 많은 갱신을 하셨습니다. (다음 갱신 가능 시각 : {$me['turntime']})"; $limitTime = GameClock::fromStorage($gameStor)->formatTick(Util::toInt($me['turntime']), true);
return "접속 제한중입니다. 1턴 이내에 너무 많은 갱신을 하셨습니다. (다음 갱신 가능 시각 : {$limitTime})";
} }
$permission = checkSecretPermission($me); $permission = checkSecretPermission($me);
@@ -156,7 +158,8 @@ class GetReservedCommand extends \sammo\BaseAPI
'year' => $year, 'year' => $year,
'month' => $month, 'month' => $month,
'turnTerm' => $turnTerm, 'turnTerm' => $turnTerm,
'date' => TimeUtil::now(true), 'date' => $clock->formatTick($clock->nowTick(), true),
'clockMode' => $clock->getMode(),
'chiefList' => $nationChiefList, 'chiefList' => $nationChiefList,
'troopList' => $troopList, 'troopList' => $troopList,
'isChief' => ($me['officer_level'] > 4), 'isChief' => ($me['officer_level'] > 4),
+4 -3
View File
@@ -10,8 +10,9 @@ use sammo\Enums\GeneralLiteQueryMode;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\General; use sammo\General;
use sammo\GeneralLite; use sammo\GeneralLite;
use sammo\GameClock;
use sammo\KVStorage;
use sammo\Session; use sammo\Session;
use sammo\TimeUtil;
use sammo\Validator; use sammo\Validator;
class AddComment extends \sammo\BaseAPI class AddComment extends \sammo\BaseAPI
@@ -47,7 +48,8 @@ class AddComment extends \sammo\BaseAPI
$generalName = $general->getName(); $generalName = $general->getName();
$nationID = $general->getNationID(); $nationID = $general->getNationID();
$nationName = $general->getStaticNation()['name']; $nationName = $general->getStaticNation()['name'];
$date = TimeUtil::now(); $db = DB::db();
$date = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
$comment = new VoteComment( $comment = new VoteComment(
@@ -61,7 +63,6 @@ class AddComment extends \sammo\BaseAPI
date: $date date: $date
); );
$db = DB::db();
$db->insert('vote_comment', $comment->toArray()); $db->insert('vote_comment', $comment->toArray());
return null; return null;
+6 -1
View File
@@ -8,6 +8,7 @@ use sammo\DB;
use sammo\DTO\VoteComment; use sammo\DTO\VoteComment;
use sammo\DTO\VoteInfo; use sammo\DTO\VoteInfo;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\GameClock;
use sammo\Json; use sammo\Json;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\Validator; use sammo\Validator;
@@ -35,13 +36,16 @@ class GetVoteDetail extends \sammo\BaseAPI
{ {
$voteID = $this->args['voteID']; $voteID = $this->args['voteID'];
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$voteStor = KVStorage::getStorage($db, 'vote'); $voteStor = KVStorage::getStorage($db, 'vote');
$rawVote = $voteStor->getValue("vote_{$voteID}"); $rawVote = $voteStor->getValue("vote_{$voteID}");
if (!$rawVote) { if (!$rawVote) {
return '설문조사가 없습니다.'; return '설문조사가 없습니다.';
} }
$voteInfo = VoteInfo::fromArray($rawVote); $rawVote = VoteInfo::normalizeGameStorage($rawVote, $clock);
$voteInfo = VoteInfo::fromGameStorage($rawVote, $clock);
$isOpen = $rawVote['endTick'] === null || $rawVote['endTick'] >= $clock->nowTick();
$votes = array_map(fn ($arr) => [Json::decode($arr[0]), $arr[1]], $db->queryAllLists( $votes = array_map(fn ($arr) => [Json::decode($arr[0]), $arr[1]], $db->queryAllLists(
@@ -70,6 +74,7 @@ class GetVoteDetail extends \sammo\BaseAPI
'comments' => $comments, 'comments' => $comments,
'myVote' => $myVote, 'myVote' => $myVote,
'userCnt' => $userCnt, 'userCnt' => $userCnt,
'isOpen' => $isOpen,
]; ];
} }
} }
+5 -3
View File
@@ -6,6 +6,7 @@ use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\DTO\VoteInfo; use sammo\DTO\VoteInfo;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\GameClock;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\Session; use sammo\Session;
@@ -25,16 +26,17 @@ class GetVoteList 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
{ {
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$voteStor = KVStorage::getStorage($db, 'vote'); $voteStor = KVStorage::getStorage($db, 'vote');
$votes = []; $votes = [];
foreach($voteStor->getAll() as $voteKey => $rawVote){ foreach($voteStor->getAll() as $voteKey => $rawVote){
if(!str_starts_with($voteKey, 'vote_')){ if(preg_match('/^vote_(\d+)$/D', $voteKey, $matches) !== 1){
continue; continue;
} }
$voteID = (int)substr($voteKey, 5); $voteID = (int)$matches[1];
$votes[$voteID] = VoteInfo::fromArray($rawVote); $votes[$voteID] = VoteInfo::fromGameStorage($rawVote, $clock);
} }
return [ return [
+19 -15
View File
@@ -9,7 +9,7 @@ use sammo\Enums\APIRecoveryType;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\RootDB; use sammo\RootDB;
use sammo\Session; use sammo\Session;
use sammo\TimeUtil; use sammo\GameClock;
use sammo\Util; use sammo\Util;
use sammo\Validator; use sammo\Validator;
@@ -37,7 +37,7 @@ class NewVote extends \sammo\BaseAPI
return null; return null;
} }
function closeOldVote(int $voteID, KVStorage $voteStor) function closeOldVote(int $voteID, KVStorage $voteStor, GameClock $clock)
{ {
$db = DB::db(); $db = DB::db();
$voteStor = KVStorage::getStorage($db, 'vote'); $voteStor = KVStorage::getStorage($db, 'vote');
@@ -45,13 +45,14 @@ class NewVote extends \sammo\BaseAPI
if (!$rawLastVoteInfo) { if (!$rawLastVoteInfo) {
return; return;
} }
$lastVoteInfo = VoteInfo::fromArray($rawLastVoteInfo); $rawLastVoteInfo = VoteInfo::normalizeGameStorage($rawLastVoteInfo, $clock);
if ($lastVoteInfo->endDate) { if ($rawLastVoteInfo['endTick'] !== null) {
return; return;
} }
$lastVoteInfo->endDate = TimeUtil::now(); $rawLastVoteInfo['endTick'] = $clock->nowTick();
$voteStor->setValue("vote_{$voteID}", $lastVoteInfo->toArray()); $rawLastVoteInfo['endDate'] = $clock->formatTick($rawLastVoteInfo['endTick']);
$voteStor->setValue("vote_{$voteID}", $rawLastVoteInfo);
} }
function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
@@ -71,7 +72,11 @@ class NewVote extends \sammo\BaseAPI
$multipleOptions = 0; $multipleOptions = 0;
} }
$now = TimeUtil::now(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$now = $clock->formatTick($nowTick);
/** @var ?string */ /** @var ?string */
$endDate = $this->args['endDate'] ?? null; $endDate = $this->args['endDate'] ?? null;
/** @var string[] */ /** @var string[] */
@@ -83,9 +88,9 @@ class NewVote extends \sammo\BaseAPI
if($endDate !== null){ if($endDate !== null){
try{ try{
$oNow = new \DateTimeImmutable($now);
$oEndDate = new \DateTimeImmutable($endDate); $oEndDate = new \DateTimeImmutable($endDate);
if($oEndDate < $oNow){ $endTick = $clock->dateTimeToTick($oEndDate);
if($endTick < $nowTick){
return '종료일이 이미 지났습니다.'; return '종료일이 이미 지났습니다.';
} }
} }
@@ -96,17 +101,13 @@ class NewVote extends \sammo\BaseAPI
$userName = $session->userName; $userName = $session->userName;
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$lastVote = $gameStor->getValue('lastVote') ?? 0; $lastVote = $gameStor->getValue('lastVote') ?? 0;
$voteID = $lastVote + 1; $voteID = $lastVote + 1;
$voteStor = KVStorage::getStorage($db, 'vote'); $voteStor = KVStorage::getStorage($db, 'vote');
if (!($this->args['keepOldVote'] ?? false)) { if (!($this->args['keepOldVote'] ?? false)) {
$this->closeOldVote($lastVote, $voteStor); $this->closeOldVote($lastVote, $voteStor, $clock);
} }
$multipleOptions = Util::valueFit($multipleOptions, 0, count($options)); $multipleOptions = Util::valueFit($multipleOptions, 0, count($options));
@@ -122,7 +123,10 @@ class NewVote extends \sammo\BaseAPI
options: $options, options: $options,
); );
$voteStor->setValue("vote_{$voteID}", $voteInfo->toArray()); $voteStor->setValue("vote_{$voteID}", $voteInfo->toArray() + [
'startTick' => $nowTick,
'endTick' => $endDate === null ? null : $clock->dateTimeToTick(new \DateTimeImmutable($endDate)),
]);
$gameStor->setValue('lastVote', $voteID); $gameStor->setValue('lastVote', $voteID);
$db->update('general', [ $db->update('general', [
+5 -2
View File
@@ -8,6 +8,7 @@ use sammo\DTO\VoteInfo;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\General; use sammo\General;
use sammo\GameClock;
use sammo\Json; use sammo\Json;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\LiteHashDRBG; use sammo\LiteHashDRBG;
@@ -54,15 +55,17 @@ class Vote extends \sammo\BaseAPI
return '선택한 항목이 없습니다.'; return '선택한 항목이 없습니다.';
} }
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$voteStor = KVStorage::getStorage($db, 'vote'); $voteStor = KVStorage::getStorage($db, 'vote');
$rawVoteInfo = $voteStor->getValue("vote_{$voteID}"); $rawVoteInfo = $voteStor->getValue("vote_{$voteID}");
if (!$rawVoteInfo) { if (!$rawVoteInfo) {
return '설문조사가 없습니다.'; return '설문조사가 없습니다.';
} }
$voteInfo = VoteInfo::fromArray($rawVoteInfo); $rawVoteInfo = VoteInfo::normalizeGameStorage($rawVoteInfo, $clock);
$voteInfo = VoteInfo::fromGameStorage($rawVoteInfo, $clock);
if ($voteInfo->endDate && $voteInfo->endDate < new \DateTimeImmutable()) { if ($rawVoteInfo['endTick'] !== null && $rawVoteInfo['endTick'] < $clock->nowTick()) {
return '설문조사가 종료되었습니다.'; return '설문조사가 종료되었습니다.';
} }
+10 -6
View File
@@ -8,6 +8,11 @@ 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();
@@ -25,8 +30,9 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
} }
static public function pickGeneralFromPool(\MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix=null):array{ static public function pickGeneralFromPool(\MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix=null):array{
$oNow = new \DateTimeImmutable(); $gameStor = KVStorage::getStorage($db, 'game_env');
$now = $oNow->format('Y-m-d H:i:s'); $clock = GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$db->update('select_pool', [ $db->update('select_pool', [
'reserved_until'=>null, 'reserved_until'=>null,
@@ -36,17 +42,15 @@ 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']);
$dexTotal = array_sum($cand['info']['dex']); $pool[] = [$cand, static::getCandidateWeight($cand['info'], $owner)];
$pool[] = [$cand, $dexTotal];
} }
if(count($pool) < $pickCnt){ if(count($pool) < $pickCnt){
throw new \RuntimeException('pool 부족'); throw new \RuntimeException('pool 부족');
} }
$gameStor = KVStorage::getStorage($db, 'game_env');
$result = []; $result = [];
$validUntil = TimeUtil::nowAddMinutes(2 * $gameStor->turnterm); $validUntil = $now + GameClock::TICKS_PER_TURN * 2;
while(count($result) < $pickCnt){ while(count($result) < $pickCnt){
$cand = $rng->choiceUsingWeightPair($pool); $cand = $rng->choiceUsingWeightPair($pool);
$poolID = $cand['id']; $poolID = $cand['id'];
+2 -2
View File
@@ -34,7 +34,7 @@ abstract class AbsGeneralPool{
* specialWar * specialWar
*/ */
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, string $validUntil) public function __construct(\MeekroDB $db, RandUtil $rng, array $info, int $validUntil)
{ {
$this->db = $db; $this->db = $db;
$this->info = $info; $this->info = $info;
@@ -92,7 +92,7 @@ abstract class AbsGeneralPool{
return $this->builder; return $this->builder;
} }
public function getValidUntil():string{ public function getValidUntil():int{
return $this->validUntil; return $this->validUntil;
} }
+55 -54
View File
@@ -146,40 +146,35 @@ abstract class Auction
return $this->info; return $this->info;
} }
public function shrinkCloseDate(?DateTimeInterface $date): ?string public function shrinkCloseTick(?int $tick): ?string
{ {
if ($date === null) {
$date = new DateTimeImmutable();
}
$this->info->closeDate = $date;
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$this->info->closeTick = $tick ?? $clock->nowTick();
$db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id); $db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id);
return null; return null;
} }
public function extendLatestBidCloseDate(?DateTimeInterface $date): ?string public function extendLatestBidCloseTick(?int $tick): ?string
{ {
if ($date === null) { if ($tick === null) {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
$date = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( $tick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60 max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID)
)); );
} }
else{ if ($this->info->detail->availableLatestBidCloseTick !== null && $tick < $this->info->detail->availableLatestBidCloseTick) {
$date = DateTimeImmutable::createFromInterface($date);
}
if ($this->info->detail->availableLatestBidCloseDate !== null && $date < $this->info->detail->availableLatestBidCloseDate) {
return '기간보다 짧습니다.'; return '기간보다 짧습니다.';
} }
$this->info->detail->availableLatestBidCloseDate = $date; $this->info->detail->availableLatestBidCloseTick = $tick;
return null; return null;
} }
public function extendCloseDate(DateTimeInterface $date, bool $force = false): ?string public function extendCloseTick(int $tick, bool $force = false): ?string
{ {
if (!$force) { if (!$force) {
if ($this->info->detail->remainCloseDateExtensionCnt === null) { if ($this->info->detail->remainCloseDateExtensionCnt === null) {
@@ -193,12 +188,11 @@ abstract class Auction
} }
} }
if ($date < $this->info->closeDate) { if ($tick < $this->info->closeTick) {
return '종료 기간보다 짧습니다.'; return '종료 기간보다 짧습니다.';
} }
$closeDate = DateTimeImmutable::createFromInterface($date); $this->info->closeTick = $tick;
$this->info->closeDate = $closeDate;
return null; return null;
} }
@@ -245,12 +239,13 @@ abstract class Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게.. //TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다. //TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$msg = new Message( $msg = new Message(
MessageType::private, MessageType::private,
$src, $src,
$dest, $dest,
$reason, $reason,
new DateTime(), DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick())),
new DateTime('9999-12-31'), new DateTime('9999-12-31'),
[] []
); );
@@ -275,7 +270,12 @@ abstract class Auction
$db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id); $db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id);
} }
private function bidInheritPoint(int $amount, \DateTimeImmutable $now, bool $tryExtendCloseDate): ?string private function bidInheritPoint(
int $amount,
int $nowTick,
\DateTimeImmutable $nowDate,
bool $tryExtendCloseDate,
): ?string
{ {
$db = DB::db(); $db = DB::db();
@@ -311,7 +311,7 @@ abstract class Auction
$general->getVar('owner'), $general->getVar('owner'),
$general->getID(), $general->getID(),
$amount, $amount,
$now, $nowDate,
new AuctionBidItemData( new AuctionBidItemData(
$general->getVar('owner_name'), $general->getVar('owner_name'),
$obfuscatedName, $obfuscatedName,
@@ -324,15 +324,16 @@ abstract class Auction
} }
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
if ($this->info->detail->availableLatestBidCloseDate !== null) { if ($this->info->detail->availableLatestBidCloseTick !== null) {
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval( $extendedCloseTick = $nowTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60 max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID)
)); );
if ($extendedCloseDate > $this->info->closeDate && $this->info->closeDate < $this->info->detail->availableLatestBidCloseDate) { if ($extendedCloseTick > $this->info->closeTick && $this->info->closeTick < $this->info->detail->availableLatestBidCloseTick) {
$this->extendCloseDate(min($extendedCloseDate, $this->info->detail->availableLatestBidCloseDate), true); $this->extendCloseTick(min($extendedCloseTick, $this->info->detail->availableLatestBidCloseTick), true);
$this->applyDB(); $this->applyDB();
} }
} }
@@ -356,12 +357,16 @@ abstract class Auction
return '경매가 이미 끝났습니다.'; return '경매가 이미 끝났습니다.';
} }
$now = new \DateTimeImmutable(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$nowDate = $clock->tickToDateTime($nowTick);
if ($auctionInfo->closeDate < $now) { if ($auctionInfo->closeTick < $nowTick) {
return '경매가 이미 끝났습니다.'; return '경매가 이미 끝났습니다.';
} }
if ($auctionInfo->openDate > $now) { if ($auctionInfo->openTick > $nowTick) {
return '경매가 아직 시작되지 않았습니다.'; return '경매가 아직 시작되지 않았습니다.';
} }
@@ -377,13 +382,11 @@ abstract class Auction
if ($auctionInfo->reqResource === ResourceType::inheritancePoint) { if ($auctionInfo->reqResource === ResourceType::inheritancePoint) {
return $this->bidInheritPoint($amount, $now, $tryExtendCloseDate); return $this->bidInheritPoint($amount, $nowTick, $nowDate, $tryExtendCloseDate);
} }
//reqResource는 말 그대로 '구매자가 내야하는 자원'이다. //reqResource는 말 그대로 '구매자가 내야하는 자원'이다.
$db = DB::db();
$highestBid = $this->getHighestBid(); $highestBid = $this->getHighestBid();
if (!$auctionInfo->detail->isReverse) { if (!$auctionInfo->detail->isReverse) {
if ($highestBid !== null && $amount <= $highestBid->amount) { if ($highestBid !== null && $amount <= $highestBid->amount) {
@@ -421,7 +424,7 @@ abstract class Auction
$general->getVar('owner'), $general->getVar('owner'),
$general->getID(), $general->getID(),
$amount, $amount,
$now, $nowDate,
new AuctionBidItemData( new AuctionBidItemData(
$general->getVar('owner_name'), $general->getVar('owner_name'),
$general->getName(), $general->getName(),
@@ -436,14 +439,13 @@ abstract class Auction
$general->increaseVar($resType->value, -$morePoint); $general->increaseVar($resType->value, -$morePoint);
$gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval( $extendedCloseTick = $nowTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60 max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID)
)); );
if ($extendedCloseDate > $this->info->closeDate) { if ($extendedCloseTick > $this->info->closeTick) {
$this->extendCloseDate($extendedCloseDate, true); $this->extendCloseTick($extendedCloseTick, true);
$this->applyDB(); $this->applyDB();
} }
@@ -456,10 +458,10 @@ abstract class Auction
public function tryFinish(): ?bool public function tryFinish(): ?bool
{ {
$now = new DateTimeImmutable(); $db = DB::db();
if ($now < $this->info->closeDate) { $gameStor = KVStorage::getStorage($db, 'game_env');
return null; $clock = GameClock::fromStorage($gameStor);
} if ($clock->nowTick() < $this->info->closeTick) return null;
//경매를 닫아야한다. //경매를 닫아야한다.
$highestBid = $this->getHighestBid(); $highestBid = $this->getHighestBid();
@@ -469,17 +471,15 @@ abstract class Auction
} }
if ($highestBid->aux->tryExtendCloseDate) { if ($highestBid->aux->tryExtendCloseDate) {
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
//연장 요청이 있었다. //연장 요청이 있었다.
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( $extendedCloseTick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60 max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY)
)); );
if ($this->extendCloseDate($extendedCloseDate) === null) { if ($this->extendCloseTick($extendedCloseTick) === null) {
$this->extendLatestBidCloseDate(null); $this->extendLatestBidCloseTick(null);
$this->applyDB(); $this->applyDB();
return false; return false;
} }
@@ -509,12 +509,13 @@ abstract class Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게.. //TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다. //TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$msg = new Message( $msg = new Message(
MessageType::private, MessageType::private,
$src, $src,
$dest, $dest,
$failReason, $failReason,
new \DateTime(), DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick())),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
+9 -7
View File
@@ -56,10 +56,11 @@ abstract class AuctionBasicResource extends Auction
} }
$now = new \DateTimeImmutable();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
$closeDate = $now->add(TimeUtil::secondsToDateInterval($closeTurnCnt * $turnTerm * 60)); $closeTick = $nowTick + GameClock::TICKS_PER_TURN * $closeTurnCnt;
$openResult = static::openAuction(new AuctionInfo( $openResult = static::openAuction(new AuctionInfo(
null, null,
@@ -68,8 +69,8 @@ abstract class AuctionBasicResource extends Auction
"$amount", "$amount",
$general->getId(), $general->getId(),
$bidderRes, $bidderRes,
$now, $nowTick,
$closeDate, $closeTick,
new AuctionInfoDetail( new AuctionInfoDetail(
"{$hostResName} {$amount} 경매", "{$hostResName} {$amount} 경매",
$general->getName(), $general->getName(),
@@ -145,12 +146,13 @@ abstract class AuctionBasicResource extends Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게.. //TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다. //TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$msg = new Message( $msg = new Message(
MessageType::private, MessageType::private,
$src, $src,
$dest, $dest,
"{$this->auctionID}{$hostResName} 경매에 입찰이 없어 취소되었습니다.", "{$this->auctionID}{$hostResName} 경매에 입찰이 없어 취소되었습니다.",
new \DateTime(), \DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick())),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
@@ -246,8 +248,8 @@ abstract class AuctionBasicResource extends Auction
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
$date = (new DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($turnTerm * 60)); $clock = GameClock::fromStorage($gameStor);
$this->shrinkCloseDate($date); $this->shrinkCloseTick($clock->nowTick() + GameClock::TICKS_PER_TURN);
} }
return null; return null;
+27 -26
View File
@@ -72,16 +72,15 @@ class AuctionUniqueItem extends Auction
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$now = new DateTimeImmutable();
[$turnTerm, $year, $month] = $gameStor->getValuesAsArray(['turnterm', 'year', 'month']); [$turnTerm, $year, $month] = $gameStor->getValuesAsArray(['turnterm', 'year', 'month']);
$clock = GameClock::fromStorage($gameStor);
$closeDate = $now->add(TimeUtil::secondsToDateInterval( $nowTick = $clock->nowTick();
max(static::MIN_AUCTION_CLOSE_MINUTES, $turnTerm * static::COEFF_AUCTION_CLOSE_MINUTES) * 60 $closeTick = $nowTick + $clock->ticksFromMinutes(
)); max(static::MIN_AUCTION_CLOSE_MINUTES, $turnTerm * static::COEFF_AUCTION_CLOSE_MINUTES)
$availableLatestBidCloseDate = $closeDate->add(TimeUtil::secondsToDateInterval( );
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID) * 60 $availableLatestBidCloseTick = $closeTick + $clock->ticksFromMinutes(
)); max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID)
);
$info = new AuctionInfo( $info = new AuctionInfo(
null, null,
@@ -90,8 +89,8 @@ class AuctionUniqueItem extends Auction
$itemKey, $itemKey,
$general->getID(), $general->getID(),
ResourceType::inheritancePoint, ResourceType::inheritancePoint,
$now, $nowTick,
$closeDate, $closeTick,
new AuctionInfoDetail( new AuctionInfoDetail(
"{$item->getName()} 경매", "{$item->getName()} 경매",
static::genObfuscatedName($general->getID()), static::genObfuscatedName($general->getID()),
@@ -100,7 +99,7 @@ class AuctionUniqueItem extends Auction
$startAmount, $startAmount,
null, null,
1, 1,
$availableLatestBidCloseDate, $availableLatestBidCloseTick,
) )
); );
@@ -267,21 +266,22 @@ class AuctionUniqueItem extends Auction
if ($availableEquipUniqueCnt <= 0) { if ($availableEquipUniqueCnt <= 0) {
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
$clock = GameClock::fromStorage($gameStor);
//제한에 걸렸다면 자동 연장 //제한에 걸렸다면 자동 연장
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( $extendedCloseTick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_UNIQUE_CNT) * 60 max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_UNIQUE_CNT)
)); );
if($bidder->getID() != $this->info->hostGeneralID){ if($bidder->getID() != $this->info->hostGeneralID){
$this->setHostAsNeutral(); $this->setHostAsNeutral();
} }
$this->extendCloseDate($extendedCloseDate, true); $this->extendCloseTick($extendedCloseTick, true);
$this->extendLatestBidCloseDate(null); $this->extendLatestBidCloseTick(null);
$this->applyDB(); $this->applyDB();
return '유니크 아이템 소유 제한 상태입니다. 종료 시간이 연장됩니다.'; return '유니크 아이템 소유 제한 상태입니다. 종료 시간이 연장됩니다.';
} }
$isExtendCloseDateRequired = false; $isExtendCloseTickRequired = false;
foreach (GameConst::$allItems as $itemType => $itemList) { foreach (GameConst::$allItems as $itemType => $itemList) {
//아직은 그런 경우는 없지만 동일 유니크를 여러 부위에 장착할 수 있을지도 모름 //아직은 그런 경우는 없지만 동일 유니크를 여러 부위에 장착할 수 있을지도 모름
if (!key_exists($itemKey, $itemList)) { if (!key_exists($itemKey, $itemList)) {
@@ -291,13 +291,13 @@ class AuctionUniqueItem extends Auction
$ownItem = $general->getItem($itemType); $ownItem = $general->getItem($itemType);
if ($ownItem->getRawClassName() == $itemKey) { if ($ownItem->getRawClassName() == $itemKey) {
//FIXME: 이 경우에는 환불이 되던가 해야함. //FIXME: 이 경우에는 환불이 되던가 해야함.
$isExtendCloseDateRequired = true; $isExtendCloseTickRequired = true;
$reasons[] = '이미 그 유니크를 가지고 있습니다.'; $reasons[] = '이미 그 유니크를 가지고 있습니다.';
continue; continue;
} }
if (!$ownItem->isBuyable()) { if (!$ownItem->isBuyable()) {
$isExtendCloseDateRequired = true; $isExtendCloseTickRequired = true;
$reasons[] = '이미 다른 유니크를 가지고 있습니다.'; $reasons[] = '이미 다른 유니크를 가지고 있습니다.';
continue; continue;
} }
@@ -313,18 +313,19 @@ class AuctionUniqueItem extends Auction
} }
if (!$availableItemTypes) { if (!$availableItemTypes) {
if ($isExtendCloseDateRequired) { if ($isExtendCloseTickRequired) {
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
$clock = GameClock::fromStorage($gameStor);
//동일 부위 제한에 걸렸다면 자동 연장 //동일 부위 제한에 걸렸다면 자동 연장
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( $extendedCloseTick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID) * 60 max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID)
)); );
if($bidder->getID() != $this->info->hostGeneralID){ if($bidder->getID() != $this->info->hostGeneralID){
$this->setHostAsNeutral(); $this->setHostAsNeutral();
} }
$this->extendCloseDate($extendedCloseDate, true); $this->extendCloseTick($extendedCloseTick, true);
$this->extendLatestBidCloseDate(null); $this->extendLatestBidCloseTick(null);
$this->applyDB(); $this->applyDB();
} }
return join(' ', $reasons); return join(' ', $reasons);
@@ -0,0 +1,640 @@
<?php
namespace sammo;
use sammo\Scenario\GeneralBuilder;
final class CentennialAllStarGrowthService
{
public const POOL_CLASS = 'SPoolUnderU100';
public const AUX_KEY = 'event100_allstar';
public const TRAIT_UNLOCK_PROGRESS = 0.4;
public const NPC_PROGRESS_MULTIPLIER = 0.9;
private const STAT_KEYS = ['leadership', 'strength', 'intel'];
private const DEX_KEYS = ['dex1', 'dex2', 'dex3', 'dex4', 'dex5'];
public static function isActive(): bool
{
return GameConst::$targetGeneralPool === self::POOL_CLASS;
}
public static function isStatResetAllowed(): bool
{
return !self::isActive();
}
public static function initialAux(array $targetInfo, ?array $userInitialStats = null): array
{
$granted = array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
if ($userInitialStats !== null) {
foreach (self::STAT_KEYS as $key) {
$initial = (int) ($userInitialStats[$key] ?? GameConst::$defaultStatMin);
$granted[$key] = max(0, $initial - min($initial, GameConst::$defaultStatMin));
}
}
return [
'targetId' => (string) ($targetInfo['uniqueName'] ?? ''),
'granted' => $granted,
'dexConsumed' => array_fill_keys(self::DEX_KEYS, 0),
'dexFloor' => array_fill_keys(self::DEX_KEYS, 0),
'progressMonth' => -1,
'milestone' => 0,
'naturalSpecialDomestic' => null,
'eventSpecialDomestic' => null,
'userInitialStats' => $userInitialStats,
'dexTargetRatio' => 1.0,
];
}
public static function attachInitialTarget(GeneralBuilder $builder, array $targetInfo): void
{
$builder->setAuxVar(self::AUX_KEY, self::initialAux($targetInfo));
}
/**
* Builds an ordinary-user stat total while preserving the selected
* candidate's relative strengths as closely as integer stats allow.
*
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateUserInitialStats(array $targetInfo): array
{
$targets = [];
$bases = [];
foreach (self::STAT_KEYS as $key) {
$target = min(
GameConst::$defaultStatMax,
max(0, (int) ($targetInfo[$key] ?? 0))
);
$targets[$key] = $target;
$bases[$key] = min($target, GameConst::$defaultStatMin);
}
$targetTotal = array_sum($targets);
$desiredTotal = min(GameConst::$defaultStatTotal, $targetTotal);
$baseTotal = array_sum($bases);
$capacityTotal = $targetTotal - $baseTotal;
if ($capacityTotal <= 0 || $desiredTotal <= $baseTotal) {
return $bases;
}
$ratio = ($desiredTotal - $baseTotal) / $capacityTotal;
$result = [];
$fractions = [];
foreach (self::STAT_KEYS as $idx => $key) {
$raw = $bases[$key] + ($targets[$key] - $bases[$key]) * $ratio;
$result[$key] = (int) floor($raw);
$fractions[] = [
'key' => $key,
'fraction' => $raw - $result[$key],
'order' => $idx,
];
}
usort($fractions, static function (array $lhs, array $rhs): int {
$fractionOrder = $rhs['fraction'] <=> $lhs['fraction'];
return $fractionOrder !== 0 ? $fractionOrder : $lhs['order'] <=> $rhs['order'];
});
$remainder = $desiredTotal - array_sum($result);
foreach ($fractions as $fraction) {
if ($remainder <= 0) {
break;
}
$key = $fraction['key'];
if ($result[$key] >= $targets[$key]) {
continue;
}
$result[$key]++;
$remainder--;
}
return $result;
}
/**
* Returns the event stat baseline that a newly selected target receives at
* the supplied game date. Organic growth can still leave the actual stat
* above this baseline.
*
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateUserCurrentTargetStats(
array $targetInfo,
array $env
): array {
$initialStats = self::calculateUserInitialStats($targetInfo);
$progress = self::calculateProgress(
(int) $env['startyear'],
(int) $env['year'],
(int) $env['month']
);
$result = [];
foreach (self::STAT_KEYS as $key) {
$target = min(
GameConst::$maxLevel,
max(0, (int) ($targetInfo[$key] ?? 0))
);
$result[$key] = max(
$initialStats[$key],
CentennialAllStarGrowth::statFloor(
$target,
GameConst::$defaultStatMin,
$progress
)
);
}
return $result;
}
public static function prepareInitialUser(
GeneralBuilder $builder,
array $targetInfo
): void {
$initialStats = self::calculateUserInitialStats($targetInfo);
$builder->setStat(
$initialStats['leadership'],
$initialStats['strength'],
$initialStats['intel']
);
$builder->setAuxVar(
self::AUX_KEY,
self::initialAux($targetInfo, $initialStats)
);
}
/**
* Old 100th-season characters did not distinguish their form-entered
* initial stats from organic growth. Before their first reselection, treat
* the ordinary creation range as the replaceable initial allocation.
*/
public static function prepareLegacyUserReselection(General $general): void
{
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux) || is_array($aux['userInitialStats'] ?? null)) {
return;
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$legacyInitialStats = [];
foreach (self::STAT_KEYS as $key) {
$current = (int) $general->getVar($key);
$granted[$key] = self::calculateLegacyUserGrant(
$current,
(int) ($granted[$key] ?? 0)
);
$beforeEventGrant = max(
0,
$current - max(0, (int) ($aux['granted'][$key] ?? 0))
);
$legacyInitialStats[$key] = min(
$beforeEventGrant,
GameConst::$defaultStatMax
);
}
$aux['granted'] = $granted;
$aux['userInitialStats'] = $legacyInitialStats;
$general->setAuxVar(self::AUX_KEY, $aux);
}
public static function calculateLegacyUserGrant(int $current, int $eventGrant): int
{
$eventGrant = max(0, $eventGrant);
$beforeEventGrant = max(0, $current - $eventGrant);
$replaceableInitialGrant = max(
0,
min($beforeEventGrant, GameConst::$defaultStatMax)
- min($beforeEventGrant, GameConst::$defaultStatMin)
);
return $eventGrant + $replaceableInitialGrant;
}
public static function calculateProgress(
int $startYear,
int $year,
int $month,
float $progressMultiplier = 1.0
): float {
if ($progressMultiplier < 0 || $progressMultiplier > 1) {
throw new \InvalidArgumentException('progress multiplier must be between 0 and 1');
}
return min(
1,
CentennialAllStarGrowth::progress($startYear, $year, $month)
* $progressMultiplier
);
}
public static function calculateDexTargetFloor(
int $target,
array $env,
float $targetRatio = 1.0
): int {
if ($targetRatio < 0 || $targetRatio > 1) {
throw new \InvalidArgumentException('dex target ratio must be between 0 and 1');
}
$target = min(GameConst::$dexLimit, max(0, $target));
$scaledTarget = (int) floor($target * $targetRatio);
$progress = self::calculateProgress(
(int) $env['startyear'],
(int) $env['year'],
(int) $env['month']
);
return CentennialAllStarGrowth::dexFloor($scaledTarget, $progress);
}
/**
* Mutates the General object but leaves persistence to the caller.
*
* @return array{progress:float,milestone:int,previousMilestone:int,targetChanged:bool,changed:bool}
*/
public static function applyTarget(
General $general,
array $targetInfo,
array $env,
float $progressMultiplier = 1.0,
float $dexTargetRatio = 1.0
): array
{
$startYear = (int) $env['startyear'];
$year = (int) $env['year'];
$month = (int) $env['month'];
$progress = self::calculateProgress(
$startYear,
$year,
$month,
$progressMultiplier
);
$progressMonth = (int) floor(
max(0, ($year - $startYear) * 12 + $month - 1)
* $progressMultiplier
);
$targetId = (string) ($targetInfo['uniqueName'] ?? '');
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux)) {
$aux = self::initialAux($targetInfo);
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$targetChanged = ($aux['targetId'] ?? '') !== $targetId;
$dexConsumed = is_array($aux['dexConsumed'] ?? null)
? $aux['dexConsumed']
: array_fill_keys(self::DEX_KEYS, 0);
if ($targetChanged) {
$dexConsumed = array_fill_keys(self::DEX_KEYS, 0);
}
$dexFloor = is_array($aux['dexFloor'] ?? null)
? $aux['dexFloor']
: array_fill_keys(self::DEX_KEYS, 0);
$isUserTarget = is_array($aux['userInitialStats'] ?? null);
$nextUserInitialStats = $targetChanged && $isUserTarget
? self::calculateUserInitialStats($targetInfo)
: ($aux['userInitialStats'] ?? null);
$previousDexTargetRatio = is_numeric($aux['dexTargetRatio'] ?? null)
? (float) $aux['dexTargetRatio']
: 1.0;
$dexTargetRatioChanged = $previousDexTargetRatio !== $dexTargetRatio;
$userCurrentTargetStats = $isUserTarget
? self::calculateUserCurrentTargetStats($targetInfo, $env)
: null;
$changed = $dexTargetRatioChanged;
foreach (self::STAT_KEYS as $key) {
if (!array_key_exists($key, $targetInfo)) {
continue;
}
$target = min(GameConst::$maxLevel, max(0, (int) $targetInfo[$key]));
$floor = CentennialAllStarGrowth::statFloor(
$target,
GameConst::$defaultStatMin,
$progress
);
if ($userCurrentTargetStats !== null) {
$floor = $userCurrentTargetStats[$key];
}
$current = (int) $general->getVar($key);
if ($targetChanged) {
$result = CentennialAllStarGrowth::replaceTarget(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
} else {
$result = CentennialAllStarGrowth::advance(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
}
if ($result['value'] !== $current) {
$general->updateVar($key, $result['value']);
$changed = true;
}
$granted[$key] = $result['granted'];
}
$targetDex = $targetInfo['dex'] ?? [];
foreach (self::DEX_KEYS as $idx => $key) {
if (!array_key_exists($idx, $targetDex)) {
continue;
}
$floor = max(
0,
self::calculateDexTargetFloor(
(int) $targetDex[$idx],
$env,
$dexTargetRatio
) - max(0, (int) ($dexConsumed[$key] ?? 0))
);
$dexFloor[$key] = $floor;
$current = (int) $general->getVar($key);
if ($targetChanged || $dexTargetRatioChanged) {
$result = CentennialAllStarGrowth::replaceTarget(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
} else {
$result = CentennialAllStarGrowth::advance(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
}
if ($result['value'] !== $current) {
$general->updateVar($key, $result['value']);
$changed = true;
}
$granted[$key] = $result['granted'];
}
$oldEventSpecial = $aux['eventSpecialDomestic'] ?? null;
if ($targetChanged && $oldEventSpecial !== null
&& $general->getVar('special') === $oldEventSpecial
) {
$general->updateVar(
'special',
$aux['naturalSpecialDomestic'] ?? GameConst::$defaultSpecialDomestic
);
$changed = true;
$aux['eventSpecialDomestic'] = null;
}
$targetSpecial = $targetInfo['specialDomestic'] ?? null;
if ($progress >= self::TRAIT_UNLOCK_PROGRESS && is_string($targetSpecial) && $targetSpecial !== '') {
if (($aux['naturalSpecialDomestic'] ?? null) === null) {
$aux['naturalSpecialDomestic'] = $general->getVar('special');
}
if ($general->getVar('special') !== $targetSpecial) {
$general->updateVar('special', $targetSpecial);
$changed = true;
}
$aux['eventSpecialDomestic'] = $targetSpecial;
}
$previousMilestone = (int) ($aux['milestone'] ?? 0);
$milestone = min(5, (int) floor($progress * 5 + 0.0000001));
$aux['targetId'] = $targetId;
$aux['granted'] = $granted;
$aux['dexConsumed'] = $dexConsumed;
$aux['dexFloor'] = $dexFloor;
$aux['progressMonth'] = max((int) ($aux['progressMonth'] ?? -1), $progressMonth);
$aux['milestone'] = max($previousMilestone, $milestone);
$aux['userInitialStats'] = $nextUserInitialStats;
$aux['dexTargetRatio'] = $dexTargetRatio;
$general->setAuxVar(self::AUX_KEY, $aux);
return [
'progress' => $progress,
'milestone' => $milestone,
'previousMilestone' => $previousMilestone,
'targetChanged' => $targetChanged,
'changed' => $changed || $targetChanged || $milestone > $previousMilestone,
];
}
public static function progressMultiplierFor(General $general): float
{
return self::progressMultiplierForNPCType($general->getNPCType());
}
public static function progressMultiplierForNPCType(int $npcType): float
{
return in_array($npcType, [3, 4], true)
? self::NPC_PROGRESS_MULTIPLIER
: 1.0;
}
public static function dexTargetRatioForNPCType(int $npcType): float
{
return in_array($npcType, [3, 4], true)
? GameConst::$centennialNpcDexTargetRatio
: 1.0;
}
public static function applyCurrentTargetToBuiltNPC(
\MeekroDB $db,
GeneralBuilder $builder,
array $targetInfo,
array $env
): ?array {
if (!self::isActive()) {
return null;
}
$general = General::createObjFromDB($builder->getGeneralID());
if (!in_array($general->getNPCType(), [3, 4], true)) {
return null;
}
self::initializeGeneratedNPC($general, $targetInfo);
$result = self::applyTarget(
$general,
$targetInfo,
$env,
self::NPC_PROGRESS_MULTIPLIER,
GameConst::$centennialNpcDexTargetRatio
);
$general->applyDB($db);
return $result;
}
/**
* Keeps the ordinary M/G-general stat total while aligning its strong,
* middle, and weak stats with the selected all-star target. The target's
* creation-date growth floor is applied immediately afterwards.
*/
public static function initializeGeneratedNPC(
General $general,
array $targetInfo
): void {
$generatedStats = [];
foreach (self::STAT_KEYS as $key) {
$generatedStats[$key] = (int) $general->getVar($key);
}
$initialStats = self::calculateGeneratedNPCInitialStats(
$targetInfo,
$generatedStats
);
foreach (self::STAT_KEYS as $key) {
$general->updateVar($key, $initialStats[$key]);
}
foreach (self::DEX_KEYS as $key) {
$general->updateVar($key, 0);
}
$general->setAuxVar(self::AUX_KEY, self::initialAux($targetInfo));
}
/**
* @param array<string, mixed> $targetInfo
* @param array{leadership:int,strength:int,intel:int} $generatedStats
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateGeneratedNPCInitialStats(
array $targetInfo,
array $generatedStats
): array {
$targetOrder = self::STAT_KEYS;
$keyOrder = array_flip(self::STAT_KEYS);
usort(
$targetOrder,
static function (string $lhs, string $rhs) use ($targetInfo, $keyOrder): int {
$targetCompare = (int) ($targetInfo[$rhs] ?? 0)
<=> (int) ($targetInfo[$lhs] ?? 0);
return $targetCompare !== 0
? $targetCompare
: $keyOrder[$lhs] <=> $keyOrder[$rhs];
}
);
$generatedValues = array_map(
static fn (string $key): int => (int) ($generatedStats[$key] ?? 0),
self::STAT_KEYS
);
rsort($generatedValues, SORT_NUMERIC);
$result = array_fill_keys(self::STAT_KEYS, 0);
foreach ($targetOrder as $idx => $key) {
$result[$key] = $generatedValues[$idx];
}
return $result;
}
/**
* Moves the event-backed part of a dex conversion with the converted
* value and consumes any guaranteed floor crossed by the source value.
* This keeps the monthly floor from refilling points already converted.
*/
public static function reconcileDexConversion(
General $general,
string $sourceKey,
string $destinationKey,
int $sourceBefore,
int $sourceAfter,
int $destinationBefore,
int $destinationAfter,
float $convertCoeff
): void {
if (!in_array($sourceKey, self::DEX_KEYS, true)
|| !in_array($destinationKey, self::DEX_KEYS, true)
|| $sourceKey === $destinationKey
) {
throw new \InvalidArgumentException('invalid dex conversion keys');
}
if ($convertCoeff < 0 || $convertCoeff > 1) {
throw new \InvalidArgumentException('dex conversion coefficient must be between 0 and 1');
}
$sourceDecrease = max(0, $sourceBefore - $sourceAfter);
$destinationIncrease = max(0, $destinationAfter - $destinationBefore);
if ($sourceDecrease === 0 && $destinationIncrease === 0) {
return;
}
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux)) {
return;
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$dexConsumed = is_array($aux['dexConsumed'] ?? null)
? $aux['dexConsumed']
: array_fill_keys(self::DEX_KEYS, 0);
$dexFloor = is_array($aux['dexFloor'] ?? null)
? $aux['dexFloor']
: [];
$sourceGrantedBefore = min(
max(0, $sourceBefore),
max(0, (int) ($granted[$sourceKey] ?? 0))
);
/*
* A dex value can contain both organic and event-backed points. Split
* the actual command deltas by their share of the source total instead
* of consuming either bucket first. This keeps the combined ownership
* stable when conversion and target reselection are interleaved.
*/
$eventGrantRemoved = $sourceBefore > 0
? intdiv($sourceDecrease * $sourceGrantedBefore, $sourceBefore)
: 0;
$sourceGrantedAfter = max(0, $sourceGrantedBefore - $eventGrantRemoved);
$destinationGrantedBefore = min(
max(0, $destinationBefore),
max(0, (int) ($granted[$destinationKey] ?? 0))
);
$eventGrantTransferred = $sourceBefore > 0
? intdiv($destinationIncrease * $sourceGrantedBefore, $sourceBefore)
: 0;
$eventGrantTransferred = min(
$destinationIncrease,
$eventGrantRemoved,
$eventGrantTransferred
);
$granted[$sourceKey] = $sourceGrantedAfter;
$granted[$destinationKey] = min(
max(0, $destinationAfter),
$destinationGrantedBefore + $eventGrantTransferred
);
$sourceFloor = max(
0,
(int) ($dexFloor[$sourceKey] ?? $sourceBefore)
);
$gapBefore = max(0, $sourceFloor - $sourceBefore);
$gapAfter = max(0, $sourceFloor - $sourceAfter);
$dexConsumed[$sourceKey] = max(
0,
(int) ($dexConsumed[$sourceKey] ?? 0)
+ max(0, $gapAfter - $gapBefore)
);
$aux['granted'] = $granted;
$aux['dexConsumed'] = $dexConsumed;
$general->setAuxVar(self::AUX_KEY, $aux);
}
public static function recordableValue(General $general, string $key): int
{
$aux = $general->getAuxVar(self::AUX_KEY);
$granted = is_array($aux) && is_array($aux['granted'] ?? null)
? (int) ($aux['granted'][$key] ?? 0)
: 0;
return CentennialAllStarGrowth::recordableValue((int) $general->getVar($key), $granted);
}
public static function recordableRawValue(array $general, string $key): int
{
$aux = Json::decode($general['aux'] ?? '{}');
$eventAux = is_array($aux[self::AUX_KEY] ?? null) ? $aux[self::AUX_KEY] : [];
$granted = is_array($eventAux['granted'] ?? null)
? (int) ($eventAux['granted'][$key] ?? 0)
: 0;
return CentennialAllStarGrowth::recordableValue((int) ($general[$key] ?? 0), $granted);
}
}
@@ -14,6 +14,7 @@ use function \sammo\tryUniqueItemLottery;
use \sammo\Constraint\ConstraintHelper; use \sammo\Constraint\ConstraintHelper;
use sammo\StaticEventHandler; use sammo\StaticEventHandler;
use sammo\CentennialAllStarGrowthService;
class che_숙련전환 extends Command\GeneralCommand class che_숙련전환 extends Command\GeneralCommand
{ {
@@ -157,6 +158,7 @@ class che_숙련전환 extends Command\GeneralCommand
$logger = $general->getLogger(); $logger = $general->getLogger();
$srcDex = $general->getVar('dex' . $this->srcArmType); $srcDex = $general->getVar('dex' . $this->srcArmType);
$destDex = $general->getVar('dex' . $this->destArmType);
$cutDex = Util::toInt($srcDex * static::$decreaseCoeff); $cutDex = Util::toInt($srcDex * static::$decreaseCoeff);
$cutDexText = number_format($cutDex); $cutDexText = number_format($cutDex);
$addDex = Util::toInt($cutDex * static::$convertCoeff); $addDex = Util::toInt($cutDex * static::$convertCoeff);
@@ -164,6 +166,19 @@ class che_숙련전환 extends Command\GeneralCommand
$general->increaseVar('dex' . $this->srcArmType, -$cutDex); $general->increaseVar('dex' . $this->srcArmType, -$cutDex);
$general->increaseVar('dex' . $this->destArmType, $addDex); $general->increaseVar('dex' . $this->destArmType, $addDex);
// 100기 이벤트 지급분을 목적 숙련으로 옮기고 소비한 성장 하한은 다시 채우지 않는다.
if (CentennialAllStarGrowthService::isActive()) {
CentennialAllStarGrowthService::reconcileDexConversion(
$general,
'dex' . $this->srcArmType,
'dex' . $this->destArmType,
$srcDex,
$general->getVar('dex' . $this->srcArmType),
$destDex,
$general->getVar('dex' . $this->destArmType),
static::$convertCoeff
);
}
$josaUl = JosaUtil::pick($cutDex, '을'); $josaUl = JosaUtil::pick($cutDex, '을');
$josaRo = JosaUtil::pick($addDex, '로'); $josaRo = JosaUtil::pick($addDex, '로');
@@ -7,6 +7,7 @@ use \sammo\Util;
use \sammo\JosaUtil; use \sammo\JosaUtil;
use \sammo\General; use \sammo\General;
use \sammo\ActionLogger; use \sammo\ActionLogger;
use \sammo\CentennialAllStarGrowthService;
use \sammo\GameConst; use \sammo\GameConst;
use \sammo\LastTurn; use \sammo\LastTurn;
use \sammo\GameUnitConst; use \sammo\GameUnitConst;
@@ -190,6 +191,12 @@ class che_인재탐색 extends Command\GeneralCommand
$newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env); $newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env);
$newNPC->build($this->env); $newNPC->build($this->env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$newNPC,
$pickedNPC->getInfo(),
$this->env
);
$pickedNPC->occupyGeneralName(); $pickedNPC->occupyGeneralName();
$npcName = $newNPC->getGeneralName(); $npcName = $newNPC->getGeneralName();
$josaRa = JosaUtil::pick($npcName, '라'); $josaRa = JosaUtil::pick($npcName, '라');
+1 -1
View File
@@ -193,7 +193,7 @@ class che_몰수 extends Command\NationCommand
$src, $src,
$src, $src,
$text, $text,
new \DateTime(), Message::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
+1 -1
View File
@@ -159,7 +159,7 @@ class che_발령 extends Command\NationCommand
$destGeneral->getLogger()->pushGeneralActionLog("<Y>{$generalName}</>에 의해 <G><b>{$destCityName}</b></>{$josaRo} 발령됐습니다. <1>$date</>"); $destGeneral->getLogger()->pushGeneralActionLog("<Y>{$generalName}</>에 의해 <G><b>{$destCityName}</b></>{$josaRo} 발령됐습니다. <1>$date</>");
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']); $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
if (cutTurn($general->getTurnTime(), $this->env['turnterm']) != cutTurn($destGeneral->getTurnTime(), $this->env['turnterm'])) { if (cutTurn($general->getTurnTick(), $this->env['turnterm']) != cutTurn($destGeneral->getTurnTick(), $this->env['turnterm'])) {
$yearMonth += 1; $yearMonth += 1;
} }
$destGeneral->setAuxVar('last발령', $yearMonth); $destGeneral->setAuxVar('last발령', $yearMonth);
@@ -198,8 +198,9 @@ class che_불가침제의 extends Command\NationCommand
$destNation['color'] $destNation['color']
); );
$now = new \DateTime($date); $clock = \sammo\GameClock::fromStorage(\sammo\KVStorage::getStorage($db, 'game_env'));
$validUntil = new \DateTime($date); $now = \DateTime::createFromImmutable($clock->tickToDateTime($general->getTurnTick()));
$validUntil = clone $now;
$validMinutes = max(30, $env['turnterm'] * 3); $validMinutes = max(30, $env['turnterm'] * 3);
$validUntil->add(new \DateInterval("PT{$validMinutes}M")); $validUntil->add(new \DateInterval("PT{$validMinutes}M"));
@@ -147,8 +147,9 @@ class che_불가침파기제의 extends Command\NationCommand{
$destNation['color'] $destNation['color']
); );
$now = new \DateTime($date); $clock = \sammo\GameClock::fromStorage(\sammo\KVStorage::getStorage($db, 'game_env'));
$validUntil = new \DateTime($date); $now = \DateTime::createFromImmutable($clock->tickToDateTime($general->getTurnTick()));
$validUntil = clone $now;
$validMinutes = max(30, $env['turnterm']*3); $validMinutes = max(30, $env['turnterm']*3);
$validUntil->add(new \DateInterval("PT{$validMinutes}M")); $validUntil->add(new \DateInterval("PT{$validMinutes}M"));
@@ -7,6 +7,7 @@ use \sammo\Util;
use \sammo\JosaUtil; use \sammo\JosaUtil;
use \sammo\General; use \sammo\General;
use \sammo\ActionLogger; use \sammo\ActionLogger;
use \sammo\CentennialAllStarGrowthService;
use \sammo\GameConst; use \sammo\GameConst;
use \sammo\LastTurn; use \sammo\LastTurn;
use \sammo\GameUnitConst; use \sammo\GameUnitConst;
@@ -160,6 +161,12 @@ class che_의병모집 extends Command\NationCommand
$newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env); $newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env);
$newNPC->build($this->env); $newNPC->build($this->env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$newNPC,
$pickedNPC->getInfo(),
$this->env
);
$pickedNPC->occupyGeneralName(); $pickedNPC->occupyGeneralName();
} }
@@ -145,8 +145,9 @@ class che_종전제의 extends Command\NationCommand{
$destNation['color'] $destNation['color']
); );
$now = new \DateTime($date); $clock = \sammo\GameClock::fromStorage(\sammo\KVStorage::getStorage($db, 'game_env'));
$validUntil = new \DateTime($date); $now = \DateTime::createFromImmutable($clock->tickToDateTime($general->getTurnTick()));
$validUntil = clone $now;
$validMinutes = max(30, $env['turnterm']*3); $validMinutes = max(30, $env['turnterm']*3);
$validUntil->add(new \DateInterval("PT{$validMinutes}M")); $validUntil->add(new \DateInterval("PT{$validMinutes}M"));
+1 -1
View File
@@ -137,7 +137,7 @@ class che_천도 extends Command\NationCommand
$nationID = $general->getNationID(); $nationID = $general->getNationID();
$nationStor = \sammo\KVStorage::getStorage(DB::db(), $nationID, 'nation_env'); $nationStor = \sammo\KVStorage::getStorage(DB::db(), $nationID, 'nation_env');
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()]; $nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTick()];
if ($lastTurn->getCommand() != $commandName || $lastTurn->getArg() !== $this->arg) { if ($lastTurn->getCommand() != $commandName || $lastTurn->getArg() !== $this->arg) {
$this->setResultTurn(new LastTurn( $this->setResultTurn(new LastTurn(
+4 -8
View File
@@ -2,11 +2,9 @@
namespace sammo\DTO; namespace sammo\DTO;
use LDTO\Attr\Convert;
use LDTO\Attr\JsonString; use LDTO\Attr\JsonString;
use LDTO\Attr\NullIsUndefined; use LDTO\Attr\NullIsUndefined;
use LDTO\Attr\RawName; use LDTO\Attr\RawName;
use LDTO\Converter\DateTimeConverter;
use sammo\Enums\AuctionType; use sammo\Enums\AuctionType;
use sammo\Enums\ResourceType; use sammo\Enums\ResourceType;
@@ -23,12 +21,10 @@ class AuctionInfo extends \LDTO\DTO
#[RawName('req_resource')] #[RawName('req_resource')]
public ResourceType $reqResource, public ResourceType $reqResource,
#[RawName('open_date')] #[RawName('open_tick')]
#[Convert(DateTimeConverter::class)] public int $openTick,
public \DateTimeImmutable $openDate, #[RawName('close_tick')]
#[RawName('close_date')] public int $closeTick,
#[Convert(DateTimeConverter::class)]
public \DateTimeImmutable $closeDate,
#[JsonString] #[JsonString]
public AuctionInfoDetail $detail, public AuctionInfoDetail $detail,
+1 -4
View File
@@ -2,9 +2,7 @@
namespace sammo\DTO; namespace sammo\DTO;
use LDTO\Attr\Convert;
use LDTO\Attr\NullIsUndefined; use LDTO\Attr\NullIsUndefined;
use LDTO\Converter\DateTimeConverter;
class AuctionInfoDetail extends \LDTO\DTO class AuctionInfoDetail extends \LDTO\DTO
{ {
@@ -21,8 +19,7 @@ class AuctionInfoDetail extends \LDTO\DTO
#[NullIsUndefined] #[NullIsUndefined]
public ?int $remainCloseDateExtensionCnt, public ?int $remainCloseDateExtensionCnt,
#[NullIsUndefined] #[NullIsUndefined]
#[Convert(DateTimeConverter::class)] public ?int $availableLatestBidCloseTick,
public ?\DateTimeImmutable $availableLatestBidCloseDate,
) { ) {
} }
} }
+1 -4
View File
@@ -2,10 +2,8 @@
namespace sammo\DTO; namespace sammo\DTO;
use LDTO\Attr\Convert;
use LDTO\Attr\NullIsUndefined; use LDTO\Attr\NullIsUndefined;
use LDTO\Attr\RawName; use LDTO\Attr\RawName;
use LDTO\Converter\DateTimeConverter;
class GeneralAccessLog extends \LDTO\DTO class GeneralAccessLog extends \LDTO\DTO
{ {
@@ -20,8 +18,7 @@ class GeneralAccessLog extends \LDTO\DTO
public ?int $userID, public ?int $userID,
#[RawName('last_refresh')] #[RawName('last_refresh')]
#[Convert(DateTimeConverter::class)] public ?int $lastRefresh,
public \DateTimeImmutable $lastRefresh,
public int $refresh, public int $refresh,
+33
View File
@@ -2,8 +2,41 @@
namespace sammo\DTO; namespace sammo\DTO;
use sammo\GameClock;
use sammo\Util;
class VoteInfo extends \LDTO\DTO class VoteInfo extends \LDTO\DTO
{ {
/**
* 기존 문자열만 가진 vote도 읽되, 저장 경계에서는 반드시 tick을 함께 둡니다.
*
* @return array<string,mixed>
*/
public static function normalizeGameStorage(array $raw, GameClock $clock): array
{
if (!array_key_exists('startTick', $raw)) {
$raw['startTick'] = $clock->dateTimeToTick(new \DateTimeImmutable((string)$raw['startDate']));
}
if (!array_key_exists('endTick', $raw)) {
$raw['endTick'] = ($raw['endDate'] ?? null) === null
? null
: $clock->dateTimeToTick(new \DateTimeImmutable((string)$raw['endDate']));
}
$raw['startTick'] = Util::toInt($raw['startTick']);
$raw['endTick'] = $raw['endTick'] === null ? null : Util::toInt($raw['endTick']);
$raw['startDate'] = $clock->formatTick($raw['startTick']);
$raw['endDate'] = $raw['endTick'] === null ? null : $clock->formatTick($raw['endTick']);
return $raw;
}
public static function fromGameStorage(array $raw, GameClock $clock): self
{
$raw = self::normalizeGameStorage($raw, $clock);
unset($raw['startTick'], $raw['endTick']);
return self::fromArray($raw);
}
public function __construct( public function __construct(
public int $id, public int $id,
public string $title, public string $title,
+3 -3
View File
@@ -49,7 +49,7 @@ class DiplomaticMessage extends Message{
$this->validDiplomacy = false; $this->validDiplomacy = false;
} }
if($this->validUntil < (new \DateTime())){ if($this->validUntil < $this->date){
$this->validDiplomacy = false; $this->validDiplomacy = false;
} }
} }
@@ -215,7 +215,7 @@ class DiplomaticMessage extends Message{
$this->dest, $this->dest,
$this->src, $this->src,
"【외교】{$year}{$month}월: {$this->src->nationName}{$josaYi} {$this->dest->nationName}에게 제안한 {$this->diplomacyDetail}", "【외교】{$year}{$month}월: {$this->src->nationName}{$josaYi} {$this->dest->nationName}에게 제안한 {$this->diplomacyDetail}",
new \DateTime(), Message::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[ [
'delete'=>$this->id, 'delete'=>$this->id,
@@ -231,7 +231,7 @@ class DiplomaticMessage extends Message{
$this->dest, $this->dest,
$this->src, $this->src,
"【외교】{$year}{$month}월: {$this->src->nationName}{$josaYi} {$this->dest->nationName}에게 제안한 {$this->diplomacyDetail}", "【외교】{$year}{$month}월: {$this->src->nationName}{$josaYi} {$this->dest->nationName}에게 제안한 {$this->diplomacyDetail}",
new \DateTime(), Message::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[ [
'delete'=>$this->id, 'delete'=>$this->id,
@@ -0,0 +1,55 @@
<?php
namespace sammo\Event\Action;
use sammo\ActionLogger;
use sammo\CentennialAllStarGrowthService;
use sammo\DB;
use sammo\General;
use sammo\Json;
class AdvanceCentennialAllStar extends \sammo\Event\Action
{
public function run(array $env)
{
if (!CentennialAllStarGrowthService::isActive()) {
return [__CLASS__, 0];
}
$db = DB::db();
$updated = 0;
foreach ($db->query(
'SELECT general.no, select_pool.info
FROM general
JOIN select_pool ON select_pool.general_id = general.no'
) as $row) {
$general = General::createObjFromDB((int) $row['no']);
$targetInfo = Json::decode($row['info']);
$result = CentennialAllStarGrowthService::applyTarget(
$general,
$targetInfo,
$env,
CentennialAllStarGrowthService::progressMultiplierFor($general),
CentennialAllStarGrowthService::dexTargetRatioForNPCType(
$general->getNPCType()
)
);
if ($result['milestone'] > $result['previousMilestone']) {
$percent = $result['milestone'] * 20;
$general->getLogger()->pushGeneralActionLog(
"<L>올스타 동조율</>이 <C>{$percent}%</>에 도달했습니다!",
ActionLogger::PLAIN
);
$general->getLogger()->pushGeneralHistoryLog(
"<L>올스타 동조율 {$percent}% 달성</>"
);
}
if ($general->applyDB($db)) {
$updated++;
}
}
return [__CLASS__, $updated];
}
}
+9 -1
View File
@@ -5,6 +5,7 @@ namespace sammo\Event\Action;
use \sammo\GameConst; use \sammo\GameConst;
use \sammo\Util; use \sammo\Util;
use \sammo\DB; use \sammo\DB;
use sammo\CentennialAllStarGrowthService;
use sammo\LiteHashDRBG; use sammo\LiteHashDRBG;
use sammo\RandUtil; use sammo\RandUtil;
use sammo\UniqueConst; use sammo\UniqueConst;
@@ -35,7 +36,8 @@ class CreateManyNPC extends \sammo\Event\Action
))); )));
$result = []; $result = [];
foreach (pickGeneralFromPool(DB::db(), $rng, 0, $cnt) as $pickedNPC) { $db = DB::db();
foreach (pickGeneralFromPool($db, $rng, 0, $cnt) as $pickedNPC) {
$age = $rng->nextRangeInt(20, 25); $age = $rng->nextRangeInt(20, 25);
$birthYear = $env['year'] - $age; $birthYear = $env['year'] - $age;
$deathYear = $env['year'] + $rng->nextRangeInt(10, 50); $deathYear = $env['year'] + $rng->nextRangeInt(10, 50);
@@ -50,6 +52,12 @@ class CreateManyNPC extends \sammo\Event\Action
} }
$newNPC->fillRemainSpecAsZero($env); $newNPC->fillRemainSpecAsZero($env);
$newNPC->build($env); $newNPC->build($env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$newNPC,
$pickedNPC->getInfo(),
$env
);
$pickedNPC->occupyGeneralName(); $pickedNPC->occupyGeneralName();
$result[] = [ $result[] = [
$newNPC->getGeneralName(), $newNPC->getGeneralID() $newNPC->getGeneralName(), $newNPC->getGeneralID()
+1 -1
View File
@@ -123,7 +123,7 @@ class OpenNationBetting extends \sammo\Event\Action
} }
$logger->flush(); $logger->flush();
$now = new DateTime(); $now = Message::gameNow();
$text = "새로운 {$name} 내기가 열렸습니다. 천통국 베팅란을 확인해주세요."; $text = "새로운 {$name} 내기가 열렸습니다. 천통국 베팅란을 확인해주세요.";
$src = new MessageTarget(0, '', 0, 'System', '#000000'); $src = new MessageTarget(0, '', 0, 'System', '#000000');
+6
View File
@@ -184,6 +184,10 @@ class GameConstBase
/** @var array 선택할 수 없으나 게임 내에 유효한 장수 내정 특기 */ /** @var array 선택할 수 없으나 게임 내에 유효한 장수 내정 특기 */
public static $optionalSpecialDomestic = [ public static $optionalSpecialDomestic = [
'None', 'None',
'che_event_귀병', 'che_event_신산', 'che_event_환술', 'che_event_집중', 'che_event_신중',
'che_event_반계', 'che_event_보병', 'che_event_궁병', 'che_event_기병', 'che_event_공성',
'che_event_돌격', 'che_event_무쌍', 'che_event_견고', 'che_event_위압', 'che_event_저격',
'che_event_필살', 'che_event_징병', 'che_event_의술', 'che_event_격노', 'che_event_척사',
]; ];
/** @var string 기본 전투 특기 */ /** @var string 기본 전투 특기 */
@@ -416,6 +420,8 @@ class GameConstBase
public static $retirementYear = 80; public static $retirementYear = 80;
public static $targetGeneralPool = 'RandomNameGeneral'; public static $targetGeneralPool = 'RandomNameGeneral';
/** @var float 100기 올스타 NPC의 원본 목표 대비 최종 숙련 비율 */
public static $centennialNpcDexTargetRatio = 0.4;
public static $generalPoolAllowOption = ['stat', 'ego', 'picture']; public static $generalPoolAllowOption = ['stat', 'ego', 'picture'];
public static $randGenFirstName = [ public static $randGenFirstName = [
+66 -22
View File
@@ -281,16 +281,13 @@ class General extends GeneralBase implements iAction
$this->calcCache[$cacheKey] = $result; $this->calcCache[$cacheKey] = $result;
return $result; return $result;
} }
$recwar = new \DateTimeImmutable($this->getVar('recent_war')); $tickDiff = Util::toInt($this->getVar('turntime')) - Util::toInt($this->getVar('recent_war'));
$turnNow = new \DateTimeImmutable($this->getVar('turntime')); if ($tickDiff <= 0) {
$secDiff = TimeUtil::DateIntervalToSeconds($recwar->diff($turnNow));
if ($secDiff <= 0) {
$this->calcCache[$cacheKey] = 0; $this->calcCache[$cacheKey] = 0;
return 0; return 0;
} }
$result = intdiv(Util::toInt($secDiff), 60 * $turnTerm); $result = intdiv($tickDiff, GameClock::TICKS_PER_TURN);
$this->calcCache[$cacheKey] = $result; $this->calcCache[$cacheKey] = $result;
return $result; return $result;
} }
@@ -1045,18 +1042,64 @@ class General extends GeneralBase implements iAction
/** @var Map<GeneralAccessLog,int>|null */ /** @var Map<GeneralAccessLog,int>|null */
$rawAccessLog = null; $rawAccessLog = null;
$rankColumnValues = array_map(fn (\BackedEnum $e) => $e->value, $rankColumn);
if (!$accessLogColumn) { if (!$accessLogColumn) {
$rawGeneral = $db->queryFirstRow('SELECT %l FROM general WHERE no = %i', Util::formatListOfBackticks($column), $generalID); if ($rankColumn) {
$rawGeneral = $db->queryFirstRow(
'SELECT %l, (
SELECT GROUP_CONCAT(
CONCAT(`type`, CHAR(61), `value`)
ORDER BY `type`
)
FROM rank_data
WHERE rank_data.general_id = general.no
AND `type` IN %ls
) AS `_rank_values`
FROM general WHERE no = %i',
Util::formatListOfBackticks($column),
$rankColumnValues,
$generalID
);
} else {
$rawGeneral = $db->queryFirstRow(
'SELECT %l FROM general WHERE no = %i',
Util::formatListOfBackticks($column),
$generalID
);
}
} else { } else {
$rawGeneral = $db->queryFirstRow( if ($rankColumn) {
'SELECT %l, %l FROM `general` LEFT JOIN general_access_log $rawGeneral = $db->queryFirstRow(
ON general.no = general_access_log.general_id WHERE no = %i', 'SELECT %l, %l, (
Util::formatListOfBackticks($column), SELECT GROUP_CONCAT(
Util::formatListOfBackticks($accessLogColumn), CONCAT(`type`, CHAR(61), `value`)
$generalID ORDER BY `type`
); )
FROM rank_data
WHERE rank_data.general_id = general.no
AND `type` IN %ls
) AS `_rank_values`
FROM `general` LEFT JOIN general_access_log
ON general.no = general_access_log.general_id
WHERE no = %i',
Util::formatListOfBackticks($column),
Util::formatListOfBackticks($accessLogColumn),
$rankColumnValues,
$generalID
);
} else {
$rawGeneral = $db->queryFirstRow(
'SELECT %l, %l FROM `general` LEFT JOIN general_access_log
ON general.no = general_access_log.general_id WHERE no = %i',
Util::formatListOfBackticks($column),
Util::formatListOfBackticks($accessLogColumn),
$generalID
);
}
}
if ($accessLogColumn) {
$rawAccessLog = new Map(); $rawAccessLog = new Map();
foreach ($accessLogColumn as $accessLogKey) { foreach ($accessLogColumn as $accessLogKey) {
if (!key_exists($accessLogKey->value, $rawGeneral)) { if (!key_exists($accessLogKey->value, $rawGeneral)) {
@@ -1076,15 +1119,16 @@ class General extends GeneralBase implements iAction
$rawRankValues = new Map(); $rawRankValues = new Map();
if ($rankColumn) { if ($rankColumn) {
$rawValue = $db->queryAllLists( $rawRankPairs = $rawGeneral['_rank_values'];
'SELECT `type`, `value` FROM rank_data WHERE general_id = %i AND `type` IN %ls', foreach ($rawRankPairs === null || $rawRankPairs === ''
$generalID, ? []
array_map(fn (\BackedEnum $e) => $e->value, $rankColumn) : explode(',', $rawRankPairs) as $rawRankPair
); ) {
foreach ($rawValue as [$rawRankType, $rankValue]) { [$rawRankType, $rankValue] = explode('=', $rawRankPair, 2);
$rankType = RankColumn::tryFrom($rawRankType); $rankType = RankColumn::from($rawRankType);
$rawRankValues->put($rankType, $rankValue); $rawRankValues->put($rankType, (int) $rankValue);
} }
unset($rawGeneral['_rank_values']);
} }
+14 -17
View File
@@ -302,7 +302,7 @@ class GeneralAI
$this->calcWarRoute(); $this->calcWarRoute();
$troopCandidate = []; $troopCandidate = [];
$chiefTurn = cutTurn($this->general->getTurnTime(), $this->env['turnterm']); $chiefTurn = cutTurn($this->general->getTurnTick(), $this->env['turnterm']);
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']); $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
foreach ($this->troopLeaders as $troopLeader) { foreach ($this->troopLeaders as $troopLeader) {
@@ -319,7 +319,7 @@ class GeneralAI
$last발령 = $troopLeader->getAuxVar('last발령'); $last발령 = $troopLeader->getAuxVar('last발령');
if ($last발령) { if ($last발령) {
$leaderTurn = cutTurn($troopLeader->getTurnTime(), $this->env['turnterm']); $leaderTurn = cutTurn($troopLeader->getTurnTick(), $this->env['turnterm']);
$compYearMonth = $yearMonth; $compYearMonth = $yearMonth;
if ($chiefTurn < $leaderTurn) { if ($chiefTurn < $leaderTurn) {
$compYearMonth += 1; $compYearMonth += 1;
@@ -405,7 +405,7 @@ class GeneralAI
return null; return null;
} }
$chiefTurn = cutTurn($this->general->getTurnTime(), $this->env['turnterm']); $chiefTurn = cutTurn($this->general->getTurnTick(), $this->env['turnterm']);
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']); $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
$troopCandidate = []; $troopCandidate = [];
@@ -428,7 +428,7 @@ class GeneralAI
$last발령 = $troopLeader->getAuxVar('last발령'); $last발령 = $troopLeader->getAuxVar('last발령');
if ($last발령) { if ($last발령) {
$leaderTurn = cutTurn($troopLeader->getTurnTime(), $this->env['turnterm']); $leaderTurn = cutTurn($troopLeader->getTurnTick(), $this->env['turnterm']);
$compYearMonth = $yearMonth; $compYearMonth = $yearMonth;
if ($chiefTurn < $leaderTurn) { if ($chiefTurn < $leaderTurn) {
$compYearMonth += 1; $compYearMonth += 1;
@@ -586,8 +586,8 @@ class GeneralAI
continue; continue;
} }
$generalTurnTime = $userGeneral->getTurnTime(); $generalTurnTime = $userGeneral->getTurnTick();
$troopTurnTime = $troopLeader->getTurnTime(); $troopTurnTime = $troopLeader->getTurnTick();
if ($generalTurnTime < $troopTurnTime) { //NOTE: 어차피 수뇌 턴이 제일 빠르다 if ($generalTurnTime < $troopTurnTime) { //NOTE: 어차피 수뇌 턴이 제일 빠르다
$generalCadidates[$generalID] = $userGeneral; $generalCadidates[$generalID] = $userGeneral;
@@ -782,7 +782,7 @@ class GeneralAI
if ( if (
key_exists($troopLeader->getCityID(), $this->supplyCities) && key_exists($troopLeader->getCityID(), $this->supplyCities) &&
$this->troopLeaders[$troopID]->getTurnTime() < $lostGeneral->getTurnTime() $this->troopLeaders[$troopID]->getTurnTick() < $lostGeneral->getTurnTick()
) { ) {
//이미 탈출 가능한 부대를 탔다 //이미 탈출 가능한 부대를 탔다
continue; continue;
@@ -1986,7 +1986,7 @@ class GeneralAI
if ($lastTurn->getCommand() === '천도' && $lastTurn->getArg()['destCityID'] != $this->nation['capital']) { if ($lastTurn->getCommand() === '천도' && $lastTurn->getArg()['destCityID'] != $this->nation['capital']) {
$cmd = buildNationCommandClass('che_천도', $this->general, $this->env, $lastTurn, $lastTurn->getArg()); $cmd = buildNationCommandClass('che_천도', $this->general, $this->env, $lastTurn, $lastTurn->getArg());
if ($cmd->hasFullConditionMet()) { if ($cmd->hasFullConditionMet()) {
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()]; $nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTick()];
$this->reqUpdateInstance = true; $this->reqUpdateInstance = true;
return $cmd; return $cmd;
} }
@@ -1995,12 +1995,9 @@ class GeneralAI
$lastTrial = $nationStor->last천도Trial; $lastTrial = $nationStor->last천도Trial;
if ($lastTrial) { if ($lastTrial) {
[$lastTrialLevel, $lastTrialTurnTime] = $lastTrial; [$lastTrialLevel, $lastTrialTurnTime] = $lastTrial;
$timeDiffSeconds = TimeUtil::DateIntervalToSeconds( $timeDiffTick = abs($general->getTurnTick() - Util::toInt($lastTrialTurnTime));
date_create_immutable($lastTrialTurnTime)->diff( if ($timeDiffTick < intdiv(GameClock::TICKS_PER_TURN, 2)
date_create_immutable($general->getTurnTime()) && $lastTrialLevel !== $general->getVar('officer_level')) { //0.5Turn
)
);
if ($timeDiffSeconds < $turnTerm * 30 && $lastTrialLevel !== $general->getVar('officer_level')) { //0.5Turn
return null; return null;
} }
} }
@@ -2109,7 +2106,7 @@ class GeneralAI
} }
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()]; $nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTick()];
$this->reqUpdateInstance = true; $this->reqUpdateInstance = true;
return $cmd; return $cmd;
} }
@@ -3134,7 +3131,7 @@ class GeneralAI
$rawCity = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $this->general->getCityID()); $rawCity = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $this->general->getCityID());
$this->general->setRawCity($rawCity); $this->general->setRawCity($rawCity);
} }
if (!in_array($this->general->getRawCity()['level'], [5, 6])) { if (in_array($this->general->getRawCity()['level'], [5, 6])) {
return null; return null;
} }
} }
@@ -3730,7 +3727,7 @@ class GeneralAI
$src, $src,
$src, $src,
$general->getVar('npcmsg'), $general->getVar('npcmsg'),
new \DateTime(), Message::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
+24 -1
View File
@@ -60,6 +60,16 @@ abstract class GeneralBase
return null; return null;
} }
$rawTurnTime = $this->getVar('turntime');
// 비교용 Dummy와 과거 fixture는 문자열을 잠시 허용하되 제품 DB는 tick만 사용합니다.
if (is_string($rawTurnTime) && !ctype_digit(ltrim($rawTurnTime, '-'))) {
$formattedTurnTime = $rawTurnTime;
} else {
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$formattedTurnTime = $clock->formatTick(Util::toInt($rawTurnTime), true);
}
return [ return [
self::TURNTIME_FULL_MS => function ($turntime) { self::TURNTIME_FULL_MS => function ($turntime) {
return $turntime; return $turntime;
@@ -73,7 +83,20 @@ abstract class GeneralBase
self::TURNTIME_HM => function ($turntime) { self::TURNTIME_HM => function ($turntime) {
return substr($turntime, 11, 5); return substr($turntime, 11, 5);
}, },
][$short]($this->getVar('turntime')); ][$short]($formattedTurnTime);
}
function getTurnTick(): ?int
{
if (!key_exists('turntime', $this->raw)) {
return null;
}
$rawTurnTime = $this->getVar('turntime');
if (is_string($rawTurnTime) && !ctype_digit(ltrim($rawTurnTime, '-'))) {
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
return $clock->dateTimeToTick(new \DateTimeImmutable($rawTurnTime));
}
return Util::toInt($rawTurnTime);
} }
function getNPCType(): int function getNPCType(): int
File diff suppressed because it is too large Load Diff
+6 -7
View File
@@ -59,7 +59,7 @@ class RandomNameGeneral extends AbsGeneralPool{
'generalName'=>$generalName, 'generalName'=>$generalName,
'imgsvr'=>0, 'imgsvr'=>0,
'picture'=>null 'picture'=>null
], '9999-12-31 12:00:00'); ], PHP_INT_MAX);
} }
static public function pickGeneralFromPool(MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix = null): array static public function pickGeneralFromPool(MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix = null): array
@@ -68,26 +68,25 @@ class RandomNameGeneral extends AbsGeneralPool{
$result = []; $result = [];
$dbInsert = []; $dbInsert = [];
$oNow = new \DateTimeImmutable();
for($i=0;$i<$pickCnt;$i++){ for($i=0;$i<$pickCnt;$i++){
$result[] = static::pickGeneral1FromPool($db, $rng, $owner, $prefix); $result[] = static::pickGeneral1FromPool($db, $rng, $owner, $prefix);
} }
if($owner){ if($owner){
$now = $oNow->format('Y-m-d H:i:s'); $gameStor = \sammo\KVStorage::getStorage($db, 'game_env');
$clock = \sammo\GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$db->delete('select_pool', [ $db->delete('select_pool', [
'reserved_until'=>null, 'reserved_until'=>null,
'owner'=>null, 'owner'=>null,
],'(reserved_until < %s OR reserved_until IS NULL) AND general_id IS null', $now); ],'(reserved_until < %s OR reserved_until IS NULL) AND general_id IS null', $now);
$validUntil = $oNow->add(new \DateInterval(sprintf('PT%dS', 30))); $validUntil = $now + $clock->ticksFromSeconds(30);
foreach($result as $pickedGeneral){ foreach($result as $pickedGeneral){
$dbInsert[] = [ $dbInsert[] = [
'owner'=>$owner, 'owner'=>$owner,
'uniqueName'=>$pickedGeneral->getUniqueName(), 'uniqueName'=>$pickedGeneral->getUniqueName(),
'info'=>$pickedGeneral->getInfo(), 'info'=>$pickedGeneral->getInfo(),
'reserved_until'=>$validUntil->format(('Y-m-d H:i:s')) 'reserved_until'=>$validUntil
]; ];
} }
$db->insert('select_pool', $dbInsert); $db->insert('select_pool', $dbInsert);
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace sammo\GeneralPool;
use sammo\AbsFromUserPool;
use sammo\CentennialAllStarGrowthService;
use sammo\Json;
use sammo\RandUtil;
class SPoolUnderU100 extends AbsFromUserPool
{
private const MIN_DEX_WEIGHT = 100000;
private const STAT_BONUS_MIN_TOTAL = 160;
private const STAT_BONUS_MAX_TOTAL = 190;
private const STAT_BONUS_MAX_MULTIPLIER = 1.5;
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, int $validUntil)
{
$targetInfo = $info;
$initialInfo = $info;
foreach ([
'leadership',
'strength',
'intel',
'experience',
'dedication',
'dex',
'specialDomestic',
'specialWar',
] as $targetKey) {
unset($initialInfo[$targetKey]);
}
parent::__construct($db, $rng, $initialInfo, $validUntil);
$this->info = $targetInfo;
CentennialAllStarGrowthService::attachInitialTarget($this->builder, $targetInfo);
}
public static function getPoolName(): string
{
return '100기 올스타 클래식';
}
protected static function getCandidateWeight(array $info, int $owner): int|float
{
$dexWeight = max(
self::MIN_DEX_WEIGHT,
array_sum($info['dex'] ?? [])
);
if ($owner <= 0) {
return $dexWeight;
}
$statTotal = array_sum([
(int) ($info['leadership'] ?? 0),
(int) ($info['strength'] ?? 0),
(int) ($info['intel'] ?? 0),
]);
$normalizedStat = min(1, max(
0,
($statTotal - self::STAT_BONUS_MIN_TOTAL)
/ (self::STAT_BONUS_MAX_TOTAL - self::STAT_BONUS_MIN_TOTAL)
));
$statMultiplier = 1
+ (self::STAT_BONUS_MAX_MULTIPLIER - 1) * $normalizedStat;
return $dexWeight * $statMultiplier;
}
public static function initPool(\MeekroDB $db)
{
$jsonData = Json::decode(file_get_contents(__DIR__ . '/Pool/UnderS100.json'));
$columns = $jsonData['columns'];
$sqlValues = [];
foreach ($jsonData['data'] as $idx => $rawItem) {
if (count($rawItem) !== count($columns)) {
throw new \RuntimeException(($rawItem[0] ?? (string) $idx) . ' Error');
}
$item = array_combine($columns, $rawItem);
$uniqueName = sprintf('A100%04d', $idx + 1);
$item['uniqueName'] = $uniqueName;
$item['event100Growth'] = true;
$sqlValues[] = [
'unique_name' => $uniqueName,
'info' => Json::encode($item),
];
}
$db->insert('select_pool', $sqlValues);
}
}
+1 -1
View File
@@ -345,7 +345,7 @@ class InheritancePointManager
'general_id'=>$general->getID(), 'general_id'=>$general->getID(),
'year'=>$year, 'year'=>$year,
'month'=>$month, 'month'=>$month,
'value'=>$inheritStor->getAll(true) 'value'=>Json::encode($inheritStor->getAll(true))
]); ]);
} }
+75 -18
View File
@@ -15,6 +15,9 @@ class Message
protected $sendCnt = 0; protected $sendCnt = 0;
private ?int $sendTimeTick = null;
private ?int $sendValidUntilTick = null;
public function __construct( public function __construct(
public MessageType $msgType, public MessageType $msgType,
public MessageTarget $src, public MessageTarget $src,
@@ -26,6 +29,12 @@ class Message
) { ) {
} }
public static function gameNow(): \DateTime
{
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
return \DateTime::createFromImmutable($clock->nowDateTime());
}
public function setSentInfo(int $mailbox, int $messageID) : self public function setSentInfo(int $mailbox, int $messageID) : self
{ {
if(!Message::isValidMailBox($mailbox)){ if(!Message::isValidMailBox($mailbox)){
@@ -78,6 +87,15 @@ class Message
} }
public function toArray():array{ public function toArray():array{
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$messageTick = $clock->dateTimeToTick($this->date);
$deleteUntilTick = GameClock::addTicks($messageTick, $clock->ticksFromMinutes(5));
$deleteRemainingTicks = max(0, $deleteUntilTick - $clock->nowTick());
$deleteRemainingTicks = min($deleteRemainingTicks, $clock->ticksFromSeconds(2_147_483));
$deleteRemainingMilliseconds = intdiv(
$deleteRemainingTicks * 1000,
$clock->ticksPerSecond(),
);
if($this->msgType === MessageType::public){ if($this->msgType === MessageType::public){
$src = $this->src->toArray(); $src = $this->src->toArray();
$dest = null; $dest = null;
@@ -98,12 +116,15 @@ class Message
'dest'=>$dest, 'dest'=>$dest,
'text'=>$this->msg, 'text'=>$this->msg,
'option'=>$this->msgOption, 'option'=>$this->msgOption,
'time'=>$this->date->format('Y-m-d H:i:s') 'time'=>$this->date->format('Y-m-d H:i:s'),
'deleteRemainingMilliseconds'=>$deleteRemainingMilliseconds,
'clockMode'=>$clock->getMode(),
]; ];
} }
public static function buildFromArray(array $row) : Message public static function buildFromArray(array $row) : Message
{ {
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$dbMessage = Json::decode($row['message']); $dbMessage = Json::decode($row['message']);
$msgType = MessageType::from($row['type']); $msgType = MessageType::from($row['type']);
@@ -116,8 +137,8 @@ class Message
$src, $src,
$dest, $dest,
$dbMessage['text'], $dbMessage['text'],
new \DateTime($row['time']), \DateTime::createFromImmutable($clock->tickToDateTime(Util::toInt($row['time']))),
new \DateTime($row['valid_until']), \DateTime::createFromImmutable($clock->tickToDateTime(Util::toInt($row['valid_until']))),
$option $option
]; ];
@@ -151,9 +172,12 @@ class Message
public static function getMessageByID(int $messageID) : ?Message public static function getMessageByID(int $messageID) : ?Message
{ {
$db = DB::db(); $db = DB::db();
$now = new \DateTime(); $clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$row = $db->queryFirstRow('SELECT * FROM `message` WHERE `id` = %i AND valid_until', $messageID); $row = $db->queryFirstRow(
//FIXME: $now가 들어가야 하는데 안 들어가있는데? 'SELECT * FROM `message` WHERE `id` = %i AND valid_until > %i',
$messageID,
$clock->nowTick(),
);
if (!$row) { if (!$row) {
return null; return null;
} }
@@ -171,12 +195,12 @@ class Message
{ {
$db = DB::db(); $db = DB::db();
$date = (new \DateTime())->format('Y-m-d H:i:s'); $date = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->nowTick();
$where = new \WhereClause('and'); $where = new \WhereClause('and');
$where->add('mailbox = %i', $mailbox); $where->add('mailbox = %i', $mailbox);
$where->add('type = %s', $msgType->value); $where->add('type = %s', $msgType->value);
$where->add('valid_until > %s', $date); $where->add('valid_until > %i', $date);
if ($fromSeq > 0) { if ($fromSeq > 0) {
$where->add('id >= %i', $fromSeq); $where->add('id >= %i', $fromSeq);
} }
@@ -203,12 +227,12 @@ class Message
{ {
$db = DB::db(); $db = DB::db();
$date = (new \DateTime())->format('Y-m-d H:i:s'); $date = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->nowTick();
$where = new \WhereClause('and'); $where = new \WhereClause('and');
$where->add('mailbox = %i', $mailbox); $where->add('mailbox = %i', $mailbox);
$where->add('type = %s', $msgType->value); $where->add('type = %s', $msgType->value);
$where->add('valid_until > %s', $date); $where->add('valid_until > %i', $date);
$where->add('id < %i', $toSeq); $where->add('id < %i', $toSeq);
if ($limit > 0) { if ($limit > 0) {
@@ -236,7 +260,8 @@ class Message
return '시스템 외교 메시지는 삭제할 수 없습니다.'; return '시스템 외교 메시지는 삭제할 수 없습니다.';
} }
$prev5min = new \DateTime(); $clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$prev5min = \DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick()));
$prev5min->sub(new \DateInterval('PT5M')); $prev5min->sub(new \DateInterval('PT5M'));
if($msgObj->date < $prev5min){ if($msgObj->date < $prev5min){
@@ -265,14 +290,15 @@ class Message
} }
$in1min = new \DateTime(); $now = \DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick()));
$in1min = clone $now;
$in1min->add(new \DateInterval('PT1M')); $in1min->add(new \DateInterval('PT1M'));
$newMsg = new Message( $newMsg = new Message(
$msgObj->msgType, $msgObj->msgType,
$msgObj->src, $msgObj->src,
$msgObj->dest, $msgObj->dest,
"req_del_msg", "req_del_msg",
new \DateTime(), $now,
$in1min, $in1min,
$msgOption $msgOption
); );
@@ -300,13 +326,15 @@ class Message
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
[$timeTick, $validUntilTick] = $this->resolveSendTicks($clock);
$db->insert('message', [ $db->insert('message', [
'mailbox' => $mailbox, 'mailbox' => $mailbox,
'type' => $this->msgType->value, 'type' => $this->msgType->value,
'src' => $src_id, 'src' => $src_id,
'dest' => $dest_id, 'dest' => $dest_id,
'time' => $this->date->format('Y-m-d H:i:s'), 'time' => $timeTick,
'valid_until' => $this->validUntil->format('Y-m-d H:i:s'), 'valid_until' => $validUntilTick,
'message' => Json::encode([ 'message' => Json::encode([
'src'=>($this->src)?($this->src->toArray()):[], 'src'=>($this->src)?($this->src->toArray()):[],
'dest'=>($this->dest)?($this->dest->toArray()):[], 'dest'=>($this->dest)?($this->dest->toArray()):[],
@@ -317,6 +345,32 @@ class Message
return [$mailbox, $db->insertId()]; return [$mailbox, $db->insertId()];
} }
/** @return array{0:int, 1:int} */
protected function resolveSendTicks(GameClock $clock): array
{
if ($this->sendTimeTick !== null && $this->sendValidUntilTick !== null) {
return [$this->sendTimeTick, $this->sendValidUntilTick];
}
$timeTick = $clock->nowTick();
if (Util::toInt($this->validUntil->format('Y')) >= 9000) {
$validUntilTick = GameClock::MAX_SAFE_TICK;
} else {
$validitySeconds = $this->validUntil->getTimestamp() - $this->date->getTimestamp();
$validUntilTick = GameClock::addTicks(
$timeTick,
$clock->ticksFromSeconds($validitySeconds),
);
}
$this->sendTimeTick = $timeTick;
$this->sendValidUntilTick = $validUntilTick;
$this->date = \DateTime::createFromImmutable($clock->tickToDateTime($timeTick));
$this->validUntil = \DateTime::createFromImmutable($clock->tickToDateTime($validUntilTick));
return [$timeTick, $validUntilTick];
}
private function sendToSender():array{ private function sendToSender():array{
if($this->sendCnt > 1){ if($this->sendCnt > 1){
throw new \RuntimeException('이미 전송한 메일입니다.'); throw new \RuntimeException('이미 전송한 메일입니다.');
@@ -430,7 +484,7 @@ class Message
$src, $src,
$dest, $dest,
$msg, $msg,
new \DateTime(), self::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
@@ -464,6 +518,8 @@ class Message
} }
public function invalidate(?array $newMsgOption=null, bool $hideMsg=true){ public function invalidate(?array $newMsgOption=null, bool $hideMsg=true){
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$validUntilTick = $clock->dateTimeToTick($this->validUntil);
if($newMsgOption !== null){ if($newMsgOption !== null){
$this->msgOption = $newMsgOption; $this->msgOption = $newMsgOption;
} }
@@ -471,7 +527,8 @@ class Message
$this->msgOption['invalid'] = true; $this->msgOption['invalid'] = true;
if($hideMsg){ if($hideMsg){
$this->validUntil = new \DateTime('2000-12-31'); $validUntilTick = GameClock::addTicks($clock->nowTick(), -1);
$this->validUntil = \DateTime::createFromImmutable($clock->tickToDateTime($validUntilTick));
} }
else{ else{
if(key_exists('receiverMessageID', $this->msgOption)){ if(key_exists('receiverMessageID', $this->msgOption)){
@@ -489,7 +546,7 @@ class Message
'text' => $this->msg, 'text' => $this->msg,
'option' => $this->msgOption 'option' => $this->msgOption
]), ]),
'valid_until'=>$this->validUntil->format('Y-m-d H:i:s'), 'valid_until'=>$validUntilTick,
], 'id=%i', $this->id); ], 'id=%i', $this->id);
} }
+1 -1
View File
@@ -142,7 +142,7 @@ class RaiseInvaderMessage extends Message
$srcTarget = MessageTarget::buildSystemTarget(); $srcTarget = MessageTarget::buildSystemTarget();
$destTarget = MessageTarget::buildQuick($destGeneralID); $destTarget = MessageTarget::buildQuick($destGeneralID);
if ($date === null) { if ($date === null) {
$date = new \DateTime(); $date = Message::gameNow();
} }
/** /**
+25 -7
View File
@@ -245,14 +245,16 @@ class ResetHelper{
true true
); );
$requestedTime = new \DateTimeImmutable($turntime);
if($sync == 0) { if($sync == 0) {
// 현재 시간을 1월로 맞춤 // 현재 시간을 1월로 맞춤
$starttime = cutTurn($turntime, $turnterm); $baseTime = new \DateTimeImmutable(cutTurnDateTime($turntime, $turnterm));
$month = 1; $month = 1;
$year = $startyear; $year = $startyear;
} else { } else {
// 현재 시간과 동기화 // 현재 시간과 동기화
[$starttime, $yearPulled, $month] = cutDay($turntime, $turnterm); [$baseTimeString, $yearPulled, $month] = cutDay($turntime, $turnterm);
$baseTime = new \DateTimeImmutable($baseTimeString);
if($yearPulled){ if($yearPulled){
$year = $startyear-1; $year = $startyear-1;
} }
@@ -261,6 +263,18 @@ class ResetHelper{
} }
} }
$wallNow = GameClock::readWallTime();
$initialClock = new GameClock(
$baseTime,
$turnterm,
0,
GameClock::MODE_REALTIME,
$wallNow,
fn (): \DateTimeImmutable => $wallNow,
);
$requestedTick = $initialClock->dateTimeToTick($requestedTime);
$currentTick = $initialClock->dateTimeToTick($wallNow);
$killturn = 4800 / $turnterm; $killturn = 4800 / $turnterm;
if($npcmode == 1) { $killturn = intdiv($killturn, 3); } if($npcmode == 1) { $killturn = intdiv($killturn, 3); }
@@ -283,10 +297,14 @@ class ResetHelper{
'maxnation'=>GameConst::$defaultMaxNation, 'maxnation'=>GameConst::$defaultMaxNation,
'refreshLimit'=>30000, 'refreshLimit'=>30000,
'develcost'=>$develcost, 'develcost'=>$develcost,
'turntime'=>$turntime, 'turntime'=>$requestedTick,
'starttime'=>$starttime, 'starttime'=>0,
'opentime'=>$turntime, 'opentime'=>$requestedTick,
'turnterm'=>$turnterm, 'turnterm'=>$turnterm,
'clock_base_time'=>TimeUtil::format($baseTime, true),
'clock_tick'=>$currentTick,
'clock_mode'=>GameClock::MODE_REALTIME,
'clock_wall_anchor'=>TimeUtil::format($wallNow, true),
'killturn'=>$killturn, 'killturn'=>$killturn,
'genius'=>GameConst::$defaultMaxGenius, 'genius'=>GameConst::$defaultMaxGenius,
'show_img_level'=>$show_img_level, 'show_img_level'=>$show_img_level,
@@ -308,7 +326,7 @@ class ResetHelper{
'name'=>$admin['name'], 'name'=>$admin['name'],
'picture'=>$admin['picture'], 'picture'=>$admin['picture'],
'imgsvr'=>$admin['imgsvr'], 'imgsvr'=>$admin['imgsvr'],
'turntime'=>$turntime, 'turntime'=>$requestedTick,
'killturn'=>9999, 'killturn'=>9999,
'crewtype'=>GameUnitConst::DEFAULT_CREWTYPE 'crewtype'=>GameUnitConst::DEFAULT_CREWTYPE
]); ]);
@@ -344,7 +362,7 @@ class ResetHelper{
$db->insert('ng_games', [ $db->insert('ng_games', [
'server_id'=>$serverID, 'server_id'=>$serverID,
'date'=>$turntime, 'date'=>TimeUtil::format($requestedTime, false),
'winner_nation'=>null, 'winner_nation'=>null,
'map'=>$scenarioObj->getMapTheme(), 'map'=>$scenarioObj->getMapTheme(),
'season'=>$seasonIdx, 'season'=>$seasonIdx,
+5 -5
View File
@@ -136,13 +136,13 @@ class GeneralBuilder{
$this->specialWar = GameConst::$defaultSpecialWar; $this->specialWar = GameConst::$defaultSpecialWar;
} }
try{ try{
$this->specialDomestic = SpecialityHelper::getDomesticClassByName($special);
$this->specialWar = GameConst::$defaultSpecialWar;
}
catch (\Exception $e){
$this->specialDomestic = GameConst::$defaultSpecialDomestic; $this->specialDomestic = GameConst::$defaultSpecialDomestic;
$this->specialWar = SpecialityHelper::getWarClassByName($special); $this->specialWar = SpecialityHelper::getWarClassByName($special);
} }
catch (\Exception $e){
$this->specialDomestic = SpecialityHelper::getDomesticClassByName($special);
$this->specialWar = GameConst::$defaultSpecialWar;
}
return $this; return $this;
} }
@@ -653,7 +653,7 @@ class GeneralBuilder{
$officerLevel = $nationID?1:0; $officerLevel = $nationID?1:0;
} }
$turntime = \sammo\getRandTurn($this->rng, $env['turnterm'], new \DateTimeImmutable($env['turntime'])); $turntime = \sammo\getRandTurn($this->rng, $env['turnterm'], Util::toInt($env['turntime']));
if($this->killturn){ if($this->killturn){
$killturn = $this->killturn; $killturn = $this->killturn;
+6 -6
View File
@@ -35,7 +35,7 @@ class ScoutMessage extends Message
$this->validScout = false; $this->validScout = false;
} }
if ($this->validUntil <= new \DateTime()) { if ($this->validUntil <= $this->date) {
$this->validScout = false; $this->validScout = false;
} }
} }
@@ -108,7 +108,7 @@ class ScoutMessage extends Message
$this->src, $this->src,
$this->dest, $this->dest,
"{$this->src->nationName}{$josaRo} 등용 제의 수락", "{$this->src->nationName}{$josaRo} 등용 제의 수락",
new \DateTime(), Message::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[ [
'delete' => $this->id 'delete' => $this->id
@@ -122,11 +122,11 @@ class ScoutMessage extends Message
public static function invalidateAll(int $generalID, ?int $exceptMsgID = null) public static function invalidateAll(int $generalID, ?int $exceptMsgID = null)
{ {
$db = DB::db(); $db = DB::db();
$now = TimeUtil::now(); $now = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->nowTick();
//XXX: 뭔가 기존 쿼리가 애매하다. invalid 관련해서 다른 옵션이 가능한가? //XXX: 뭔가 기존 쿼리가 애매하다. invalid 관련해서 다른 옵션이 가능한가?
$rawMsgList = Util::convertArrayToDict($db->query( $rawMsgList = Util::convertArrayToDict($db->query(
'SELECT * FROM `message` WHERE 'SELECT * FROM `message` WHERE
`mailbox` = %i AND `type` = "private" AND `dest` = `mailbox` AND `valid_until` > %s AND `mailbox` = %i AND `type` = "private" AND `dest` = `mailbox` AND `valid_until` > %i AND
JSON_VALUE(message, "$.option.action") = %s', JSON_VALUE(message, "$.option.action") = %s',
$generalID, $generalID,
$now, $now,
@@ -156,7 +156,7 @@ class ScoutMessage extends Message
$this->src, $this->src,
$this->dest, $this->dest,
"{$this->src->nationName}{$josaRo} 등용 제의 거부", "{$this->src->nationName}{$josaRo} 등용 제의 거부",
new \DateTime(), Message::gameNow(),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[ [
'delete' => $this->id 'delete' => $this->id
@@ -206,7 +206,7 @@ class ScoutMessage extends Message
$srcGeneral = $db->queryFirstRow('SELECT `name`, nation FROM general WHERE `no`=%i', $srcGeneralID); $srcGeneral = $db->queryFirstRow('SELECT `name`, nation FROM general WHERE `no`=%i', $srcGeneralID);
$destGeneral = $db->queryFirstRow('SELECT `name`, nation, `officer_level` FROM general WHERE `no`=%i', $destGeneralID); $destGeneral = $db->queryFirstRow('SELECT `name`, nation, `officer_level` FROM general WHERE `no`=%i', $destGeneralID);
if ($date === null) { if ($date === null) {
$date = new \DateTime(); $date = Message::gameNow();
} }
if ($destGeneral['officer_level'] == 12) { if ($destGeneral['officer_level'] == 12) {
+9 -21
View File
@@ -40,34 +40,22 @@ final class ServerTool
$locked = tryLock(); $locked = tryLock();
} }
$oldunit = $admin['turnterm'] * 60; if($turnterm == $admin['turnterm']){
$unit = $turnterm * 60;
if($unit == $oldunit){
if($locked){ if($locked){
unlock(); unlock();
} }
return null; return null;
} }
$unitDiff = $unit / $oldunit; $oldClock = GameClock::fromStorage($gameStor);
$currentTick = $oldClock->nowTick();
$servTurnTime = new \DateTimeImmutable($admin['turntime']); $currentDisplay = $oldClock->tickToDateTime($currentTick);
foreach ($db->query('SELECT no,turntime FROM general') as $gen) { $oldClock->persistTick($gameStor, $currentTick);
$genTurnTime = new \DateTimeImmutable($gen['turntime']);
$timeDiff = TimeUtil::DateIntervalToSeconds($genTurnTime->diff($servTurnTime));
$timeDiff *= $unitDiff;
$newGenTurnTime = $servTurnTime->add(TimeUtil::secondsToDateInterval($timeDiff));
$db->update('general', [
'turntime' => $newGenTurnTime->format('Y-m-d H:i:s.u')
], 'no=%i', $gen['no']);
}
$turn = ($admin['year'] - $admin['startyear']) * 12 + $admin['month'] - 1;
$starttime = $servTurnTime->sub(TimeUtil::secondsToDateInterval($turn * $unit))->format('Y-m-d H:i:s');
$starttime = cutTurn($starttime, $turnterm, false);
$gameStor->turnterm = $turnterm; $gameStor->turnterm = $turnterm;
$gameStor->starttime = $starttime; $gameStor->clock_base_time = TimeUtil::format(
GameClock::baseTimeForProjection($currentDisplay, $currentTick, $turnterm),
true,
);
pushGlobalHistoryLog(["<R>★</>턴시간이 <C>{$turnterm}분</>으로 변경됩니다."]); pushGlobalHistoryLog(["<R>★</>턴시간이 <C>{$turnterm}분</>으로 변경됩니다."]);
if($locked){ if($locked){

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