fix: complete logical clock wall-time isolation
This commit is contained in:
@@ -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,6 @@ switch ($btn) {
|
|||||||
pushGlobalHistoryLog(["<R>★</><S>{$log}</>"]);
|
pushGlobalHistoryLog(["<R>★</><S>{$log}</>"]);
|
||||||
break;
|
break;
|
||||||
case "변경1":
|
case "변경1":
|
||||||
$clock = GameClock::fromStorage($gameStor);
|
|
||||||
$gameStor->clock_base_time = TimeUtil::format(GameClock::baseTimeForProjection(
|
$gameStor->clock_base_time = TimeUtil::format(GameClock::baseTimeForProjection(
|
||||||
new \DateTimeImmutable($starttime),
|
new \DateTimeImmutable($starttime),
|
||||||
Util::toInt($gameStor->starttime),
|
Util::toInt($gameStor->starttime),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
+2
-1
@@ -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) {
|
||||||
|
|||||||
+7
-3
@@ -1087,7 +1087,7 @@ function updateTraffic()
|
|||||||
'month' => $admin['month'],
|
'month' => $admin['month'],
|
||||||
'refresh' => $admin['refresh'],
|
'refresh' => $admin['refresh'],
|
||||||
'online' => $online,
|
'online' => $online,
|
||||||
'date' => TimeUtil::now(),
|
'date' => GameClock::fromStorage($gameStor)->formatNow(),
|
||||||
];
|
];
|
||||||
$gameStor->recentTraffic = $recentTraffic;
|
$gameStor->recentTraffic = $recentTraffic;
|
||||||
|
|
||||||
@@ -1376,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')) {
|
||||||
@@ -1787,9 +1789,11 @@ function deleteNation(General $lord, bool $applyDB): array
|
|||||||
$db->delete('troop', 'nation=%i', $nationID);
|
$db->delete('troop', 'nation=%i', $nationID);
|
||||||
|
|
||||||
// 국가 삭제
|
// 국가 삭제
|
||||||
|
$gameDate = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
|
||||||
$db->insert('ng_old_nations', [
|
$db->insert('ng_old_nations', [
|
||||||
'server_id' => UniqueConst::$serverID,
|
'server_id' => UniqueConst::$serverID,
|
||||||
'nation' => $nationID,
|
'nation' => $nationID,
|
||||||
|
'date' => $gameDate,
|
||||||
'data' => Json::encode($nation)
|
'data' => Json::encode($nation)
|
||||||
]);
|
]);
|
||||||
$db->delete('nation', 'nation=%i', $nationID);
|
$db->delete('nation', 'nation=%i', $nationID);
|
||||||
|
|||||||
@@ -840,10 +840,12 @@ function checkEmperior()
|
|||||||
|
|
||||||
storeOldGenerals(0, $admin['year'], $admin['month']);
|
storeOldGenerals(0, $admin['year'], $admin['month']);
|
||||||
storeOldGenerals($nation['nation'], $admin['year'], $admin['month']);
|
storeOldGenerals($nation['nation'], $admin['year'], $admin['month']);
|
||||||
|
$gameDate = GameClock::fromStorage($gameStor)->formatNow();
|
||||||
|
|
||||||
$db->insert('ng_old_nations', [
|
$db->insert('ng_old_nations', [
|
||||||
'server_id' => UniqueConst::$serverID,
|
'server_id' => UniqueConst::$serverID,
|
||||||
'nation' => $nation['nation'],
|
'nation' => $nation['nation'],
|
||||||
|
'date' => $gameDate,
|
||||||
'data' => Json::encode($nation)
|
'data' => Json::encode($nation)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -851,6 +853,7 @@ function checkEmperior()
|
|||||||
$db->insert('ng_old_nations', [
|
$db->insert('ng_old_nations', [
|
||||||
'server_id' => UniqueConst::$serverID,
|
'server_id' => UniqueConst::$serverID,
|
||||||
'nation' => 0,
|
'nation' => 0,
|
||||||
|
'date' => $gameDate,
|
||||||
'data' => Json::encode([
|
'data' => Json::encode([
|
||||||
'nation' => 0,
|
'nation' => 0,
|
||||||
'name' => '재야',
|
'name' => '재야',
|
||||||
|
|||||||
+2
-1
@@ -7,6 +7,7 @@ include "func.php";
|
|||||||
Session::requireLogin()->loginGame()->setReadOnly();
|
Session::requireLogin()->loginGame()->setReadOnly();
|
||||||
|
|
||||||
$mapName = GameConst::$mapName;
|
$mapName = GameConst::$mapName;
|
||||||
|
$frontClock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -28,7 +29,7 @@ $mapName = GameConst::$mapName;
|
|||||||
|
|
||||||
'maxTurn' => GameConst::$maxTurn,
|
'maxTurn' => GameConst::$maxTurn,
|
||||||
'maxPushTurn' => 12,
|
'maxPushTurn' => 12,
|
||||||
'serverNow' => TimeUtil::now(false),
|
'serverNow' => $frontClock->formatTick($frontClock->nowTick()),
|
||||||
]
|
]
|
||||||
], false) ?>
|
], false) ?>
|
||||||
<?= WebUtil::printJS('../d_shared/common_path.js') ?>
|
<?= WebUtil::printJS('../d_shared/common_path.js') ?>
|
||||||
|
|||||||
+11
-8
@@ -25,20 +25,23 @@ if(!$reserved){
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$reservedDate = new \DateTime($reserved['date']);
|
$reservedDate = new \DateTimeImmutable($reserved['date']);
|
||||||
$now = new \DateTime();
|
$now = GameClock::readWallTime();
|
||||||
|
|
||||||
|
|
||||||
$status = 'not_yet';
|
$status = 'not_yet';
|
||||||
|
|
||||||
list($isUnited, $lastTurn) = $gameStor->getValuesAsArray(['isunited', 'turntime']);
|
list($isUnited, $unitedWallAnchor) = $gameStor->getValuesAsArray(['isunited', 'autoreset_united_wall_anchor']);
|
||||||
if($isUnited === null || $lastTurn === null){
|
if($isUnited === null){
|
||||||
$isUnited = 2;
|
$isUnited = 2;
|
||||||
$lastTurn = '2000-01-01';
|
|
||||||
}
|
}
|
||||||
|
$lastTurn = null;
|
||||||
if($lastTurn !== null){
|
if($isUnited > 0){
|
||||||
$lastTurn = new \DateTime($lastTurn);
|
if(!is_string($unitedWallAnchor) || $unitedWallAnchor === ''){
|
||||||
|
$unitedWallAnchor = TimeUtil::format($now, true);
|
||||||
|
$gameStor->autoreset_united_wall_anchor = $unitedWallAnchor;
|
||||||
|
}
|
||||||
|
$lastTurn = new \DateTimeImmutable($unitedWallAnchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($lastTurn === null){
|
if($lastTurn === null){
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -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'])){
|
||||||
|
|||||||
@@ -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){
|
||||||
|
|||||||
@@ -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'] = [
|
||||||
|
|||||||
@@ -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, '이');
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ if($token && !$refresh){
|
|||||||
'pick'=>Json::decode($token['pick_result']),
|
'pick'=>Json::decode($token['pick_result']),
|
||||||
'pickMoreFrom'=>$clock->formatTick($pickMoreFrom),
|
'pickMoreFrom'=>$clock->formatTick($pickMoreFrom),
|
||||||
'pickMoreSeconds'=>intdiv($pickMoreFrom - $now, $clock->ticksPerSecond()),
|
'pickMoreSeconds'=>intdiv($pickMoreFrom - $now, $clock->ticksPerSecond()),
|
||||||
'validUntil'=>$clock->formatTick(Util::toInt($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(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,5 +173,7 @@ Json::die([
|
|||||||
'pick'=>$pickResult,
|
'pick'=>$pickResult,
|
||||||
'pickMoreFrom'=>$clock->formatTick(($inserted===-1)?$pickMoreFrom:$now),
|
'pickMoreFrom'=>$clock->formatTick(($inserted===-1)?$pickMoreFrom:$now),
|
||||||
'pickMoreSeconds'=>($inserted===-1)?$pickMoreSecond:0,
|
'pickMoreSeconds'=>($inserted===-1)?$pickMoreSecond:0,
|
||||||
'validUntil'=>$clock->formatTick($validUntil)
|
'validUntil'=>$clock->formatTick($validUntil),
|
||||||
|
'validForSeconds'=>max(0, intdiv($validUntil - $now, $clock->ticksPerSecond())),
|
||||||
|
'clockMode'=>$clock->getMode(),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ if($tokens){
|
|||||||
Json::die([
|
Json::die([
|
||||||
'result'=>true,
|
'result'=>true,
|
||||||
'pick'=>$pick,
|
'pick'=>$pick,
|
||||||
'validUntil'=>$clock->formatTick(Util::toInt($valid_until))
|
'validUntil'=>$clock->formatTick(Util::toInt($valid_until)),
|
||||||
|
'validForSeconds'=>max(0, intdiv(Util::toInt($valid_until) - $now, $clock->ticksPerSecond())),
|
||||||
|
'clockMode'=>$clock->getMode(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,5 +108,7 @@ sortTokens($pick);//좀 무식하지만..
|
|||||||
Json::die([
|
Json::die([
|
||||||
'result'=>true,
|
'result'=>true,
|
||||||
'pick'=>$pick,
|
'pick'=>$pick,
|
||||||
'validUntil'=>$valid_until === null ? null : $clock->formatTick(Util::toInt($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(),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -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'),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ $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['starttime'] = substr($clock->formatTick(Util::toInt($admin['opentime'])), 5, 11);
|
$admin['starttime'] = substr($clock->formatTick(Util::toInt($admin['opentime'])), 5, 11);
|
||||||
$admin['turntime'] = substr($clock->formatTick(Util::toInt($admin['turntime'])), 5, 11);
|
$admin['turntime'] = substr($clock->formatTick(Util::toInt($admin['turntime'])), 5, 11);
|
||||||
unset($admin['npcmode']);
|
unset($admin['npcmode']);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use sammo\GameConst;
|
|||||||
use sammo\GameClock;
|
use sammo\GameClock;
|
||||||
use sammo\Json;
|
use sammo\Json;
|
||||||
use sammo\KVStorage;
|
use sammo\KVStorage;
|
||||||
use sammo\TimeUtil;
|
|
||||||
use sammo\Util;
|
use sammo\Util;
|
||||||
|
|
||||||
use function sammo\cutTurn;
|
use function sammo\cutTurn;
|
||||||
@@ -33,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;
|
||||||
@@ -85,11 +85,12 @@ class GetReservedCommand extends \sammo\BaseAPI
|
|||||||
return [
|
return [
|
||||||
'result' => true,
|
'result' => true,
|
||||||
'turnTimeTick' => Util::toInt($turnTime),
|
'turnTimeTick' => Util::toInt($turnTime),
|
||||||
'turnTime' => GameClock::fromStorage($gameStor)->formatTick(Util::toInt($turnTime)),
|
'turnTime' => $clock->formatTick(Util::toInt($turnTime)),
|
||||||
'turnTerm' => $turnTerm,
|
'turnTerm' => $turnTerm,
|
||||||
'year' => $year,
|
'year' => $year,
|
||||||
'month' => $month,
|
'month' => $month,
|
||||||
'date' => GameClock::fromStorage($gameStor)->formatTick(GameClock::fromStorage($gameStor)->nowTick(), 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,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ 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;
|
||||||
@@ -185,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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'),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,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'],
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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;
|
||||||
@@ -40,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(
|
||||||
@@ -158,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),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 [
|
||||||
|
|||||||
@@ -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', [
|
||||||
|
|||||||
@@ -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 '설문조사가 종료되었습니다.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -3727,7 +3727,7 @@ class GeneralAI
|
|||||||
$src,
|
$src,
|
||||||
$src,
|
$src,
|
||||||
$general->getVar('npcmsg'),
|
$general->getVar('npcmsg'),
|
||||||
new \DateTime(),
|
Message::gameNow(),
|
||||||
new \DateTime('9999-12-31'),
|
new \DateTime('9999-12-31'),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|||||||
+24
-5
@@ -26,6 +26,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 +84,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,7 +113,9 @@ 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(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,7 +463,7 @@ class Message
|
|||||||
$src,
|
$src,
|
||||||
$dest,
|
$dest,
|
||||||
$msg,
|
$msg,
|
||||||
new \DateTime(),
|
self::gameNow(),
|
||||||
new \DateTime('9999-12-31'),
|
new \DateTime('9999-12-31'),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@@ -480,6 +497,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;
|
||||||
}
|
}
|
||||||
@@ -487,7 +506,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)){
|
||||||
@@ -505,8 +525,7 @@ class Message
|
|||||||
'text' => $this->msg,
|
'text' => $this->msg,
|
||||||
'option' => $this->msgOption
|
'option' => $this->msgOption
|
||||||
]),
|
]),
|
||||||
'valid_until'=>GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))
|
'valid_until'=>$validUntilTick,
|
||||||
->dateTimeToTick($this->validUntil),
|
|
||||||
], 'id=%i', $this->id);
|
], 'id=%i', $this->id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -156,7 +156,7 @@ class ScoutMessage extends Message
|
|||||||
$this->src,
|
$this->src,
|
||||||
$this->dest,
|
$this->dest,
|
||||||
"{$this->src->nationName}{$josaRo} 등용 제의 거부",
|
"{$this->src->nationName}{$josaRo} 등용 제의 거부",
|
||||||
new \DateTime(),
|
Message::gameNow(),
|
||||||
new \DateTime('9999-12-31'),
|
new \DateTime('9999-12-31'),
|
||||||
[
|
[
|
||||||
'delete' => $this->id
|
'delete' => $this->id
|
||||||
@@ -206,7 +206,7 @@ class ScoutMessage extends Message
|
|||||||
$srcGeneral = $db->queryFirstRow('SELECT `name`, nation FROM general WHERE `no`=%i', $srcGeneralID);
|
$srcGeneral = $db->queryFirstRow('SELECT `name`, nation FROM general WHERE `no`=%i', $srcGeneralID);
|
||||||
$destGeneral = $db->queryFirstRow('SELECT `name`, nation, `officer_level` FROM general WHERE `no`=%i', $destGeneralID);
|
$destGeneral = $db->queryFirstRow('SELECT `name`, nation, `officer_level` FROM general WHERE `no`=%i', $destGeneralID);
|
||||||
if ($date === null) {
|
if ($date === null) {
|
||||||
$date = new \DateTime();
|
$date = Message::gameNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($destGeneral['officer_level'] == 12) {
|
if ($destGeneral['officer_level'] == 12) {
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ class TurnExecutionHelper
|
|||||||
$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()
|
||||||
{
|
{
|
||||||
$this->applyDB();
|
$this->applyDB();
|
||||||
@@ -452,7 +457,10 @@ class TurnExecutionHelper
|
|||||||
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;
|
||||||
@@ -499,7 +507,13 @@ class TurnExecutionHelper
|
|||||||
|
|
||||||
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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//토너먼트 처리
|
//토너먼트 처리
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
+2
-2
@@ -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',
|
||||||
|
|||||||
@@ -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
@@ -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>();
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export type MsgItem = {
|
|||||||
delete?: number;
|
delete?: number;
|
||||||
};
|
};
|
||||||
time: string;
|
time: string;
|
||||||
|
deleteRemainingMilliseconds: number;
|
||||||
|
clockMode: "realtime" | "manual";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MsgPrintItem = MsgItem & {
|
export type MsgPrintItem = MsgItem & {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type ChiefResponse = {
|
|||||||
month: number;
|
month: number;
|
||||||
turnTerm: number;
|
turnTerm: number;
|
||||||
date: string;
|
date: string;
|
||||||
|
clockMode: "realtime" | "manual";
|
||||||
chiefList: Record<
|
chiefList: Record<
|
||||||
number,
|
number,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,4 +30,5 @@ export type VoteDetailResult = ValidResponse & {
|
|||||||
comments: VoteComment[],
|
comments: VoteComment[],
|
||||||
myVote: null|number[],
|
myVote: null|number[],
|
||||||
userCnt: number,
|
userCnt: number,
|
||||||
|
isOpen: boolean,
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ import axios from 'axios';
|
|||||||
import { initTooltip } from "@/legacy/initTooltip";
|
import { initTooltip } from "@/legacy/initTooltip";
|
||||||
import { TemplateEngine } from '@util/TemplateEngine';
|
import { TemplateEngine } from '@util/TemplateEngine';
|
||||||
import type { InvalidResponse } from '@/defs';
|
import type { InvalidResponse } from '@/defs';
|
||||||
import { getDateTimeNow } from '@util/getDateTimeNow';
|
|
||||||
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
|
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
|
||||||
import { loadPlugin as loadAdminPlugin } from '@/gateway/admin_server';
|
import { loadPlugin as loadAdminPlugin } from '@/gateway/admin_server';
|
||||||
import '@/gateway/common';
|
import '@/gateway/common';
|
||||||
@@ -108,6 +107,7 @@ type ReservedGameInfo = {
|
|||||||
|
|
||||||
type GameInfo = {
|
type GameInfo = {
|
||||||
isUnited: number,
|
isUnited: number,
|
||||||
|
isOpen: boolean,
|
||||||
npcMode: '불가' | '가능' | '선택 생성',
|
npcMode: '불가' | '가능' | '선택 생성',
|
||||||
year: number,
|
year: number,
|
||||||
month: number,
|
month: number,
|
||||||
@@ -175,7 +175,6 @@ async function Entrance_UpdateServer() {
|
|||||||
|
|
||||||
async function Entrance_drawServerList(serverInfos: ServerResponseItem[]) {
|
async function Entrance_drawServerList(serverInfos: ServerResponseItem[]) {
|
||||||
const $serverList = $('#server_list');
|
const $serverList = $('#server_list');
|
||||||
const now = getDateTimeNow();
|
|
||||||
|
|
||||||
const serverDetailInfoP: Record<string, Promise<ServerDetailResponse>> = {};
|
const serverDetailInfoP: Record<string, Promise<ServerDetailResponse>> = {};
|
||||||
|
|
||||||
@@ -239,7 +238,7 @@ async function Entrance_drawServerList(serverInfos: ServerResponseItem[]) {
|
|||||||
} else if (game.isUnited == 2) {
|
} else if (game.isUnited == 2) {
|
||||||
$serverHtml.find('.n_country').html('§천하통일§');
|
$serverHtml.find('.n_country').html('§천하통일§');
|
||||||
$serverHtml.find('.server_date').html(`${game.starttime} <br>~ ${game.turntime}`);
|
$serverHtml.find('.server_date').html(`${game.starttime} <br>~ ${game.turntime}`);
|
||||||
} else if (game.opentime <= now) {
|
} else if (game.isOpen) {
|
||||||
$serverHtml.find('.n_country').html(`<${game.nationCnt}국 경쟁중>`);
|
$serverHtml.find('.n_country').html(`<${game.nationCnt}국 경쟁중>`);
|
||||||
$serverHtml.find('.server_date').html(`${game.starttime} ~`);
|
$serverHtml.find('.server_date').html(`${game.starttime} ~`);
|
||||||
} else {
|
} else {
|
||||||
@@ -247,7 +246,7 @@ async function Entrance_drawServerList(serverInfos: ServerResponseItem[]) {
|
|||||||
$serverHtml.find('.server_date').html(`${game.starttime} ~`);
|
$serverHtml.find('.server_date').html(`${game.starttime} ~`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.opentime <= now) {
|
if (game.isOpen) {
|
||||||
$serverHtml.append(
|
$serverHtml.append(
|
||||||
TemplateEngine(serverTextInfo, game)
|
TemplateEngine(serverTextInfo, game)
|
||||||
);
|
);
|
||||||
|
|||||||
+8
-13
@@ -1,15 +1,11 @@
|
|||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import type { InvalidResponse } from '@/defs';
|
import type { InvalidResponse } from '@/defs';
|
||||||
import { getDateTimeNow } from '@util/getDateTimeNow';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { convertFormData } from '@util/convertFormData';
|
import { convertFormData } from '@util/convertFormData';
|
||||||
import { isBrightColor } from "@util/isBrightColor";
|
import { isBrightColor } from "@util/isBrightColor";
|
||||||
import { unwrap } from '@util/unwrap';
|
import { unwrap } from '@util/unwrap';
|
||||||
import _, { isError, isString } from 'lodash-es';
|
import _, { isError, isString } from 'lodash-es';
|
||||||
import { addMinutes } from 'date-fns';
|
|
||||||
import { parseTime } from '@util/parseTime';
|
|
||||||
import { formatTime } from '@util/formatTime';
|
|
||||||
import { TemplateEngine } from '@util/TemplateEngine';
|
import { TemplateEngine } from '@util/TemplateEngine';
|
||||||
import { isNotNull } from '@util/isNotNull';
|
import { isNotNull } from '@util/isNotNull';
|
||||||
import { unwrap_any } from '@util/unwrap_any';
|
import { unwrap_any } from '@util/unwrap_any';
|
||||||
@@ -32,8 +28,8 @@ const messageTemplate = `<div
|
|||||||
</div>
|
</div>
|
||||||
<div class="msg_body">
|
<div class="msg_body">
|
||||||
<div class="msg_header">
|
<div class="msg_header">
|
||||||
<%if(!this.option.action && src.id == myGeneralID && now <= last5min && invalidType == 'msg_valid' && !deletable){%>
|
<%if(!this.option.action && src.id == myGeneralID && deleteRemainingMilliseconds > 0 && invalidType == 'msg_valid' && !deletable){%>
|
||||||
<button type="button" data-erase_until="<%last5min%>" class="btn btn btn-outline-warning btn-sm btn-delete-msg" style='float:right'>❌</button>
|
<button type="button" data-erase_until="<%eraseUntil%>" class="btn btn btn-outline-warning btn-sm btn-delete-msg" style='float:right'>❌</button>
|
||||||
<%}%>
|
<%}%>
|
||||||
<%if(msgType == 'private') {%>
|
<%if(msgType == 'private') {%>
|
||||||
<%if(src.name == generalName){%>
|
<%if(src.name == generalName){%>
|
||||||
@@ -101,8 +97,7 @@ type MsgPrintItem = MsgItem & {
|
|||||||
nationType: 'local' | 'src' | 'dest';
|
nationType: 'local' | 'src' | 'dest';
|
||||||
myGeneralID: number;
|
myGeneralID: number;
|
||||||
allowButton: boolean;
|
allowButton: boolean;
|
||||||
last5min: string;
|
eraseUntil: number;
|
||||||
now: string;
|
|
||||||
invalidType: 'msg_invalid' | 'msg_valid';
|
invalidType: 'msg_invalid' | 'msg_valid';
|
||||||
deletable: boolean;
|
deletable: boolean;
|
||||||
src: MsgTarget & { colorType: 'bright' | 'dark' },
|
src: MsgTarget & { colorType: 'bright' | 'dark' },
|
||||||
@@ -217,7 +212,7 @@ async function showOldMsg(msgType: MsgType): Promise<MsgResponse> {
|
|||||||
function redrawMsg(msgResponse: MsgResponse, addFront: boolean): MsgResponse {
|
function redrawMsg(msgResponse: MsgResponse, addFront: boolean): MsgResponse {
|
||||||
function checkErasable(obj: MsgResponse) {
|
function checkErasable(obj: MsgResponse) {
|
||||||
|
|
||||||
const now = getDateTimeNow();
|
const now = Date.now();
|
||||||
$('.btn-delete-msg').each(function () {
|
$('.btn-delete-msg').each(function () {
|
||||||
const $btn = $(this);
|
const $btn = $(this);
|
||||||
const eraseUntil = $btn.data('erase_until');
|
const eraseUntil = $btn.data('erase_until');
|
||||||
@@ -257,7 +252,6 @@ function redrawMsg(msgResponse: MsgResponse, addFront: boolean): MsgResponse {
|
|||||||
|
|
||||||
let needRefreshLastContact = (msgType == 'private');
|
let needRefreshLastContact = (msgType == 'private');
|
||||||
|
|
||||||
const now = getDateTimeNow();
|
|
||||||
//list의 맨 앞이 가장 최신 메시지임.
|
//list의 맨 앞이 가장 최신 메시지임.
|
||||||
const $msgs: JQuery<HTMLElement>[] = msgSource.map(function (msg) {
|
const $msgs: JQuery<HTMLElement>[] = msgSource.map(function (msg) {
|
||||||
|
|
||||||
@@ -311,7 +305,9 @@ function redrawMsg(msgResponse: MsgResponse, addFront: boolean): MsgResponse {
|
|||||||
allowButton = true;
|
allowButton = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const last5min = formatTime(addMinutes(parseTime(msg.time), 5));
|
const eraseUntil = msg.clockMode === "manual"
|
||||||
|
? Number.MAX_SAFE_INTEGER
|
||||||
|
: Date.now() + msg.deleteRemainingMilliseconds;
|
||||||
let invalidType: MsgPrintItem['invalidType'];
|
let invalidType: MsgPrintItem['invalidType'];
|
||||||
if (msg.option && msg.option.invalid) {
|
if (msg.option && msg.option.invalid) {
|
||||||
invalidType = 'msg_invalid';
|
invalidType = 'msg_invalid';
|
||||||
@@ -336,9 +332,8 @@ function redrawMsg(msgResponse: MsgResponse, addFront: boolean): MsgResponse {
|
|||||||
myGeneralID: unwrap(myGeneralID),
|
myGeneralID: unwrap(myGeneralID),
|
||||||
src,
|
src,
|
||||||
dest,
|
dest,
|
||||||
now,
|
|
||||||
allowButton,
|
allowButton,
|
||||||
last5min,
|
eraseUntil,
|
||||||
invalidType,
|
invalidType,
|
||||||
deletable,
|
deletable,
|
||||||
defaultIcon,
|
defaultIcon,
|
||||||
|
|||||||
@@ -43,8 +43,12 @@ type GeneralPoolResponse = {
|
|||||||
result: true,
|
result: true,
|
||||||
pick: CardItem[],
|
pick: CardItem[],
|
||||||
validUntil: string,
|
validUntil: string,
|
||||||
|
validForSeconds: number,
|
||||||
|
clockMode: "realtime" | "manual",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let logicalClockRunning = true;
|
||||||
|
|
||||||
declare const characterInfo: Record<string, { name: string, info: string }>;
|
declare const characterInfo: Record<string, { name: string, info: string }>;
|
||||||
declare const hasGeneralID: number;
|
declare const hasGeneralID: number;
|
||||||
declare const isCentennialAllStar: boolean;
|
declare const isCentennialAllStar: boolean;
|
||||||
@@ -186,6 +190,9 @@ async function buildGeneral(e: JQuery.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateOutdateTimer() {
|
function updateOutdateTimer() {
|
||||||
|
if (!logicalClockRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const $validUntilText = $('#valid_until_text');
|
const $validUntilText = $('#valid_until_text');
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const validUntil = $validUntilText.data('until');
|
const validUntil = $validUntilText.data('until');
|
||||||
@@ -205,9 +212,10 @@ function updateOutdateTimer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function printGenerals(value: GeneralPoolResponse) {
|
function printGenerals(value: GeneralPoolResponse) {
|
||||||
|
logicalClockRunning = value.clockMode === "realtime";
|
||||||
$('.card_holder').empty();
|
$('.card_holder').empty();
|
||||||
$('#valid_until').show();
|
$('#valid_until').show();
|
||||||
$('#valid_until_text').html(value.validUntil).data('until', (new Date(value.validUntil)).getTime()).css('color', 'white');
|
$('#valid_until_text').html(value.validUntil).data('until', Date.now() + value.validForSeconds * 1000).css('color', 'white');
|
||||||
$('#outdate_token').hide();
|
$('#outdate_token').hide();
|
||||||
|
|
||||||
const pick = value.pick.map(v => v);//XXX: 의도가 뭐였지? clone?
|
const pick = value.pick.map(v => v);//XXX: 의도가 뭐였지? clone?
|
||||||
|
|||||||
+17
-2
@@ -70,8 +70,12 @@ type NPCToken = {
|
|||||||
pickMoreFrom: string,
|
pickMoreFrom: string,
|
||||||
pickMoreSeconds: number,
|
pickMoreSeconds: number,
|
||||||
validUntil: string,
|
validUntil: string,
|
||||||
|
validForSeconds: number,
|
||||||
|
clockMode: "realtime" | "manual",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let logicalClockRunning = true;
|
||||||
|
|
||||||
const templateGeneralCard =
|
const templateGeneralCard =
|
||||||
'<div class="general_card">\
|
'<div class="general_card">\
|
||||||
<h4 class="bg1 with_border"><%name%></h4>\
|
<h4 class="bg1 with_border"><%name%></h4>\
|
||||||
@@ -147,6 +151,9 @@ async function pickGeneral(this: HTMLElement, e: JQuery.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateOutdateTimer() {
|
function updateOutdateTimer() {
|
||||||
|
if (!logicalClockRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const $validUntilText = $('#valid_until_text');
|
const $validUntilText = $('#valid_until_text');
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const validUntil = $validUntilText.data('until');
|
const validUntil = $validUntilText.data('until');
|
||||||
@@ -168,6 +175,13 @@ function updateOutdateTimer() {
|
|||||||
function updatePickMoreTimer() {
|
function updatePickMoreTimer() {
|
||||||
const $btn = $('#btn_pick_more');
|
const $btn = $('#btn_pick_more');
|
||||||
|
|
||||||
|
if (!logicalClockRunning) {
|
||||||
|
const remain = Number($btn.data('remaining'));
|
||||||
|
$btn.prop('disabled', remain > 0);
|
||||||
|
$btn.html(remain > 0 ? '다른 장수 보기(논리 시계 대기)' : '다른 장수 보기');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const remain = ($btn.data('available') - now) / 1000;
|
const remain = ($btn.data('available') - now) / 1000;
|
||||||
if (remain <= 0) {
|
if (remain <= 0) {
|
||||||
@@ -182,12 +196,13 @@ function updatePickMoreTimer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function printGenerals(value: NPCToken) {
|
function printGenerals(value: NPCToken) {
|
||||||
|
logicalClockRunning = value.clockMode === "realtime";
|
||||||
$('.card_holder').empty();
|
$('.card_holder').empty();
|
||||||
$('#valid_until').show();
|
$('#valid_until').show();
|
||||||
$('#valid_until_text').html(value.validUntil).data('until', (new Date(value.validUntil)).getTime()).css('color', 'white');
|
$('#valid_until_text').html(value.validUntil).data('until', Date.now() + value.validForSeconds * 1000).css('color', 'white');
|
||||||
$('#outdate_token').hide();
|
$('#outdate_token').hide();
|
||||||
const time = Date.now() + value.pickMoreSeconds * 1000;
|
const time = Date.now() + value.pickMoreSeconds * 1000;
|
||||||
$('#btn_pick_more').data('available', time).prop('disabled', true);
|
$('#btn_pick_more').data('available', time).data('remaining', value.pickMoreSeconds).prop('disabled', true);
|
||||||
|
|
||||||
const pick = $.map(value.pick, function (value) {
|
const pick = $.map(value.pick, function (value) {
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ anchor에 고정하므로 표시 시각이 튀지 않습니다.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
php scripts/verify-game-clock-engine.php --apply --engine-calls=2
|
php scripts/verify-game-clock-engine.php --apply --engine-calls=2
|
||||||
|
php scripts/verify-game-clock-engine.php --apply --until-unification --max-months=2400
|
||||||
```
|
```
|
||||||
|
|
||||||
이 검증기는 manual mode만 허용하고, 엔진 호출 전후 clock tick이 벽시계 때문에
|
이 검증기는 manual mode만 허용하고, 엔진 호출 전후 clock tick이 벽시계 때문에
|
||||||
변하지 않았는지와 마지막 처리 tick이 현재 tick을 넘지 않았는지 검사합니다.
|
변하지 않았는지와 마지막 처리 tick이 현재 tick을 넘지 않았는지 검사합니다.
|
||||||
|
`--until-unification`은 달력이나 DB 시각을 고쳐 쓰지 않고, 매월 manual clock만
|
||||||
|
정확히 다음 turn tick으로 옮긴 뒤 실제 `TurnExecutionHelper`를 반복 호출합니다.
|
||||||
|
각 호출에서 시계 고정과 처리 tick 상한을 재검사하고 `isunited=2|3`이 되지 않으면
|
||||||
|
성공으로 취급하지 않습니다. 반드시 격리 복제 DB에서 실행하세요.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use sammo\DB;
|
use sammo\DB;
|
||||||
|
use sammo\DTO\VoteInfo;
|
||||||
use sammo\GameClock;
|
use sammo\GameClock;
|
||||||
use sammo\Json;
|
use sammo\Json;
|
||||||
use sammo\KVStorage;
|
use sammo\KVStorage;
|
||||||
@@ -269,6 +270,30 @@ try {
|
|||||||
'last천도Trial',
|
'last천도Trial',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
foreach ($db->query(
|
||||||
|
'SELECT `key`, value FROM storage WHERE namespace = %s AND `key` LIKE %s AND `key` NOT LIKE %s',
|
||||||
|
'vote',
|
||||||
|
'vote\_%',
|
||||||
|
'%\_wall\_backup',
|
||||||
|
) as $row) {
|
||||||
|
$rawVote = Json::decode((string)$row['value']);
|
||||||
|
if (!is_array($rawVote)) {
|
||||||
|
throw new RuntimeException("{$row['key']} vote 저장값이 객체가 아닙니다.");
|
||||||
|
}
|
||||||
|
$db->insertUpdate('storage', [
|
||||||
|
'namespace' => 'vote',
|
||||||
|
'key' => "{$row['key']}_wall_backup",
|
||||||
|
'value' => Json::encode($rawVote),
|
||||||
|
]);
|
||||||
|
$db->update('storage', [
|
||||||
|
'value' => Json::encode(VoteInfo::normalizeGameStorage($rawVote, $conversionClock)),
|
||||||
|
], 'namespace = %s AND `key` = %s', 'vote', $row['key']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Historical/display DATETIME columns remain dates, but inserts must always
|
||||||
|
// receive a GameClock-projected value rather than silently reading MariaDB time.
|
||||||
|
$db->query('ALTER TABLE ng_old_nations MODIFY `date` DATETIME NOT NULL');
|
||||||
|
$db->query('ALTER TABLE ng_diplomacy MODIFY `date` DATETIME NOT NULL');
|
||||||
|
|
||||||
$db->query(
|
$db->query(
|
||||||
'ALTER TABLE general '
|
'ALTER TABLE general '
|
||||||
|
|||||||
@@ -19,16 +19,25 @@ $_SERVER['REQUEST_URI'] ??= '/cli/verify-game-clock-engine';
|
|||||||
|
|
||||||
require dirname(__DIR__) . '/hwe/lib.php';
|
require dirname(__DIR__) . '/hwe/lib.php';
|
||||||
require dirname(__DIR__) . '/hwe/func.php';
|
require dirname(__DIR__) . '/hwe/func.php';
|
||||||
|
while (ob_get_level() > 0) {
|
||||||
|
ob_end_flush();
|
||||||
|
}
|
||||||
|
ob_implicit_flush(true);
|
||||||
|
|
||||||
$options = getopt('', ['apply', 'engine-calls:']);
|
$options = getopt('', ['apply', 'engine-calls:', 'until-unification', 'max-months:']);
|
||||||
if (!isset($options['apply'])) {
|
if (!isset($options['apply'])) {
|
||||||
fwrite(STDERR, "Usage: php scripts/verify-game-clock-engine.php --apply [--engine-calls=N]\n");
|
fwrite(STDERR, "Usage: php scripts/verify-game-clock-engine.php --apply [--engine-calls=N] [--until-unification --max-months=N]\n");
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
$engineCalls = filter_var($options['engine-calls'] ?? '1', FILTER_VALIDATE_INT);
|
$untilUnification = isset($options['until-unification']);
|
||||||
|
$engineCalls = filter_var($options['engine-calls'] ?? ($untilUnification ? '100' : '1'), FILTER_VALIDATE_INT);
|
||||||
if ($engineCalls === false || $engineCalls < 1 || $engineCalls > 1000) {
|
if ($engineCalls === false || $engineCalls < 1 || $engineCalls > 1000) {
|
||||||
throw new InvalidArgumentException('--engine-calls는 1..1000이어야 합니다.');
|
throw new InvalidArgumentException('--engine-calls는 1..1000이어야 합니다.');
|
||||||
}
|
}
|
||||||
|
$maxMonths = filter_var($options['max-months'] ?? '2400', FILTER_VALIDATE_INT);
|
||||||
|
if ($maxMonths === false || $maxMonths < 1 || $maxMonths > 10000) {
|
||||||
|
throw new InvalidArgumentException('--max-months는 1..10000이어야 합니다.');
|
||||||
|
}
|
||||||
|
|
||||||
$db = DB::db();
|
$db = DB::db();
|
||||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||||
@@ -39,6 +48,112 @@ if ($clock->getMode() !== GameClock::MODE_MANUAL) {
|
|||||||
|
|
||||||
$fixedNowTick = $clock->nowTick();
|
$fixedNowTick = $clock->nowTick();
|
||||||
$before = $gameStor->getValues(['year', 'month', 'turntime']);
|
$before = $gameStor->getValues(['year', 'month', 'turntime']);
|
||||||
|
|
||||||
|
if ($untilUnification) {
|
||||||
|
$startedAt = GameClock::readWallTime();
|
||||||
|
$startProjection = $clock->formatTick($fixedNowTick, true);
|
||||||
|
$advancedMonths = 0;
|
||||||
|
$totalEngineCalls = 0;
|
||||||
|
while ($advancedMonths < $maxMonths) {
|
||||||
|
$gameStor->resetCache();
|
||||||
|
$state = $gameStor->getValues(['year', 'month', 'turntime', 'turnterm', 'isunited']);
|
||||||
|
if (in_array(Util::toInt($state['isunited']), [2, 3], true)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$beforeYearMonth = Util::joinYearMonth(Util::toInt($state['year']), Util::toInt($state['month']));
|
||||||
|
$clock = GameClock::fromStorage($gameStor);
|
||||||
|
$nextMonthBoundary = $clock->addTurns(
|
||||||
|
\sammo\cutTurn(Util::toInt($state['turntime']), Util::toInt($state['turnterm'])),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
$nextMonthTick = GameClock::addTicks($nextMonthBoundary, 1);
|
||||||
|
|
||||||
|
if (!\sammo\tryLock()) {
|
||||||
|
throw new RuntimeException('manual clock 전진을 위한 GAME lock을 획득하지 못했습니다.');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$clock->persistTick($gameStor, $nextMonthTick, GameClock::MODE_MANUAL);
|
||||||
|
} finally {
|
||||||
|
$gameStor->resetCache();
|
||||||
|
\sammo\unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
$monthAdvanced = false;
|
||||||
|
for ($call = 0; $call < $engineCalls; $call++) {
|
||||||
|
$executed = false;
|
||||||
|
$locked = false;
|
||||||
|
TurnExecutionHelper::executeAllCommand($executed, $locked);
|
||||||
|
$totalEngineCalls++;
|
||||||
|
$gameStor->resetCache();
|
||||||
|
$clock = GameClock::fromStorage($gameStor);
|
||||||
|
if ($clock->getMode() !== GameClock::MODE_MANUAL || $clock->nowTick() !== $nextMonthTick) {
|
||||||
|
throw new RuntimeException('실제 턴 엔진 실행 중 manual clock 상태가 벽시계에 의해 바뀌었습니다.');
|
||||||
|
}
|
||||||
|
$afterCall = $gameStor->getValues(['year', 'month', 'turntime', 'isunited']);
|
||||||
|
if (Util::toInt($afterCall['turntime']) > $nextMonthTick) {
|
||||||
|
throw new RuntimeException('마지막 실행 tick이 현재 manual clock tick을 넘어갔습니다.');
|
||||||
|
}
|
||||||
|
if (in_array(Util::toInt($afterCall['isunited']), [2, 3], true)) {
|
||||||
|
$monthAdvanced = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// turntime is the authoritative completed schedule boundary. Some
|
||||||
|
// legacy monthly state is cached until the next storage read, so do
|
||||||
|
// not spin merely because year/month from that same call is stale.
|
||||||
|
if (Util::toInt($afterCall['turntime']) >= $nextMonthBoundary) {
|
||||||
|
$monthAdvanced = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$afterYearMonth = Util::joinYearMonth(Util::toInt($afterCall['year']), Util::toInt($afterCall['month']));
|
||||||
|
if ($afterYearMonth !== $beforeYearMonth) {
|
||||||
|
$monthAdvanced = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($locked) {
|
||||||
|
throw new RuntimeException('통일 전 실제 턴 엔진이 GAME lock 또는 동결 상태에 머물렀습니다.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$monthAdvanced) {
|
||||||
|
throw new RuntimeException("한 달을 {$engineCalls}회 엔진 호출 안에 완료하지 못했습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$advancedMonths++;
|
||||||
|
if ($advancedMonths % 12 === 0) {
|
||||||
|
$gameStor->resetCache();
|
||||||
|
printf(
|
||||||
|
"progress months=%d game=%d-%02d clock_tick=%d nations=%d engine_calls=%d\n",
|
||||||
|
$advancedMonths,
|
||||||
|
Util::toInt($gameStor->year),
|
||||||
|
Util::toInt($gameStor->month),
|
||||||
|
GameClock::fromStorage($gameStor)->nowTick(),
|
||||||
|
Util::toInt($db->queryFirstField('SELECT COUNT(*) FROM nation WHERE level > 0')),
|
||||||
|
$totalEngineCalls,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$gameStor->resetCache();
|
||||||
|
$finalState = $gameStor->getValues(['year', 'month', 'turntime', 'isunited']);
|
||||||
|
if (!in_array(Util::toInt($finalState['isunited']), [2, 3], true)) {
|
||||||
|
throw new RuntimeException("{$maxMonths}개월 안에 천하통일에 도달하지 못했습니다.");
|
||||||
|
}
|
||||||
|
$finalClock = GameClock::fromStorage($gameStor);
|
||||||
|
printf(
|
||||||
|
"UNIFIED months=%d engine_calls=%d game=%d-%02d isunited=%d clock_tick=%d projected=%s start_projected=%s wall_elapsed=%.6f\n",
|
||||||
|
$advancedMonths,
|
||||||
|
$totalEngineCalls,
|
||||||
|
Util::toInt($finalState['year']),
|
||||||
|
Util::toInt($finalState['month']),
|
||||||
|
Util::toInt($finalState['isunited']),
|
||||||
|
$finalClock->nowTick(),
|
||||||
|
$finalClock->formatNow(true),
|
||||||
|
$startProjection,
|
||||||
|
(float)GameClock::readWallTime()->format('U.u') - (float)$startedAt->format('U.u'),
|
||||||
|
);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
$executedCount = 0;
|
$executedCount = 0;
|
||||||
for ($call = 0; $call < $engineCalls; $call++) {
|
for ($call = 0; $call < $engineCalls; $call++) {
|
||||||
$executed = false;
|
$executed = false;
|
||||||
|
|||||||
+11
-1
@@ -117,6 +117,11 @@ final class GameClock
|
|||||||
return self::addTicks($this->anchorTick, $this->ticksBetween($this->wallAnchor, $this->wallNow()));
|
return self::addTicks($this->anchorTick, $this->ticksBetween($this->wallAnchor, $this->wallNow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function nowDateTime(): \DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->tickToDateTime($this->nowTick());
|
||||||
|
}
|
||||||
|
|
||||||
public function ticksFromSeconds(int|float $seconds): int
|
public function ticksFromSeconds(int|float $seconds): int
|
||||||
{
|
{
|
||||||
if (is_int($seconds)) {
|
if (is_int($seconds)) {
|
||||||
@@ -207,6 +212,11 @@ final class GameClock
|
|||||||
return TimeUtil::format($this->tickToDateTime($tick), $withFraction);
|
return TimeUtil::format($this->tickToDateTime($tick), $withFraction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function formatNow(bool $withFraction = false): string
|
||||||
|
{
|
||||||
|
return $this->formatTick($this->nowTick(), $withFraction);
|
||||||
|
}
|
||||||
|
|
||||||
public static function baseTimeForProjection(
|
public static function baseTimeForProjection(
|
||||||
\DateTimeInterface $projectedTime,
|
\DateTimeInterface $projectedTime,
|
||||||
int $tick,
|
int $tick,
|
||||||
@@ -249,7 +259,7 @@ final class GameClock
|
|||||||
|
|
||||||
public function advance(KVStorage $gameStor, int $deltaTick): int
|
public function advance(KVStorage $gameStor, int $deltaTick): int
|
||||||
{
|
{
|
||||||
$nextTick = $this->nowTick() + $deltaTick;
|
$nextTick = self::addTicks($this->nowTick(), $deltaTick);
|
||||||
$this->persistTick($gameStor, $nextTick);
|
$this->persistTick($gameStor, $nextTick);
|
||||||
return $nextTick;
|
return $nextTick;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-16
@@ -239,13 +239,16 @@ class Session
|
|||||||
$loginDate = $this->get($serverID.static::GAME_KEY_DATE);
|
$loginDate = $this->get($serverID.static::GAME_KEY_DATE);
|
||||||
$generalID = $this->get($serverID.static::GAME_KEY_GENERAL_ID);
|
$generalID = $this->get($serverID.static::GAME_KEY_GENERAL_ID);
|
||||||
$generalName = $this->get($serverID.static::GAME_KEY_GENERAL_NAME);
|
$generalName = $this->get($serverID.static::GAME_KEY_GENERAL_NAME);
|
||||||
$deadTime = $this->get($serverID.static::GAME_KEY_EXPECTED_DEADTIME);
|
$deadTick = $this->get($serverID.static::GAME_KEY_EXPECTED_DEADTIME);
|
||||||
|
|
||||||
$now = time();
|
$wallNow = time();
|
||||||
|
$db = DB::db();
|
||||||
|
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||||
|
$gameNowTick = GameClock::fromStorage($gameStor)->nowTick();
|
||||||
if (
|
if (
|
||||||
$globalLoginDate < $loginDate &&
|
$globalLoginDate < $loginDate &&
|
||||||
$generalID && $generalName && $loginDate && $deadTime
|
$generalID && $generalName && $loginDate && $deadTick
|
||||||
&& $loginDate + 1800 > $now && $deadTime > $now
|
&& $loginDate + 1800 > $wallNow && $deadTick > $gameNowTick
|
||||||
) {
|
) {
|
||||||
//로그인 정보는 30분간 유지한다.
|
//로그인 정보는 30분간 유지한다.
|
||||||
if ($result !== null) {
|
if ($result !== null) {
|
||||||
@@ -254,13 +257,10 @@ class Session
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($generalID || $generalName || $loginDate || $deadTime) {
|
if ($generalID || $generalName || $loginDate || $deadTick) {
|
||||||
$this->logoutGame();
|
$this->logoutGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
$db = DB::db();
|
|
||||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
|
||||||
|
|
||||||
$general = $db->queryFirstRow(
|
$general = $db->queryFirstRow(
|
||||||
'SELECT `no`, `name`, `killturn`, `turntime` from general where `owner` = %i',
|
'SELECT `no`, `name`, `killturn`, `turntime` from general where `owner` = %i',
|
||||||
$userID
|
$userID
|
||||||
@@ -272,16 +272,15 @@ class Session
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
$turnterm = $gameStor->turnterm;
|
|
||||||
$isUnited = $gameStor->isunited != 0;
|
$isUnited = $gameStor->isunited != 0;
|
||||||
|
|
||||||
$generalID = $general['no'];
|
$generalID = $general['no'];
|
||||||
$generalName = $general['name'];
|
$generalName = $general['name'];
|
||||||
$nextTurn = new \DateTime($general['turntime']);
|
$deadTick = GameClock::addTicks(
|
||||||
$nextTurn = $nextTurn->getTimestamp();
|
Util::toInt($general['turntime']),
|
||||||
|
Util::toInt($general['killturn']) * GameClock::TICKS_PER_TURN,
|
||||||
$deadTime = $nextTurn + $general['killturn'] * $turnterm;
|
);
|
||||||
if ($deadTime < $now && !$isUnited) {
|
if ($deadTick < $gameNowTick && !$isUnited) {
|
||||||
$locked = $db->queryFirstField('SELECT plock FROM plock WHERE `type` = "GAME" LIMIT 1');
|
$locked = $db->queryFirstField('SELECT plock FROM plock WHERE `type` = "GAME" LIMIT 1');
|
||||||
if (!$locked) {
|
if (!$locked) {
|
||||||
if ($result !== null) {
|
if ($result !== null) {
|
||||||
@@ -291,10 +290,10 @@ class Session
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->set($serverID.static::GAME_KEY_DATE, $now);
|
$this->set($serverID.static::GAME_KEY_DATE, $wallNow);
|
||||||
$this->set($serverID.static::GAME_KEY_GENERAL_ID, $generalID);
|
$this->set($serverID.static::GAME_KEY_GENERAL_ID, $generalID);
|
||||||
$this->set($serverID.static::GAME_KEY_GENERAL_NAME, $generalName);
|
$this->set($serverID.static::GAME_KEY_GENERAL_NAME, $generalName);
|
||||||
$this->set($serverID.static::GAME_KEY_EXPECTED_DEADTIME, $deadTime);
|
$this->set($serverID.static::GAME_KEY_EXPECTED_DEADTIME, $deadTick);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ final class GameClockBoundaryTest extends TestCase
|
|||||||
'/\bNOW\s*\(/i',
|
'/\bNOW\s*\(/i',
|
||||||
'/\bCURRENT_TIMESTAMP\b/i',
|
'/\bCURRENT_TIMESTAMP\b/i',
|
||||||
'/\bCURDATE\s*\(/i',
|
'/\bCURDATE\s*\(/i',
|
||||||
|
'/\btime\s*\(/i',
|
||||||
] as $pattern) {
|
] as $pattern) {
|
||||||
self::assertDoesNotMatchRegularExpression($pattern, $source, $relativePath);
|
self::assertDoesNotMatchRegularExpression($pattern, $source, $relativePath);
|
||||||
}
|
}
|
||||||
@@ -25,7 +26,7 @@ final class GameClockBoundaryTest extends TestCase
|
|||||||
|
|
||||||
public static function gameSchedulingFiles(): array
|
public static function gameSchedulingFiles(): array
|
||||||
{
|
{
|
||||||
return array_map(static fn (string $path): array => [$path], [
|
$paths = [
|
||||||
'hwe/sammo/TurnExecutionHelper.php',
|
'hwe/sammo/TurnExecutionHelper.php',
|
||||||
'hwe/sammo/Auction.php',
|
'hwe/sammo/Auction.php',
|
||||||
'hwe/sammo/AuctionBasicResource.php',
|
'hwe/sammo/AuctionBasicResource.php',
|
||||||
@@ -36,7 +37,43 @@ final class GameClockBoundaryTest extends TestCase
|
|||||||
'hwe/sammo/AbsFromUserPool.php',
|
'hwe/sammo/AbsFromUserPool.php',
|
||||||
'hwe/sammo/GeneralPool/RandomNameGeneral.php',
|
'hwe/sammo/GeneralPool/RandomNameGeneral.php',
|
||||||
'hwe/sammo/API/General/DieOnPrestart.php',
|
'hwe/sammo/API/General/DieOnPrestart.php',
|
||||||
]);
|
'hwe/sammo/Message.php',
|
||||||
|
'hwe/sammo/DiplomaticMessage.php',
|
||||||
|
'hwe/sammo/ScoutMessage.php',
|
||||||
|
'hwe/sammo/RaiseInvaderMessage.php',
|
||||||
|
'hwe/sammo/GeneralAI.php',
|
||||||
|
'hwe/sammo/API/Vote/NewVote.php',
|
||||||
|
'hwe/sammo/API/Vote/Vote.php',
|
||||||
|
'hwe/sammo/API/Vote/GetVoteList.php',
|
||||||
|
'hwe/sammo/API/Vote/GetVoteDetail.php',
|
||||||
|
'hwe/sammo/API/Vote/AddComment.php',
|
||||||
|
'hwe/sammo/API/Nation/SetNotice.php',
|
||||||
|
'hwe/j_get_select_npc_token.php',
|
||||||
|
'hwe/j_get_select_pool.php',
|
||||||
|
'hwe/j_set_npc_control.php',
|
||||||
|
'hwe/j_board_article_add.php',
|
||||||
|
'hwe/j_board_comment_add.php',
|
||||||
|
'hwe/a_traffic.php',
|
||||||
|
'hwe/j_server_basic_info.php',
|
||||||
|
'hwe/j_diplomacy_send_letter.php',
|
||||||
|
'hwe/j_diplomacy_respond_letter.php',
|
||||||
|
'hwe/j_diplomacy_destroy_letter.php',
|
||||||
|
'hwe/j_diplomacy_rollback_letter.php',
|
||||||
|
];
|
||||||
|
foreach (['hwe/sammo/Command', 'hwe/sammo/Event'] as $relativeDirectory) {
|
||||||
|
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
|
||||||
|
__DIR__ . '/../' . $relativeDirectory,
|
||||||
|
\FilesystemIterator::SKIP_DOTS,
|
||||||
|
));
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if ($file->isFile() && $file->getExtension() === 'php') {
|
||||||
|
$paths[] = $relativeDirectory . '/' . $iterator->getSubPathName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$paths = array_values(array_unique($paths));
|
||||||
|
sort($paths);
|
||||||
|
return array_map(static fn (string $path): array => [$path], $paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTickSchemaDoesNotUseDatabaseDefaultsForGameSchedules(): void
|
public function testTickSchemaDoesNotUseDatabaseDefaultsForGameSchedules(): void
|
||||||
@@ -55,5 +92,55 @@ final class GameClockBoundaryTest extends TestCase
|
|||||||
] as $expected) {
|
] as $expected) {
|
||||||
self::assertStringContainsString($expected, $schema);
|
self::assertStringContainsString($expected, $schema);
|
||||||
}
|
}
|
||||||
|
self::assertDoesNotMatchRegularExpression('/\b(?:CURRENT_TIMESTAMP|NOW\s*\()/i', $schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testMonthlyTrafficTimestampUsesLogicalClock(): void
|
||||||
|
{
|
||||||
|
$source = file_get_contents(__DIR__ . '/../hwe/func.php');
|
||||||
|
self::assertIsString($source);
|
||||||
|
self::assertMatchesRegularExpression(
|
||||||
|
'/function updateTraffic\(\).*?GameClock::fromStorage\(\$gameStor\)->formatNow\(\).*?function CheckOverhead\(/s',
|
||||||
|
$source,
|
||||||
|
);
|
||||||
|
self::assertDoesNotMatchRegularExpression(
|
||||||
|
'/function updateTraffic\(\).*?TimeUtil::now\(.*?function CheckOverhead\(/s',
|
||||||
|
$source,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGameLoginDeathCheckUsesTicksWhileSessionTtlRemainsOperational(): void
|
||||||
|
{
|
||||||
|
$source = file_get_contents(__DIR__ . '/../src/sammo/Session.php');
|
||||||
|
self::assertIsString($source);
|
||||||
|
self::assertMatchesRegularExpression(
|
||||||
|
'/function loginGame\(.*?GameClock::fromStorage\(\$gameStor\)->nowTick\(\).*?GameClock::TICKS_PER_TURN.*?function logoutGame\(/s',
|
||||||
|
$source,
|
||||||
|
);
|
||||||
|
self::assertDoesNotMatchRegularExpression(
|
||||||
|
'/function loginGame\(.*?new\s+\\?DateTime(?:Immutable)?\([^)]*turntime.*?function logoutGame\(/s',
|
||||||
|
$source,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBrowserDoesNotCompareProjectedGameDatesToItsWallClock(): void
|
||||||
|
{
|
||||||
|
$expectations = [
|
||||||
|
'hwe/ts/PageVote.vue' => ['currentVote.value.isOpen'],
|
||||||
|
'hwe/ts/components/MessagePlate.vue' => ['msg.clockMode === "manual"'],
|
||||||
|
'hwe/ts/gateway/entrance.ts' => ['game.isOpen'],
|
||||||
|
'hwe/ts/select_npc.ts' => ['logicalClockRunning'],
|
||||||
|
'hwe/ts/select_general_from_pool.ts' => ['logicalClockRunning'],
|
||||||
|
];
|
||||||
|
foreach ($expectations as $path => $needles) {
|
||||||
|
$source = file_get_contents(__DIR__ . '/../' . $path);
|
||||||
|
self::assertIsString($source);
|
||||||
|
foreach ($needles as $needle) {
|
||||||
|
self::assertStringContainsString($needle, $source, $path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self::assertStringNotContainsString('formatTime(new Date())', file_get_contents(__DIR__ . '/../hwe/ts/PageVote.vue'));
|
||||||
|
self::assertStringNotContainsString('game.opentime <= now', file_get_contents(__DIR__ . '/../hwe/ts/gateway/entrance.ts'));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace sammo;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
require_once __DIR__ . '/../src/sammo/GameClock.php';
|
require_once __DIR__ . '/../src/sammo/GameClock.php';
|
||||||
|
require_once __DIR__ . '/../hwe/sammo/TurnExecutionHelper.php';
|
||||||
|
|
||||||
final class GameClockTest extends TestCase
|
final class GameClockTest extends TestCase
|
||||||
{
|
{
|
||||||
@@ -58,6 +59,7 @@ final class GameClockTest extends TestCase
|
|||||||
);
|
);
|
||||||
|
|
||||||
self::assertSame(123_456, $clock->nowTick());
|
self::assertSame(123_456, $clock->nowTick());
|
||||||
|
self::assertSame($clock->formatTick(123_456), $clock->formatNow());
|
||||||
self::assertFalse($wallRead);
|
self::assertFalse($wallRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,4 +98,10 @@ final class GameClockTest extends TestCase
|
|||||||
$this->expectException(\OverflowException::class);
|
$this->expectException(\OverflowException::class);
|
||||||
GameClock::addTicks(GameClock::MAX_SAFE_TICK, 1);
|
GameClock::addTicks(GameClock::MAX_SAFE_TICK, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGlobalCompletionTickIsMonotonicAcrossSubTickExecution(): void
|
||||||
|
{
|
||||||
|
self::assertSame(36_000_000, TurnExecutionHelper::monotonicCompletionTick(36_000_000, 35_500_000));
|
||||||
|
self::assertSame(36_500_000, TurnExecutionHelper::monotonicCompletionTick(36_000_000, 36_500_000));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace sammo;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use sammo\DTO\VoteInfo;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../src/sammo/GameClock.php';
|
||||||
|
require_once __DIR__ . '/../hwe/sammo/DTO/VoteInfo.php';
|
||||||
|
|
||||||
|
final class VoteGameClockTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testLegacyDatesAreConvertedToStableTicksAndProjectedAgain(): void
|
||||||
|
{
|
||||||
|
$base = new \DateTimeImmutable('2035-01-01 00:00:00.000000');
|
||||||
|
$clock = new GameClock($base, 60, 0, GameClock::MODE_MANUAL, $base);
|
||||||
|
$raw = [
|
||||||
|
'id' => 7,
|
||||||
|
'title' => '논리 시계 투표',
|
||||||
|
'multipleOptions' => 1,
|
||||||
|
'opener' => 'SYSTEM',
|
||||||
|
'startDate' => '2035-01-01 01:00:00',
|
||||||
|
'endDate' => '2035-01-01 03:00:00',
|
||||||
|
'options' => ['찬성', '반대'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$stored = VoteInfo::normalizeGameStorage($raw, $clock);
|
||||||
|
|
||||||
|
self::assertSame(GameClock::TICKS_PER_TURN, $stored['startTick']);
|
||||||
|
self::assertSame(GameClock::TICKS_PER_TURN * 3, $stored['endTick']);
|
||||||
|
self::assertSame('2035-01-01 01:00:00', $stored['startDate']);
|
||||||
|
self::assertSame('2035-01-01 03:00:00', $stored['endDate']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStoredTicksRemainAuthoritativeWhenProjectionBaseChanges(): void
|
||||||
|
{
|
||||||
|
$oldBase = new \DateTimeImmutable('2035-01-01 00:00:00');
|
||||||
|
$newBase = new \DateTimeImmutable('2040-05-01 12:00:00');
|
||||||
|
$clock = new GameClock($newBase, 60, 0, GameClock::MODE_MANUAL, $newBase);
|
||||||
|
$stored = [
|
||||||
|
'id' => 8,
|
||||||
|
'title' => 'tick 우선',
|
||||||
|
'multipleOptions' => 1,
|
||||||
|
'opener' => null,
|
||||||
|
'startDate' => $oldBase->format('Y-m-d H:i:s'),
|
||||||
|
'endDate' => $oldBase->modify('+1 hour')->format('Y-m-d H:i:s'),
|
||||||
|
'startTick' => GameClock::TICKS_PER_TURN * 2,
|
||||||
|
'endTick' => GameClock::TICKS_PER_TURN * 4,
|
||||||
|
'options' => ['A'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$normalized = VoteInfo::normalizeGameStorage($stored, $clock);
|
||||||
|
self::assertSame('2040-05-01 14:00:00', $normalized['startDate']);
|
||||||
|
self::assertSame('2040-05-01 16:00:00', $normalized['endDate']);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user