merge: refresh ng_compare for logical game clock

This commit is contained in:
2026-08-04 12:09:58 +00:00
124 changed files with 2713 additions and 807 deletions
+2 -2
View File
@@ -21,7 +21,7 @@ class BidUniqueAuction extends \sammo\BaseAPI
])
->rule('int', 'amount')
->rule('int', 'auctionID')
->rule('boolean', 'extendCloseDate');
->rule('boolean', 'extendCloseTick');
if (!$v->validate()) {
return $v->errorStr();
@@ -38,7 +38,7 @@ class BidUniqueAuction extends \sammo\BaseAPI
{
$auctionID = $this->args['auctionID'];
$amount = $this->args['amount'];
$tryExtendCloseDate = $this->args['extendCloseDate'] ?? false;
$tryExtendCloseDate = $this->args['extendCloseTick'] ?? false;
$generalID = $session->generalID;
$general = General::createObjFromDB($generalID);
@@ -15,6 +15,8 @@ use sammo\General;
use sammo\Json;
use sammo\TimeUtil;
use sammo\Util;
use sammo\GameClock;
use sammo\KVStorage;
use function sammo\getAuctionLogRecent;
@@ -33,12 +35,13 @@ class GetActiveResourceAuctionList extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$buyRiceList = [];
$sellRiceList = [];
/** @var AuctionInfo[] */
$auctions = array_map(fn ($raw) => AuctionInfo::fromArray($raw), $db->query(
'SELECT * FROM `ng_auction` WHERE `type` IN %ls AND `finished` = 0 ORDER BY `close_date` ASC',
'SELECT * FROM `ng_auction` WHERE `type` IN %ls AND `finished` = 0 ORDER BY `close_tick` ASC',
[
AuctionType::BuyRice->value,
AuctionType::SellRice->value,
@@ -87,8 +90,8 @@ class GetActiveResourceAuctionList extends \sammo\BaseAPI
'type' => $auction->type->value,
'hostGeneralID' => $auction->hostGeneralID,
'hostName' => $auction->detail->hostName,
'openDate' => TimeUtil::format($auction->openDate, false),
'closeDate' => TimeUtil::format($auction->closeDate, false),
'openDate' => $clock->formatTick($auction->openTick),
'closeDate' => $clock->formatTick($auction->closeTick),
'amount' => $auction->detail->amount,
'startBidAmount' => $auction->detail->startBidAmount,
'finishBidAmount' => $auction->detail->finishBidAmount,
@@ -14,6 +14,8 @@ use sammo\Enums\GeneralQueryMode;
use sammo\Enums\InheritanceKey;
use sammo\InheritancePointManager;
use sammo\TimeUtil;
use sammo\GameClock;
use sammo\KVStorage;
use sammo\Validator;
use sammo\General;
@@ -42,6 +44,7 @@ class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$generalID = $session->generalID;
$auctionID = $this->args['auctionID'];
@@ -92,9 +95,11 @@ class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
'target' => $auction->target,
'isCallerHost' => $auction->hostGeneralID === $generalID,
'hostName' => $auction->detail->hostName,
'closeDate' => TimeUtil::format($auction->closeDate, false),
'closeDate' => $clock->formatTick($auction->closeTick),
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
'availableLatestBidCloseDate' => TimeUtil::format($auction->detail->availableLatestBidCloseDate, false),
'availableLatestBidCloseDate' => $auction->detail->availableLatestBidCloseTick === null
? null
: $clock->formatTick($auction->detail->availableLatestBidCloseTick),
],
'bidList' => $responseBid,
'obfuscatedName' => $obfuscatedName,
@@ -12,6 +12,8 @@ use sammo\Enums\APIRecoveryType;
use sammo\Enums\AuctionType;
use sammo\TimeUtil;
use sammo\Util;
use sammo\GameClock;
use sammo\KVStorage;
class GetUniqueItemAuctionList extends \sammo\BaseAPI
{
@@ -28,12 +30,13 @@ class GetUniqueItemAuctionList extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$generalID = $session->generalID;
/** @var AuctionInfo[] */
$auctions = array_map(fn($raw)=>AuctionInfo::fromArray($raw), $db->query(
'SELECT * FROM `ng_auction` WHERE `type` = %s ORDER BY `close_date` ASC',
'SELECT * FROM `ng_auction` WHERE `type` = %s ORDER BY `close_tick` ASC',
AuctionType::UniqueItem->value
) ?? []);
@@ -85,9 +88,11 @@ class GetUniqueItemAuctionList extends \sammo\BaseAPI
'target' => $auction->target,
'isCallerHost' => $auction->hostGeneralID === $generalID,
'hostName' => $auction->detail->hostName,
'closeDate' => TimeUtil::format($auction->closeDate, false),
'closeDate' => $clock->formatTick($auction->closeTick),
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
'availableLatestBidCloseDate' => TimeUtil::format($auction->detail->availableLatestBidCloseDate, false),
'availableLatestBidCloseDate' => $auction->detail->availableLatestBidCloseTick === null
? null
: $clock->formatTick($auction->detail->availableLatestBidCloseTick),
'highestBid' => [
'generalName' => $highestBid->aux->generalName,
'amount' => $highestBid->amount,
+7 -3
View File
@@ -7,9 +7,10 @@ use DateTimeInterface;
use sammo\DB;
use sammo\Enums\APIRecoveryType;
use sammo\GameConst;
use sammo\GameClock;
use sammo\Json;
use sammo\KVStorage;
use sammo\TimeUtil;
use sammo\Util;
use function sammo\cutTurn;
@@ -31,6 +32,7 @@ class GetReservedCommand extends \sammo\BaseAPI
$commandList = [];
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$generalID = $session->generalID;
$invalidTurnList = 0;
@@ -82,11 +84,13 @@ class GetReservedCommand extends \sammo\BaseAPI
return [
'result' => true,
'turnTime' => $turnTime,
'turnTimeTick' => Util::toInt($turnTime),
'turnTime' => $clock->formatTick(Util::toInt($turnTime)),
'turnTerm' => $turnTerm,
'year' => $year,
'month' => $month,
'date' => TimeUtil::now(true),
'date' => $clock->formatTick($clock->nowTick(), true),
'clockMode' => $clock->getMode(),
'turn' => $commandList,
'autorun_limit' => $generalAux['autorun_limit'] ?? null,
];
+8 -5
View File
@@ -11,7 +11,8 @@ use sammo\Session;
use sammo\General;
use sammo\JosaUtil;
use sammo\KVStorage;
use sammo\TimeUtil;
use sammo\GameClock;
use sammo\Util;
use function sammo\addTurn;
use function sammo\increaseRefresh;
@@ -37,6 +38,8 @@ class DieOnPrestart extends \sammo\BaseAPI
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['turnterm', 'opentime', 'turntime', 'year', 'month']);
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$general = $db->queryFirstRow('SELECT no,name,nation,owner_name,npc FROM general WHERE owner=%i AND npc = 0', $userID);
if (!$general) {
@@ -67,9 +70,9 @@ class DieOnPrestart extends \sammo\BaseAPI
}
$targetTime = $generalObj->getAuxVar('prestart_delete_after');
if (!is_string($targetTime) || $targetTime === '') {
if (!is_int($targetTime)) {
$targetTime = addTurn(
$lastRefresh ?: TimeUtil::now(),
$lastRefresh === null ? $nowTick : Util::toInt($lastRefresh),
$gameStor->turnterm,
GameConst::$minTurnDieOnPrestart
);
@@ -78,8 +81,8 @@ class DieOnPrestart extends \sammo\BaseAPI
}
//서버 가오픈시 할 수 있는 행동
if ($targetTime > TimeUtil::now()) {
$targetTimeShort = substr($targetTime, 0, 19);
if ($targetTime > $nowTick) {
$targetTimeShort = $clock->formatTick($targetTime);
return "아직 삭제할 수 없습니다. {$targetTimeShort} 부터 가능합니다.";
}
+18 -8
View File
@@ -14,13 +14,13 @@ use sammo\Enums\GeneralColumn;
use sammo\Enums\GeneralQueryMode;
use sammo\Enums\RankColumn;
use sammo\GameConst;
use sammo\GameClock;
use sammo\General;
use sammo\KVStorage;
use sammo\LastTurn;
use sammo\Validator;
use sammo\Session;
use sammo\TimeUtil;
use sammo\Util;
use function sammo\buildNationCommandClass;
@@ -105,6 +105,7 @@ class GetFrontInfo extends \sammo\BaseAPI
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$gameStor->cacheValues(['isunited', 'opentime', 'refresh']);
$lastHistoryID = $this->args['lastWorldHistoryID'];
@@ -158,6 +159,7 @@ class GetFrontInfo extends \sammo\BaseAPI
private function generateGlobalInfo(MeekroDB $db): array
{
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
[
$scenarioText, $extendedGeneral, $isFiction, $npcMode,
@@ -182,8 +184,9 @@ class GetFrontInfo extends \sammo\BaseAPI
$lastVote = null;
if ($lastVoteID) {
$voteStor = KVStorage::getStorage($db, 'vote');
$lastVote = VoteInfo::fromArray($voteStor->getValue("vote_{$lastVoteID}"));
if ($lastVote->endDate && $lastVote->endDate < TimeUtil::now()) {
$rawLastVote = VoteInfo::normalizeGameStorage($voteStor->getValue("vote_{$lastVoteID}"), $clock);
$lastVote = VoteInfo::fromGameStorage($rawLastVote, $clock);
if ($rawLastVote['endTick'] !== null && $rawLastVote['endTick'] < $clock->nowTick()) {
$lastVote = null;
}
}
@@ -210,7 +213,8 @@ class GetFrontInfo extends \sammo\BaseAPI
'month' => $month,
'autorunUser' => $autorunUser,
'turnterm' => $turnterm,
'lastExecuted' => $lastExecuted,
'lastExecutedTick' => $lastExecuted,
'lastExecuted' => $clock->formatTick(Util::toInt($lastExecuted), true),
'lastVoteID' => $lastVoteID,
'develCost' => $develCost,
'noticeMsg' => $noticeMsg,
@@ -224,7 +228,8 @@ class GetFrontInfo extends \sammo\BaseAPI
'isLocked' => $isLocked,
'tournamentType' => $tournamentType,
'tournamentState' => $tournamentState,
'tournamentTime' => $tournamentTime,
'tournamentTimeTick' => $tournamentTime,
'tournamentTime' => $tournamentTime === null ? null : $clock->formatTick(Util::toInt($tournamentTime)),
'genCount' => $globalGenCount,
'generalCntLimit' => $generalCntLimit,
'serverCnt' => $serverCnt,
@@ -361,6 +366,7 @@ class GetFrontInfo extends \sammo\BaseAPI
public function generateGeneralInfo(MeekroDB $db, General $general, array $rawNation): array
{
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$permission = checkSecretPermission($general->getRaw());
@@ -425,8 +431,12 @@ class GetFrontInfo extends \sammo\BaseAPI
'crew' => $general->getVar(GeneralColumn::crew), // number;
'train' => $general->getVar(GeneralColumn::train), // number;
'atmos' => $general->getVar(GeneralColumn::atmos), // number;
'turntime' => $general->getVar(GeneralColumn::turntime), // string;
'recent_war' => $general->getVar(GeneralColumn::recent_war), // string;
'turntimeTick' => $general->getTurnTick(), // number;
'turntime' => $general->getTurnTime(), // string;
'recent_war_tick' => $general->getVar(GeneralColumn::recent_war), // number|null;
'recent_war' => $general->getVar(GeneralColumn::recent_war) === null
? null
: $clock->formatTick(Util::toInt($general->getVar(GeneralColumn::recent_war)), true), // string|null;
'horse' => $general->getVar(GeneralColumn::horse), // GameObjClassKey;
'weapon' => $general->getVar(GeneralColumn::weapon), // GameObjClassKey;
'book' => $general->getVar(GeneralColumn::book), // GameObjClassKey;
@@ -544,7 +554,7 @@ class GetFrontInfo extends \sammo\BaseAPI
'result' => false,
'reason' => '접속 제한중입니다.',
'recovery' => APIRecoveryType::GameQuota,
'recovery_arg' => $general->getVar('turntime'),
'recovery_arg' => $general->getTurnTime(),
];
}
+8 -9
View File
@@ -11,6 +11,7 @@ use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralAccessLogColumn;
use sammo\Enums\RankColumn;
use sammo\GameConst;
use sammo\GameClock;
use sammo\GameUnitConst;
use sammo\General;
use sammo\InheritancePointManager;
@@ -165,6 +166,7 @@ class Join extends \sammo\BaseAPI
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['year', 'month', 'maxgeneral', 'scenario', 'show_img_level', 'block_general_create', 'turnterm', 'turntime', 'genius', 'npcmode']);
$clock = GameClock::fromStorage($gameStor);
########## 동일 정보 존재여부 확인. ##########
$block_general_create = $gameStor->getValue('block_general_create');
@@ -222,7 +224,7 @@ class Join extends \sammo\BaseAPI
$userLogger = new UserLogger($userID, $admin['year'], $admin['month'], false);
$now = TimeUtil::now(false);
$now = $clock->nowTick();
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'MakeGeneral',
@@ -360,17 +362,14 @@ class Join extends \sammo\BaseAPI
$userLogger->push(sprintf("턴 시간 %02d:%02d 로 지정", intdiv($inheritTurntime, 60), $inheritTurntime % 60), "inheritPoint");
$inheritTurntime += $rng->nextRangeInt(0, 999999) / 1000000;
$turntime = new \DateTimeImmutable(cutTurn($admin['turntime'], $admin['turnterm']));
$turntime = $turntime->add(TimeUtil::secondsToDateInterval($inheritTurntime));
$turntime = TimeUtil::format($turntime, true);
$inheritTurnMicrosecond = $rng->nextRangeInt(0, 999999);
$turntime = cutTurn(Util::toInt($admin['turntime']), $admin['turnterm'])
+ $clock->ticksFromSeconds($inheritTurntime)
+ intdiv($inheritTurnMicrosecond * $clock->ticksPerSecond(), 1_000_000);
} else {
$turntime = getRandTurn($rng, $admin['turnterm'], new \DateTimeImmutable($admin['turntime']));
$turntime = getRandTurn($rng, $admin['turnterm'], Util::toInt($admin['turntime']));
}
$now = TimeUtil::now(true);
if ($now >= $turntime) {
$turntime = addTurn($turntime, $admin['turnterm']);
}
+5 -1
View File
@@ -8,6 +8,8 @@ use DateTimeInterface;
use sammo\Enums\APIRecoveryType;
use sammo\TurnExecutionHelper;
use sammo\UniqueConst;
use sammo\GameClock;
use sammo\KVStorage;
class ExecuteEngine extends \sammo\BaseAPI
{
@@ -35,11 +37,13 @@ class ExecuteEngine extends \sammo\BaseAPI
$updated = false;
$locked = false;
$lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked);
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
return [
'result' => true,
'updated' => $updated,
'locked' => $locked,
'lastExecuted' => $lastExecuted,
'lastExecutedTick' => $lastExecuted,
'lastExecuted' => $clock->formatTick($lastExecuted, true),
];
}
}
+3 -1
View File
@@ -3,6 +3,7 @@
namespace sammo\API\Global;
use sammo\DB;
use sammo\GameClock;
use sammo\Enums\APIRecoveryType;
use sammo\Json;
use sammo\KVStorage;
@@ -145,7 +146,8 @@ class GeneralList extends \sammo\BaseAPI
if (static::$withToken) {
$now = (new \DateTimeImmutable())->format('Y-m-d H:i:s');
$gameStor = KVStorage::getStorage($db, 'game_env');
$now = GameClock::fromStorage($gameStor)->nowTick();
$tokens = [];
foreach ($db->query('SELECT * FROM select_npc_token WHERE `valid_until`>=%s', $now) as $token) {
$validUntil = $token['valid_until'];
@@ -10,7 +10,6 @@ use sammo\Enums\RankColumn;
use sammo\GameConst;
use sammo\General;
use sammo\KVStorage;
use sammo\TimeUtil;
use sammo\UserLogger;
class BuyRandomUnique extends \sammo\BaseAPI
@@ -56,7 +55,7 @@ class BuyRandomUnique extends \sammo\BaseAPI
$userLogger->push("{$reqAmount} 포인트로 랜덤 유니크 구입", "inheritPoint");
$userLogger->flush();
$general->setAuxVar('inheritRandomUnique', TimeUtil::now());
$general->setAuxVar('inheritRandomUnique', true);
$inheritStor->setValue('previous', [$previousPoint - $reqAmount, null]);
$general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $reqAmount);
$general->applyDB($db);
+2 -2
View File
@@ -116,7 +116,7 @@ class CheckOwner extends \sammo\BaseAPI
$src,
$dest,
"{$destGeneralName}의 소유자는 {$destGeneralOwnerName} 입니다.",
new \DateTime(),
Message::gameNow(),
new \DateTime('9999-12-31'),
[]
);
@@ -142,7 +142,7 @@ class CheckOwner extends \sammo\BaseAPI
$src,
$dest,
"소유자명이 누군가에 의해 확인되었습니다.",
new \DateTime(),
Message::gameNow(),
new \DateTime('9999-12-31'),
[]
);
@@ -64,13 +64,11 @@ class ResetTurnTime extends \sammo\BaseAPI
$gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm');
$currTurnTime = new DateTimeImmutable($general->getTurnTime());
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'ResetTurnTime',
$userID,
$general->getAuxVar('nextTurnTimeBase') ?? $general->getTurnTime()
$general->getAuxVar('nextTurnTimeBase') ?? $general->getTurnTick()
)));
$afterTurn = $rng->nextFloat1() * $turnTerm * 60;
+4 -4
View File
@@ -44,7 +44,7 @@ class SendMessage extends \sammo\BaseAPI
private function genPublicMessage(MessageTarget $src, string $text): Message
{
$now = new \DateTime();
$now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31');
$msg = new Message(
@@ -62,7 +62,7 @@ class SendMessage extends \sammo\BaseAPI
private function genNationalMessage(MessageTarget $src, string $text): Message
{
$now = new \DateTime();
$now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31');
$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
{
$now = new \DateTime();
$now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31');
$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
{
$now = new \DateTime();
$now = Message::gameNow();
$unlimited = new \DateTime('9999-12-31');
$db = DB::db();
+6 -2
View File
@@ -8,6 +8,7 @@ use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralLiteQueryMode;
use sammo\Enums\GeneralQueryMode;
use sammo\General;
use sammo\GameClock;
use sammo\GeneralLite;
use sammo\Session;
use sammo\Util;
@@ -264,8 +265,11 @@ class GeneralList extends \sammo\BaseAPI
'honorText' => fn ($rawGeneral) => getHonor($rawGeneral['experience']),
'dedLevelText' => fn ($rawGeneral) => getDedLevelText($rawGeneral['dedlevel']),
//'0000-00-00 11:23';
'turntime' => fn ($rawGeneral) => substr($rawGeneral['turntime'], 0, 19),
'recent_war' => fn ($rawGeneral) => substr($rawGeneral['recent_war'], 0, 19),
'turntime' => fn ($rawGeneral) => GameClock::fromStorage($gameStor)
->formatTick(Util::toInt($rawGeneral['turntime'])),
'recent_war' => fn ($rawGeneral) => $rawGeneral['recent_war'] === null
? null
: GameClock::fromStorage($gameStor)->formatTick(Util::toInt($rawGeneral['recent_war'])),
'bill' => fn ($rawGeneral) => getBillByLevel($rawGeneral['dedlevel']),
'reservedCommand' => fn ($rawGeneral) => $reservedCommand[$rawGeneral['no']] ?? null,
'autorun_limit' => fn ($rawGeneral) => ($rawGeneral['aux'] ?? [])['autorun_limit'] ?? 0,
+3 -2
View File
@@ -6,8 +6,8 @@ use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\Enums\APIRecoveryType;
use sammo\GameClock;
use sammo\KVStorage;
use sammo\TimeUtil;
use sammo\Validator;
use sammo\WebUtil;
@@ -51,8 +51,9 @@ class SetNotice extends \sammo\BaseAPI
$nationID = $me['nation'];
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
$gameNow = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
$nationStor->nationNotice = [
'date'=>TimeUtil::now(),
'date'=>$gameNow,
'msg'=>WebUtil::htmlPurify($msg),
'author'=>$me['name'],
'authorID'=>$me['no'],
@@ -8,10 +8,10 @@ use sammo\DB;
use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralQueryMode;
use sammo\GameConst;
use sammo\GameClock;
use sammo\General;
use sammo\Json;
use sammo\KVStorage;
use sammo\TimeUtil;
use sammo\Util;
use function sammo\checkLimit;
@@ -39,6 +39,7 @@ class GetReservedCommand extends \sammo\BaseAPI
increaseRefresh("사령부", 1);
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$userID = $session->userID;
$me = $db->queryFirstRow(
@@ -51,7 +52,8 @@ class GetReservedCommand extends \sammo\BaseAPI
$nationID = $me['nation'];
$limitState = checkLimit($me['refresh_score']);
if ($limitState >= 2) {
return "접속 제한중입니다. 1턴 이내에 너무 많은 갱신을 하셨습니다. (다음 갱신 가능 시각 : {$me['turntime']})";
$limitTime = GameClock::fromStorage($gameStor)->formatTick(Util::toInt($me['turntime']), true);
return "접속 제한중입니다. 1턴 이내에 너무 많은 갱신을 하셨습니다. (다음 갱신 가능 시각 : {$limitTime})";
}
$permission = checkSecretPermission($me);
@@ -156,7 +158,8 @@ class GetReservedCommand extends \sammo\BaseAPI
'year' => $year,
'month' => $month,
'turnTerm' => $turnTerm,
'date' => TimeUtil::now(true),
'date' => $clock->formatTick($clock->nowTick(), true),
'clockMode' => $clock->getMode(),
'chiefList' => $nationChiefList,
'troopList' => $troopList,
'isChief' => ($me['officer_level'] > 4),
+4 -3
View File
@@ -10,8 +10,9 @@ use sammo\Enums\GeneralLiteQueryMode;
use sammo\Enums\GeneralQueryMode;
use sammo\General;
use sammo\GeneralLite;
use sammo\GameClock;
use sammo\KVStorage;
use sammo\Session;
use sammo\TimeUtil;
use sammo\Validator;
class AddComment extends \sammo\BaseAPI
@@ -47,7 +48,8 @@ class AddComment extends \sammo\BaseAPI
$generalName = $general->getName();
$nationID = $general->getNationID();
$nationName = $general->getStaticNation()['name'];
$date = TimeUtil::now();
$db = DB::db();
$date = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->formatNow();
$comment = new VoteComment(
@@ -61,7 +63,6 @@ class AddComment extends \sammo\BaseAPI
date: $date
);
$db = DB::db();
$db->insert('vote_comment', $comment->toArray());
return null;
+6 -1
View File
@@ -8,6 +8,7 @@ use sammo\DB;
use sammo\DTO\VoteComment;
use sammo\DTO\VoteInfo;
use sammo\Enums\APIRecoveryType;
use sammo\GameClock;
use sammo\Json;
use sammo\KVStorage;
use sammo\Validator;
@@ -35,13 +36,16 @@ class GetVoteDetail extends \sammo\BaseAPI
{
$voteID = $this->args['voteID'];
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$voteStor = KVStorage::getStorage($db, 'vote');
$rawVote = $voteStor->getValue("vote_{$voteID}");
if (!$rawVote) {
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(
@@ -70,6 +74,7 @@ class GetVoteDetail extends \sammo\BaseAPI
'comments' => $comments,
'myVote' => $myVote,
'userCnt' => $userCnt,
'isOpen' => $isOpen,
];
}
}
+5 -3
View File
@@ -6,6 +6,7 @@ use DateTimeInterface;
use sammo\DB;
use sammo\DTO\VoteInfo;
use sammo\Enums\APIRecoveryType;
use sammo\GameClock;
use sammo\KVStorage;
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
{
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$voteStor = KVStorage::getStorage($db, 'vote');
$votes = [];
foreach($voteStor->getAll() as $voteKey => $rawVote){
if(!str_starts_with($voteKey, 'vote_')){
if(preg_match('/^vote_(\d+)$/D', $voteKey, $matches) !== 1){
continue;
}
$voteID = (int)substr($voteKey, 5);
$votes[$voteID] = VoteInfo::fromArray($rawVote);
$voteID = (int)$matches[1];
$votes[$voteID] = VoteInfo::fromGameStorage($rawVote, $clock);
}
return [
+19 -15
View File
@@ -9,7 +9,7 @@ use sammo\Enums\APIRecoveryType;
use sammo\KVStorage;
use sammo\RootDB;
use sammo\Session;
use sammo\TimeUtil;
use sammo\GameClock;
use sammo\Util;
use sammo\Validator;
@@ -37,7 +37,7 @@ class NewVote extends \sammo\BaseAPI
return null;
}
function closeOldVote(int $voteID, KVStorage $voteStor)
function closeOldVote(int $voteID, KVStorage $voteStor, GameClock $clock)
{
$db = DB::db();
$voteStor = KVStorage::getStorage($db, 'vote');
@@ -45,13 +45,14 @@ class NewVote extends \sammo\BaseAPI
if (!$rawLastVoteInfo) {
return;
}
$lastVoteInfo = VoteInfo::fromArray($rawLastVoteInfo);
if ($lastVoteInfo->endDate) {
$rawLastVoteInfo = VoteInfo::normalizeGameStorage($rawLastVoteInfo, $clock);
if ($rawLastVoteInfo['endTick'] !== null) {
return;
}
$lastVoteInfo->endDate = TimeUtil::now();
$voteStor->setValue("vote_{$voteID}", $lastVoteInfo->toArray());
$rawLastVoteInfo['endTick'] = $clock->nowTick();
$rawLastVoteInfo['endDate'] = $clock->formatTick($rawLastVoteInfo['endTick']);
$voteStor->setValue("vote_{$voteID}", $rawLastVoteInfo);
}
function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
@@ -71,7 +72,11 @@ class NewVote extends \sammo\BaseAPI
$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 */
$endDate = $this->args['endDate'] ?? null;
/** @var string[] */
@@ -83,9 +88,9 @@ class NewVote extends \sammo\BaseAPI
if($endDate !== null){
try{
$oNow = new \DateTimeImmutable($now);
$oEndDate = new \DateTimeImmutable($endDate);
if($oEndDate < $oNow){
$endTick = $clock->dateTimeToTick($oEndDate);
if($endTick < $nowTick){
return '종료일이 이미 지났습니다.';
}
}
@@ -96,17 +101,13 @@ class NewVote extends \sammo\BaseAPI
$userName = $session->userName;
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$lastVote = $gameStor->getValue('lastVote') ?? 0;
$voteID = $lastVote + 1;
$voteStor = KVStorage::getStorage($db, 'vote');
if (!($this->args['keepOldVote'] ?? false)) {
$this->closeOldVote($lastVote, $voteStor);
$this->closeOldVote($lastVote, $voteStor, $clock);
}
$multipleOptions = Util::valueFit($multipleOptions, 0, count($options));
@@ -122,7 +123,10 @@ class NewVote extends \sammo\BaseAPI
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);
$db->update('general', [
+5 -2
View File
@@ -8,6 +8,7 @@ use sammo\DTO\VoteInfo;
use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralQueryMode;
use sammo\General;
use sammo\GameClock;
use sammo\Json;
use sammo\KVStorage;
use sammo\LiteHashDRBG;
@@ -54,15 +55,17 @@ class Vote extends \sammo\BaseAPI
return '선택한 항목이 없습니다.';
}
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$voteStor = KVStorage::getStorage($db, 'vote');
$rawVoteInfo = $voteStor->getValue("vote_{$voteID}");
if (!$rawVoteInfo) {
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 '설문조사가 종료되었습니다.';
}
+5 -5
View File
@@ -30,8 +30,9 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
}
static public function pickGeneralFromPool(\MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix=null):array{
$oNow = new \DateTimeImmutable();
$now = $oNow->format('Y-m-d H:i:s');
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$db->update('select_pool', [
'reserved_until'=>null,
@@ -48,9 +49,8 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
throw new \RuntimeException('pool 부족');
}
$gameStor = KVStorage::getStorage($db, 'game_env');
$result = [];
$validUntil = TimeUtil::nowAddMinutes(2 * $gameStor->turnterm);
$result = [];
$validUntil = $now + GameClock::TICKS_PER_TURN * 2;
while(count($result) < $pickCnt){
$cand = $rng->choiceUsingWeightPair($pool);
$poolID = $cand['id'];
+3 -3
View File
@@ -34,7 +34,7 @@ abstract class AbsGeneralPool{
* specialWar
*/
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, string $validUntil)
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, int $validUntil)
{
$this->db = $db;
$this->info = $info;
@@ -92,7 +92,7 @@ abstract class AbsGeneralPool{
return $this->builder;
}
public function getValidUntil():string{
public function getValidUntil():int{
return $this->validUntil;
}
@@ -109,4 +109,4 @@ abstract class AbsGeneralPool{
abstract public static function getPoolName():string;
abstract public static function initPool(\MeekroDB $db);
}
}
+55 -54
View File
@@ -146,40 +146,35 @@ abstract class Auction
return $this->info;
}
public function shrinkCloseDate(?DateTimeInterface $date): ?string
public function shrinkCloseTick(?int $tick): ?string
{
if ($date === null) {
$date = new DateTimeImmutable();
}
$this->info->closeDate = $date;
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$this->info->closeTick = $tick ?? $clock->nowTick();
$db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id);
return null;
}
public function extendLatestBidCloseDate(?DateTimeInterface $date): ?string
public function extendLatestBidCloseTick(?int $tick): ?string
{
if ($date === null) {
if ($tick === null) {
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$turnTerm = $gameStor->getValue('turnterm');
$date = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60
));
$tick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID)
);
}
else{
$date = DateTimeImmutable::createFromInterface($date);
}
if ($this->info->detail->availableLatestBidCloseDate !== null && $date < $this->info->detail->availableLatestBidCloseDate) {
if ($this->info->detail->availableLatestBidCloseTick !== null && $tick < $this->info->detail->availableLatestBidCloseTick) {
return '기간보다 짧습니다.';
}
$this->info->detail->availableLatestBidCloseDate = $date;
$this->info->detail->availableLatestBidCloseTick = $tick;
return null;
}
public function extendCloseDate(DateTimeInterface $date, bool $force = false): ?string
public function extendCloseTick(int $tick, bool $force = false): ?string
{
if (!$force) {
if ($this->info->detail->remainCloseDateExtensionCnt === null) {
@@ -193,12 +188,11 @@ abstract class Auction
}
}
if ($date < $this->info->closeDate) {
if ($tick < $this->info->closeTick) {
return '종료 기간보다 짧습니다.';
}
$closeDate = DateTimeImmutable::createFromInterface($date);
$this->info->closeDate = $closeDate;
$this->info->closeTick = $tick;
return null;
}
@@ -245,12 +239,13 @@ abstract class Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$msg = new Message(
MessageType::private,
$src,
$dest,
$reason,
new DateTime(),
DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick())),
new DateTime('9999-12-31'),
[]
);
@@ -275,7 +270,12 @@ abstract class Auction
$db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id);
}
private function bidInheritPoint(int $amount, \DateTimeImmutable $now, bool $tryExtendCloseDate): ?string
private function bidInheritPoint(
int $amount,
int $nowTick,
\DateTimeImmutable $nowDate,
bool $tryExtendCloseDate,
): ?string
{
$db = DB::db();
@@ -311,7 +311,7 @@ abstract class Auction
$general->getVar('owner'),
$general->getID(),
$amount,
$now,
$nowDate,
new AuctionBidItemData(
$general->getVar('owner_name'),
$obfuscatedName,
@@ -324,15 +324,16 @@ abstract class Auction
}
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$turnTerm = $gameStor->getValue('turnterm');
if ($this->info->detail->availableLatestBidCloseDate !== null) {
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60
));
if ($this->info->detail->availableLatestBidCloseTick !== null) {
$extendedCloseTick = $nowTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID)
);
if ($extendedCloseDate > $this->info->closeDate && $this->info->closeDate < $this->info->detail->availableLatestBidCloseDate) {
$this->extendCloseDate(min($extendedCloseDate, $this->info->detail->availableLatestBidCloseDate), true);
if ($extendedCloseTick > $this->info->closeTick && $this->info->closeTick < $this->info->detail->availableLatestBidCloseTick) {
$this->extendCloseTick(min($extendedCloseTick, $this->info->detail->availableLatestBidCloseTick), true);
$this->applyDB();
}
}
@@ -356,12 +357,16 @@ abstract class Auction
return '경매가 이미 끝났습니다.';
}
$now = new \DateTimeImmutable();
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$nowDate = $clock->tickToDateTime($nowTick);
if ($auctionInfo->closeDate < $now) {
if ($auctionInfo->closeTick < $nowTick) {
return '경매가 이미 끝났습니다.';
}
if ($auctionInfo->openDate > $now) {
if ($auctionInfo->openTick > $nowTick) {
return '경매가 아직 시작되지 않았습니다.';
}
@@ -377,13 +382,11 @@ abstract class Auction
if ($auctionInfo->reqResource === ResourceType::inheritancePoint) {
return $this->bidInheritPoint($amount, $now, $tryExtendCloseDate);
return $this->bidInheritPoint($amount, $nowTick, $nowDate, $tryExtendCloseDate);
}
//reqResource는 말 그대로 '구매자가 내야하는 자원'이다.
$db = DB::db();
$highestBid = $this->getHighestBid();
if (!$auctionInfo->detail->isReverse) {
if ($highestBid !== null && $amount <= $highestBid->amount) {
@@ -421,7 +424,7 @@ abstract class Auction
$general->getVar('owner'),
$general->getID(),
$amount,
$now,
$nowDate,
new AuctionBidItemData(
$general->getVar('owner_name'),
$general->getName(),
@@ -436,14 +439,13 @@ abstract class Auction
$general->increaseVar($resType->value, -$morePoint);
$gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm');
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60
));
$extendedCloseTick = $nowTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID)
);
if ($extendedCloseDate > $this->info->closeDate) {
$this->extendCloseDate($extendedCloseDate, true);
if ($extendedCloseTick > $this->info->closeTick) {
$this->extendCloseTick($extendedCloseTick, true);
$this->applyDB();
}
@@ -456,10 +458,10 @@ abstract class Auction
public function tryFinish(): ?bool
{
$now = new DateTimeImmutable();
if ($now < $this->info->closeDate) {
return null;
}
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
if ($clock->nowTick() < $this->info->closeTick) return null;
//경매를 닫아야한다.
$highestBid = $this->getHighestBid();
@@ -469,17 +471,15 @@ abstract class Auction
}
if ($highestBid->aux->tryExtendCloseDate) {
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm');
//연장 요청이 있었다.
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60
));
$extendedCloseTick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY)
);
if ($this->extendCloseDate($extendedCloseDate) === null) {
$this->extendLatestBidCloseDate(null);
if ($this->extendCloseTick($extendedCloseTick) === null) {
$this->extendLatestBidCloseTick(null);
$this->applyDB();
return false;
}
@@ -509,12 +509,13 @@ abstract class Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$msg = new Message(
MessageType::private,
$src,
$dest,
$failReason,
new \DateTime(),
DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick())),
new \DateTime('9999-12-31'),
[]
);
+9 -7
View File
@@ -56,10 +56,11 @@ abstract class AuctionBasicResource extends Auction
}
$now = new \DateTimeImmutable();
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$turnTerm = $gameStor->getValue('turnterm');
$closeDate = $now->add(TimeUtil::secondsToDateInterval($closeTurnCnt * $turnTerm * 60));
$closeTick = $nowTick + GameClock::TICKS_PER_TURN * $closeTurnCnt;
$openResult = static::openAuction(new AuctionInfo(
null,
@@ -68,8 +69,8 @@ abstract class AuctionBasicResource extends Auction
"$amount",
$general->getId(),
$bidderRes,
$now,
$closeDate,
$nowTick,
$closeTick,
new AuctionInfoDetail(
"{$hostResName} {$amount} 경매",
$general->getName(),
@@ -145,12 +146,13 @@ abstract class AuctionBasicResource extends Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$msg = new Message(
MessageType::private,
$src,
$dest,
"{$this->auctionID}{$hostResName} 경매에 입찰이 없어 취소되었습니다.",
new \DateTime(),
\DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick())),
new \DateTime('9999-12-31'),
[]
);
@@ -246,8 +248,8 @@ abstract class AuctionBasicResource extends Auction
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm');
$date = (new DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($turnTerm * 60));
$this->shrinkCloseDate($date);
$clock = GameClock::fromStorage($gameStor);
$this->shrinkCloseTick($clock->nowTick() + GameClock::TICKS_PER_TURN);
}
return null;
+27 -26
View File
@@ -72,16 +72,15 @@ class AuctionUniqueItem extends Auction
$gameStor = KVStorage::getStorage($db, 'game_env');
$now = new DateTimeImmutable();
[$turnTerm, $year, $month] = $gameStor->getValuesAsArray(['turnterm', 'year', 'month']);
$closeDate = $now->add(TimeUtil::secondsToDateInterval(
max(static::MIN_AUCTION_CLOSE_MINUTES, $turnTerm * static::COEFF_AUCTION_CLOSE_MINUTES) * 60
));
$availableLatestBidCloseDate = $closeDate->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID) * 60
));
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$closeTick = $nowTick + $clock->ticksFromMinutes(
max(static::MIN_AUCTION_CLOSE_MINUTES, $turnTerm * static::COEFF_AUCTION_CLOSE_MINUTES)
);
$availableLatestBidCloseTick = $closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID)
);
$info = new AuctionInfo(
null,
@@ -90,8 +89,8 @@ class AuctionUniqueItem extends Auction
$itemKey,
$general->getID(),
ResourceType::inheritancePoint,
$now,
$closeDate,
$nowTick,
$closeTick,
new AuctionInfoDetail(
"{$item->getName()} 경매",
static::genObfuscatedName($general->getID()),
@@ -100,7 +99,7 @@ class AuctionUniqueItem extends Auction
$startAmount,
null,
1,
$availableLatestBidCloseDate,
$availableLatestBidCloseTick,
)
);
@@ -267,21 +266,22 @@ class AuctionUniqueItem extends Auction
if ($availableEquipUniqueCnt <= 0) {
$turnTerm = $gameStor->getValue('turnterm');
$clock = GameClock::fromStorage($gameStor);
//제한에 걸렸다면 자동 연장
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_UNIQUE_CNT) * 60
));
$extendedCloseTick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_UNIQUE_CNT)
);
if($bidder->getID() != $this->info->hostGeneralID){
$this->setHostAsNeutral();
}
$this->extendCloseDate($extendedCloseDate, true);
$this->extendLatestBidCloseDate(null);
$this->extendCloseTick($extendedCloseTick, true);
$this->extendLatestBidCloseTick(null);
$this->applyDB();
return '유니크 아이템 소유 제한 상태입니다. 종료 시간이 연장됩니다.';
}
$isExtendCloseDateRequired = false;
$isExtendCloseTickRequired = false;
foreach (GameConst::$allItems as $itemType => $itemList) {
//아직은 그런 경우는 없지만 동일 유니크를 여러 부위에 장착할 수 있을지도 모름
if (!key_exists($itemKey, $itemList)) {
@@ -291,13 +291,13 @@ class AuctionUniqueItem extends Auction
$ownItem = $general->getItem($itemType);
if ($ownItem->getRawClassName() == $itemKey) {
//FIXME: 이 경우에는 환불이 되던가 해야함.
$isExtendCloseDateRequired = true;
$isExtendCloseTickRequired = true;
$reasons[] = '이미 그 유니크를 가지고 있습니다.';
continue;
}
if (!$ownItem->isBuyable()) {
$isExtendCloseDateRequired = true;
$isExtendCloseTickRequired = true;
$reasons[] = '이미 다른 유니크를 가지고 있습니다.';
continue;
}
@@ -313,18 +313,19 @@ class AuctionUniqueItem extends Auction
}
if (!$availableItemTypes) {
if ($isExtendCloseDateRequired) {
if ($isExtendCloseTickRequired) {
$turnTerm = $gameStor->getValue('turnterm');
$clock = GameClock::fromStorage($gameStor);
//동일 부위 제한에 걸렸다면 자동 연장
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID) * 60
));
$extendedCloseTick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID)
);
if($bidder->getID() != $this->info->hostGeneralID){
$this->setHostAsNeutral();
}
$this->extendCloseDate($extendedCloseDate, true);
$this->extendLatestBidCloseDate(null);
$this->extendCloseTick($extendedCloseTick, true);
$this->extendLatestBidCloseTick(null);
$this->applyDB();
}
return join(' ', $reasons);
+1 -1
View File
@@ -193,7 +193,7 @@ class che_몰수 extends Command\NationCommand
$src,
$src,
$text,
new \DateTime(),
Message::gameNow(),
new \DateTime('9999-12-31'),
[]
);
+1 -1
View File
@@ -159,7 +159,7 @@ class che_발령 extends Command\NationCommand
$destGeneral->getLogger()->pushGeneralActionLog("<Y>{$generalName}</>에 의해 <G><b>{$destCityName}</b></>{$josaRo} 발령됐습니다. <1>$date</>");
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
if (cutTurn($general->getTurnTime(), $this->env['turnterm']) != cutTurn($destGeneral->getTurnTime(), $this->env['turnterm'])) {
if (cutTurn($general->getTurnTick(), $this->env['turnterm']) != cutTurn($destGeneral->getTurnTick(), $this->env['turnterm'])) {
$yearMonth += 1;
}
$destGeneral->setAuxVar('last발령', $yearMonth);
@@ -198,8 +198,9 @@ class che_불가침제의 extends Command\NationCommand
$destNation['color']
);
$now = new \DateTime($date);
$validUntil = new \DateTime($date);
$clock = \sammo\GameClock::fromStorage(\sammo\KVStorage::getStorage($db, 'game_env'));
$now = \DateTime::createFromImmutable($clock->tickToDateTime($general->getTurnTick()));
$validUntil = clone $now;
$validMinutes = max(30, $env['turnterm'] * 3);
$validUntil->add(new \DateInterval("PT{$validMinutes}M"));
@@ -147,8 +147,9 @@ class che_불가침파기제의 extends Command\NationCommand{
$destNation['color']
);
$now = new \DateTime($date);
$validUntil = new \DateTime($date);
$clock = \sammo\GameClock::fromStorage(\sammo\KVStorage::getStorage($db, 'game_env'));
$now = \DateTime::createFromImmutable($clock->tickToDateTime($general->getTurnTick()));
$validUntil = clone $now;
$validMinutes = max(30, $env['turnterm']*3);
$validUntil->add(new \DateInterval("PT{$validMinutes}M"));
@@ -217,4 +218,4 @@ class che_불가침파기제의 extends Command\NationCommand{
],
];
}
}
}
@@ -145,8 +145,9 @@ class che_종전제의 extends Command\NationCommand{
$destNation['color']
);
$now = new \DateTime($date);
$validUntil = new \DateTime($date);
$clock = \sammo\GameClock::fromStorage(\sammo\KVStorage::getStorage($db, 'game_env'));
$now = \DateTime::createFromImmutable($clock->tickToDateTime($general->getTurnTick()));
$validUntil = clone $now;
$validMinutes = max(30, $env['turnterm']*3);
$validUntil->add(new \DateInterval("PT{$validMinutes}M"));
@@ -203,4 +204,4 @@ class che_종전제의 extends Command\NationCommand{
],
];
}
}
}
+1 -1
View File
@@ -137,7 +137,7 @@ class che_천도 extends Command\NationCommand
$nationID = $general->getNationID();
$nationStor = \sammo\KVStorage::getStorage(DB::db(), $nationID, 'nation_env');
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()];
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTick()];
if ($lastTurn->getCommand() != $commandName || $lastTurn->getArg() !== $this->arg) {
$this->setResultTurn(new LastTurn(
+4 -8
View File
@@ -2,11 +2,9 @@
namespace sammo\DTO;
use LDTO\Attr\Convert;
use LDTO\Attr\JsonString;
use LDTO\Attr\NullIsUndefined;
use LDTO\Attr\RawName;
use LDTO\Converter\DateTimeConverter;
use sammo\Enums\AuctionType;
use sammo\Enums\ResourceType;
@@ -23,12 +21,10 @@ class AuctionInfo extends \LDTO\DTO
#[RawName('req_resource')]
public ResourceType $reqResource,
#[RawName('open_date')]
#[Convert(DateTimeConverter::class)]
public \DateTimeImmutable $openDate,
#[RawName('close_date')]
#[Convert(DateTimeConverter::class)]
public \DateTimeImmutable $closeDate,
#[RawName('open_tick')]
public int $openTick,
#[RawName('close_tick')]
public int $closeTick,
#[JsonString]
public AuctionInfoDetail $detail,
+1 -4
View File
@@ -2,9 +2,7 @@
namespace sammo\DTO;
use LDTO\Attr\Convert;
use LDTO\Attr\NullIsUndefined;
use LDTO\Converter\DateTimeConverter;
class AuctionInfoDetail extends \LDTO\DTO
{
@@ -21,8 +19,7 @@ class AuctionInfoDetail extends \LDTO\DTO
#[NullIsUndefined]
public ?int $remainCloseDateExtensionCnt,
#[NullIsUndefined]
#[Convert(DateTimeConverter::class)]
public ?\DateTimeImmutable $availableLatestBidCloseDate,
public ?int $availableLatestBidCloseTick,
) {
}
}
+2 -5
View File
@@ -2,10 +2,8 @@
namespace sammo\DTO;
use LDTO\Attr\Convert;
use LDTO\Attr\NullIsUndefined;
use LDTO\Attr\RawName;
use LDTO\Converter\DateTimeConverter;
class GeneralAccessLog extends \LDTO\DTO
{
@@ -20,8 +18,7 @@ class GeneralAccessLog extends \LDTO\DTO
public ?int $userID,
#[RawName('last_refresh')]
#[Convert(DateTimeConverter::class)]
public \DateTimeImmutable $lastRefresh,
public ?int $lastRefresh,
public int $refresh,
@@ -35,4 +32,4 @@ class GeneralAccessLog extends \LDTO\DTO
public int $refreshScoreTotal,
) {
}
}
}
+33
View File
@@ -2,8 +2,41 @@
namespace sammo\DTO;
use sammo\GameClock;
use sammo\Util;
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 int $id,
public string $title,
+4 -4
View File
@@ -49,7 +49,7 @@ class DiplomaticMessage extends Message{
$this->validDiplomacy = false;
}
if($this->validUntil < (new \DateTime())){
if($this->validUntil < $this->date){
$this->validDiplomacy = false;
}
}
@@ -215,7 +215,7 @@ class DiplomaticMessage extends Message{
$this->dest,
$this->src,
"【외교】{$year}{$month}월: {$this->src->nationName}{$josaYi} {$this->dest->nationName}에게 제안한 {$this->diplomacyDetail}",
new \DateTime(),
Message::gameNow(),
new \DateTime('9999-12-31'),
[
'delete'=>$this->id,
@@ -231,7 +231,7 @@ class DiplomaticMessage extends Message{
$this->dest,
$this->src,
"【외교】{$year}{$month}월: {$this->src->nationName}{$josaYi} {$this->dest->nationName}에게 제안한 {$this->diplomacyDetail}",
new \DateTime(),
Message::gameNow(),
new \DateTime('9999-12-31'),
[
'delete'=>$this->id,
@@ -281,4 +281,4 @@ class DiplomaticMessage extends Message{
return self::DECLINED;
}
}
}
+1 -1
View File
@@ -123,7 +123,7 @@ class OpenNationBetting extends \sammo\Event\Action
}
$logger->flush();
$now = new DateTime();
$now = Message::gameNow();
$text = "새로운 {$name} 내기가 열렸습니다. 천통국 베팅란을 확인해주세요.";
$src = new MessageTarget(0, '', 0, 'System', '#000000');
+7 -10
View File
@@ -281,16 +281,13 @@ class General extends GeneralBase implements iAction
$this->calcCache[$cacheKey] = $result;
return $result;
}
$recwar = new \DateTimeImmutable($this->getVar('recent_war'));
$turnNow = new \DateTimeImmutable($this->getVar('turntime'));
$secDiff = TimeUtil::DateIntervalToSeconds($recwar->diff($turnNow));
if ($secDiff <= 0) {
$this->calcCache[$cacheKey] = 0;
return 0;
}
$result = intdiv(Util::toInt($secDiff), 60 * $turnTerm);
$tickDiff = Util::toInt($this->getVar('turntime')) - Util::toInt($this->getVar('recent_war'));
if ($tickDiff <= 0) {
$this->calcCache[$cacheKey] = 0;
return 0;
}
$result = intdiv($tickDiff, GameClock::TICKS_PER_TURN);
$this->calcCache[$cacheKey] = $result;
return $result;
}
+13 -16
View File
@@ -355,7 +355,7 @@ class GeneralAI
$this->calcWarRoute();
$troopCandidate = [];
$chiefTurn = cutTurn($this->general->getTurnTime(), $this->env['turnterm']);
$chiefTurn = cutTurn($this->general->getTurnTick(), $this->env['turnterm']);
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
foreach ($this->troopLeaders as $troopLeader) {
@@ -372,7 +372,7 @@ class GeneralAI
$last발령 = $troopLeader->getAuxVar('last발령');
if ($last발령) {
$leaderTurn = cutTurn($troopLeader->getTurnTime(), $this->env['turnterm']);
$leaderTurn = cutTurn($troopLeader->getTurnTick(), $this->env['turnterm']);
$compYearMonth = $yearMonth;
if ($chiefTurn < $leaderTurn) {
$compYearMonth += 1;
@@ -458,7 +458,7 @@ class GeneralAI
return null;
}
$chiefTurn = cutTurn($this->general->getTurnTime(), $this->env['turnterm']);
$chiefTurn = cutTurn($this->general->getTurnTick(), $this->env['turnterm']);
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
$troopCandidate = [];
@@ -481,7 +481,7 @@ class GeneralAI
$last발령 = $troopLeader->getAuxVar('last발령');
if ($last발령) {
$leaderTurn = cutTurn($troopLeader->getTurnTime(), $this->env['turnterm']);
$leaderTurn = cutTurn($troopLeader->getTurnTick(), $this->env['turnterm']);
$compYearMonth = $yearMonth;
if ($chiefTurn < $leaderTurn) {
$compYearMonth += 1;
@@ -639,8 +639,8 @@ class GeneralAI
continue;
}
$generalTurnTime = $userGeneral->getTurnTime();
$troopTurnTime = $troopLeader->getTurnTime();
$generalTurnTime = $userGeneral->getTurnTick();
$troopTurnTime = $troopLeader->getTurnTick();
if ($generalTurnTime < $troopTurnTime) { //NOTE: 어차피 수뇌 턴이 제일 빠르다
$generalCadidates[$generalID] = $userGeneral;
@@ -835,7 +835,7 @@ class GeneralAI
if (
key_exists($troopLeader->getCityID(), $this->supplyCities) &&
$this->troopLeaders[$troopID]->getTurnTime() < $lostGeneral->getTurnTime()
$this->troopLeaders[$troopID]->getTurnTick() < $lostGeneral->getTurnTick()
) {
//이미 탈출 가능한 부대를 탔다
continue;
@@ -2070,7 +2070,7 @@ class GeneralAI
if ($lastTurn->getCommand() === '천도' && $lastTurn->getArg()['destCityID'] != $this->nation['capital']) {
$cmd = buildNationCommandClass('che_천도', $this->general, $this->env, $lastTurn, $lastTurn->getArg());
if ($cmd->hasFullConditionMet()) {
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()];
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTick()];
$this->reqUpdateInstance = true;
return $cmd;
}
@@ -2079,12 +2079,9 @@ class GeneralAI
$lastTrial = $nationStor->last천도Trial;
if ($lastTrial) {
[$lastTrialLevel, $lastTrialTurnTime] = $lastTrial;
$timeDiffSeconds = TimeUtil::DateIntervalToSeconds(
date_create_immutable($lastTrialTurnTime)->diff(
date_create_immutable($general->getTurnTime())
)
);
if ($timeDiffSeconds < $turnTerm * 30 && $lastTrialLevel !== $general->getVar('officer_level')) { //0.5Turn
$timeDiffTick = abs($general->getTurnTick() - Util::toInt($lastTrialTurnTime));
if ($timeDiffTick < intdiv(GameClock::TICKS_PER_TURN, 2)
&& $lastTrialLevel !== $general->getVar('officer_level')) { //0.5Turn
return null;
}
}
@@ -2193,7 +2190,7 @@ class GeneralAI
}
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()];
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTick()];
$this->reqUpdateInstance = true;
return $cmd;
}
@@ -3977,7 +3974,7 @@ class GeneralAI
$src,
$src,
$general->getVar('npcmsg'),
new \DateTime(),
Message::gameNow(),
new \DateTime('9999-12-31'),
[]
);
+33 -10
View File
@@ -54,14 +54,24 @@ abstract class GeneralBase
);
}
function getTurnTime(int $short = self::TURNTIME_FULL_MS): ?string
{
if(!key_exists('turntime', $this->raw)){
return null;
}
return [
self::TURNTIME_FULL_MS => function ($turntime) {
function getTurnTime(int $short = self::TURNTIME_FULL_MS): ?string
{
if(!key_exists('turntime', $this->raw)){
return null;
}
$rawTurnTime = $this->getVar('turntime');
// 비교용 Dummy와 과거 fixture는 문자열을 잠시 허용하되 제품 DB는 tick만 사용합니다.
if (is_string($rawTurnTime) && !ctype_digit(ltrim($rawTurnTime, '-'))) {
$formattedTurnTime = $rawTurnTime;
} else {
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$formattedTurnTime = $clock->formatTick(Util::toInt($rawTurnTime), true);
}
return [
self::TURNTIME_FULL_MS => function ($turntime) {
return $turntime;
},
self::TURNTIME_FULL => function ($turntime) {
@@ -73,8 +83,21 @@ abstract class GeneralBase
self::TURNTIME_HM => function ($turntime) {
return substr($turntime, 11, 5);
},
][$short]($this->getVar('turntime'));
}
][$short]($formattedTurnTime);
}
function getTurnTick(): ?int
{
if (!key_exists('turntime', $this->raw)) {
return null;
}
$rawTurnTime = $this->getVar('turntime');
if (is_string($rawTurnTime) && !ctype_digit(ltrim($rawTurnTime, '-'))) {
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
return $clock->dateTimeToTick(new \DateTimeImmutable($rawTurnTime));
}
return Util::toInt($rawTurnTime);
}
function getNPCType(): int
{
+7 -8
View File
@@ -59,7 +59,7 @@ class RandomNameGeneral extends AbsGeneralPool{
'generalName'=>$generalName,
'imgsvr'=>0,
'picture'=>null
], '9999-12-31 12:00:00');
], PHP_INT_MAX);
}
static public function pickGeneralFromPool(MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix = null): array
@@ -68,26 +68,25 @@ class RandomNameGeneral extends AbsGeneralPool{
$result = [];
$dbInsert = [];
$oNow = new \DateTimeImmutable();
for($i=0;$i<$pickCnt;$i++){
$result[] = static::pickGeneral1FromPool($db, $rng, $owner, $prefix);
}
if($owner){
$now = $oNow->format('Y-m-d H:i:s');
$gameStor = \sammo\KVStorage::getStorage($db, 'game_env');
$clock = \sammo\GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$db->delete('select_pool', [
'reserved_until'=>null,
'owner'=>null,
],'(reserved_until < %s OR reserved_until IS NULL) AND general_id IS null', $now);
$validUntil = $oNow->add(new \DateInterval(sprintf('PT%dS', 30)));
$validUntil = $now + $clock->ticksFromSeconds(30);
foreach($result as $pickedGeneral){
$dbInsert[] = [
'owner'=>$owner,
'uniqueName'=>$pickedGeneral->getUniqueName(),
'info'=>$pickedGeneral->getInfo(),
'reserved_until'=>$validUntil->format(('Y-m-d H:i:s'))
'reserved_until'=>$validUntil
];
}
$db->insert('select_pool', $dbInsert);
@@ -99,4 +98,4 @@ class RandomNameGeneral extends AbsGeneralPool{
public static function initPool(\MeekroDB $db){
//do Nothing
}
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ class SPoolUnderU100 extends AbsFromUserPool
private const STAT_BONUS_MAX_TOTAL = 190;
private const STAT_BONUS_MAX_MULTIPLIER = 1.5;
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, string $validUntil)
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, int $validUntil)
{
$targetInfo = $info;
$initialInfo = $info;
+75 -18
View File
@@ -15,6 +15,9 @@ class Message
protected $sendCnt = 0;
private ?int $sendTimeTick = null;
private ?int $sendValidUntilTick = null;
public function __construct(
public MessageType $msgType,
public MessageTarget $src,
@@ -26,6 +29,12 @@ class Message
) {
}
public static function gameNow(): \DateTime
{
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
return \DateTime::createFromImmutable($clock->nowDateTime());
}
public function setSentInfo(int $mailbox, int $messageID) : self
{
if(!Message::isValidMailBox($mailbox)){
@@ -78,6 +87,15 @@ class Message
}
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){
$src = $this->src->toArray();
$dest = null;
@@ -98,12 +116,15 @@ class Message
'dest'=>$dest,
'text'=>$this->msg,
'option'=>$this->msgOption,
'time'=>$this->date->format('Y-m-d H:i:s')
'time'=>$this->date->format('Y-m-d H:i:s'),
'deleteRemainingMilliseconds'=>$deleteRemainingMilliseconds,
'clockMode'=>$clock->getMode(),
];
}
public static function buildFromArray(array $row) : Message
{
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$dbMessage = Json::decode($row['message']);
$msgType = MessageType::from($row['type']);
@@ -116,8 +137,8 @@ class Message
$src,
$dest,
$dbMessage['text'],
new \DateTime($row['time']),
new \DateTime($row['valid_until']),
\DateTime::createFromImmutable($clock->tickToDateTime(Util::toInt($row['time']))),
\DateTime::createFromImmutable($clock->tickToDateTime(Util::toInt($row['valid_until']))),
$option
];
@@ -151,9 +172,12 @@ class Message
public static function getMessageByID(int $messageID) : ?Message
{
$db = DB::db();
$now = new \DateTime();
$row = $db->queryFirstRow('SELECT * FROM `message` WHERE `id` = %i AND valid_until', $messageID);
//FIXME: $now가 들어가야 하는데 안 들어가있는데?
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$row = $db->queryFirstRow(
'SELECT * FROM `message` WHERE `id` = %i AND valid_until > %i',
$messageID,
$clock->nowTick(),
);
if (!$row) {
return null;
}
@@ -171,12 +195,12 @@ class Message
{
$db = DB::db();
$date = (new \DateTime())->format('Y-m-d H:i:s');
$date = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->nowTick();
$where = new \WhereClause('and');
$where->add('mailbox = %i', $mailbox);
$where->add('type = %s', $msgType->value);
$where->add('valid_until > %s', $date);
$where->add('valid_until > %i', $date);
if ($fromSeq > 0) {
$where->add('id >= %i', $fromSeq);
}
@@ -203,12 +227,12 @@ class Message
{
$db = DB::db();
$date = (new \DateTime())->format('Y-m-d H:i:s');
$date = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->nowTick();
$where = new \WhereClause('and');
$where->add('mailbox = %i', $mailbox);
$where->add('type = %s', $msgType->value);
$where->add('valid_until > %s', $date);
$where->add('valid_until > %i', $date);
$where->add('id < %i', $toSeq);
if ($limit > 0) {
@@ -236,7 +260,8 @@ class Message
return '시스템 외교 메시지는 삭제할 수 없습니다.';
}
$prev5min = new \DateTime();
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$prev5min = \DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick()));
$prev5min->sub(new \DateInterval('PT5M'));
if($msgObj->date < $prev5min){
@@ -265,14 +290,15 @@ class Message
}
$in1min = new \DateTime();
$now = \DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick()));
$in1min = clone $now;
$in1min->add(new \DateInterval('PT1M'));
$newMsg = new Message(
$msgObj->msgType,
$msgObj->src,
$msgObj->dest,
"req_del_msg",
new \DateTime(),
$now,
$in1min,
$msgOption
);
@@ -300,13 +326,15 @@ class Message
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
[$timeTick, $validUntilTick] = $this->resolveSendTicks($clock);
$db->insert('message', [
'mailbox' => $mailbox,
'type' => $this->msgType->value,
'src' => $src_id,
'dest' => $dest_id,
'time' => $this->date->format('Y-m-d H:i:s'),
'valid_until' => $this->validUntil->format('Y-m-d H:i:s'),
'time' => $timeTick,
'valid_until' => $validUntilTick,
'message' => Json::encode([
'src'=>($this->src)?($this->src->toArray()):[],
'dest'=>($this->dest)?($this->dest->toArray()):[],
@@ -317,6 +345,32 @@ class Message
return [$mailbox, $db->insertId()];
}
/** @return array{0:int, 1:int} */
protected function resolveSendTicks(GameClock $clock): array
{
if ($this->sendTimeTick !== null && $this->sendValidUntilTick !== null) {
return [$this->sendTimeTick, $this->sendValidUntilTick];
}
$timeTick = $clock->nowTick();
if (Util::toInt($this->validUntil->format('Y')) >= 9000) {
$validUntilTick = GameClock::MAX_SAFE_TICK;
} else {
$validitySeconds = $this->validUntil->getTimestamp() - $this->date->getTimestamp();
$validUntilTick = GameClock::addTicks(
$timeTick,
$clock->ticksFromSeconds($validitySeconds),
);
}
$this->sendTimeTick = $timeTick;
$this->sendValidUntilTick = $validUntilTick;
$this->date = \DateTime::createFromImmutable($clock->tickToDateTime($timeTick));
$this->validUntil = \DateTime::createFromImmutable($clock->tickToDateTime($validUntilTick));
return [$timeTick, $validUntilTick];
}
private function sendToSender():array{
if($this->sendCnt > 1){
throw new \RuntimeException('이미 전송한 메일입니다.');
@@ -430,7 +484,7 @@ class Message
$src,
$dest,
$msg,
new \DateTime(),
self::gameNow(),
new \DateTime('9999-12-31'),
[]
);
@@ -464,6 +518,8 @@ class Message
}
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){
$this->msgOption = $newMsgOption;
}
@@ -471,7 +527,8 @@ class Message
$this->msgOption['invalid'] = true;
if($hideMsg){
$this->validUntil = new \DateTime('2000-12-31');
$validUntilTick = GameClock::addTicks($clock->nowTick(), -1);
$this->validUntil = \DateTime::createFromImmutable($clock->tickToDateTime($validUntilTick));
}
else{
if(key_exists('receiverMessageID', $this->msgOption)){
@@ -489,7 +546,7 @@ class Message
'text' => $this->msg,
'option' => $this->msgOption
]),
'valid_until'=>$this->validUntil->format('Y-m-d H:i:s'),
'valid_until'=>$validUntilTick,
], 'id=%i', $this->id);
}
+1 -1
View File
@@ -142,7 +142,7 @@ class RaiseInvaderMessage extends Message
$srcTarget = MessageTarget::buildSystemTarget();
$destTarget = MessageTarget::buildQuick($destGeneralID);
if ($date === null) {
$date = new \DateTime();
$date = Message::gameNow();
}
/**
+33 -15
View File
@@ -252,21 +252,35 @@ class ResetHelper{
true
);
if($sync == 0) {
// 현재 시간을 1월로 맞춤
$starttime = cutTurn($turntime, $turnterm);
$month = 1;
$year = $startyear;
} else {
// 현재 시간과 동기화
[$starttime, $yearPulled, $month] = cutDay($turntime, $turnterm);
$requestedTime = new \DateTimeImmutable($turntime);
if($sync == 0) {
// 현재 시간을 1월로 맞춤
$baseTime = new \DateTimeImmutable(cutTurnDateTime($turntime, $turnterm));
$month = 1;
$year = $startyear;
} else {
// 현재 시간과 동기화
[$baseTimeString, $yearPulled, $month] = cutDay($turntime, $turnterm);
$baseTime = new \DateTimeImmutable($baseTimeString);
if($yearPulled){
$year = $startyear-1;
}
else{
$year = $startyear;
}
}
}
$wallNow = GameClock::readWallTime();
$initialClock = new GameClock(
$baseTime,
$turnterm,
0,
GameClock::MODE_REALTIME,
$wallNow,
fn (): \DateTimeImmutable => $wallNow,
);
$requestedTick = $initialClock->dateTimeToTick($requestedTime);
$currentTick = $initialClock->dateTimeToTick($wallNow);
$killturn = 4800 / $turnterm;
if($npcmode == 1) { $killturn = intdiv($killturn, 3); }
@@ -290,10 +304,14 @@ class ResetHelper{
'maxnation'=>GameConst::$defaultMaxNation,
'refreshLimit'=>30000,
'develcost'=>$develcost,
'turntime'=>$turntime,
'starttime'=>$starttime,
'opentime'=>$turntime,
'turnterm'=>$turnterm,
'turntime'=>$requestedTick,
'starttime'=>0,
'opentime'=>$requestedTick,
'turnterm'=>$turnterm,
'clock_base_time'=>TimeUtil::format($baseTime, true),
'clock_tick'=>$currentTick,
'clock_mode'=>GameClock::MODE_REALTIME,
'clock_wall_anchor'=>TimeUtil::format($wallNow, true),
'killturn'=>$killturn,
'genius'=>GameConst::$defaultMaxGenius,
'show_img_level'=>$show_img_level,
@@ -319,7 +337,7 @@ class ResetHelper{
'name'=>$admin['name'],
'picture'=>$admin['picture'],
'imgsvr'=>$admin['imgsvr'],
'turntime'=>$turntime,
'turntime'=>$requestedTick,
'killturn'=>9999,
'crewtype'=>GameUnitConst::DEFAULT_CREWTYPE
]);
@@ -355,7 +373,7 @@ class ResetHelper{
$db->insert('ng_games', [
'server_id'=>$serverID,
'date'=>$turntime,
'date'=>TimeUtil::format($requestedTime, false),
'winner_nation'=>null,
'map'=>$scenarioObj->getMapTheme(),
'season'=>$seasonIdx,
+6 -6
View File
@@ -136,13 +136,13 @@ class GeneralBuilder{
$this->specialWar = GameConst::$defaultSpecialWar;
}
try{
$this->specialDomestic = SpecialityHelper::getDomesticClassByName($special);
$this->specialWar = GameConst::$defaultSpecialWar;
}
catch (\Exception $e){
$this->specialDomestic = GameConst::$defaultSpecialDomestic;
$this->specialWar = SpecialityHelper::getWarClassByName($special);
}
catch (\Exception $e){
$this->specialDomestic = SpecialityHelper::getDomesticClassByName($special);
$this->specialWar = GameConst::$defaultSpecialWar;
}
return $this;
}
@@ -653,7 +653,7 @@ class GeneralBuilder{
$officerLevel = $nationID?1:0;
}
$turntime = \sammo\getRandTurn($this->rng, $env['turnterm'], new \DateTimeImmutable($env['turntime']));
$turntime = \sammo\getRandTurn($this->rng, $env['turnterm'], Util::toInt($env['turntime']));
if($this->killturn){
$killturn = $this->killturn;
@@ -735,4 +735,4 @@ class GeneralBuilder{
return true; //생성되었다.
}
}
}
+8 -8
View File
@@ -35,7 +35,7 @@ class ScoutMessage extends Message
$this->validScout = false;
}
if ($this->validUntil <= new \DateTime()) {
if ($this->validUntil <= $this->date) {
$this->validScout = false;
}
}
@@ -108,7 +108,7 @@ class ScoutMessage extends Message
$this->src,
$this->dest,
"{$this->src->nationName}{$josaRo} 등용 제의 수락",
new \DateTime(),
Message::gameNow(),
new \DateTime('9999-12-31'),
[
'delete' => $this->id
@@ -122,11 +122,11 @@ class ScoutMessage extends Message
public static function invalidateAll(int $generalID, ?int $exceptMsgID = null)
{
$db = DB::db();
$now = TimeUtil::now();
$now = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->nowTick();
//XXX: 뭔가 기존 쿼리가 애매하다. invalid 관련해서 다른 옵션이 가능한가?
$rawMsgList = Util::convertArrayToDict($db->query(
'SELECT * FROM `message` WHERE
`mailbox` = %i AND `type` = "private" AND `dest` = `mailbox` AND `valid_until` > %s AND
`mailbox` = %i AND `type` = "private" AND `dest` = `mailbox` AND `valid_until` > %i AND
JSON_VALUE(message, "$.option.action") = %s',
$generalID,
$now,
@@ -156,7 +156,7 @@ class ScoutMessage extends Message
$this->src,
$this->dest,
"{$this->src->nationName}{$josaRo} 등용 제의 거부",
new \DateTime(),
Message::gameNow(),
new \DateTime('9999-12-31'),
[
'delete' => $this->id
@@ -205,9 +205,9 @@ class ScoutMessage extends Message
$db = DB::db();
$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);
if ($date === null) {
$date = new \DateTime();
}
if ($date === null) {
$date = Message::gameNow();
}
if ($destGeneral['officer_level'] == 12) {
if ($reason !== null) {
+9 -21
View File
@@ -40,34 +40,22 @@ final class ServerTool
$locked = tryLock();
}
$oldunit = $admin['turnterm'] * 60;
$unit = $turnterm * 60;
if($unit == $oldunit){
if($turnterm == $admin['turnterm']){
if($locked){
unlock();
}
return null;
}
$unitDiff = $unit / $oldunit;
$servTurnTime = new \DateTimeImmutable($admin['turntime']);
foreach ($db->query('SELECT no,turntime FROM general') as $gen) {
$genTurnTime = new \DateTimeImmutable($gen['turntime']);
$timeDiff = TimeUtil::DateIntervalToSeconds($genTurnTime->diff($servTurnTime));
$timeDiff *= $unitDiff;
$newGenTurnTime = $servTurnTime->add(TimeUtil::secondsToDateInterval($timeDiff));
$db->update('general', [
'turntime' => $newGenTurnTime->format('Y-m-d H:i:s.u')
], 'no=%i', $gen['no']);
}
$turn = ($admin['year'] - $admin['startyear']) * 12 + $admin['month'] - 1;
$starttime = $servTurnTime->sub(TimeUtil::secondsToDateInterval($turn * $unit))->format('Y-m-d H:i:s');
$starttime = cutTurn($starttime, $turnterm, false);
$oldClock = GameClock::fromStorage($gameStor);
$currentTick = $oldClock->nowTick();
$currentDisplay = $oldClock->tickToDateTime($currentTick);
$oldClock->persistTick($gameStor, $currentTick);
$gameStor->turnterm = $turnterm;
$gameStor->starttime = $starttime;
$gameStor->clock_base_time = TimeUtil::format(
GameClock::baseTimeForProjection($currentDisplay, $currentTick, $turnterm),
true,
);
pushGlobalHistoryLog(["<R>★</>턴시간이 <C>{$turnterm}분</>으로 변경됩니다."]);
if($locked){
+47 -36
View File
@@ -26,13 +26,19 @@ class TurnExecutionHelper
ksort(self::$comparisonActionGeneralIds, SORT_STRING);
return self::$comparisonActionGeneralIds;
}
/** @var General*/
protected $generalObj;
public function __construct(General $general)
public function __construct(General $general)
{
$this->generalObj = $general;
}
}
public static function monotonicCompletionTick(int $completedTick, int $candidateTick): int
{
return max($completedTick, $candidateTick);
}
public function __destruct()
{
@@ -233,14 +239,13 @@ class TurnExecutionHelper
$general->rebirth();
}
$turntime = addTurn($general->getTurnTime(), $gameStor->turnterm);
$turntime = addTurn($general->getTurnTick(), $gameStor->turnterm);
$nextTurnTimeBase = $general->getAuxVar('nextTurnTimeBase');
if($nextTurnTimeBase !== null){
$turntime = cutTurn($turntime, $gameStor->turnterm);
$turntimeObj = new \DateTimeImmutable($turntime);
$turntimeObj = $turntimeObj->add(TimeUtil::secondsToDateInterval($nextTurnTimeBase));
$turntime = TimeUtil::format($turntimeObj, true);
$clock = GameClock::fromStorage($gameStor);
$turntime += $clock->ticksFromSeconds($nextTurnTimeBase);
$general->setAuxVar('nextTurnTimeBase', null);
}
@@ -248,7 +253,7 @@ class TurnExecutionHelper
}
static public function executeGeneralCommandUntil(string $date, \DateTimeInterface $limitActionTime, int $year, int $month)
static public function executeGeneralCommandUntil(int $date, \DateTimeInterface $limitActionTime, int $year, int $month)
{
$db = DB::db();
$generalsTodo = $db->query(
@@ -258,8 +263,9 @@ class TurnExecutionHelper
$currentTurn = null;
$gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor = KVStorage::getStorage($db, 'game_env');
$autorun_user = $gameStor->autorun_user;
$traceCityChanges = PHP_SAPI === 'cli'
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
&& getenv('REF_AI_TRACE_CITY_CHANGES') === '1';
@@ -278,20 +284,15 @@ class TurnExecutionHelper
];
}
}
foreach ($generalsTodo as $rawGeneral) {
// The comparison harness fixes the logical clock. A real wall-clock
// timeout here made the final post-month drain depend on host load,
// so identical seeds processed a variable number of new generals.
// Preserve the production timeout unless deterministic comparison
// mode was explicitly enabled.
if (getenv('REF_DETERMINISTIC_INSTALL_ENABLED') !== '1') {
$currActionTime = new \DateTimeImmutable();
$currActionTime = GameClock::readWallTime();
if ($currActionTime > $limitActionTime) {
return [true, $currentTurn];
}
}
$general = General::createObjFromDB($rawGeneral['no']);
if (
PHP_SAPI === 'cli'
@@ -392,7 +393,7 @@ class TurnExecutionHelper
$resultNationTurn = $turnObj->processNationCommand(
$rng,
$nationCommandObj
);
);
$nationStor->setValue($lastNationTurnKey, $resultNationTurn->toRaw());
if (
PHP_SAPI === 'cli'
@@ -414,8 +415,8 @@ class TurnExecutionHelper
$hasReservedTurn = true;
}
if ($ai) {
$newGeneralCommandObj = $ai->chooseGeneralTurn($generalCommandObj); // npc AI 처리
if ($ai) {
$newGeneralCommandObj = $ai->chooseGeneralTurn($generalCommandObj); // npc AI 처리
if ($generalCommandObj !== $newGeneralCommandObj) {
$autorunMode = true;
$generalCommandObj = $newGeneralCommandObj;
@@ -551,7 +552,7 @@ class TurnExecutionHelper
pullNationCommand($general->getVar('nation'), $general->getVar('officer_level'));
pullGeneralCommand($general->getID());
$currentTurn = $general->getTurnTime();
$currentTurn = $general->getTurnTick();
$general->increaseVarWithLimit('myset', GameConst::$incDefSettingChange, null, GameConst::$maxDefSettingChange);
if (($autorun_user['limit_minutes'] ?? false) && $general->getNPCType() < 2 && $hasReservedTurn) {
@@ -614,13 +615,14 @@ class TurnExecutionHelper
return true;
}
static public function executeAllCommand(&$executed = false, &$locked = false): string
static public function executeAllCommand(&$executed = false, &$locked = false): int
{
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
if (TimeUtil::now(true) < $gameStor->turntime) {
$clock = GameClock::fromStorage($gameStor);
if ($clock->nowTick() < $gameStor->turntime) {
//턴 시각 이전이면 아무것도 하지 않음
return $gameStor->turntime;
}
@@ -644,7 +646,7 @@ class TurnExecutionHelper
//접속자 수 따라서 갱신제한 변경
CheckOverhead();
$date = TimeUtil::now(true);
$date = $clock->nowTick();
// 최종 처리 월턴의 다음 월턴시간 구함
//$lastExecuted = $gameStor->turntime;
$prevTurn = cutTurn($gameStor->turntime, $gameStor->turnterm);
@@ -657,7 +659,7 @@ class TurnExecutionHelper
$maxActionTime = max($maxActionTime * 2 / 3, $maxActionTime - 10);
}
$limitActionTime = (new \DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($maxActionTime));
$limitActionTime = GameClock::readWallTime()->add(TimeUtil::secondsToDateInterval($maxActionTime));
// 현재 턴 이전 월턴까지 모두처리.
//최종 처리 이후 다음 월턴이 현재 시간보다 전이라면
@@ -674,18 +676,21 @@ class TurnExecutionHelper
updateTraffic();
if ($executionOver) {
if ($currentTurn !== null) {
$executed = true;
$gameStor->turntime = $currentTurn;
if ($currentTurn !== null) {
$executed = true;
$gameStor->turntime = self::monotonicCompletionTick(
Util::toInt($gameStor->turntime),
$currentTurn,
);
}
unlock();
return $gameStor->turntime;
}
$monthlyRng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'monthly',
$gameStor->year,
UniqueConst::$hiddenSeed,
'monthly',
$gameStor->year,
$gameStor->month
)));
@@ -715,7 +720,7 @@ class TurnExecutionHelper
static::runEventHandler($db, $gameStor, EventTarget::PreMonth);
$traceMonthlyGeneral('after-pre-month-event');
if (!preUpdateMonthly()) {
unlock();
unlock();
throw new \RuntimeException('preUpdateMonthly() 처리 에러');
}
$traceMonthlyGeneral('after-pre-update');
@@ -725,7 +730,7 @@ class TurnExecutionHelper
// 분기계산. 장수들 턴보다 먼저 있다면 먼저처리
if ($gameStor->month == 1) {
checkStatistic();
}
}
static::runEventHandler($db, $gameStor, EventTarget::Month);
$traceMonthlyGeneral('after-month-event');
postUpdateMonthly($monthlyRng);
@@ -748,10 +753,16 @@ class TurnExecutionHelper
$gameStor->month
);
if ($currentTurn !== null) {
$executed = true;
$gameStor->turntime = $currentTurn;
}
if ($currentTurn !== null) {
$executed = true;
// A general's sub-tick can be just before the monthly boundary that
// was completed above. Never move the global completion cursor back
// behind an already-applied monthly event.
$gameStor->turntime = self::monotonicCompletionTick(
Util::toInt($gameStor->turntime),
$currentTurn,
);
}
//토너먼트 처리
processTournament();
+2 -1
View File
@@ -55,7 +55,8 @@ class UserLogger
}
$db = DB::db();
$date = TimeUtil::now();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$date = $clock->formatTick($clock->nowTick());
$serverID = UniqueConst::$serverID;
$request = array_map(function ($textAndType) use ($date, $serverID) {
[$text, $type] = $textAndType;
+5 -5
View File
@@ -57,16 +57,16 @@ class WarUnitGeneral extends WarUnit
$this->general->increaseRankVar(RankColumn::warnum, 1);
if ($this->isAttacker) {
$semiTurn = $general->getTurnTime();
$semiTurn = $general->getTurnTick();
} else if ($oppose !== null) {
$semiTurn = $oppose->getGeneral()->getTurnTime();
$semiTurn = $oppose->getGeneral()->getTurnTick();
} else {
LogText("WarUnitGeneral::setOppose", "defender인데 oppose가 null {$general->getID()}, {$general->getTurnTime()}");
$semiTurn = $general->getTurnTime();
$semiTurn = $general->getTurnTick();
}
$phase = $this->getRealPhase();
$semiTurn = substr($semiTurn, 0, strlen($semiTurn) - 2);
$semiTurn .= sprintf("%02d", Util::valueFit($phase, 0, 99));
$semiTurn -= $semiTurn % 100;
$semiTurn += Util::valueFit($phase, 0, 99);
$general->setVar('recent_war', $semiTurn);
}