Compare commits

...
119 changed files with 2744 additions and 790 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>
+9 -3
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;
@@ -78,4 +84,4 @@ switch ($btn) {
break; break;
} }
header('location:_admin1.php'); header('location:_admin1.php');
+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;
+3 -2
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='조회하기'>
@@ -179,4 +180,4 @@ $generalObj = General::createObjFromDB($gen, null, GeneralQueryMode::FullWithAcc
</table> </table>
</body> </body>
</html> </html>
+1 -3
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>
@@ -122,4 +120,4 @@ $sel[$type] = "selected";
</table> </table>
</body> </body>
</html> </html>
+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) {
+2 -2
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']) ?? [
@@ -537,4 +537,4 @@ $templates = new \League\Plates\Engine('templates');
</table> </table>
</body> </body>
</html> </html>
+3 -1
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']] ?? '-';
@@ -284,4 +286,4 @@ $templates = new \League\Plates\Engine('templates');
</table> </table>
</body> </body>
</html> </html>
+12 -8
View File
@@ -19,8 +19,10 @@ $userID = Session::getUserID();
$generalID = $session->generalID; $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);
$nowTick = $clock->nowTick();
$me = General::createObjFromDB($generalID, null, GeneralQueryMode::FullWithAccessLog); $me = General::createObjFromDB($generalID, null, GeneralQueryMode::FullWithAccessLog);
@@ -40,27 +42,29 @@ $lastRefresh = $db->queryFirstField(
); );
$nextChange = $me->getAuxVar('next_change'); $nextChange = $me->getAuxVar('next_change');
if (!is_string($nextChange) || $nextChange === '') { if (!is_int($nextChange)) {
$nextChange = null; $nextChange = null;
} }
$nextChangeDisplay = $nextChange === null ? null : $clock->formatTick($nextChange);
increaseRefresh("내정보", 1); increaseRefresh("내정보", 1);
if ($gameStor->turntime <= $gameStor->opentime) { if ($gameStor->turntime <= $gameStor->opentime) {
$targetTime = $me->getAuxVar('prestart_delete_after'); $targetTime = $me->getAuxVar('prestart_delete_after');
if (!is_string($targetTime) || $targetTime === '') { if (!is_int($targetTime)) {
$targetTime = addTurn( $targetTime = addTurn(
$lastRefresh ?: TimeUtil::now(), $lastRefresh === null ? $nowTick : Util::toInt($lastRefresh),
$gameStor->turnterm, $gameStor->turnterm,
GameConst::$minTurnDieOnPrestart GameConst::$minTurnDieOnPrestart
); );
$me->setAuxVar('prestart_delete_after', $targetTime); $me->setAuxVar('prestart_delete_after', $targetTime);
$me->applyDB($db); $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;
} }
} }
@@ -173,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; ?>
@@ -188,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) : ?>
다른 장수 선택 (<?= $nextChange ? substr($nextChange, 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; ?>
+3 -3
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강") {
@@ -248,4 +248,4 @@ if($btn == "자동개최설정") {
$gameStor->tnmt_msg = $msg; $gameStor->tnmt_msg = $msg;
} }
header('location:b_tournament.php'); header('location:b_tournament.php');
+115 -119
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,45 +925,37 @@ 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) { function subTurn(int $tick, int $turnterm, int $turn = 1, bool $withFraction = true): int
return $date->format('Y-m-d H:i:s.u'); {
} return $tick - GameClock::TICKS_PER_TURN * $turn;
return $date->format('Y-m-d H:i:s'); }
}
function cutTurn(int $tick, int $turnterm, bool $withFraction = true): int
function subTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true) {
{ $remainder = $tick % GameClock::TICKS_PER_TURN;
$date = new \DateTime($date); if ($remainder < 0) {
$target = $turnterm * $turn; $remainder += GameClock::TICKS_PER_TURN;
$date->sub(new \DateInterval("PT{$target}M")); }
if ($withFraction) { return $tick - $remainder;
return $date->format('Y-m-d H:i:s.u'); }
}
return $date->format('Y-m-d H:i:s'); /** 시나리오 초기화 입력인 벽시계를 기존 01:00 기준 월 경계로 정렬합니다. */
} function cutTurnDateTime(string $date, int $turnterm, bool $withFraction = true): string
{
function cutTurn($date, int $turnterm, bool $withFraction = true) $dateObj = new \DateTime($date);
{ $baseDate = new \DateTime($dateObj->format('Y-m-d'));
$date = new \DateTime($date); $baseDate->sub(new \DateInterval('P1D'));
$baseDate->add(new \DateInterval('PT1H'));
$baseDate = new \DateTime($date->format('Y-m-d')); $diffMin = intdiv($dateObj->getTimestamp() - $baseDate->getTimestamp(), 60);
$baseDate->sub(new \DateInterval("P1D")); $diffMin -= $diffMin % $turnterm;
$baseDate->add(new \DateInterval("PT1H")); $baseDate->add(new \DateInterval("PT{$diffMin}M"));
return $baseDate->format($withFraction ? 'Y-m-d H:i:s.u' : 'Y-m-d H:i:s');
$diffMin = intdiv($date->getTimeStamp() - $baseDate->getTimeStamp(), 60); }
$diffMin -= $diffMin % $turnterm;
$baseDate->add(new \DateInterval("PT{$diffMin}M"));
if ($withFraction) {
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(); $db = DB::db();
$date = TimeUtil::format($dateObj, false); $gameStor = KVStorage::getStorage($db, 'game_env');
$date = GameClock::fromStorage($gameStor)->nowTick();
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$isunited = $gameStor->isunited; $isunited = $gameStor->isunited;
$opentime = $gameStor->opentime; $opentime = $gameStor->opentime;
@@ -1088,12 +1082,12 @@ function updateTraffic()
if(count($recentTraffic) >= 5){ if(count($recentTraffic) >= 5){
array_shift($recentTraffic); array_shift($recentTraffic);
} }
$recentTraffic[] = [ $recentTraffic[] = [
'year'=>$admin['year'], 'year'=>$admin['year'],
'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;
@@ -1145,18 +1139,19 @@ function unlock(): bool
return $db->affectedRows() > 0; return $db->affectedRows() > 0;
} }
function timeover(): bool function timeover(): bool
{ {
$db = DB::db(); $db = DB::db();
$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;
@@ -1185,20 +1179,17 @@ 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")) $db->update('general', [
->format('Y-m-d H:i:s'); 'turntime' => $db->sqleval('turntime + %i', $delayTick)
], true);
$db->update('general', [ $db->update('ng_auction', [
'turntime' => $db->sqleval('DATE_ADD(turntime, INTERVAL %i MINUTE)', $minute) 'close_tick' => $db->sqleval('close_tick + %i', $delayTick)
], 'turntime<=DATE_ADD(turntime, INTERVAL %i MINUTE)', $term); ], 'finished = 0');
$db->update('ng_auction', [
'close_date' => $db->sqleval('DATE_ADD(close_date, INTERVAL %i MINUTE)', $minute)
], 'finished = 0');
} }
} }
@@ -1247,17 +1238,15 @@ function updateOnline()
$gameStor->online_nation = join(', ', $onlineNation); $gameStor->online_nation = join(', ', $onlineNation);
} }
function turnDate($curtime) function turnDate($curtime)
{ {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$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')) {
@@ -1705,12 +1696,21 @@ function tryUniqueItemLottery(RandUtil $rng, General $general, string $acquireTy
return giveRandomUniqueItem($rng, $general, $acquireType); return giveRandomUniqueItem($rng, $general, $acquireType);
} }
function getAdmin() 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[] */
function deleteNation(General $lord, bool $applyDB): array function deleteNation(General $lord, bool $applyDB): array
@@ -1788,11 +1788,13 @@ function deleteNation(General $lord, bool $applyDB): array
// 부대 삭제 // 부대 삭제
$db->delete('troop', 'nation=%i', $nationID); $db->delete('troop', 'nation=%i', $nationID);
// 국가 삭제 // 국가 삭제
$db->insert('ng_old_nations', [ $gameDate = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
'server_id' => UniqueConst::$serverID, $db->insert('ng_old_nations', [
'nation' => $nationID, 'server_id' => UniqueConst::$serverID,
'data' => Json::encode($nation) 'nation' => $nationID,
'date' => $gameDate,
'data' => Json::encode($nation)
]); ]);
$db->delete('nation', 'nation=%i', $nationID); $db->delete('nation', 'nation=%i', $nationID);
$db->delete('nation_turn', 'nation_id=%i', $nationID); $db->delete('nation_turn', 'nation_id=%i', $nationID);
@@ -2199,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) { $randSecond = $rng->nextRangeInt(0, 60 * $term - 1);
//do Nothing $randMicrosecond = $rng->nextRangeInt(0, 999999); // 레거시 RNG 소비 6자리 유지
} else {
throw new MustNotBeReachedException(); return $baseTick
} + $clock->ticksFromSeconds($randSecond)
+ intdiv($randMicrosecond * $clock->ticksPerSecond(), 1_000_000);
$randSecond = $rng->nextRangeInt(0, 60 * $term - 1); }
$randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수
function getRandTurn2(RandUtil $rng, int $term, ?int $baseTick = null): int
return TimeUtil::format($baseDateTime->add(TimeUtil::secondsToDateInterval($randSecond + $randFraction)), true); {
} $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
function getRandTurn2(RandUtil $rng, $term, ?\DateTimeInterface $baseDateTime = null) $baseTick ??= $clock->nowTick();
{ $randSecond = $rng->nextRangeInt(0, 60 * $term - 1);
if ($baseDateTime === null) { $randMicrosecond = $rng->nextRangeInt(0, 999999); // 레거시 RNG 소비 6자리 유지
$baseDateTime = new \DateTimeImmutable();
} else if ($baseDateTime instanceof \DateTime) { return $baseTick
$baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime); - $clock->ticksFromSeconds($randSecond)
} else { - intdiv($randMicrosecond * $clock->ticksPerSecond(), 1_000_000);
throw new MustNotBeReachedException(); }
}
$randSecond = $rng->nextRangeInt(0, 60 * $term - 1);
$randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수
return $baseDateTime->sub(TimeUtil::secondsToDateInterval($randSecond + $randFraction))->format('Y-m-d H:i:s.u');
}
+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) {
+65 -37
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){
@@ -494,4 +522,4 @@ function setNationCommand(int $generalID, array $turnList, string $command, ?arr
'arg_test'=>true, 'arg_test'=>true,
'reason'=>'success' 'reason'=>'success'
]; ];
} }
+27 -16
View File
@@ -651,16 +651,24 @@ 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']);
return [ $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 [
'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){
@@ -830,20 +838,23 @@ function checkEmperior()
$nation['aux'] += $nationStor->max_power ?? []; $nation['aux'] += $nationStor->max_power ?? [];
$nation['history'] = getNationHistoryLogAll($nation['nation']); $nation['history'] = getNationHistoryLogAll($nation['nation']);
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', [
'server_id' => UniqueConst::$serverID, $db->insert('ng_old_nations', [
'nation' => $nation['nation'], 'server_id' => UniqueConst::$serverID,
'data' => Json::encode($nation) 'nation' => $nation['nation'],
'date' => $gameDate,
'data' => Json::encode($nation)
]); ]);
$noNationGeneral = $db->queryFirstColumn('SELECT `no` FROM general WHERE nation=0'); $noNationGeneral = $db->queryFirstColumn('SELECT `no` FROM general WHERE nation=0');
$db->insert('ng_old_nations', [ $db->insert('ng_old_nations', [
'server_id' => UniqueConst::$serverID, 'server_id' => UniqueConst::$serverID,
'nation' => 0, 'nation' => 0,
'data' => Json::encode([ 'date' => $gameDate,
'data' => Json::encode([
'nation' => 0, 'nation' => 0,
'name' => '재야', 'name' => '재야',
'generals' => $noNationGeneral 'generals' => $noNationGeneral
+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>
+6 -2
View File
@@ -61,8 +61,12 @@ 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})";
} }
+11 -8
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;
@@ -1171,7 +1174,7 @@ function fight($tnmt_type, $tnmt, $phs, $group, $g1, $g2, $type)
} }
$damage1 *= $factor1; $damage1 *= $factor1;
$damage2 *= $factor2; $damage2 *= $factor2;
//1합 승부 //1합 승부
if ($phase == 1) { if ($phase == 1) {
+3 -2
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') ?>
@@ -42,4 +43,4 @@ $mapName = GameConst::$mapName;
<div id="app"></div> <div id="app"></div>
</body> </body>
</html> </html>
+15 -12
View File
@@ -25,21 +25,24 @@ 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($isUnited > 0){
if($lastTurn !== null){ if(!is_string($unitedWallAnchor) || $unitedWallAnchor === ''){
$lastTurn = new \DateTime($lastTurn); $unitedWallAnchor = TimeUtil::format($now, true);
} $gameStor->autoreset_united_wall_anchor = $unitedWallAnchor;
}
$lastTurn = new \DateTimeImmutable($unitedWallAnchor);
}
if($lastTurn === null){ if($lastTurn === null){
//이미 리셋된 상태임 //이미 리셋된 상태임
@@ -99,4 +102,4 @@ $result['affected']=1;
$prefix = DB::prefix(); $prefix = DB::prefix();
ServConfig::getServerList()[$prefix]->openServer(); ServConfig::getServerList()[$prefix]->openServer();
Json::die($result); Json::die($result);
+3 -2
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,
@@ -79,4 +80,4 @@ Json::die([
'result'=>true, 'result'=>true,
'reason'=>'success', 'reason'=>'success',
'row_id'=>$db->insertId() 'row_id'=>$db->insertId()
]); ]);
+3 -2
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'],
@@ -84,4 +85,4 @@ Json::die([
'result'=>true, 'result'=>true,
'reason'=>'success', 'reason'=>'success',
'row_id'=>$db->insertId() 'row_id'=>$db->insertId()
]); ]);
+2 -2
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'])){
@@ -138,4 +138,4 @@ Json::die([
'result'=>true, 'result'=>true,
'reason'=>'success', 'reason'=>'success',
'state'=>$lastState 'state'=>$lastState
]); ]);
+2 -2
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){
@@ -133,4 +133,4 @@ $msgID = $msg->send();
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'reason'=>'success' 'reason'=>'success'
]); ]);
+2 -2
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'] = [
@@ -91,4 +91,4 @@ $msgID = $msg->send();
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'reason'=>'success' 'reason'=>'success'
]); ]);
+5 -3
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, '이');
@@ -195,4 +197,4 @@ Json::die([
'result'=>true, 'result'=>true,
'reason'=>'success', 'reason'=>'success',
'row_id'=>$db->insertId() 'row_id'=>$db->insertId()
]); ]);
+23 -23
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(),
]);
+11 -9
View File
@@ -43,12 +43,10 @@ function putInfoText(&$info, ?array $currentTargetEnv){
$session = Session::requireLogin([])->setReadOnly(); $session = Session::requireLogin([])->setReadOnly();
$userID = Session::getUserID(); $userID = Session::getUserID();
$oNow = new \DateTimeImmutable(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$now = $oNow->format('Y-m-d H:i:s'); $clock = GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$eventEnv = $gameStor->getValues(['npcmode', 'startyear', 'year', 'month']); $eventEnv = $gameStor->getValues(['npcmode', 'startyear', 'year', 'month']);
$npcmode = $eventEnv['npcmode']; $npcmode = $eventEnv['npcmode'];
@@ -88,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(),
]); ]);
} }
@@ -108,5 +108,7 @@ 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(),
]);
+2 -2
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
)); ));
} }
@@ -220,4 +220,4 @@ catch(\Exception $e){
'result'=>false, 'result'=>false,
'reason'=>$e->getMessage() 'reason'=>$e->getMessage()
]); ]);
} }
+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'),
[] []
); );
+3 -3
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){
@@ -139,4 +139,4 @@ $rootDB->insert('member_log', [
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'reason'=>'success' 'reason'=>'success'
]); ]);
+3 -3
View File
@@ -37,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){
@@ -176,7 +176,7 @@ $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( $builder->setAuxVar(
'prestart_delete_after', 'prestart_delete_after',
addTurn($now, $env['turnterm'], GameConst::$minTurnDieOnPrestart) addTurn($now, $env['turnterm'], GameConst::$minTurnDieOnPrestart)
+5 -2
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']);
@@ -131,4 +134,4 @@ if($general){
Json::die([ Json::die([
'game'=>$admin, 'game'=>$admin,
'me'=>$me?:null 'me'=>$me?:null
]); ]);
+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'];
+3 -3
View File
@@ -22,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){
@@ -181,7 +181,7 @@ if ($isCentennialAllStar) {
if(key_exists('generalName', $info)){ if(key_exists('generalName', $info)){
$generalObj->updateVar('name', $info['generalName']); $generalObj->updateVar('name', $info['generalName']);
} }
$generalObj->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $turnterm)); $generalObj->setAuxVar('next_change', $now + GameClock::TICKS_PER_TURN * 12);
$userNick = $ownerInfo['name']; $userNick = $ownerInfo['name'];
$generalObj->setVar('owner_name', $userNick); $generalObj->setVar('owner_name', $userNick);
+4 -2
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,
]; ];
+8 -5
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,6 +38,8 @@ 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) { if (!$general) {
@@ -67,9 +70,9 @@ class DieOnPrestart extends \sammo\BaseAPI
} }
$targetTime = $generalObj->getAuxVar('prestart_delete_after'); $targetTime = $generalObj->getAuxVar('prestart_delete_after');
if (!is_string($targetTime) || $targetTime === '') { if (!is_int($targetTime)) {
$targetTime = addTurn( $targetTime = addTurn(
$lastRefresh ?: TimeUtil::now(), $lastRefresh === null ? $nowTick : Util::toInt($lastRefresh),
$gameStor->turnterm, $gameStor->turnterm,
GameConst::$minTurnDieOnPrestart GameConst::$minTurnDieOnPrestart
); );
@@ -78,8 +81,8 @@ class DieOnPrestart extends \sammo\BaseAPI
} }
//서버 가오픈시 할 수 있는 행동 //서버 가오픈시 할 수 있는 행동
if ($targetTime > TimeUtil::now()) { if ($targetTime > $nowTick) {
$targetTimeShort = substr($targetTime, 0, 19); $targetTimeShort = $clock->formatTick($targetTime);
return "아직 삭제할 수 없습니다. {$targetTimeShort} 부터 가능합니다."; return "아직 삭제할 수 없습니다. {$targetTimeShort} 부터 가능합니다.";
} }
+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(),
]; ];
} }
+8 -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']);
} }
+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'),
[] []
); );
@@ -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 '설문조사가 종료되었습니다.';
} }
+5 -5
View File
@@ -30,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,
@@ -48,9 +49,8 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
throw new \RuntimeException('pool 부족'); throw new \RuntimeException('pool 부족');
} }
$gameStor = KVStorage::getStorage($db, 'game_env'); $result = [];
$result = []; $validUntil = $now + GameClock::TICKS_PER_TURN * 2;
$validUntil = TimeUtil::nowAddMinutes(2 * $gameStor->turnterm);
while(count($result) < $pickCnt){ while(count($result) < $pickCnt){
$cand = $rng->choiceUsingWeightPair($pool); $cand = $rng->choiceUsingWeightPair($pool);
$poolID = $cand['id']; $poolID = $cand['id'];
+3 -3
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;
} }
@@ -109,4 +109,4 @@ abstract class AbsGeneralPool{
abstract public static function getPoolName():string; abstract public static function getPoolName():string;
abstract public static function initPool(\MeekroDB $db); abstract public static function initPool(\MeekroDB $db);
} }
+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);
+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"));
@@ -217,4 +218,4 @@ class che_불가침파기제의 extends Command\NationCommand{
], ],
]; ];
} }
} }
@@ -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"));
@@ -203,4 +204,4 @@ class che_종전제의 extends Command\NationCommand{
], ],
]; ];
} }
} }
+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,
) { ) {
} }
} }
+2 -5
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,
@@ -35,4 +32,4 @@ class GeneralAccessLog extends \LDTO\DTO
public int $refreshScoreTotal, public int $refreshScoreTotal,
) { ) {
} }
} }
+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,
+4 -4
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,
@@ -281,4 +281,4 @@ class DiplomaticMessage extends Message{
return self::DECLINED; return self::DECLINED;
} }
} }
+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');
+70 -26
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)); $this->calcCache[$cacheKey] = 0;
return 0;
if ($secDiff <= 0) { }
$this->calcCache[$cacheKey] = 0;
return 0; $result = intdiv($tickDiff, GameClock::TICKS_PER_TURN);
}
$result = intdiv(Util::toInt($secDiff), 60 * $turnTerm);
$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']);
} }
+13 -16
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;
} }
@@ -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'),
[] []
); );
+33 -10
View File
@@ -54,14 +54,24 @@ abstract class GeneralBase
); );
} }
function getTurnTime(int $short = self::TURNTIME_FULL_MS): ?string function getTurnTime(int $short = self::TURNTIME_FULL_MS): ?string
{ {
if(!key_exists('turntime', $this->raw)){ if(!key_exists('turntime', $this->raw)){
return null; return null;
} }
return [ $rawTurnTime = $this->getVar('turntime');
self::TURNTIME_FULL_MS => function ($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 [
self::TURNTIME_FULL_MS => function ($turntime) {
return $turntime; return $turntime;
}, },
self::TURNTIME_FULL => function ($turntime) { self::TURNTIME_FULL => function ($turntime) {
@@ -73,8 +83,21 @@ 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
{ {
+7 -8
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);
@@ -99,4 +98,4 @@ class RandomNameGeneral extends AbsGeneralPool{
public static function initPool(\MeekroDB $db){ public static function initPool(\MeekroDB $db){
//do Nothing //do Nothing
} }
} }
+1 -1
View File
@@ -14,7 +14,7 @@ class SPoolUnderU100 extends AbsFromUserPool
private const STAT_BONUS_MAX_TOTAL = 190; private const STAT_BONUS_MAX_TOTAL = 190;
private const STAT_BONUS_MAX_MULTIPLIER = 1.5; private const STAT_BONUS_MAX_MULTIPLIER = 1.5;
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, string $validUntil) public function __construct(\MeekroDB $db, RandUtil $rng, array $info, int $validUntil)
{ {
$targetInfo = $info; $targetInfo = $info;
$initialInfo = $info; $initialInfo = $info;
+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();
} }
/** /**
+34 -16
View File
@@ -245,21 +245,35 @@ class ResetHelper{
true true
); );
if($sync == 0) { $requestedTime = new \DateTimeImmutable($turntime);
// 현재 시간을 1월로 맞춤 if($sync == 0) {
$starttime = cutTurn($turntime, $turnterm); // 현재 시간을 1월로 맞춤
$month = 1; $baseTime = new \DateTimeImmutable(cutTurnDateTime($turntime, $turnterm));
$year = $startyear; $month = 1;
} else { $year = $startyear;
// 현재 시간과 동기화 } 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;
} }
else{ else{
$year = $startyear; $year = $startyear;
} }
} }
$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,
@@ -368,4 +386,4 @@ class ResetHelper{
'result'=>true 'result'=>true
]; ];
} }
} }
+6 -6
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;
@@ -735,4 +735,4 @@ class GeneralBuilder{
return true; //생성되었다. return true; //생성되었다.
} }
} }
+8 -8
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
@@ -205,9 +205,9 @@ class ScoutMessage extends Message
$db = DB::db(); $db = DB::db();
$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) {
if ($reason !== null) { if ($reason !== null) {
+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){
+36 -22
View File
@@ -6,15 +6,20 @@ use sammo\Enums\EventTarget;
use sammo\Enums\InheritanceKey; use sammo\Enums\InheritanceKey;
use \Symfony\Component\Lock; use \Symfony\Component\Lock;
class TurnExecutionHelper class TurnExecutionHelper
{ {
/** @var General*/ /** @var General*/
protected $generalObj; protected $generalObj;
public function __construct(General $general) public function __construct(General $general)
{ {
$this->generalObj = $general; $this->generalObj = $general;
} }
public static function monotonicCompletionTick(int $completedTick, int $candidateTick): int
{
return max($completedTick, $candidateTick);
}
public function __destruct() public function __destruct()
{ {
@@ -215,14 +220,13 @@ class TurnExecutionHelper
$general->rebirth(); $general->rebirth();
} }
$turntime = addTurn($general->getTurnTime(), $gameStor->turnterm); $turntime = addTurn($general->getTurnTick(), $gameStor->turnterm);
$nextTurnTimeBase = $general->getAuxVar('nextTurnTimeBase'); $nextTurnTimeBase = $general->getAuxVar('nextTurnTimeBase');
if($nextTurnTimeBase !== null){ if($nextTurnTimeBase !== null){
$turntime = cutTurn($turntime, $gameStor->turnterm); $turntime = cutTurn($turntime, $gameStor->turnterm);
$turntimeObj = new \DateTimeImmutable($turntime); $clock = GameClock::fromStorage($gameStor);
$turntimeObj = $turntimeObj->add(TimeUtil::secondsToDateInterval($nextTurnTimeBase)); $turntime += $clock->ticksFromSeconds($nextTurnTimeBase);
$turntime = TimeUtil::format($turntimeObj, true);
$general->setAuxVar('nextTurnTimeBase', null); $general->setAuxVar('nextTurnTimeBase', null);
} }
@@ -230,7 +234,7 @@ class TurnExecutionHelper
} }
static public function executeGeneralCommandUntil(string $date, \DateTimeInterface $limitActionTime, int $year, int $month) static public function executeGeneralCommandUntil(int $date, \DateTimeInterface $limitActionTime, int $year, int $month)
{ {
$db = DB::db(); $db = DB::db();
$generalsTodo = $db->query( $generalsTodo = $db->query(
@@ -244,7 +248,7 @@ class TurnExecutionHelper
$autorun_user = $gameStor->autorun_user; $autorun_user = $gameStor->autorun_user;
foreach ($generalsTodo as $rawGeneral) { foreach ($generalsTodo as $rawGeneral) {
$currActionTime = new \DateTimeImmutable(); $currActionTime = GameClock::readWallTime();
if ($currActionTime > $limitActionTime) { if ($currActionTime > $limitActionTime) {
return [true, $currentTurn]; return [true, $currentTurn];
} }
@@ -350,7 +354,7 @@ class TurnExecutionHelper
pullNationCommand($general->getVar('nation'), $general->getVar('officer_level')); pullNationCommand($general->getVar('nation'), $general->getVar('officer_level'));
pullGeneralCommand($general->getID()); pullGeneralCommand($general->getID());
$currentTurn = $general->getTurnTime(); $currentTurn = $general->getTurnTick();
$general->increaseVarWithLimit('myset', GameConst::$incDefSettingChange, null, GameConst::$maxDefSettingChange); $general->increaseVarWithLimit('myset', GameConst::$incDefSettingChange, null, GameConst::$maxDefSettingChange);
if (($autorun_user['limit_minutes'] ?? false) && $general->getNPCType() < 2 && $hasReservedTurn) { if (($autorun_user['limit_minutes'] ?? false) && $general->getNPCType() < 2 && $hasReservedTurn) {
@@ -390,13 +394,14 @@ class TurnExecutionHelper
return true; return true;
} }
static public function executeAllCommand(&$executed = false, &$locked = false): string static public function executeAllCommand(&$executed = false, &$locked = false): int
{ {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
if (TimeUtil::now(true) < $gameStor->turntime) { $clock = GameClock::fromStorage($gameStor);
if ($clock->nowTick() < $gameStor->turntime) {
//턴 시각 이전이면 아무것도 하지 않음 //턴 시각 이전이면 아무것도 하지 않음
return $gameStor->turntime; return $gameStor->turntime;
} }
@@ -420,7 +425,7 @@ class TurnExecutionHelper
//접속자 수 따라서 갱신제한 변경 //접속자 수 따라서 갱신제한 변경
CheckOverhead(); CheckOverhead();
$date = TimeUtil::now(true); $date = $clock->nowTick();
// 최종 처리 월턴의 다음 월턴시간 구함 // 최종 처리 월턴의 다음 월턴시간 구함
//$lastExecuted = $gameStor->turntime; //$lastExecuted = $gameStor->turntime;
$prevTurn = cutTurn($gameStor->turntime, $gameStor->turnterm); $prevTurn = cutTurn($gameStor->turntime, $gameStor->turnterm);
@@ -433,7 +438,7 @@ class TurnExecutionHelper
$maxActionTime = max($maxActionTime * 2 / 3, $maxActionTime - 10); $maxActionTime = max($maxActionTime * 2 / 3, $maxActionTime - 10);
} }
$limitActionTime = (new \DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($maxActionTime)); $limitActionTime = GameClock::readWallTime()->add(TimeUtil::secondsToDateInterval($maxActionTime));
// 현재 턴 이전 월턴까지 모두처리. // 현재 턴 이전 월턴까지 모두처리.
//최종 처리 이후 다음 월턴이 현재 시간보다 전이라면 //최종 처리 이후 다음 월턴이 현재 시간보다 전이라면
@@ -450,9 +455,12 @@ class TurnExecutionHelper
updateTraffic(); updateTraffic();
if ($executionOver) { if ($executionOver) {
if ($currentTurn !== null) { if ($currentTurn !== null) {
$executed = true; $executed = true;
$gameStor->turntime = $currentTurn; $gameStor->turntime = self::monotonicCompletionTick(
Util::toInt($gameStor->turntime),
$currentTurn,
);
} }
unlock(); unlock();
return $gameStor->turntime; return $gameStor->turntime;
@@ -497,10 +505,16 @@ class TurnExecutionHelper
$gameStor->month $gameStor->month
); );
if ($currentTurn !== null) { if ($currentTurn !== null) {
$executed = true; $executed = true;
$gameStor->turntime = $currentTurn; // A general's sub-tick can be just before the monthly boundary that
} // was completed above. Never move the global completion cursor back
// behind an already-applied monthly event.
$gameStor->turntime = self::monotonicCompletionTick(
Util::toInt($gameStor->turntime),
$currentTurn,
);
}
//토너먼트 처리 //토너먼트 처리
processTournament(); processTournament();
+2 -1
View File
@@ -55,7 +55,8 @@ class UserLogger
} }
$db = DB::db(); $db = DB::db();
$date = TimeUtil::now(); $clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$date = $clock->formatTick($clock->nowTick());
$serverID = UniqueConst::$serverID; $serverID = UniqueConst::$serverID;
$request = array_map(function ($textAndType) use ($date, $serverID) { $request = array_map(function ($textAndType) use ($date, $serverID) {
[$text, $type] = $textAndType; [$text, $type] = $textAndType;
+5 -5
View File
@@ -57,16 +57,16 @@ class WarUnitGeneral extends WarUnit
$this->general->increaseRankVar(RankColumn::warnum, 1); $this->general->increaseRankVar(RankColumn::warnum, 1);
if ($this->isAttacker) { if ($this->isAttacker) {
$semiTurn = $general->getTurnTime(); $semiTurn = $general->getTurnTick();
} else if ($oppose !== null) { } else if ($oppose !== null) {
$semiTurn = $oppose->getGeneral()->getTurnTime(); $semiTurn = $oppose->getGeneral()->getTurnTick();
} else { } else {
LogText("WarUnitGeneral::setOppose", "defender인데 oppose가 null {$general->getID()}, {$general->getTurnTime()}"); LogText("WarUnitGeneral::setOppose", "defender인데 oppose가 null {$general->getID()}, {$general->getTurnTime()}");
$semiTurn = $general->getTurnTime(); $semiTurn = $general->getTurnTick();
} }
$phase = $this->getRealPhase(); $phase = $this->getRealPhase();
$semiTurn = substr($semiTurn, 0, strlen($semiTurn) - 2); $semiTurn -= $semiTurn % 100;
$semiTurn .= sprintf("%02d", Util::valueFit($phase, 0, 99)); $semiTurn += Util::valueFit($phase, 0, 99);
$general->setVar('recent_war', $semiTurn); $general->setVar('recent_war', $semiTurn);
} }
+14 -14
View File
@@ -45,8 +45,8 @@ CREATE TABLE `general` (
`book` VARCHAR(20) NOT NULL DEFAULT 'None', `book` VARCHAR(20) NOT NULL DEFAULT 'None',
`horse` VARCHAR(20) NOT NULL DEFAULT 'None', `horse` VARCHAR(20) NOT NULL DEFAULT 'None',
`item` VARCHAR(20) NOT NULL DEFAULT 'None', `item` VARCHAR(20) NOT NULL DEFAULT 'None',
`turntime` DATETIME(6) NOT NULL, `turntime` BIGINT NOT NULL,
`recent_war` DATETIME(6) NULL DEFAULT NULL, `recent_war` BIGINT NULL DEFAULT NULL,
`makelimit` INT(2) NULL DEFAULT '0', `makelimit` INT(2) NULL DEFAULT '0',
`killturn` INT(3) NULL DEFAULT NULL, `killturn` INT(3) NULL DEFAULT NULL,
`block` INT(1) NULL DEFAULT '0', `block` INT(1) NULL DEFAULT '0',
@@ -97,7 +97,7 @@ CREATE TABLE `general_access_log` (
`id` INT(11) NOT NULL AUTO_INCREMENT, `id` INT(11) NOT NULL AUTO_INCREMENT,
`general_id` INT(11) NOT NULL, `general_id` INT(11) NOT NULL,
`user_id` INT(11) NULL DEFAULT NULL, `user_id` INT(11) NULL DEFAULT NULL,
`last_refresh` DATETIME NULL DEFAULT NULL, `last_refresh` BIGINT NULL DEFAULT NULL,
`refresh` INT(11) NOT NULL DEFAULT '0', `refresh` INT(11) NOT NULL DEFAULT '0',
`refresh_total` INT(11) NOT NULL DEFAULT '0', `refresh_total` INT(11) NOT NULL DEFAULT '0',
`refresh_score` INT(11) NOT NULL DEFAULT '0', `refresh_score` INT(11) NOT NULL DEFAULT '0',
@@ -244,8 +244,8 @@ CREATE TABLE `message` (
`type` ENUM('private', 'national', 'public', 'diplomacy') NOT NULL, `type` ENUM('private', 'national', 'public', 'diplomacy') NOT NULL,
`src` INT(11) NOT NULL, `src` INT(11) NOT NULL,
`dest` INT(11) NOT NULL, `dest` INT(11) NOT NULL,
`time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `time` BIGINT NOT NULL,
`valid_until` DATETIME NOT NULL DEFAULT '9999-12-31 23:59:59', `valid_until` BIGINT NOT NULL,
`message` TEXT NOT NULL COLLATE 'utf8mb4_bin', `message` TEXT NOT NULL COLLATE 'utf8mb4_bin',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
INDEX `by_mailbox` (`mailbox`, `type`, `id`), INDEX `by_mailbox` (`mailbox`, `type`, `id`),
@@ -297,7 +297,7 @@ CREATE TABLE IF NOT EXISTS `ng_old_nations` (
`server_id` CHAR(20) NOT NULL DEFAULT '0', `server_id` CHAR(20) NOT NULL DEFAULT '0',
`nation` INT(11) NOT NULL DEFAULT '0', `nation` INT(11) NOT NULL DEFAULT '0',
`data` LONGTEXT NOT NULL DEFAULT '{}' COLLATE 'utf8mb4_bin', `data` LONGTEXT NOT NULL DEFAULT '{}' COLLATE 'utf8mb4_bin',
`date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `date` DATETIME NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
INDEX `server_id` (`server_id`, `nation`), INDEX `server_id` (`server_id`, `nation`),
CONSTRAINT `json` CHECK (json_valid(`data`)) CONSTRAINT `json` CHECK (json_valid(`data`))
@@ -395,7 +395,7 @@ CREATE TABLE `ng_diplomacy` (
`state` ENUM('proposed', 'activated', 'cancelled', 'replaced') NOT NULL DEFAULT 'proposed', `state` ENUM('proposed', 'activated', 'cancelled', 'replaced') NOT NULL DEFAULT 'proposed',
`text_brief` TEXT NOT NULL, `text_brief` TEXT NOT NULL,
`text_detail` TEXT NOT NULL, `text_detail` TEXT NOT NULL,
`date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `date` DATETIME NOT NULL,
`src_signer` INT(11) NOT NULL, `src_signer` INT(11) NOT NULL,
`dest_signer` INT(11) NULL DEFAULT NULL, `dest_signer` INT(11) NULL DEFAULT NULL,
`aux` TEXT NULL DEFAULT NULL COLLATE 'utf8mb4_bin', `aux` TEXT NULL DEFAULT NULL COLLATE 'utf8mb4_bin',
@@ -536,8 +536,8 @@ CREATE TABLE IF NOT EXISTS `reserved_open` (
CREATE TABLE `select_npc_token` ( CREATE TABLE `select_npc_token` (
`id` INT(11) NOT NULL AUTO_INCREMENT, `id` INT(11) NOT NULL AUTO_INCREMENT,
`owner` INT(11) NOT NULL, `owner` INT(11) NOT NULL,
`valid_until` DATETIME NOT NULL, `valid_until` BIGINT NOT NULL,
`pick_more_from` DATETIME NOT NULL, `pick_more_from` BIGINT NOT NULL,
`pick_result` TEXT NOT NULL COLLATE 'utf8mb4_bin', `pick_result` TEXT NOT NULL COLLATE 'utf8mb4_bin',
`nonce` INT(11) NOT NULL, `nonce` INT(11) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@@ -553,7 +553,7 @@ CREATE TABLE `select_pool` (
`unique_name` VARCHAR(20) NOT NULL, `unique_name` VARCHAR(20) NOT NULL,
`owner` INT(11) NULL DEFAULT NULL, `owner` INT(11) NULL DEFAULT NULL,
`general_id` INT(11) NULL DEFAULT NULL, `general_id` INT(11) NULL DEFAULT NULL,
`reserved_until` DATETIME NULL DEFAULT NULL, `reserved_until` BIGINT NULL DEFAULT NULL,
`info` TEXT NOT NULL, `info` TEXT NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE INDEX `unique_name` (`unique_name`), UNIQUE INDEX `unique_name` (`unique_name`),
@@ -694,11 +694,11 @@ CREATE TABLE `ng_auction` (
`target` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_bin', `target` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_bin',
`host_general_id` INT(11) NOT NULL, `host_general_id` INT(11) NOT NULL,
`req_resource` ENUM('gold','rice','inheritPoint') NOT NULL COLLATE 'utf8mb4_bin', `req_resource` ENUM('gold','rice','inheritPoint') NOT NULL COLLATE 'utf8mb4_bin',
`open_date` DATETIME NOT NULL, `open_tick` BIGINT NOT NULL,
`close_date` DATETIME NOT NULL, `close_tick` BIGINT NOT NULL,
`detail` LONGTEXT NOT NULL COLLATE 'utf8mb4_bin', `detail` LONGTEXT NOT NULL COLLATE 'utf8mb4_bin',
PRIMARY KEY (`id`) USING BTREE, PRIMARY KEY (`id`) USING BTREE,
INDEX `by_close` (`finished`, `type`, `close_date`) USING BTREE, INDEX `by_close` (`finished`, `type`, `close_tick`) USING BTREE,
INDEX `by_general_id` (`host_general_id`, `type`, `finished`) USING BTREE, INDEX `by_general_id` (`host_general_id`, `type`, `finished`) USING BTREE,
CONSTRAINT `detail` CHECK (json_valid(`detail`)) CONSTRAINT `detail` CHECK (json_valid(`detail`))
) )
@@ -721,4 +721,4 @@ CREATE TABLE `ng_auction_bid` (
CONSTRAINT `aux` CHECK (json_valid(`aux`)) CONSTRAINT `aux` CHECK (json_valid(`aux`))
) )
COLLATE='utf8mb4_general_ci' COLLATE='utf8mb4_general_ci'
ENGINE = Aria; ENGINE = Aria;
+14
View File
@@ -0,0 +1,14 @@
import assert from 'assert';
import { resolveGatewayOpenState } from '../ts/gateway/resolveGatewayOpenState';
describe('resolveGatewayOpenState', () => {
it('keeps the logical server decision even when projected dates disagree with wall time', () => {
assert.strictEqual(resolveGatewayOpenState(false, '2000-01-01 00:00:00', '2026-08-04 00:00:00'), false);
assert.strictEqual(resolveGatewayOpenState(true, '2042-01-01 00:00:00', '2026-08-04 00:00:00'), true);
});
it('falls back to legacy wall time only when isOpen is absent', () => {
assert.strictEqual(resolveGatewayOpenState(undefined, '2026-08-03 00:00:00', '2026-08-04 00:00:00'), true);
assert.strictEqual(resolveGatewayOpenState(undefined, '2026-08-05 00:00:00', '2026-08-04 00:00:00'), false);
});
});
+3 -1
View File
@@ -42,6 +42,7 @@
:maxTurn="maxChiefTurn" :maxTurn="maxChiefTurn"
:maxPushTurn="Math.floor(maxChiefTurn / 2)" :maxPushTurn="Math.floor(maxChiefTurn / 2)"
:date="date" :date="date"
:clockMode="clockMode"
:officer="officer" :officer="officer"
@raiseReload="reloadTable()" @raiseReload="reloadTable()"
/> />
@@ -139,6 +140,7 @@ const tableObj = reactive<Omit<OptionalFull<ChiefResponse>, "result">>({
month: undefined, month: undefined,
turnTerm: undefined, turnTerm: undefined,
date: undefined, date: undefined,
clockMode: undefined,
troopList: undefined, troopList: undefined,
chiefList: undefined, chiefList: undefined,
isChief: undefined, isChief: undefined,
@@ -149,7 +151,7 @@ const tableObj = reactive<Omit<OptionalFull<ChiefResponse>, "result">>({
unitSet: undefined, unitSet: undefined,
}); });
const { year, month, turnTerm, date, chiefList, troopList, officerLevel, commandList } = toRefs(tableObj); const { year, month, turnTerm, date, clockMode, chiefList, troopList, officerLevel, commandList } = toRefs(tableObj);
let postFilterNationCommand = function (turnObj: TurnObj): TurnObj { let postFilterNationCommand = function (turnObj: TurnObj): TurnObj {
return turnObj; return turnObj;
+1 -9
View File
@@ -195,7 +195,6 @@ import { onMounted, reactive, ref, watch, computed } from "vue";
import type { VoteInfo, VoteDetailResult } from "@/defs/API/Vote"; import type { VoteInfo, VoteDetailResult } from "@/defs/API/Vote";
import { SammoAPI } from "@/SammoAPI"; import { SammoAPI } from "@/SammoAPI";
import { isString, range, sum } from "lodash-es"; import { isString, range, sum } from "lodash-es";
import { formatTime } from "@/util/formatTime";
import { isBrightColor } from "@/util/isBrightColor"; import { isBrightColor } from "@/util/isBrightColor";
import { formatVoteColor } from "@/utilGame/formatVoteColor"; import { formatVoteColor } from "@/utilGame/formatVoteColor";
@@ -239,14 +238,7 @@ const canVote = computed(() => {
if (currentVote.value.myVote) { if (currentVote.value.myVote) {
return false; return false;
} }
const endDate = currentVote.value.voteInfo.endDate; return currentVote.value.isOpen;
if (endDate) {
const now = formatTime(new Date());
if (now > endDate) {
return false;
}
}
return true;
}); });
const currentVoteID = ref<number>(); const currentVoteID = ref<number>();
+3 -1
View File
@@ -13,7 +13,7 @@
class="col alert alert-primary m-0 p-0" class="col alert alert-primary m-0 p-0"
style="text-align: center; display: flex; justify-content: center; align-items: center" style="text-align: center; display: flex; justify-content: center; align-items: center"
> >
<SimpleClock :serverTime="serverNow" /> <SimpleClock :serverTime="serverNow" :running="clockRunning" />
</div> </div>
<div class="col d-grid"> <div class="col d-grid">
<BDropdown right text="반복"> <BDropdown right text="반복">
@@ -467,6 +467,7 @@ async function pushGeneralCommand(amount: number) {
} }
const serverNow = ref(new Date()); const serverNow = ref(new Date());
const clockRunning = ref(true);
function pushGeneralCommandSingle(e: Event) { function pushGeneralCommandSingle(e: Event) {
//NOTE: split 구현에 버그가 있어서, 수동으로 구분해야함 //NOTE: split 구현에 버그가 있어서, 수동으로 구분해야함
@@ -548,6 +549,7 @@ async function reloadCommandList() {
} }
serverNow.value = parseTime(result.date); serverNow.value = parseTime(result.date);
clockRunning.value = result.clockMode === "realtime";
} }
async function reserveCommandDirect(args: [number[], TurnObj][], reload = true): Promise<boolean> { async function reserveCommandDirect(args: [number[], TurnObj][], reload = true): Promise<boolean> {
+5 -1
View File
@@ -27,7 +27,7 @@
<div class="row gx-1 gy-1 py-1"> <div class="row gx-1 gy-1 py-1">
<div class="col-lg-4 mx-0 mb-0 mt-1 d-grid"> <div class="col-lg-4 mx-0 mb-0 mt-1 d-grid">
<div class="alert alert-primary mb-0 center" style="padding: 0.5rem 0"> <div class="alert alert-primary mb-0 center" style="padding: 0.5rem 0">
<SimpleClock :serverTime="parseTime(props.date)" /> <SimpleClock :serverTime="parseTime(props.date)" :running="props.clockMode === 'realtime'" />
</div> </div>
</div> </div>
@@ -289,6 +289,10 @@ const props = defineProps({
maxTurn: VueTypes.integer.isRequired, maxTurn: VueTypes.integer.isRequired,
maxPushTurn: VueTypes.integer.isRequired, maxPushTurn: VueTypes.integer.isRequired,
date: VueTypes.string.isRequired, date: VueTypes.string.isRequired,
clockMode: {
type: String as PropType<"realtime" | "manual">,
required: true,
},
year: VueTypes.integer.isRequired, year: VueTypes.integer.isRequired,
month: VueTypes.integer.isRequired, month: VueTypes.integer.isRequired,
turnTerm: VueTypes.integer.isRequired, turnTerm: VueTypes.integer.isRequired,
+2 -5
View File
@@ -126,8 +126,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { MsgItem, MsgTarget, MsgType } from "@/defs/API/Message"; import type { MsgItem, MsgTarget, MsgType } from "@/defs/API/Message";
import { parseTime } from "@/util/parseTime";
import { differenceInMilliseconds, addMinutes } from "date-fns/esm";
import { computed, onMounted, ref, toRef, watch, type ComputedRef, type Ref } from "vue"; import { computed, onMounted, ref, toRef, watch, type ComputedRef, type Ref } from "vue";
import linkifyStr from "linkify-string"; import linkifyStr from "linkify-string";
import { SammoAPI } from "@/SammoAPI"; import { SammoAPI } from "@/SammoAPI";
@@ -205,11 +203,10 @@ function testDeletable(msg: MsgItem): boolean {
if (msg.option.invalid) return false; if (msg.option.invalid) return false;
if (!(msg.option.deletable ?? true)) return false; if (!(msg.option.deletable ?? true)) return false;
const now = new Date(); const timeDiff = msg.deleteRemainingMilliseconds;
const last5min = addMinutes(parseTime(msg.time), 5);
const timeDiff = differenceInMilliseconds(last5min, now);
if (timeDiff <= 0) return false; if (timeDiff <= 0) return false;
if (msg.clockMode === "manual") return true;
deletableTimer.value = window.setTimeout(() => { deletableTimer.value = window.setTimeout(() => {
deletable.value = testDeletable(msg); deletable.value = testDeletable(msg);
+27 -7
View File
@@ -4,7 +4,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { addMilliseconds } from "date-fns"; import { addMilliseconds } from "date-fns";
import { type PropType, ref, onMounted, watch } from "vue"; import { type PropType, ref, onMounted, onUnmounted, watch } from "vue";
import { formatTime } from "@/util/formatTime"; import { formatTime } from "@/util/formatTime";
const props = defineProps({ const props = defineProps({
serverTime: { serverTime: {
@@ -17,25 +17,39 @@ const props = defineProps({
required: false, required: false,
default: "HH:mm:ss", default: "HH:mm:ss",
}, },
running: {
type: Boolean,
default: true,
},
}); });
const timeDiff = ref(0); const timeDiff = ref(0);
const serverNow = ref(""); const serverNow = ref("");
watch( watch(
() => props.serverTime, () => [props.serverTime, props.running] as const,
(newValue) => { ([newValue]) => {
const clientNow = new Date(); const clientNow = new Date();
timeDiff.value = newValue.getTime() - clientNow.getTime(); timeDiff.value = newValue.getTime() - clientNow.getTime();
updateNow();
} }
); );
let timer: ReturnType<typeof setTimeout> | undefined;
function updateNow() { function updateNow() {
const serverNowObj = addMilliseconds(new Date(), timeDiff.value); if (timer !== undefined) {
clearTimeout(timer);
timer = undefined;
}
const serverNowObj = props.running
? addMilliseconds(new Date(), timeDiff.value)
: props.serverTime;
serverNow.value = formatTime(serverNowObj, props.timeFormat); serverNow.value = formatTime(serverNowObj, props.timeFormat);
setTimeout(() => { if (props.running) {
updateNow(); timer = setTimeout(() => {
}, 1000 - serverNowObj.getMilliseconds()); updateNow();
}, 1000 - serverNowObj.getMilliseconds());
}
} }
onMounted(() => { onMounted(() => {
@@ -43,4 +57,10 @@ onMounted(() => {
timeDiff.value = props.serverTime.getTime() - clientNow.getTime(); timeDiff.value = props.serverTime.getTime() - clientNow.getTime();
updateNow(); updateNow();
}); });
onUnmounted(() => {
if (timer !== undefined) {
clearTimeout(timer);
}
});
</script> </script>
+1
View File
@@ -7,6 +7,7 @@ export type ReservedCommandResponse = {
year: number; year: number;
month: number; month: number;
date: string; date: string;
clockMode: "realtime" | "manual";
turn: TurnObj[]; turn: TurnObj[];
autorun_limit: null | number; autorun_limit: null | number;
}; };

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