Compare commits

..
Author SHA1 Message Date
Hide_D ed3c84aa8e feat: replace game schedules with logical ticks 2026-08-03 05:13:47 +00:00
Hide_D f4a1e2258d merge: preserve S100 generated NPC stat floor 2026-07-31 05:06:38 +00:00
Hide_D 4ffd7b5bc0 fix: preserve S100 generated NPC stat floor 2026-07-31 05:06:15 +00:00
Hide_D e7c773709d merge: apply S100 cooldown and growth fixes 2026-07-31 01:24:41 +00:00
Hide_D 1e30659bdd test: verify S100 final-growth reselection 2026-07-31 01:19:23 +00:00
Hide_D 162f198f08 fix: stabilize S100 cooldowns and generated NPC growth 2026-07-31 01:11:47 +00:00
Hide_D 4aa09a294e perf: reduce MariaDB turn queue round trips 2026-07-30 17:12:22 +00:00
Hide_D 191abf83c0 test: isolate S100 inheritance browser logs 2026-07-29 08:51:51 +00:00
Hide_D edfeae5f0f fix: block S100 inheritance stat resets 2026-07-29 08:42:38 +00:00
Hide_D 424184a51a fix: define mixed S100 dex conversion ownership 2026-07-29 07:29:43 +00:00
Hide_D 03a321f95d fix: prevent S100 dex conversion refills 2026-07-29 06:36:33 +00:00
Hide_D e3615fa6bb fix: cap S100 NPC dex growth at forty percent 2026-07-29 05:20:53 +00:00
Hide_D 32991f736d fix: expose S100 event traits to frontend 2026-07-29 05:11:19 +00:00
Hide_D 8b2748d3d4 feat: 재선택 현재 능력치 기준 표시 2026-07-28 16:56:02 +00:00
Hide_D 883080e275 feat: 100기 NPC 동조 및 후보 추첨 기능 개선과 초기 능력치 계산 로직 추가 2026-07-28 16:28:13 +00:00
Hide_D ce807e2564 유니크 획득 확률 조금 더 증가 2026-07-28 16:11:24 +00:00
Hide_D 9a2ac82373 시나리오 문제점 수정 2026-07-28 16:07:21 +00:00
Hide_D 3c35539c16 CreateManyNPC 추가 2026-07-28 16:04:50 +00:00
Hide_D 5ab08403c6 feat: 100기 NPC 동조와 후보 추첨 보완 2026-07-28 16:02:14 +00:00
Hide_D c1ce478d22 feat: exclude prior event seasons from all-star pool 2026-07-28 00:43:46 +00:00
Hide_D d8bf8ba922 feat: expand 100th-season pool through phase 99 2026-07-28 00:29:25 +00:00
Hide_D 95f66e4c39 feat: add 100th-season all-star growth event 2026-07-25 10:46:23 +00:00
Hide_D 4de7ebec17 요양 깃수 추가 2026-03-04 14:16:08 +00:00
Hide_D d43dff35fb fix: 디버그 코드 복구 2026-02-18 18:54:46 +00:00
Hide_D 5b13f8bd30 ip 기록 2026-02-17 22:31:08 +09:00
Hide_D e51bc6f711 fix: 턴 데몬에 cookie jar 적용 2026-02-17 20:35:15 +09:00
Hide_D 1b08aa0c58 wip: 디버그 코드 추가 2026-02-17 19:12:28 +09:00
Hide_D f2547fe207 dbg 2026-02-17 18:32:04 +09:00
류화영 cec7726731 fix: 방랑군이 빈번하게 해산하는 문제 해결 시도 2026-01-19 22:50:11 +09:00
Hide_D d679a6fed8 fix: InheritancePointManager 버그 수정 2025-11-01 17:38:02 +00:00
132 changed files with 122225 additions and 1788 deletions
+5 -2
View File
@@ -13,6 +13,9 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
[$turntime, $tnmt_time] = $gameStor->getValuesAsArray(['turntime','tnmt_time']); [$turntime, $tnmt_time] = $gameStor->getValuesAsArray(['turntime','tnmt_time']);
$clock = GameClock::fromStorage($gameStor);
$turntimeDisplay = $clock->formatTick(Util::toInt($turntime), true);
$tnmtTimeDisplay = $tnmt_time === null ? '-' : $clock->formatTick(Util::toInt($tnmt_time), true);
$plock = $db->queryFirstField('SELECT plock FROM plock WHERE `type` ="GAME"'); $plock = $db->queryFirstField('SELECT plock FROM plock WHERE `type` ="GAME"');
?> ?>
@@ -30,8 +33,8 @@ $plock = $db->queryFirstField('SELECT plock FROM plock WHERE `type` ="GAME"');
</head> </head>
<body> <body>
<form action=_119_b.php method=post> <form action=_119_b.php method=post>
시간조정 : <input type=text size=3 name=minute><input type=submit name=btn value='분당김'><input type=submit name=btn value='분지연'> 최종갱신 : <?=$turntime?><br> 시간조정 : <input type=text size=3 name=minute><input type=submit name=btn value='분당김'><input type=submit name=btn value='분지연'> 최종갱신 : <?=$turntimeDisplay?> (tick <?=$turntime?>)<br>
시간조정 : <input type=text size=3 name=minute2><input type=submit name=btn value='토너분당김'><input type=submit name=btn value='토너분지연'> 토너먼트 : <?=$tnmt_time?><br> 시간조정 : <input type=text size=3 name=minute2><input type=submit name=btn value='토너분당김'><input type=submit name=btn value='토너분지연'> 토너먼트 : <?=$tnmtTimeDisplay?> (tick <?=$tnmt_time?>)<br>
봉급지급 : <input type=submit name=btn value='금지급'><input type=submit name=btn value='쌀지급'><br> 봉급지급 : <input type=submit name=btn value='금지급'><input type=submit name=btn value='쌀지급'><br>
락 풀 기 : <input type=submit name=btn value='락걸기'><input type=submit name=btn value='락풀기'> 현재 : <?=$plock>0?"동결중":"가동중"?><br> 락 풀 기 : <input type=submit name=btn value='락걸기'><input type=submit name=btn value='락풀기'> 현재 : <?=$plock>0?"동결중":"가동중"?><br>
</form> </form>
+9 -36
View File
@@ -38,21 +38,9 @@ switch ($btn) {
usleep(500000); usleep(500000);
} }
$gameStor->cacheValues(['turntime', 'starttime', 'tnmt_time']); $clock = GameClock::fromStorage($gameStor);
$turntime = (new \DateTimeImmutable($gameStor->turntime))->sub(new \DateInterval("PT{$minute}M")); // 스케줄 전체를 벽시계에서 빼지 않고 논리 현재 tick만 앞으로 이동합니다.
$starttime = (new \DateTimeImmutable($gameStor->starttime))->sub(new \DateInterval("PT{$minute}M")); $clock->advance($gameStor, $clock->ticksFromMinutes($minute));
$tnmt_time = (new \DateTimeImmutable($gameStor->tnmt_time))->sub(new \DateInterval("PT{$minute}M"));
$gameStor->turntime = $turntime->format('Y-m-d H:i:s.u');
$gameStor->starttime = $starttime->format('Y-m-d H:i:s');
$gameStor->tnmt_time = $tnmt_time->format('Y-m-d H:i:s');
$db->update('general', [
'turntime' => $db->sqleval('DATE_SUB(turntime, INTERVAL %i MINUTE)', $minute)
], true);
$db->update('ng_auction', [
'close_date' => $db->sqleval('DATE_SUB(close_date, INTERVAL %i MINUTE)', $minute)
], 'finished = 0');
if ($locked) { if ($locked) {
unlock(); unlock();
} }
@@ -66,34 +54,19 @@ switch ($btn) {
} }
usleep(500000); usleep(500000);
} }
$gameStor->cacheValues(['turntime', 'starttime', 'tnmt_time']); $clock = GameClock::fromStorage($gameStor);
$turntime = (new \DateTimeImmutable($gameStor->turntime))->add(new \DateInterval("PT{$minute}M")); $clock->advance($gameStor, -$clock->ticksFromMinutes($minute));
$starttime = (new \DateTimeImmutable($gameStor->starttime))->add(new \DateInterval("PT{$minute}M"));
$tnmt_time = (new \DateTimeImmutable($gameStor->tnmt_time))->add(new \DateInterval("PT{$minute}M"));
$gameStor->turntime = $turntime->format('Y-m-d H:i:s.u');
$gameStor->starttime = $starttime->format('Y-m-d H:i:s');
$gameStor->tnmt_time = $tnmt_time->format('Y-m-d H:i:s');
$db->update('general', [
'turntime' => $db->sqleval('DATE_ADD(turntime, INTERVAL %i MINUTE)', $minute)
], true);
$db->update('ng_auction', [
'close_date' => $db->sqleval('DATE_ADD(close_date, INTERVAL %i MINUTE)', $minute)
], 'finished = 0');
if ($locked) { if ($locked) {
unlock(); unlock();
} }
break; break;
case "토너분당김": case "토너분당김":
$tnmt_time = new \DateTime($gameStor->tnmt_time); $clock = GameClock::fromStorage($gameStor);
$tnmt_time->sub(new \DateInterval("PT{$minute2}M")); $gameStor->tnmt_time = Util::toInt($gameStor->tnmt_time) - $clock->ticksFromMinutes($minute2);
$gameStor->tnmt_time = $tnmt_time->format('Y-m-d H:i:s');
break; break;
case "토너분지연": case "토너분지연":
$tnmt_time = new \DateTimeImmutable($gameStor->tnmt_time); $clock = GameClock::fromStorage($gameStor);
$tnmt_time->add(new \DateInterval("PT{$minute2}M")); $gameStor->tnmt_time = Util::toInt($gameStor->tnmt_time) + $clock->ticksFromMinutes($minute2);
$gameStor->tnmt_time = $tnmt_time->format('Y-m-d H:i:s');
break; break;
case "금지급": case "금지급":
processGoldIncome(); processGoldIncome();
+2 -2
View File
@@ -40,7 +40,7 @@ $admin = getAdmin();
</tr> </tr>
<tr> <tr>
<td width=110 align=right>시작시간변경</td> <td width=110 align=right>시작시간변경</td>
<td width=285><input type=text size=20 maxlength=20 style=color:white;background-color:black;text-align:right; name=starttime value='<?=substr($admin['starttime'], 0, 19)?>'><input type=submit name=btn value=변경1></td> <td width=285><input type=text size=20 maxlength=20 style=color:white;background-color:black;text-align:right; name=starttime value='<?=substr($admin['starttime_display'], 0, 19)?>'><input type=submit name=btn value=변경1></td>
</tr> </tr>
<tr> <tr>
<td width=110 align=right>최대 장수</td> <td width=110 align=right>최대 장수</td>
@@ -52,7 +52,7 @@ $admin = getAdmin();
<td width=110 align=right>시작 년도</td> <td width=110 align=right>시작 년도</td>
<td width=285><input type=text size=3 maxlength=3 style=color:white;background-color:black;text-align:right; name=startyear value='<?=$admin['startyear']?>'><input type=submit name=btn value=변경4></td> <td width=285><input type=text size=3 maxlength=3 style=color:white;background-color:black;text-align:right; name=startyear value='<?=$admin['startyear']?>'><input type=submit name=btn value=변경4></td>
<td width=110 align=right>최근 갱신 시간</td> <td width=110 align=right>최근 갱신 시간</td>
<td width=285>&nbsp;<?=$admin['turntime']?></td> <td width=285>&nbsp;<?=$admin['turntime_display']?> (tick <?=$admin['turntime']?>)</td>
</tr> </tr>
<tr> <tr>
<td width=110 align=right>턴시간</td> <td width=110 align=right>턴시간</td>
+6 -1
View File
@@ -43,7 +43,12 @@ switch ($btn) {
pushGlobalHistoryLog(["<R>★</><S>{$log}</>"]); pushGlobalHistoryLog(["<R>★</><S>{$log}</>"]);
break; break;
case "변경1": case "변경1":
$gameStor->starttime = (new \DateTime($starttime))->format('Y-m-d H:i:s'); $clock = GameClock::fromStorage($gameStor);
$gameStor->clock_base_time = TimeUtil::format(GameClock::baseTimeForProjection(
new \DateTimeImmutable($starttime),
Util::toInt($gameStor->starttime),
$clock->getTurnTermMinutes(),
), true);
break; break;
case "변경2": case "변경2":
$gameStor->maxgeneral = $maxgeneral; $gameStor->maxgeneral = $maxgeneral;
+1 -1
View File
@@ -99,7 +99,7 @@ switch ($btn) {
], '`no` IN %li', $genlist); ], '`no` IN %li', $genlist);
break; break;
case "강제 사망": case "강제 사망":
$date = TimeUtil::now(true); $date = GameClock::fromStorage($gameStor)->nowTick();
$db->update('general', [ $db->update('general', [
'killturn' => 0, 'killturn' => 0,
'turntime' => $date, 'turntime' => $date,
+2 -1
View File
@@ -42,6 +42,7 @@ if ($session->userGrade < 6) {
} }
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
if ($btn == '정렬하기') { if ($btn == '정렬하기') {
$gen = 0; $gen = 0;
@@ -111,7 +112,7 @@ $generalObj = General::createObjFromDB($gen, null, GeneralQueryMode::FullWithAcc
대상장수 : 대상장수 :
<select name=gen size=1> <select name=gen size=1>
<?php foreach ($generalBasicList as $general) : ?> <?php foreach ($generalBasicList as $general) : ?>
<option <?= $gen == $general['no'] ? 'selected' : '' ?> value='<?= $general['no'] ?>'><?= $general['name'] ?> (<?= substr($general['turntime'], 14, 5) ?>)</option> <option <?= $gen == $general['no'] ? 'selected' : '' ?> value='<?= $general['no'] ?>'><?= $general['name'] ?> (<?= substr($clock->formatTick(Util::toInt($general['turntime'])), 14, 5) ?>)</option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<input type=submit name=btn value='조회하기'> <input type=submit name=btn value='조회하기'>
+4 -1
View File
@@ -243,11 +243,14 @@ $templates = new \League\Plates\Engine(__DIR__ . '/templates');
"SELECT nation,no,name,owner_name as ownerName, owner, picture, imgsvr, "SELECT nation,no,name,owner_name as ownerName, owner, picture, imgsvr,
experience, dedication, experience, dedication,
dex1, dex2, dex3, dex4, dex5, dex1, dex2, dex3, dex4, dex5,
horse, weapon, book, item horse, weapon, book, item, aux
FROM general WHERE %l", FROM general WHERE %l",
$btn == "NPC 보기" ? "npc>=2" : "npc<2" $btn == "NPC 보기" ? "npc>=2" : "npc<2"
) as $general) { ) as $general) {
$generalID = $general['no']; $generalID = $general['no'];
foreach (['dex1', 'dex2', 'dex3', 'dex4', 'dex5'] as $dexKey) {
$general[$dexKey] = CentennialAllStarGrowthService::recordableRawValue($general, $dexKey);
}
$general['bgColor'] = $nationColor[$general['nation']] ?? GameConst::$basecolor4; $general['bgColor'] = $nationColor[$general['nation']] ?? GameConst::$basecolor4;
$general['fgColor'] = newColor($general['bgColor']); $general['fgColor'] = newColor($general['bgColor']);
$general['nationName'] = $nationName[$general['nation']]; $general['nationName'] = $nationName[$general['nation']];
+1 -1
View File
@@ -16,7 +16,7 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
increaseRefresh("현재도시", 1); increaseRefresh("현재도시", 1);
$lastExecute = substr($gameStor->turntime, 5, 14); $lastExecute = substr(GameClock::fromStorage($gameStor)->formatTick(Util::toInt($gameStor->turntime)), 5, 14);
$me = $db->queryFirstRow('SELECT no,nation,officer_level,city from general where owner=%i', $userID); $me = $db->queryFirstRow('SELECT no,nation,officer_level,city from general where owner=%i', $userID);
$myNation = $db->queryFirstRow('SELECT nation,level,spy FROM nation WHERE nation=%i', $me['nation']) ?? [ $myNation = $db->queryFirstRow('SELECT nation,level,spy FROM nation WHERE nation=%i', $me['nation']) ?? [
+2
View File
@@ -129,6 +129,7 @@ $templates = new \League\Plates\Engine('templates');
); );
$generalTurnList = []; $generalTurnList = [];
$clock = GameClock::fromStorage($gameStor);
foreach ($db->queryAllLists( foreach ($db->queryAllLists(
'SELECT general_id, turn_idx, brief FROM general_turn WHERE general_id IN %li AND turn_idx < 5 ORDER BY general_id ASC, turn_idx ASC', 'SELECT general_id, turn_idx, brief FROM general_turn WHERE general_id IN %li AND turn_idx < 5 ORDER BY general_id ASC, turn_idx ASC',
@@ -142,6 +143,7 @@ $templates = new \League\Plates\Engine('templates');
$genCntEff = 0; $genCntEff = 0;
foreach ($generals as &$general) { foreach ($generals as &$general) {
$general['turntime'] = $clock->formatTick(Util::toInt($general['turntime']), true);
$general['cityText'] = CityConst::byID($general['city'])->name; $general['cityText'] = CityConst::byID($general['city'])->name;
$general['troopText'] = $troopName[$general['troop']] ?? '-'; $general['troopText'] = $troopName[$general['troop']] ?? '-';
+24 -6
View File
@@ -21,8 +21,8 @@ $generalID = $session->generalID;
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['turntime', 'opentime', 'autorun_user', 'npcmode']); $gameStor->cacheValues(['turntime', 'opentime', 'autorun_user', 'npcmode']);
$clock = GameClock::fromStorage($gameStor);
increaseRefresh("내정보", 1); $nowTick = $clock->nowTick();
$me = General::createObjFromDB($generalID, null, GeneralQueryMode::FullWithAccessLog); $me = General::createObjFromDB($generalID, null, GeneralQueryMode::FullWithAccessLog);
@@ -41,12 +41,30 @@ $lastRefresh = $db->queryFirstField(
$generalID $generalID
); );
$targetTime = addTurn($lastRefresh, $gameStor->turnterm, GameConst::$minTurnDieOnPrestart); $nextChange = $me->getAuxVar('next_change');
if (!is_int($nextChange)) {
$nextChange = null;
}
$nextChangeDisplay = $nextChange === null ? null : $clock->formatTick($nextChange);
increaseRefresh("내정보", 1);
if ($gameStor->turntime <= $gameStor->opentime) { if ($gameStor->turntime <= $gameStor->opentime) {
$targetTime = $me->getAuxVar('prestart_delete_after');
if (!is_int($targetTime)) {
$targetTime = addTurn(
$lastRefresh === null ? $nowTick : Util::toInt($lastRefresh),
$gameStor->turnterm,
GameConst::$minTurnDieOnPrestart
);
$me->setAuxVar('prestart_delete_after', $targetTime);
$me->applyDB($db);
}
$targetTimeDisplay = $clock->formatTick($targetTime);
//서버 가오픈시 할 수 있는 행동 //서버 가오픈시 할 수 있는 행동
if ($me->getNPCType() == 0 && $me->getNationID() == 0) { if ($me->getNPCType() == 0 && $me->getNationID() == 0) {
$showDieOnPrestartBtn = true; $showDieOnPrestartBtn = true;
if ($targetTime <= TimeUtil::now()) { if ($targetTime <= $nowTick) {
$availableDieOnPrestart = true; $availableDieOnPrestart = true;
} }
} }
@@ -159,7 +177,7 @@ $changeDefence999Atmos = $me->onCalcDomestic('changeDefenceTrain', "atmos999", $
<a href="b_myPage.php?detachNPC=1"><button type="button" style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>빙의 해체 요청</button></a>--> <a href="b_myPage.php?detachNPC=1"><button type="button" style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>빙의 해체 요청</button></a>-->
<?php if ($showDieOnPrestartBtn) : ?> <?php if ($showDieOnPrestartBtn) : ?>
가오픈 기간 내 장수 삭제 (<?= substr($targetTime, 0, 19) ?> 부터)<br> 가오픈 기간 내 장수 삭제 (<?= $targetTimeDisplay ?> 부터)<br>
<button type="button" id='dieOnPrestart' style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>장수 삭제</button><br><br> <button type="button" id='dieOnPrestart' style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>장수 삭제</button><br><br>
<?php endif; ?> <?php endif; ?>
@@ -174,7 +192,7 @@ $changeDefence999Atmos = $me->onCalcDomestic('changeDefenceTrain', "atmos999", $
<?php endif; ?> <?php endif; ?>
<?php if ($gameStor->npcmode == 2 && $me->getNPCType() == 0) : ?> <?php if ($gameStor->npcmode == 2 && $me->getNPCType() == 0) : ?>
다른 장수 선택 (<?= substr($me->getAuxVar('next_change') ?? TimeUtil::now(), 0, 19) ?> 부터)<br> 다른 장수 선택 (<?= $nextChangeDisplay ?? '지금' ?>부터)<br>
<a href="select_general_from_pool.php" id='select_general_from_pool'><button type="button" style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>다른 장수 선택</button></a><br><br> <a href="select_general_from_pool.php" id='select_general_from_pool'><button type="button" style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>다른 장수 선택</button></a><br><br>
<?php endif; ?> <?php endif; ?>
+2 -2
View File
@@ -228,10 +228,10 @@ if($btn == "자동개최설정") {
$phase = 0; $phase = 0;
startBetting($admin['tnmt_type'], 720); startBetting($admin['tnmt_type'], 720);
} elseif($btn == "베팅마감") { } elseif($btn == "베팅마감") {
$dt = date("Y-m-d H:i:s", time() + 60); $clock = GameClock::fromStorage($gameStor);
$gameStor->tournament=7; $gameStor->tournament=7;
$gameStor->phase=0; $gameStor->phase=0;
$gameStor->tnmt_time = $dt; $gameStor->tnmt_time = $clock->nowTick() + $clock->ticksFromSeconds(60);
} elseif($btn == "16강") { } elseif($btn == "16강") {
finalFight($admin['tnmt_type'], $admin['tournament'], $admin['phase'], 16); finalFight($admin['tnmt_type'], $admin['tournament'], $admin['phase'], 16);
} elseif($btn == "8강") { } elseif($btn == "8강") {
+16
View File
@@ -51,6 +51,22 @@
display: none; display: none;
} }
.picture_choice {
margin: 8px auto;
}
.picture_choice label,
.event_picture label {
display: inline-flex;
align-items: center;
gap: 4px;
margin: 4px 8px;
}
.picture_choice img {
object-fit: cover;
}
.custom_picture { .custom_picture {
display: none; display: none;
} }
+70 -119
View File
@@ -630,7 +630,11 @@ function generalInfo(General $generalObj)
$injury = "건강"; $injury = "건강";
} }
$remaining = (new \DateTimeImmutable($generalObj->getTurnTime()))->diff(new \DateTimeImmutable())->i; $clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$remaining = max(0, intdiv(
$generalObj->getTurnTick() - $clock->nowTick(),
$clock->ticksFromMinutes(1),
));
if ($nation['color'] == "") { if ($nation['color'] == "") {
$nation['color'] = "#000000"; $nation['color'] = "#000000";
@@ -921,72 +925,40 @@ function banner()
); );
} }
function addTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true) function addTurn(int $tick, int $turnterm, int $turn = 1, bool $withFraction = true): int
{ {
if($turnterm < 0){ return $tick + GameClock::TICKS_PER_TURN * $turn;
if($turnterm == -60){
return VarTurn60::fromDatetime(new \DateTimeImmutable(($date)))->addTurn($turn )->toDateStr($withFraction);
}
throw new \Exception("InvalidTurnTerm".$turnterm);
}
$date = new \DateTime($date);
$target = $turnterm * $turn;
$date->add(new \DateInterval("PT{$target}M"));
if ($withFraction) {
return $date->format('Y-m-d H:i:s.u');
}
return $date->format('Y-m-d H:i:s');
} }
function subTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true) function subTurn(int $tick, int $turnterm, int $turn = 1, bool $withFraction = true): int
{ {
if($turnterm < 0){ return $tick - GameClock::TICKS_PER_TURN * $turn;
if($turnterm == -60){
return VarTurn60::fromDatetime(new \DateTimeImmutable(($date)))->addTurn($turn * -1)->toDateStr($withFraction);
}
throw new \Exception("InvalidTurnTerm".$turnterm);
}
$date = new \DateTime($date);
$target = $turnterm * $turn;
$date->sub(new \DateInterval("PT{$target}M"));
if ($withFraction) {
return $date->format('Y-m-d H:i:s.u');
}
return $date->format('Y-m-d H:i:s');
} }
function cutTurn($date, int $turnterm, bool $withFraction = true) function cutTurn(int $tick, int $turnterm, bool $withFraction = true): int
{ {
if($turnterm < 0){ $remainder = $tick % GameClock::TICKS_PER_TURN;
if($turnterm == -60){ if ($remainder < 0) {
[$baseDate, ] = VarTurn60::fromDatetime(new \DateTimeImmutable(($date)))->cutTurn($withFraction); $remainder += GameClock::TICKS_PER_TURN;
return $baseDate;
}
throw new \Exception("InvalidTurnTerm".$turnterm);
} }
return $tick - $remainder;
}
$date = new \DateTime($date); /** 시나리오 초기화 입력인 벽시계를 기존 01:00 기준 월 경계로 정렬합니다. */
function cutTurnDateTime(string $date, int $turnterm, bool $withFraction = true): string
$baseDate = new \DateTime($date->format('Y-m-d')); {
$baseDate->sub(new \DateInterval("P1D")); $dateObj = new \DateTime($date);
$baseDate->add(new \DateInterval("PT1H")); $baseDate = new \DateTime($dateObj->format('Y-m-d'));
$baseDate->sub(new \DateInterval('P1D'));
$diffMin = intdiv($date->getTimeStamp() - $baseDate->getTimeStamp(), 60); $baseDate->add(new \DateInterval('PT1H'));
$diffMin = intdiv($dateObj->getTimestamp() - $baseDate->getTimestamp(), 60);
$diffMin -= $diffMin % $turnterm; $diffMin -= $diffMin % $turnterm;
$baseDate->add(new \DateInterval("PT{$diffMin}M")); $baseDate->add(new \DateInterval("PT{$diffMin}M"));
if ($withFraction) { return $baseDate->format($withFraction ? 'Y-m-d H:i:s.u' : 'Y-m-d H:i:s');
return $baseDate->format('Y-m-d H:i:s.u');
}
return $baseDate->format('Y-m-d H:i:s');
} }
function cutDay($date, int $turnterm, bool $withFraction = true) function cutDay($date, int $turnterm, bool $withFraction = true)
{ {
assert($turnterm > 0);
$date = new \DateTime($date); $date = new \DateTime($date);
$baseDate = new \DateTime($date->format('Y-m-d')); $baseDate = new \DateTime($date->format('Y-m-d'));
@@ -1025,11 +997,9 @@ function increaseRefresh($type = "", $cnt = 1)
$generalID = $session->generalID; $generalID = $session->generalID;
$userGrade = $session->userGrade; $userGrade = $session->userGrade;
$dateObj = new \DateTimeImmutable();
$date = TimeUtil::format($dateObj, false);
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$date = GameClock::fromStorage($gameStor)->nowTick();
$isunited = $gameStor->isunited; $isunited = $gameStor->isunited;
$opentime = $gameStor->opentime; $opentime = $gameStor->opentime;
@@ -1071,7 +1041,7 @@ function increaseRefresh($type = "", $cnt = 1)
$logDB = FileDB::db($logPath, $serverPath . '/../f_install/sql/api_log.sql'); $logDB = FileDB::db($logPath, $serverPath . '/../f_install/sql/api_log.sql');
$ip = $_SERVER['REMOTE_ADDR'] ?? 'local'; $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'local';
$date = date('Y-m-d H:i:s'); $date = date('Y-m-d H:i:s');
$logDB->insert('api_log', [ $logDB->insert('api_log', [
@@ -1130,7 +1100,6 @@ function CheckOverhead()
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
[$turnterm, $refreshLimit] = $gameStor->getValuesAsArray(['turnterm', 'refreshLimit']); [$turnterm, $refreshLimit] = $gameStor->getValuesAsArray(['turnterm', 'refreshLimit']);
$turnterm = abs($turnterm);
$nextRefreshLimit = Util::round(pow($turnterm, 0.6) * 3) * GameConst::$refreshLimitCoef; $nextRefreshLimit = Util::round(pow($turnterm, 0.6) * 3) * GameConst::$refreshLimitCoef;
@@ -1176,12 +1145,13 @@ function timeover(): bool
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
list($turnterm, $turntime) = $gameStor->getValuesAsArray(['turnterm', 'turntime']); list($turnterm, $turntime) = $gameStor->getValuesAsArray(['turnterm', 'turntime']);
$diff = (new \DateTime())->getTimestamp() - (new \DateTime($turntime))->getTimestamp(); $clock = GameClock::fromStorage($gameStor);
$diff = $clock->nowTick() - Util::toInt($turntime);
$t = min(abs($turnterm), 5); $t = min($turnterm, 5);
$term = $diff; $term = $clock->ticksFromSeconds($t);
if ($term >= $t || $term < 0) { if ($diff >= $term || $diff < 0) {
return true; return true;
} else { } else {
return false; return false;
@@ -1194,18 +1164,11 @@ function checkDelay()
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
//서버정보 //서버정보
$now = new \DateTimeImmutable(); $clock = GameClock::fromStorage($gameStor);
$turntime = new \DateTimeImmutable($gameStor->turntime); $timeMinDiff = intdiv($clock->nowTick() - Util::toInt($gameStor->turntime), $clock->ticksFromMinutes(1));
$timeMinDiff = intdiv($now->getTimestamp() - $turntime->getTimestamp(), 60);
// 1턴이상 갱신 없었으면 서버 지연 // 1턴이상 갱신 없었으면 서버 지연
$term = $gameStor->turnterm; $term = $gameStor->turnterm;
if($term < 0){
//가변 턴에는 수행 안함!
return;
}
if ($term >= 20) { if ($term >= 20) {
$threshold = 1; $threshold = 1;
} else if ($term >= 10) { } else if ($term >= 10) {
@@ -1217,18 +1180,15 @@ function checkDelay()
$iter = intdiv($timeMinDiff, $term); $iter = intdiv($timeMinDiff, $term);
if ($iter > $threshold) { if ($iter > $threshold) {
$minute = $iter * $term; $minute = $iter * $term;
$newTurntime = $turntime->add(new \DateInterval("PT{$minute}M")); $delayTick = $clock->ticksFromMinutes($minute);
$newNextTurntime = $turntime->add(new \DateInterval("PT{$term}M")); $gameStor->turntime = Util::toInt($gameStor->turntime) + $delayTick;
$gameStor->turntime = $newTurntime->format('Y-m-d H:i:s'); $gameStor->starttime = Util::toInt($gameStor->starttime) + $delayTick;
$gameStor->starttime = (new \DateTimeImmutable($gameStor->starttime))
->add(new \DateInterval("PT{$minute}M"))
->format('Y-m-d H:i:s');
$db->update('general', [ $db->update('general', [
'turntime' => $db->sqleval('DATE_ADD(turntime, INTERVAL %i MINUTE)', $minute) 'turntime' => $db->sqleval('turntime + %i', $delayTick)
], 'turntime<=DATE_ADD(turntime, INTERVAL %i MINUTE)', $term); ], true);
$db->update('ng_auction', [ $db->update('ng_auction', [
'close_date' => $db->sqleval('DATE_ADD(close_date, INTERVAL %i MINUTE)', $minute) 'close_tick' => $db->sqleval('close_tick + %i', $delayTick)
], 'finished = 0'); ], 'finished = 0');
} }
} }
@@ -1285,14 +1245,8 @@ function turnDate($curtime)
$admin = $gameStor->getValues(['startyear', 'starttime', 'turnterm', 'year', 'month']); $admin = $gameStor->getValues(['startyear', 'starttime', 'turnterm', 'year', 'month']);
$turn = $admin['starttime']; $turn = $admin['starttime'];
$term = $admin['turnterm']; $curturn = cutTurn(Util::toInt($curtime), $admin['turnterm']);
if($term == -60){ $num = intdiv($curturn - Util::toInt($turn), GameClock::TICKS_PER_TURN);
$num = VarTurn60::calcTurnDiff(new \DateTimeImmutable($turn), new \DateTimeImmutable($curtime));
}
else{
$curturn = cutTurn($curtime, $admin['turnterm']);
$num = intdiv((strtotime($curturn) - strtotime($turn)), $term * 60);
}
$date = $admin['startyear'] * 12; $date = $admin['startyear'] * 12;
$date += $num; $date += $num;
@@ -1438,6 +1392,9 @@ function CheckHall($no)
if ($valueType === 'natural') { if ($valueType === 'natural') {
$value = $generalObj->getVar($typeName); $value = $generalObj->getVar($typeName);
if (in_array($typeName, ['dex1', 'dex2', 'dex3', 'dex4', 'dex5'], true)) {
$value = CentennialAllStarGrowthService::recordableValue($generalObj, $typeName);
}
} else if ($valueType === 'rank') { } else if ($valueType === 'rank') {
$value = $generalObj->getRankVar(RankColumn::from($typeName)); $value = $generalObj->getRankVar(RankColumn::from($typeName));
} else if ($valueType === 'calc') { } else if ($valueType === 'calc') {
@@ -1741,7 +1698,16 @@ function getAdmin()
{ {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
return $gameStor->getAll(); $admin = $gameStor->getAll();
$clock = GameClock::fromStorage($gameStor);
foreach (['turntime', 'starttime', 'opentime', 'tnmt_time'] as $key) {
if (($admin[$key] ?? null) !== null) {
$admin["{$key}_display"] = $clock->formatTick(Util::toInt($admin[$key]), true);
}
}
$admin['clock_now_tick'] = $clock->nowTick();
$admin['clock_now_display'] = $clock->formatTick($admin['clock_now_tick'], true);
return $admin;
} }
/** @return General[] */ /** @return General[] */
@@ -2231,44 +2197,29 @@ function SabotageInjury(RandUtil $rng, array $cityGeneralList, string $reason):
return $injuryCount; return $injuryCount;
} }
function getRandTurn(RandUtil $rng, $term, ?\DateTimeInterface $baseDateTime = null) function getRandTurn(RandUtil $rng, int $term, ?int $baseTick = null): int
{ {
if ($baseDateTime === null) { $db = DB::db();
$baseDateTime = new \DateTimeImmutable(); $clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
} else if ($baseDateTime instanceof \DateTime) { $baseTick ??= $clock->nowTick();
$baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime);
} else if ($baseDateTime instanceof \DateTimeImmutable) {
//do Nothing
} else {
throw new MustNotBeReachedException();
}
if($term == -60){
[, $term] = VarTurn60::fromDatetime($baseDateTime)->cutTurn();
}
$randSecond = $rng->nextRangeInt(0, 60 * $term - 1); $randSecond = $rng->nextRangeInt(0, 60 * $term - 1);
$randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수 $randMicrosecond = $rng->nextRangeInt(0, 999999); // 레거시 RNG 소비 6자리 유지
return TimeUtil::format($baseDateTime->add(TimeUtil::secondsToDateInterval($randSecond + $randFraction)), true); return $baseTick
+ $clock->ticksFromSeconds($randSecond)
+ intdiv($randMicrosecond * $clock->ticksPerSecond(), 1_000_000);
} }
function getRandTurn2(RandUtil $rng, $term, ?\DateTimeInterface $baseDateTime = null) function getRandTurn2(RandUtil $rng, int $term, ?int $baseTick = null): int
{ {
if ($baseDateTime === null) { $db = DB::db();
$baseDateTime = new \DateTimeImmutable(); $clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
} else if ($baseDateTime instanceof \DateTime) { $baseTick ??= $clock->nowTick();
$baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime);
} else {
throw new MustNotBeReachedException();
}
if($term == -60){
[, $term] = VarTurn60::fromDatetime($baseDateTime)->cutTurn();
}
$randSecond = $rng->nextRangeInt(0, 60 * $term - 1); $randSecond = $rng->nextRangeInt(0, 60 * $term - 1);
$randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수 $randMicrosecond = $rng->nextRangeInt(0, 999999); // 레거시 RNG 소비 6자리 유지
return $baseDateTime->sub(TimeUtil::secondsToDateInterval($randSecond + $randFraction))->format('Y-m-d H:i:s.u'); return $baseTick
- $clock->ticksFromSeconds($randSecond)
- intdiv($randMicrosecond * $clock->ticksPerSecond(), 1_000_000);
} }
+4 -3
View File
@@ -62,11 +62,12 @@ function processAuction()
{ {
$db = DB::db(); $db = DB::db();
$now = TimeUtil::now(); $gameStor = KVStorage::getStorage($db, 'game_env');
$nowTick = GameClock::fromStorage($gameStor)->nowTick();
$auctionList = $db->queryAllLists( $auctionList = $db->queryAllLists(
'SELECT id, `type` FROM ng_auction WHERE `close_date` <= %s AND finished = 0', 'SELECT id, `type` FROM ng_auction WHERE `close_tick` <= %i AND finished = 0',
$now $nowTick
); );
if (!$auctionList) { if (!$auctionList) {
+64 -36
View File
@@ -42,15 +42,21 @@ function pushGeneralCommand(int $generalID, int $turnCnt=1){
$db = DB::db(); $db = DB::db();
$db->update('general_turn', [ $db->query(
'turn_idx'=>$db->sqleval('turn_idx + %i', $turnCnt) 'UPDATE general_turn AS dst
], 'general_id=%i ORDER BY turn_idx DESC', $generalID); LEFT JOIN general_turn AS src
$db->update('general_turn', [ ON src.general_id = dst.general_id
'turn_idx'=>$db->sqleval('turn_idx - %i', GameConst::$maxTurn), AND src.turn_idx = dst.turn_idx - %i
'action'=>'휴식', SET dst.action = IF(src.id IS NULL, %s, src.action),
'arg'=>'{}', dst.arg = IF(src.id IS NULL, %s, src.arg),
'brief'=>'휴식' dst.brief = IF(src.id IS NULL, %s, src.brief)
], 'general_id=%i AND turn_idx >= %i', $generalID, GameConst::$maxTurn); WHERE dst.general_id = %i',
$turnCnt,
'휴식',
'{}',
'휴식',
$generalID
);
} }
function pullGeneralCommand(int $generalID, int $turnCnt=1){ function pullGeneralCommand(int $generalID, int $turnCnt=1){
@@ -67,15 +73,21 @@ function pullGeneralCommand(int $generalID, int $turnCnt=1){
$db = DB::db(); $db = DB::db();
$db->update('general_turn', [ $db->query(
'turn_idx'=>$db->sqleval('turn_idx + %i', GameConst::$maxTurn), 'UPDATE general_turn AS dst
'action'=>'휴식', LEFT JOIN general_turn AS src
'arg'=>'{}', ON src.general_id = dst.general_id
'brief'=>'휴식' AND src.turn_idx = dst.turn_idx + %i
], 'general_id=%i AND turn_idx < %i', $generalID, $turnCnt); SET dst.action = IF(src.id IS NULL, %s, src.action),
$db->update('general_turn', [ dst.arg = IF(src.id IS NULL, %s, src.arg),
'turn_idx'=>$db->sqleval('turn_idx - %i', $turnCnt) dst.brief = IF(src.id IS NULL, %s, src.brief)
], 'general_id=%i ORDER BY turn_idx ASC', $generalID); WHERE dst.general_id = %i',
$turnCnt,
'휴식',
'{}',
'휴식',
$generalID
);
} }
function repeatGeneralCommand(int $generalId, int $turnCnt){ function repeatGeneralCommand(int $generalId, int $turnCnt){
@@ -126,15 +138,23 @@ function pushNationCommand(int $nationID, int $officerLevel, int $turnCnt=1){
$db = DB::db(); $db = DB::db();
$db->update('nation_turn', [ $db->query(
'turn_idx'=>$db->sqleval('turn_idx + %i', $turnCnt) 'UPDATE nation_turn AS dst
], 'nation_id=%i AND officer_level=%i ORDER BY turn_idx DESC', $nationID, $officerLevel); LEFT JOIN nation_turn AS src
$db->update('nation_turn', [ ON src.nation_id = dst.nation_id
'turn_idx'=>$db->sqleval('turn_idx - %i', GameConst::$maxChiefTurn), AND src.officer_level = dst.officer_level
'action'=>'휴식', AND src.turn_idx = dst.turn_idx - %i
'arg'=>'{}', SET dst.action = IF(src.id IS NULL, %s, src.action),
'brief'=>'휴식' dst.arg = IF(src.id IS NULL, %s, src.arg),
], 'nation_id=%i AND officer_level=%i AND turn_idx >= %i ORDER BY turn_idx ASC', $nationID, $officerLevel, GameConst::$maxChiefTurn); dst.brief = IF(src.id IS NULL, %s, src.brief)
WHERE dst.nation_id = %i AND dst.officer_level = %i',
$turnCnt,
'휴식',
'{}',
'휴식',
$nationID,
$officerLevel
);
} }
function pullNationCommand(int $nationID, int $officerLevel, int $turnCnt=1){ function pullNationCommand(int $nationID, int $officerLevel, int $turnCnt=1){
@@ -157,15 +177,23 @@ function pullNationCommand(int $nationID, int $officerLevel, int $turnCnt=1){
$db = DB::db(); $db = DB::db();
$db->update('nation_turn', [ $db->query(
'turn_idx'=>$db->sqleval('turn_idx + %i', GameConst::$maxChiefTurn), 'UPDATE nation_turn AS dst
'action'=>'휴식', LEFT JOIN nation_turn AS src
'arg'=>'{}', ON src.nation_id = dst.nation_id
'brief'=>'휴식', AND src.officer_level = dst.officer_level
], 'nation_id=%i AND officer_level=%i AND turn_idx < %i', $nationID, $officerLevel, $turnCnt); AND src.turn_idx = dst.turn_idx + %i
$db->update('nation_turn', [ SET dst.action = IF(src.id IS NULL, %s, src.action),
'turn_idx'=>$db->sqleval('turn_idx - %i', $turnCnt) dst.arg = IF(src.id IS NULL, %s, src.arg),
], 'nation_id=%i AND officer_level=%i ORDER BY turn_idx ASC', $nationID, $officerLevel); dst.brief = IF(src.id IS NULL, %s, src.brief)
WHERE dst.nation_id = %i AND dst.officer_level = %i',
$turnCnt,
'휴식',
'{}',
'휴식',
$nationID,
$officerLevel
);
} }
function repeatNationCommand(int $nationID, int $officerLevel, int $turnCnt){ function repeatNationCommand(int $nationID, int $officerLevel, int $turnCnt){
+10 -2
View File
@@ -654,13 +654,21 @@ function checkStatistic()
function convForOldGeneral(array $general, int $year, int $month) function convForOldGeneral(array $general, int $year, int $month)
{ {
$general['history'] = getGeneralHistoryLogAll($general['no']); $general['history'] = getGeneralHistoryLogAll($general['no']);
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$turnTimeDisplay = $clock->formatTick(Util::toInt($general['turntime']), true);
$general['turntime_tick'] = Util::toInt($general['turntime']);
$general['turntime'] = $turnTimeDisplay;
if ($general['recent_war'] !== null) {
$general['recent_war_tick'] = Util::toInt($general['recent_war']);
$general['recent_war'] = $clock->formatTick(Util::toInt($general['recent_war']), true);
}
return [ return [
'server_id' => UniqueConst::$serverID, 'server_id' => UniqueConst::$serverID,
'general_no' => $general['no'], 'general_no' => $general['no'],
'owner' => $general['owner'], 'owner' => $general['owner'],
'name' => $general['name'], 'name' => $general['name'],
'last_yearmonth' => $year * 100 + $month, 'last_yearmonth' => $year * 100 + $month,
'turntime' => $general['turntime'], 'turntime' => $turnTimeDisplay,
'data' => Json::encode($general) 'data' => Json::encode($general)
]; ];
} }
@@ -734,7 +742,7 @@ function checkEmperior()
/** @var int[] */ /** @var int[] */
$auctionList = $db->queryFirstColumn( $auctionList = $db->queryFirstColumn(
'SELECT `id` FROM `ng_auction` WHERE `type` = %s AND `finished` = 0 ORDER BY `close_date` ASC', 'SELECT `id` FROM `ng_auction` WHERE `type` = %s AND `finished` = 0 ORDER BY `close_tick` ASC',
AuctionType::UniqueItem->value AuctionType::UniqueItem->value
); );
foreach($auctionList as $auctionID){ foreach($auctionList as $auctionID){
+5 -6
View File
@@ -4,6 +4,10 @@ namespace sammo;
function printLimitMsg($turntime) function printLimitMsg($turntime)
{ {
if (is_int($turntime) || (is_string($turntime) && ctype_digit(ltrim($turntime, '-')))) {
$gameStor = KVStorage::getStorage(DB::db(), 'game_env');
$turntime = GameClock::fromStorage($gameStor)->formatTick(Util::toInt($turntime), true);
}
//FIXME: template로 이동. //FIXME: template로 이동.
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
@@ -181,12 +185,7 @@ function info($type = 0)
$admin = $gameStor->getValues(['year', 'month', 'turnterm', 'maxgeneral']); $admin = $gameStor->getValues(['year', 'month', 'turnterm', 'maxgeneral']);
$turnTermText = $admin['turnterm'].'분'; $termtype = "{$admin['turnterm']}분 턴";
if($admin['turnterm'] < 0){
$turnTermText = "가변 {$admin['turnterm']}분";
}
$termtype = "{$turnTermText}분 턴";
$gencount = $db->queryFirstField('SELECT count(no) FROM general WHERE npc < 2'); $gencount = $db->queryFirstField('SELECT count(no) FROM general WHERE npc < 2');
$npccount = $db->queryFirstField('SELECT count(no) FROM general WHERE npc >= 2'); $npccount = $db->queryFirstField('SELECT count(no) FROM general WHERE npc >= 2');
+4
View File
@@ -63,6 +63,10 @@ function chiefTurnTable()
function templateLimitMsg(string $turntime): string function templateLimitMsg(string $turntime): string
{ {
if (ctype_digit(ltrim($turntime, '-'))) {
$gameStor = KVStorage::getStorage(DB::db(), 'game_env');
$turntime = GameClock::fromStorage($gameStor)->formatTick(Util::toInt($turntime), true);
}
return "이미 너무 많은 접속을 하셨습니다. 다음 턴에 다시 시도해주세요. (턴시간: {$turntime})"; return "이미 너무 많은 접속을 하셨습니다. 다음 턴에 다시 시도해주세요. (턴시간: {$turntime})";
} }
+11 -10
View File
@@ -10,7 +10,6 @@ use sammo\Enums\InheritanceKey;
* @return int 토너먼트 초 단위 * @return int 토너먼트 초 단위
*/ */
function calcTournamentTerm(int $turnTerm): int{ function calcTournamentTerm(int $turnTerm): int{
$turnTerm = abs($turnTerm);
return Util::valueFit($turnTerm, 5, 120); return Util::valueFit($turnTerm, 5, 120);
} }
@@ -18,10 +17,10 @@ function processTournament()
{ {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$admin = $gameStor->getValues(['tournament', 'phase', 'tnmt_type', 'tnmt_auto', 'tnmt_time', 'turnterm', 'last_tournament_betting_id']); $admin = $gameStor->getValues(['tournament', 'phase', 'tnmt_type', 'tnmt_auto', 'tnmt_time', 'turnterm', 'last_tournament_betting_id']);
$now = new \DateTime(); $offset = intdiv($clock->nowTick() - Util::toInt($admin['tnmt_time']), $clock->ticksPerSecond());
$offset = $now->getTimestamp() - (new \DateTime($admin['tnmt_time']))->getTimestamp();
//수동일땐 무시 //수동일땐 무시
if (!$admin['tnmt_auto']) { if (!$admin['tnmt_auto']) {
@@ -123,10 +122,10 @@ function processTournament()
if ($tnmt == 6) { if ($tnmt == 6) {
$betTerm = Util::valueFit($unit * 60, null, 3600); $betTerm = Util::valueFit($unit * 60, null, 3600);
//처리 초 더한 날짜 //처리 초 더한 날짜
$dt = date("Y-m-d H:i:s", strtotime($admin['tnmt_time']) + $unit * $i + $betTerm);
$gameStor->tournament = $tnmt; $gameStor->tournament = $tnmt;
$gameStor->phase = $phase; $gameStor->phase = $phase;
$gameStor->tnmt_time = $dt; $gameStor->tnmt_time = Util::toInt($admin['tnmt_time'])
+ $clock->ticksFromSeconds($unit * $i + $betTerm);
return; return;
} }
} }
@@ -134,12 +133,11 @@ function processTournament()
$second = $unit * $iter; $second = $unit * $iter;
$gameStor->tournament = $tnmt; $gameStor->tournament = $tnmt;
$gameStor->phase = $phase; $gameStor->phase = $phase;
$gameStor->tnmt_time = (new \DateTimeImmutable($admin['tnmt_time']))->add(new \DateInterval("PT{$second}S"))->format('Y-m-d H:i:s'); $gameStor->tnmt_time = Util::toInt($admin['tnmt_time']) + $clock->ticksFromSeconds($second);
} }
function getTournamentTermText(int $turnTerm) function getTournamentTermText(int $turnTerm)
{ {
$turnTerm = abs($turnTerm);
$term = calcTournamentTerm($turnTerm); $term = calcTournamentTerm($turnTerm);
if ($term % 60 === 0) { if ($term % 60 === 0) {
@@ -162,7 +160,8 @@ function getTournamentTime()
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
list($tnmt, $tnmt_time) = $gameStor->getValuesAsArray(['tournament', 'tnmt_time']); list($tnmt, $tnmt_time) = $gameStor->getValuesAsArray(['tournament', 'tnmt_time']);
$dt = substr($tnmt_time, 11, 5); $clock = GameClock::fromStorage($gameStor);
$dt = substr($clock->formatTick(Util::toInt($tnmt_time)), 11, 5);
switch ($tnmt) { switch ($tnmt) {
case 1: case 1:
$tnmt = "개막시간 {$dt}"; $tnmt = "개막시간 {$dt}";
@@ -286,9 +285,11 @@ function startTournament($type)
$admin = $gameStor->getValues(['year', 'month', 'turnterm']); $admin = $gameStor->getValues(['year', 'month', 'turnterm']);
$turnTerm = $admin['turnterm']; $turnTerm = $admin['turnterm'];
$unit = calcTournamentTerm($turnTerm); $unit = calcTournamentTerm($turnTerm);
$clock = GameClock::fromStorage($gameStor);
$gameStor->tnmt_auto = true; $gameStor->tnmt_auto = true;
$gameStor->tnmt_time = (new \DateTimeImmutable())->add(new \DateInterval("PT{$unit}M"))->format('Y-m-d H:i:s'); // 기존 startTournament은 unit을 분으로 더하므로 그 계약을 유지합니다.
$gameStor->tnmt_time = $clock->nowTick() + $clock->ticksFromMinutes($unit);
$gameStor->tournament = 1; $gameStor->tournament = 1;
$gameStor->tnmt_type = $type; $gameStor->tnmt_type = $type;
$gameStor->last_tournament_betting_id = 0; $gameStor->last_tournament_betting_id = 0;
@@ -344,7 +345,7 @@ function startBetting($type)
{ {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
[$year, $month, $startyear] = $gameStor->getValuesAsArray(['year', 'month', 'startyear']); [$year, $month, $startyear, $turnterm] = $gameStor->getValuesAsArray(['year', 'month', 'startyear', 'turnterm']);
pushGlobalHistoryLog([ pushGlobalHistoryLog([
"<S>◆</>{$year}{$month}월:<B><b>【대회】</b></>우승자를 예상하는 <C>내기</>가 진행중입니다! 호사가의 참여를 기다립니다!" "<S>◆</>{$year}{$month}월:<B><b>【대회】</b></>우승자를 예상하는 <C>내기</>가 진행중입니다! 호사가의 참여를 기다립니다!"
], $year, $month); ], $year, $month);
-1
View File
@@ -54,7 +54,6 @@ if ($session->userGrade < 5 && !$allowReset) {
<label for="turnterm" class="col-sm-3 col-form-label">턴 시간(분)</label> <label for="turnterm" class="col-sm-3 col-form-label">턴 시간(분)</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div id="turnterm" class="btn-group btn-group-toggle" data-bs-toggle="buttons"> <div id="turnterm" class="btn-group btn-group-toggle" data-bs-toggle="buttons">
<input type="radio" class="btn-check" name="turnterm" id="turnterm_var60" value="-60" checked><label for="turnterm_var60" class="btn btn-secondary">가변60</label>
<input type="radio" class="btn-check" name="turnterm" id="turnterm_120" value="120"><label for="turnterm_120" class="btn btn-secondary">120</label> <input type="radio" class="btn-check" name="turnterm" id="turnterm_120" value="120"><label for="turnterm_120" class="btn btn-secondary">120</label>
<input type="radio" class="btn-check" name="turnterm" id="turnterm_60" value="60" checked><label for="turnterm_60" class="btn btn-secondary">60</label> <input type="radio" class="btn-check" name="turnterm" id="turnterm_60" value="60" checked><label for="turnterm_60" class="btn btn-secondary">60</label>
<input type="radio" class="btn-check" name="turnterm" id="turnterm_30" value="30"><label for="turnterm_30" class="btn btn-secondary">30</label> <input type="radio" class="btn-check" name="turnterm" id="turnterm_30" value="30"><label for="turnterm_30" class="btn btn-secondary">30</label>
+18 -23
View File
@@ -16,12 +16,10 @@ $session = Session::requireLogin([])->setReadOnly();
$userID = Session::getUserID(); $userID = Session::getUserID();
$oNow = new \DateTimeImmutable();
$now = $oNow->format('Y-m-d H:i:s');
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$oldGeneral = $db->queryFirstField('SELECT `no` FROM general WHERE `owner`=%i', $userID); $oldGeneral = $db->queryFirstField('SELECT `no` FROM general WHERE `owner`=%i', $userID);
if($oldGeneral !== null){ if($oldGeneral !== null){
@@ -36,7 +34,6 @@ list(
$turnterm, $turnterm,
$npcmode $npcmode
) = $gameStor->getValuesAsArray(['maxgeneral', 'turnterm', 'npcmode']); ) = $gameStor->getValuesAsArray(['maxgeneral', 'turnterm', 'npcmode']);
$turnterm = abs($turnterm);
if($npcmode!=1){ if($npcmode!=1){
Json::die([ Json::die([
@@ -45,15 +42,14 @@ if($npcmode!=1){
]); ]);
} }
$token = $db->queryFirstRow('SELECT * FROM select_npc_token WHERE `owner`=%i AND `valid_until`>=%s', $userID, $now); $token = $db->queryFirstRow('SELECT * FROM select_npc_token WHERE `owner`=%i AND `valid_until`>=%i', $userID, $now);
$pickResult = []; $pickResult = [];
if($token && $refresh){ if($token && $refresh){
$pickMoreFrom = (new \DateTime($token['pick_more_from']))->getTimestamp(); $pickMoreFrom = Util::toInt($token['pick_more_from']);
$nowT = $oNow->getTimestamp();
if($nowT >= $pickMoreFrom){ if($now >= $pickMoreFrom){
$oldPickResult = Json::decode($token['pick_result']); $oldPickResult = Json::decode($token['pick_result']);
foreach($keepResult as $keepId){ foreach($keepResult as $keepId){
@@ -76,15 +72,14 @@ if($token && $refresh){
} }
if($token && !$refresh){ if($token && !$refresh){
$pickMoreFrom = (new \DateTime($token['pick_more_from']))->getTimestamp(); $pickMoreFrom = Util::toInt($token['pick_more_from']);
$nowT = $oNow->getTimestamp();
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'pick'=>Json::decode($token['pick_result']), 'pick'=>Json::decode($token['pick_result']),
'pickMoreFrom'=>$token['pick_more_from'], 'pickMoreFrom'=>$clock->formatTick($pickMoreFrom),
'pickMoreSeconds'=>$pickMoreFrom-$nowT, 'pickMoreSeconds'=>intdiv($pickMoreFrom - $now, $clock->ticksPerSecond()),
'validUntil'=>$token['valid_until'] 'validUntil'=>$clock->formatTick(Util::toInt($token['valid_until']))
]); ]);
} }
@@ -101,7 +96,7 @@ foreach($db->query('SELECT `no`, `name`, leadership, strength, intel, nation, im
$weight[$general['no']] = pow($allStat, 1.5); $weight[$general['no']] = pow($allStat, 1.5);
} }
foreach($db->queryFirstColumn('SELECT pick_result FROM select_npc_token WHERE `owner`!=%i AND valid_until >=%s', $userID, $now) as $reserved){ foreach($db->queryFirstColumn('SELECT pick_result FROM select_npc_token WHERE `owner`!=%i AND valid_until >=%i', $userID, $now) as $reserved){
$reserved = Json::decode($reserved); $reserved = Json::decode($reserved);
foreach(array_keys($reserved) as $reservedNPC){ foreach(array_keys($reserved) as $reservedNPC){
if(key_exists($reservedNPC, $weight)){ if(key_exists($reservedNPC, $weight)){
@@ -132,8 +127,8 @@ $newNonce = random_int(0, 0xfffffff);
$validSecond = max(VALID_SECOND, $turnterm*40); $validSecond = max(VALID_SECOND, $turnterm*40);
$pickMoreSecond = max(PICK_MORE_SECOND, Util::round(pow($turnterm, 0.672)*8)); $pickMoreSecond = max(PICK_MORE_SECOND, Util::round(pow($turnterm, 0.672)*8));
$validUntil = $oNow->add(new \DateInterval(sprintf('PT%dS', $validSecond))); $validUntil = $now + $clock->ticksFromSeconds($validSecond);
$pickMoreFrom = $oNow->add(new \DateInterval(sprintf('PT%dS', $pickMoreSecond))); $pickMoreFrom = $now + $clock->ticksFromSeconds($pickMoreSecond);
$db->delete('select_npc_token', 'valid_until < %s', $now); $db->delete('select_npc_token', 'valid_until < %s', $now);
@@ -141,8 +136,8 @@ $inserted = 0;
if($token){ if($token){
$db->update('select_npc_token', [ $db->update('select_npc_token', [
'valid_until'=>$validUntil->format('Y-m-d H:i:s'), 'valid_until'=>$validUntil,
'pick_more_from'=>$pickMoreFrom->format('Y-m-d H:i:s'), 'pick_more_from'=>$pickMoreFrom,
'pick_result'=>Json::encode($pickResult), 'pick_result'=>Json::encode($pickResult),
'nonce'=>$newNonce 'nonce'=>$newNonce
], 'owner = %i AND nonce = %i', $userID, $token['nonce']); ], 'owner = %i AND nonce = %i', $userID, $token['nonce']);
@@ -153,8 +148,8 @@ if($token){
else{ else{
$db->insertIgnore('select_npc_token', [ $db->insertIgnore('select_npc_token', [
'owner'=>$userID, 'owner'=>$userID,
'valid_until'=>$validUntil->format('Y-m-d H:i:s'), 'valid_until'=>$validUntil,
'pick_more_from'=>'2000-01-01 01:00:00', 'pick_more_from'=>$now,
'pick_result'=>Json::encode($pickResult), 'pick_result'=>Json::encode($pickResult),
'nonce'=>$newNonce 'nonce'=>$newNonce
]); ]);
@@ -174,7 +169,7 @@ if($inserted === 0){
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'pick'=>$pickResult, 'pick'=>$pickResult,
'pickMoreFrom'=>($inserted===-1)?$pickMoreFrom->format('Y-m-d H:i:s'):'2000-01-01 01:00:00', 'pickMoreFrom'=>$clock->formatTick(($inserted===-1)?$pickMoreFrom:$now),
'pickMoreSeconds'=>($inserted===-1)?$pickMoreSecond:0, 'pickMoreSeconds'=>($inserted===-1)?$pickMoreSecond:0,
'validUntil'=>$validUntil->format('Y-m-d H:i:s') 'validUntil'=>$clock->formatTick($validUntil)
]); ]);
+26 -10
View File
@@ -10,7 +10,23 @@ function sortTokens(&$tokens){
}); });
} }
function putInfoText(&$info){ function putInfoText(&$info, ?array $currentTargetEnv){
if (($info['event100Growth'] ?? false) === true) {
if ($currentTargetEnv === null) {
$displayStats = CentennialAllStarGrowthService::calculateUserInitialStats($info);
$info['selectionStatLabel'] = '시작 능력치';
} else {
$displayStats = CentennialAllStarGrowthService::calculateUserCurrentTargetStats(
$info,
$currentTargetEnv
);
$info['selectionStatLabel'] = '현재 변경 기준 능력치';
}
$info['selectionLeadership'] = $displayStats['leadership'];
$info['selectionStrength'] = $displayStats['strength'];
$info['selectionIntel'] = $displayStats['intel'];
}
if(key_exists('specialDomestic', $info)){ if(key_exists('specialDomestic', $info)){
$class = buildGeneralSpecialDomesticClass($info['specialDomestic']); $class = buildGeneralSpecialDomesticClass($info['specialDomestic']);
$info['specialDomesticName'] = $class->getName(); $info['specialDomesticName'] = $class->getName();
@@ -27,14 +43,13 @@ function putInfoText(&$info){
$session = Session::requireLogin([])->setReadOnly(); $session = Session::requireLogin([])->setReadOnly();
$userID = Session::getUserID(); $userID = Session::getUserID();
$oNow = new \DateTimeImmutable();
$now = $oNow->format('Y-m-d H:i:s');
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$npcmode = $gameStor->getValue('npcmode'); $eventEnv = $gameStor->getValues(['npcmode', 'startyear', 'year', 'month']);
$npcmode = $eventEnv['npcmode'];
if($npcmode!=2){ if($npcmode!=2){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
@@ -43,6 +58,7 @@ if($npcmode!=2){
} }
$rawGeneral = $db->queryFirstRow('SELECT no, aux FROM general WHERE `owner` = %i', $userID); $rawGeneral = $db->queryFirstRow('SELECT no, aux FROM general WHERE `owner` = %i', $userID);
$currentTargetEnv = $rawGeneral ? $eventEnv : null;
if($rawGeneral){ if($rawGeneral){
$generalAux = Json::decode($rawGeneral['aux']); $generalAux = Json::decode($rawGeneral['aux']);
if(key_exists('next_change', $generalAux)&& $generalAux['next_change'] > $now){ if(key_exists('next_change', $generalAux)&& $generalAux['next_change'] > $now){
@@ -62,7 +78,7 @@ if($tokens){
foreach($tokens as $token){ foreach($tokens as $token){
$valid_until = $token['reserved_until']; $valid_until = $token['reserved_until'];
$info = Json::decode($token['info']); $info = Json::decode($token['info']);
putInfoText($info); putInfoText($info, $currentTargetEnv);
$info['uniqueName'] = $token['unique_name']; $info['uniqueName'] = $token['unique_name'];
$pick[] = $info; $pick[] = $info;
} }
@@ -70,7 +86,7 @@ if($tokens){
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'pick'=>$pick, 'pick'=>$pick,
'validUntil'=>$valid_until 'validUntil'=>$clock->formatTick(Util::toInt($valid_until))
]); ]);
} }
@@ -83,12 +99,12 @@ $valid_until = null;
foreach(pickGeneralFromPool($db, $rng, $userID, 14) as $pickObj){ foreach(pickGeneralFromPool($db, $rng, $userID, 14) as $pickObj){
$valid_until = $pickObj->getValidUntil(); $valid_until = $pickObj->getValidUntil();
$info = $pickObj->getInfo(); $info = $pickObj->getInfo();
putInfoText($info); putInfoText($info, $currentTargetEnv);
$pick[] = $info; $pick[] = $info;
} }
sortTokens($pick);//좀 무식하지만.. sortTokens($pick);//좀 무식하지만..
Json::die([ Json::die([
'result'=>true, 'result'=>true,
'pick'=>$pick, 'pick'=>$pick,
'validUntil'=>$valid_until 'validUntil'=>$valid_until === null ? null : $clock->formatTick(Util::toInt($valid_until))
]); ]);
+1 -1
View File
@@ -211,7 +211,7 @@ try{
$show_img_level, $show_img_level,
!!$tournament_trig, !!$tournament_trig,
$join_mode, $join_mode,
TimeUtil::now(), TimeUtil::format(GameClock::readWallTime(), false),
$autorun_user $autorun_user
)); ));
} }
+2 -2
View File
@@ -24,8 +24,8 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$rootDB = RootDB::db(); $rootDB = RootDB::db();
$oNow = new \DateTimeImmutable(); $clock = GameClock::fromStorage($gameStor);
$now = $oNow->format('Y-m-d H:i:s'); $now = $clock->nowTick();
$member = $rootDB->queryFirstRow('SELECT `name`, `penalty` FROM member WHERE `NO`=%i', $userID); $member = $rootDB->queryFirstRow('SELECT `name`, `penalty` FROM member WHERE `NO`=%i', $userID);
if(!$member){ if(!$member){
+86 -10
View File
@@ -8,10 +8,20 @@ WebUtil::requireAJAX();
$pick = Util::getPost('pick'); $pick = Util::getPost('pick');
$leadership = Util::getPost('leadership', 'int', GameConst::$defaultStatMin); $leadership = Util::getPost('leadership', 'int', GameConst::$defaultStatMin);
$strength = Util::getPost('leadership', 'int', GameConst::$defaultStatMin); $isCentennialAllStar = CentennialAllStarGrowthService::isActive();
$intel = Util::getPost('leadership', 'int', GameConst::$defaultStatMin); $strength = Util::getPost(
$isCentennialAllStar ? 'strength' : 'leadership',
'int',
GameConst::$defaultStatMin
);
$intel = Util::getPost(
$isCentennialAllStar ? 'intel' : 'leadership',
'int',
GameConst::$defaultStatMin
);
$personal = Util::getPost('personal', 'string', null); $personal = Util::getPost('personal', 'string', null);
$use_own_picture = Util::getPost('use_own_picture', 'bool', false); $use_own_picture = Util::getPost('use_own_picture', 'bool', false);
$pictureSource = Util::getPost('picture_source', 'string', 'selected');
if(!$pick){ if(!$pick){
@@ -27,8 +37,8 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$rootDB = RootDB::db(); $rootDB = RootDB::db();
$oNow = new \DateTimeImmutable(); $clock = GameClock::fromStorage($gameStor);
$now = $oNow->format('Y-m-d H:i:s'); $now = $clock->nowTick();
$hasGeneralID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID); $hasGeneralID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID);
if($hasGeneralID){ if($hasGeneralID){
@@ -60,13 +70,36 @@ if(!$selectInfo){
} }
$selectInfo = Json::decode($selectInfo); $selectInfo = Json::decode($selectInfo);
$ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID); $ownerInfo = RootDB::db()->queryFirstRow(
'SELECT `name`,`picture`,`imgsvr`,`grade` FROM member WHERE `NO`=%i',
$userID
);
if(!$ownerInfo){ if(!$ownerInfo){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
'reason'=>'멤버 정보를 가져오지 못했습니다.' 'reason'=>'멤버 정보를 가져오지 못했습니다.'
]); ]);
} }
if ($isCentennialAllStar) {
if (!in_array($pictureSource, ['selected', 'own'], true)) {
Json::die([
'result' => false,
'reason' => '올바르지 않은 전콘 선택입니다.',
]);
}
if ($pictureSource === 'own') {
$canUseOwnPicture = in_array('picture', GameConst::$generalPoolAllowOption, true)
&& $env['show_img_level'] >= 1
&& $ownerInfo['grade'] >= 1
&& $ownerInfo['picture'] !== '';
if (!$canUseOwnPicture) {
Json::die([
'result' => false,
'reason' => '사용할 수 있는 내 전콘이 없습니다.',
]);
}
}
}
$gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2'); $gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2');
@@ -80,12 +113,25 @@ if ($gencount >= $maxgeneral) {
$poolClass = getGeneralPoolClass(GameConst::$targetGeneralPool); $poolClass = getGeneralPoolClass(GameConst::$targetGeneralPool);
/** @var AbsGeneralPool */ /** @var AbsGeneralPool */
$pickedGeneral = new $poolClass($db, $selectInfo, $now); if ($isCentennialAllStar) {
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'selectPickedGeneral',
$userID,
$pick
)));
$pickedGeneral = new $poolClass($db, $rng, $selectInfo, $now);
} else {
$pickedGeneral = new $poolClass($db, $selectInfo, $now);
}
$builder = $pickedGeneral->getGeneralBuilder(); $builder = $pickedGeneral->getGeneralBuilder();
if ($isCentennialAllStar) {
CentennialAllStarGrowthService::prepareInitialUser($builder, $selectInfo);
}
foreach(GameConst::$generalPoolAllowOption as $allowOption){ foreach(GameConst::$generalPoolAllowOption as $allowOption){
if($allowOption == 'stat'){ if($allowOption == 'stat' && !$isCentennialAllStar){
$leadership = Util::valueFit($leadership, GameConst::$defaultStatMin, GameConst::$defaultStatMax); $leadership = Util::valueFit($leadership, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
$strength = Util::valueFit($strength, GameConst::$defaultStatMin, GameConst::$defaultStatMax); $strength = Util::valueFit($strength, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
$intel = Util::valueFit($intel, GameConst::$defaultStatMin, GameConst::$defaultStatMax); $intel = Util::valueFit($intel, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
@@ -98,14 +144,23 @@ foreach(GameConst::$generalPoolAllowOption as $allowOption){
} }
$builder->setStat($leadership, $strength, $intel); $builder->setStat($leadership, $strength, $intel);
} }
else if($allowOption == 'picture' && $use_own_picture){ else if(
$allowOption == 'picture'
&& (
(!$isCentennialAllStar && $use_own_picture)
|| ($isCentennialAllStar && $pictureSource === 'own')
)
){
$builder->setPicture($ownerInfo['imgsvr'], $ownerInfo['picture']); $builder->setPicture($ownerInfo['imgsvr'], $ownerInfo['picture']);
} }
else if($allowOption == 'ego'){ else if($allowOption == 'ego'){
if(!$personal || $personal == 'Random'){ if(!$personal || $personal == 'Random'){
$personal = Util::choiceRandom(GameConst::$availablePersonality); $personal = Util::choiceRandom(GameConst::$availablePersonality);
} }
if(!array_search($personal, GameConst::$availablePersonality)){ $invalidPersonal = $isCentennialAllStar
? !in_array($personal, GameConst::$availablePersonality, true)
: !array_search($personal, GameConst::$availablePersonality);
if($invalidPersonal){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
'reason'=>'올바르지 않은 성격입니다.' 'reason'=>'올바르지 않은 성격입니다.'
@@ -121,8 +176,29 @@ $builder->setOwner($userID);
$builder->setOwnerName($userNick); $builder->setOwnerName($userNick);
$builder->setKillturn(5); $builder->setKillturn(5);
$builder->setNPCType(0); $builder->setNPCType(0);
$builder->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * abs($env['turnterm']))); $builder->setAuxVar('next_change', $now + GameClock::TICKS_PER_TURN * 12);
$builder->setAuxVar(
'prestart_delete_after',
addTurn($now, $env['turnterm'], GameConst::$minTurnDieOnPrestart)
);
$builder->fillRemainSpecAsZero($env); $builder->fillRemainSpecAsZero($env);
if ($isCentennialAllStar) {
$candidateCities = $db->queryFirstColumn(
'SELECT city FROM city WHERE level >= 5 AND level <= 6 AND nation = 0'
);
if (!$candidateCities) {
$candidateCities = $db->queryFirstColumn(
'SELECT city FROM city WHERE level >= 5 AND level <= 6'
);
}
if (!$candidateCities) {
Json::die([
'result' => false,
'reason' => '장수를 생성할 소·중성이 없습니다.',
]);
}
$builder->setCityID($rng->choice($candidateCities));
}
$builder->build($env); $builder->build($env);
$generalID = $builder->getGeneralID(); $generalID = $builder->getGeneralID();
if(!$generalID){ if(!$generalID){
+3 -2
View File
@@ -19,6 +19,7 @@ if(!class_exists('\\sammo\\DB')){
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
if(file_exists(__DIR__.'/.htaccess')){ if(file_exists(__DIR__.'/.htaccess')){
$reserved = $db->queryFirstRow( $reserved = $db->queryFirstRow(
@@ -75,8 +76,8 @@ $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['starttime'] = substr($admin['opentime'], 5, 11); $admin['starttime'] = substr($clock->formatTick(Util::toInt($admin['opentime'])), 5, 11);
$admin['turntime'] = substr($admin['turntime'], 5, 11); $admin['turntime'] = substr($clock->formatTick(Util::toInt($admin['turntime'])), 5, 11);
unset($admin['npcmode']); unset($admin['npcmode']);
unset($admin['maxgeneral']); unset($admin['maxgeneral']);
unset($admin['turnterm']); unset($admin['turnterm']);
+1 -1
View File
@@ -60,7 +60,7 @@ $me->setAuxVar('use_auto_nation_turn', $use_auto_nation_turn);
$me->setVar('tnmt', $tnmt); $me->setVar('tnmt', $tnmt);
if ($me->getNPCType() == 1 && $detachNPC) { if ($me->getNPCType() == 1 && $detachNPC) {
$turnterm = abs($gameStor->turnterm); $turnterm = $gameStor->turnterm;
if ($turnterm < 10) { if ($turnterm < 10) {
$targetKillTurn = 30 / $turnterm; $targetKillTurn = 30 / $turnterm;
+2 -1
View File
@@ -77,7 +77,8 @@ $month = $query['month'];
$repeatCnt = $query['repeatCnt']; $repeatCnt = $query['repeatCnt'];
$rawAttacker = $query['attackerGeneral']; $rawAttacker = $query['attackerGeneral'];
$rawAttacker['turntime'] = TimeUtil::now(); $battleClock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$rawAttacker['turntime'] = $battleClock->formatTick($battleClock->nowTick());
$rawAttackerCity = $query['attackerCity']; $rawAttackerCity = $query['attackerCity'];
$rawAttackerNation = $query['attackerNation']; $rawAttackerNation = $query['attackerNation'];
+79 -28
View File
@@ -7,6 +7,7 @@ include "func.php";
WebUtil::requireAJAX(); WebUtil::requireAJAX();
$pick = Util::getPost('pick'); $pick = Util::getPost('pick');
$pictureSource = Util::getPost('picture_source', 'string', 'selected');
if(!$pick){ if(!$pick){
Json::die([ Json::die([
@@ -21,8 +22,8 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$rootDB = RootDB::db(); $rootDB = RootDB::db();
$oNow = new \DateTimeImmutable(); $clock = GameClock::fromStorage($gameStor);
$now = $oNow->format('Y-m-d H:i:s'); $now = $clock->nowTick();
$generalID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID); $generalID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID);
if(!$generalID){ if(!$generalID){
@@ -35,11 +36,20 @@ if(!$generalID){
list( list(
$year, $year,
$month, $month,
$startYear,
$maxgeneral, $maxgeneral,
$npcmode, $npcmode,
$turnterm $turnterm,
) = $gameStor->getValuesAsArray(['year', 'month', 'maxgeneral', 'npcmode', 'turnterm']); $showImgLevel
$turnterm = abs($turnterm); ) = $gameStor->getValuesAsArray([
'year',
'month',
'startyear',
'maxgeneral',
'npcmode',
'turnterm',
'show_img_level',
]);
if($npcmode!=2){ if($npcmode!=2){
Json::die([ Json::die([
@@ -56,7 +66,10 @@ if(!$info){
]); ]);
} }
$ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID); $ownerInfo = RootDB::db()->queryFirstRow(
'SELECT `name`,`picture`,`imgsvr`,`grade` FROM member WHERE `NO`=%i',
$userID
);
if(!$ownerInfo){ if(!$ownerInfo){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
@@ -65,6 +78,27 @@ if(!$ownerInfo){
} }
$info = Json::decode($info); $info = Json::decode($info);
$isCentennialAllStar = CentennialAllStarGrowthService::isActive();
if ($isCentennialAllStar) {
if (!in_array($pictureSource, ['current', 'own', 'selected'], true)) {
Json::die([
'result' => false,
'reason' => '올바르지 않은 전콘 선택입니다.',
]);
}
if ($pictureSource === 'own') {
$canUseOwnPicture = in_array('picture', GameConst::$generalPoolAllowOption, true)
&& $showImgLevel >= 1
&& $ownerInfo['grade'] >= 1
&& $ownerInfo['picture'] !== '';
if (!$canUseOwnPicture) {
Json::die([
'result' => false,
'reason' => '사용할 수 있는 내 전콘이 없습니다.',
]);
}
}
}
$generalObj = General::createObjFromDB($generalID); $generalObj = General::createObjFromDB($generalID);
@@ -102,35 +136,52 @@ $db->update('select_pool',[
'reserved_until'=>null, 'reserved_until'=>null,
], '(owner=%i or reserved_until < %s) AND general_id is NULL', $userID, $now); ], '(owner=%i or reserved_until < %s) AND general_id is NULL', $userID, $now);
if(key_exists('leadership', $info)){ if ($isCentennialAllStar) {
$generalObj->updateVar('leadership', $info['leadership']); CentennialAllStarGrowthService::prepareLegacyUserReselection($generalObj);
$generalObj->updateVar('strength', $info['strength']); CentennialAllStarGrowthService::applyTarget($generalObj, $info, [
$generalObj->updateVar('intel', $info['intel']); 'startyear' => $startYear,
'year' => $year,
'month' => $month,
]);
} else {
if(key_exists('leadership', $info)){
$generalObj->updateVar('leadership', $info['leadership']);
$generalObj->updateVar('strength', $info['strength']);
$generalObj->updateVar('intel', $info['intel']);
}
if(key_exists('dex', $info)){
$generalObj->updateVar('dex1', $info['dex'][0]);
$generalObj->updateVar('dex2', $info['dex'][1]);
$generalObj->updateVar('dex3', $info['dex'][2]);
$generalObj->updateVar('dex4', $info['dex'][3]);
$generalObj->updateVar('dex5', $info['dex'][4]);
}
if(key_exists('ego', $info)){
$generalObj->updateVar('personal', $info['ego']);
}
if(key_exists('specialDomestic', $info)){
$generalObj->updateVar('special', $info['specialDomestic']);
}
if(key_exists('specialWar', $info)){
$generalObj->updateVar('special2', $info['specialWar']);
}
} }
if(key_exists('picture', $info)){ if ($isCentennialAllStar) {
if ($pictureSource === 'own') {
$generalObj->updateVar('imgsvr', $ownerInfo['imgsvr']);
$generalObj->updateVar('picture', $ownerInfo['picture']);
} elseif ($pictureSource === 'selected' && key_exists('picture', $info)) {
$generalObj->updateVar('imgsvr', $info['imgsvr']);
$generalObj->updateVar('picture', $info['picture']);
}
} elseif(key_exists('picture', $info)){
$generalObj->updateVar('imgsvr', $info['imgsvr']); $generalObj->updateVar('imgsvr', $info['imgsvr']);
$generalObj->updateVar('picture', $info['picture']); $generalObj->updateVar('picture', $info['picture']);
} }
if(key_exists('generalName', $info)){ if(key_exists('generalName', $info)){
$generalObj->updateVar('name', $info['generalName']); $generalObj->updateVar('name', $info['generalName']);
} }
if(key_exists('dex', $info)){ $generalObj->setAuxVar('next_change', $now + GameClock::TICKS_PER_TURN * 12);
$generalObj->updateVar('dex1', $info['dex'][0]);
$generalObj->updateVar('dex2', $info['dex'][1]);
$generalObj->updateVar('dex3', $info['dex'][2]);
$generalObj->updateVar('dex4', $info['dex'][3]);
$generalObj->updateVar('dex5', $info['dex'][4]);
}
if(key_exists('ego', $info)){
$generalObj->updateVar('personal', $info['ego']);
}
if(key_exists('specialDomestic', $info)){
$generalObj->updateVar('special', $info['specialDomestic']);
}
if(key_exists('specialWar', $info)){
$generalObj->updateVar('special2', $info['specialWar']);
}
$generalObj->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $turnterm));
$userNick = $ownerInfo['name']; $userNick = $ownerInfo['name'];
$generalObj->setVar('owner_name', $userNick); $generalObj->setVar('owner_name', $userNick);
+3 -1
View File
@@ -11,9 +11,11 @@ $db = DB::db();
$updated = false; $updated = false;
$locked = false; $locked = false;
$lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked); $lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked);
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
Json::die([ Json::die([
'result' => true, 'result' => true,
'updated' => $updated, 'updated' => $updated,
'locked' => $locked, 'locked' => $locked,
'lastExecuted' => $lastExecuted, 'lastExecutedTick' => $lastExecuted,
'lastExecuted' => $clock->formatTick($lastExecuted, true),
]); ]);
+2 -2
View File
@@ -21,7 +21,7 @@ class BidUniqueAuction extends \sammo\BaseAPI
]) ])
->rule('int', 'amount') ->rule('int', 'amount')
->rule('int', 'auctionID') ->rule('int', 'auctionID')
->rule('boolean', 'extendCloseDate'); ->rule('boolean', 'extendCloseTick');
if (!$v->validate()) { if (!$v->validate()) {
return $v->errorStr(); return $v->errorStr();
@@ -38,7 +38,7 @@ class BidUniqueAuction extends \sammo\BaseAPI
{ {
$auctionID = $this->args['auctionID']; $auctionID = $this->args['auctionID'];
$amount = $this->args['amount']; $amount = $this->args['amount'];
$tryExtendCloseDate = $this->args['extendCloseDate'] ?? false; $tryExtendCloseDate = $this->args['extendCloseTick'] ?? false;
$generalID = $session->generalID; $generalID = $session->generalID;
$general = General::createObjFromDB($generalID); $general = General::createObjFromDB($generalID);
@@ -15,6 +15,8 @@ use sammo\General;
use sammo\Json; use sammo\Json;
use sammo\TimeUtil; use sammo\TimeUtil;
use sammo\Util; use sammo\Util;
use sammo\GameClock;
use sammo\KVStorage;
use function sammo\getAuctionLogRecent; use function sammo\getAuctionLogRecent;
@@ -33,12 +35,13 @@ class GetActiveResourceAuctionList extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{ {
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$buyRiceList = []; $buyRiceList = [];
$sellRiceList = []; $sellRiceList = [];
/** @var AuctionInfo[] */ /** @var AuctionInfo[] */
$auctions = array_map(fn ($raw) => AuctionInfo::fromArray($raw), $db->query( $auctions = array_map(fn ($raw) => AuctionInfo::fromArray($raw), $db->query(
'SELECT * FROM `ng_auction` WHERE `type` IN %ls AND `finished` = 0 ORDER BY `close_date` ASC', 'SELECT * FROM `ng_auction` WHERE `type` IN %ls AND `finished` = 0 ORDER BY `close_tick` ASC',
[ [
AuctionType::BuyRice->value, AuctionType::BuyRice->value,
AuctionType::SellRice->value, AuctionType::SellRice->value,
@@ -87,8 +90,8 @@ class GetActiveResourceAuctionList extends \sammo\BaseAPI
'type' => $auction->type->value, 'type' => $auction->type->value,
'hostGeneralID' => $auction->hostGeneralID, 'hostGeneralID' => $auction->hostGeneralID,
'hostName' => $auction->detail->hostName, 'hostName' => $auction->detail->hostName,
'openDate' => TimeUtil::format($auction->openDate, false), 'openDate' => $clock->formatTick($auction->openTick),
'closeDate' => TimeUtil::format($auction->closeDate, false), 'closeDate' => $clock->formatTick($auction->closeTick),
'amount' => $auction->detail->amount, 'amount' => $auction->detail->amount,
'startBidAmount' => $auction->detail->startBidAmount, 'startBidAmount' => $auction->detail->startBidAmount,
'finishBidAmount' => $auction->detail->finishBidAmount, 'finishBidAmount' => $auction->detail->finishBidAmount,
@@ -14,6 +14,8 @@ use sammo\Enums\GeneralQueryMode;
use sammo\Enums\InheritanceKey; use sammo\Enums\InheritanceKey;
use sammo\InheritancePointManager; use sammo\InheritancePointManager;
use sammo\TimeUtil; use sammo\TimeUtil;
use sammo\GameClock;
use sammo\KVStorage;
use sammo\Validator; use sammo\Validator;
use sammo\General; use sammo\General;
@@ -42,6 +44,7 @@ class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{ {
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$generalID = $session->generalID; $generalID = $session->generalID;
$auctionID = $this->args['auctionID']; $auctionID = $this->args['auctionID'];
@@ -92,9 +95,11 @@ class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
'target' => $auction->target, 'target' => $auction->target,
'isCallerHost' => $auction->hostGeneralID === $generalID, 'isCallerHost' => $auction->hostGeneralID === $generalID,
'hostName' => $auction->detail->hostName, 'hostName' => $auction->detail->hostName,
'closeDate' => TimeUtil::format($auction->closeDate, false), 'closeDate' => $clock->formatTick($auction->closeTick),
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt, 'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
'availableLatestBidCloseDate' => TimeUtil::format($auction->detail->availableLatestBidCloseDate, false), 'availableLatestBidCloseDate' => $auction->detail->availableLatestBidCloseTick === null
? null
: $clock->formatTick($auction->detail->availableLatestBidCloseTick),
], ],
'bidList' => $responseBid, 'bidList' => $responseBid,
'obfuscatedName' => $obfuscatedName, 'obfuscatedName' => $obfuscatedName,
@@ -12,6 +12,8 @@ use sammo\Enums\APIRecoveryType;
use sammo\Enums\AuctionType; use sammo\Enums\AuctionType;
use sammo\TimeUtil; use sammo\TimeUtil;
use sammo\Util; use sammo\Util;
use sammo\GameClock;
use sammo\KVStorage;
class GetUniqueItemAuctionList extends \sammo\BaseAPI class GetUniqueItemAuctionList extends \sammo\BaseAPI
{ {
@@ -28,12 +30,13 @@ class GetUniqueItemAuctionList extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{ {
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$generalID = $session->generalID; $generalID = $session->generalID;
/** @var AuctionInfo[] */ /** @var AuctionInfo[] */
$auctions = array_map(fn($raw)=>AuctionInfo::fromArray($raw), $db->query( $auctions = array_map(fn($raw)=>AuctionInfo::fromArray($raw), $db->query(
'SELECT * FROM `ng_auction` WHERE `type` = %s ORDER BY `close_date` ASC', 'SELECT * FROM `ng_auction` WHERE `type` = %s ORDER BY `close_tick` ASC',
AuctionType::UniqueItem->value AuctionType::UniqueItem->value
) ?? []); ) ?? []);
@@ -85,9 +88,11 @@ class GetUniqueItemAuctionList extends \sammo\BaseAPI
'target' => $auction->target, 'target' => $auction->target,
'isCallerHost' => $auction->hostGeneralID === $generalID, 'isCallerHost' => $auction->hostGeneralID === $generalID,
'hostName' => $auction->detail->hostName, 'hostName' => $auction->detail->hostName,
'closeDate' => TimeUtil::format($auction->closeDate, false), 'closeDate' => $clock->formatTick($auction->closeTick),
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt, 'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
'availableLatestBidCloseDate' => TimeUtil::format($auction->detail->availableLatestBidCloseDate, false), 'availableLatestBidCloseDate' => $auction->detail->availableLatestBidCloseTick === null
? null
: $clock->formatTick($auction->detail->availableLatestBidCloseTick),
'highestBid' => [ 'highestBid' => [
'generalName' => $highestBid->aux->generalName, 'generalName' => $highestBid->aux->generalName,
'amount' => $highestBid->amount, 'amount' => $highestBid->amount,
+5 -2
View File
@@ -7,9 +7,11 @@ use DateTimeInterface;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\GameConst; use sammo\GameConst;
use sammo\GameClock;
use sammo\Json; use sammo\Json;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\TimeUtil; use sammo\TimeUtil;
use sammo\Util;
use function sammo\cutTurn; use function sammo\cutTurn;
@@ -82,11 +84,12 @@ class GetReservedCommand extends \sammo\BaseAPI
return [ return [
'result' => true, 'result' => true,
'turnTime' => $turnTime, 'turnTimeTick' => Util::toInt($turnTime),
'turnTime' => GameClock::fromStorage($gameStor)->formatTick(Util::toInt($turnTime)),
'turnTerm' => $turnTerm, 'turnTerm' => $turnTerm,
'year' => $year, 'year' => $year,
'month' => $month, 'month' => $month,
'date' => TimeUtil::now(true), 'date' => GameClock::fromStorage($gameStor)->formatTick(GameClock::fromStorage($gameStor)->nowTick(), true),
'turn' => $commandList, 'turn' => $commandList,
'autorun_limit' => $generalAux['autorun_limit'] ?? null, 'autorun_limit' => $generalAux['autorun_limit'] ?? null,
]; ];
+24 -11
View File
@@ -11,7 +11,8 @@ use sammo\Session;
use sammo\General; use sammo\General;
use sammo\JosaUtil; use sammo\JosaUtil;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\TimeUtil; use sammo\GameClock;
use sammo\Util;
use function sammo\addTurn; use function sammo\addTurn;
use function sammo\increaseRefresh; use function sammo\increaseRefresh;
@@ -37,8 +38,14 @@ class DieOnPrestart extends \sammo\BaseAPI
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['turnterm', 'opentime', 'turntime', 'year', 'month']); $gameStor->cacheValues(['turnterm', 'opentime', 'turntime', 'year', 'month']);
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$general = $db->queryFirstRow('SELECT no,name,nation,owner_name,npc FROM general WHERE owner=%i AND npc = 0', $userID); $general = $db->queryFirstRow('SELECT no,name,nation,owner_name,npc FROM general WHERE owner=%i AND npc = 0', $userID);
if (!$general) {
return '장수가 없습니다';
}
$lastRefresh = $db->queryFirstField( $lastRefresh = $db->queryFirstField(
'SELECT %b FROM general_access_log WHERE %b = %i', 'SELECT %b FROM general_access_log WHERE %b = %i',
GeneralAccessLogColumn::lastRefresh->value, GeneralAccessLogColumn::lastRefresh->value,
@@ -46,8 +53,9 @@ class DieOnPrestart extends \sammo\BaseAPI
$general['no'] $general['no']
); );
if (!$general) { $generalObj = General::createObjFromDB($general['no']);
return '장수가 없습니다'; if ($generalObj instanceof DummyGeneral) {
trigger_error("올바르지 않은 삭제 프로세스 $userID", E_USER_WARNING);
} }
increaseRefresh("장수 삭제", 1); increaseRefresh("장수 삭제", 1);
@@ -61,16 +69,21 @@ class DieOnPrestart extends \sammo\BaseAPI
return '이미 국가에 소속되어있습니다.'; return '이미 국가에 소속되어있습니다.';
} }
//서버 가오픈시 할 수 있는 행동 $targetTime = $generalObj->getAuxVar('prestart_delete_after');
$targetTime = addTurn($lastRefresh, $gameStor->turnterm, GameConst::$minTurnDieOnPrestart); if (!is_int($targetTime)) {
if ($targetTime > TimeUtil::now()) { $targetTime = addTurn(
$targetTimeShort = substr($targetTime, 0, 19); $lastRefresh === null ? $nowTick : Util::toInt($lastRefresh),
return "아직 삭제할 수 없습니다. {$targetTimeShort} 부터 가능합니다."; $gameStor->turnterm,
GameConst::$minTurnDieOnPrestart
);
$generalObj->setAuxVar('prestart_delete_after', $targetTime);
$generalObj->applyDB($db);
} }
$generalObj = General::createObjFromDB($general['no']); //서버 가오픈시 할 수 있는 행동
if ($generalObj instanceof DummyGeneral) { if ($targetTime > $nowTick) {
trigger_error("올바르지 않은 삭제 프로세스 $userID", E_USER_WARNING); $targetTimeShort = $clock->formatTick($targetTime);
return "아직 삭제할 수 없습니다. {$targetTimeShort} 부터 가능합니다.";
} }
$generalName = $generalObj->getName(); $generalName = $generalObj->getName();
+15 -5
View File
@@ -14,6 +14,7 @@ use sammo\Enums\GeneralColumn;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\Enums\RankColumn; use sammo\Enums\RankColumn;
use sammo\GameConst; use sammo\GameConst;
use sammo\GameClock;
use sammo\General; use sammo\General;
use sammo\KVStorage; use sammo\KVStorage;
use sammo\LastTurn; use sammo\LastTurn;
@@ -105,6 +106,7 @@ class GetFrontInfo extends \sammo\BaseAPI
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$gameStor->cacheValues(['isunited', 'opentime', 'refresh']); $gameStor->cacheValues(['isunited', 'opentime', 'refresh']);
$lastHistoryID = $this->args['lastWorldHistoryID']; $lastHistoryID = $this->args['lastWorldHistoryID'];
@@ -158,6 +160,7 @@ class GetFrontInfo extends \sammo\BaseAPI
private function generateGlobalInfo(MeekroDB $db): array private function generateGlobalInfo(MeekroDB $db): array
{ {
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
[ [
$scenarioText, $extendedGeneral, $isFiction, $npcMode, $scenarioText, $extendedGeneral, $isFiction, $npcMode,
@@ -210,7 +213,8 @@ class GetFrontInfo extends \sammo\BaseAPI
'month' => $month, 'month' => $month,
'autorunUser' => $autorunUser, 'autorunUser' => $autorunUser,
'turnterm' => $turnterm, 'turnterm' => $turnterm,
'lastExecuted' => $lastExecuted, 'lastExecutedTick' => $lastExecuted,
'lastExecuted' => $clock->formatTick(Util::toInt($lastExecuted), true),
'lastVoteID' => $lastVoteID, 'lastVoteID' => $lastVoteID,
'develCost' => $develCost, 'develCost' => $develCost,
'noticeMsg' => $noticeMsg, 'noticeMsg' => $noticeMsg,
@@ -224,7 +228,8 @@ class GetFrontInfo extends \sammo\BaseAPI
'isLocked' => $isLocked, 'isLocked' => $isLocked,
'tournamentType' => $tournamentType, 'tournamentType' => $tournamentType,
'tournamentState' => $tournamentState, 'tournamentState' => $tournamentState,
'tournamentTime' => $tournamentTime, 'tournamentTimeTick' => $tournamentTime,
'tournamentTime' => $tournamentTime === null ? null : $clock->formatTick(Util::toInt($tournamentTime)),
'genCount' => $globalGenCount, 'genCount' => $globalGenCount,
'generalCntLimit' => $generalCntLimit, 'generalCntLimit' => $generalCntLimit,
'serverCnt' => $serverCnt, 'serverCnt' => $serverCnt,
@@ -361,6 +366,7 @@ class GetFrontInfo extends \sammo\BaseAPI
public function generateGeneralInfo(MeekroDB $db, General $general, array $rawNation): array public function generateGeneralInfo(MeekroDB $db, General $general, array $rawNation): array
{ {
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$permission = checkSecretPermission($general->getRaw()); $permission = checkSecretPermission($general->getRaw());
@@ -425,8 +431,12 @@ class GetFrontInfo extends \sammo\BaseAPI
'crew' => $general->getVar(GeneralColumn::crew), // number; 'crew' => $general->getVar(GeneralColumn::crew), // number;
'train' => $general->getVar(GeneralColumn::train), // number; 'train' => $general->getVar(GeneralColumn::train), // number;
'atmos' => $general->getVar(GeneralColumn::atmos), // number; 'atmos' => $general->getVar(GeneralColumn::atmos), // number;
'turntime' => $general->getVar(GeneralColumn::turntime), // string; 'turntimeTick' => $general->getTurnTick(), // number;
'recent_war' => $general->getVar(GeneralColumn::recent_war), // string; 'turntime' => $general->getTurnTime(), // string;
'recent_war_tick' => $general->getVar(GeneralColumn::recent_war), // number|null;
'recent_war' => $general->getVar(GeneralColumn::recent_war) === null
? null
: $clock->formatTick(Util::toInt($general->getVar(GeneralColumn::recent_war)), true), // string|null;
'horse' => $general->getVar(GeneralColumn::horse), // GameObjClassKey; 'horse' => $general->getVar(GeneralColumn::horse), // GameObjClassKey;
'weapon' => $general->getVar(GeneralColumn::weapon), // GameObjClassKey; 'weapon' => $general->getVar(GeneralColumn::weapon), // GameObjClassKey;
'book' => $general->getVar(GeneralColumn::book), // GameObjClassKey; 'book' => $general->getVar(GeneralColumn::book), // GameObjClassKey;
@@ -544,7 +554,7 @@ class GetFrontInfo extends \sammo\BaseAPI
'result' => false, 'result' => false,
'reason' => '접속 제한중입니다.', 'reason' => '접속 제한중입니다.',
'recovery' => APIRecoveryType::GameQuota, 'recovery' => APIRecoveryType::GameQuota,
'recovery_arg' => $general->getVar('turntime'), 'recovery_arg' => $general->getTurnTime(),
]; ];
} }
+17 -20
View File
@@ -11,6 +11,7 @@ use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralAccessLogColumn; use sammo\Enums\GeneralAccessLogColumn;
use sammo\Enums\RankColumn; use sammo\Enums\RankColumn;
use sammo\GameConst; use sammo\GameConst;
use sammo\GameClock;
use sammo\GameUnitConst; use sammo\GameUnitConst;
use sammo\General; use sammo\General;
use sammo\InheritancePointManager; use sammo\InheritancePointManager;
@@ -28,7 +29,6 @@ use sammo\UniqueConst;
use sammo\UserLogger; use sammo\UserLogger;
use sammo\Util; use sammo\Util;
use sammo\Validator; use sammo\Validator;
use sammo\VarTurn60;
use sammo\WebUtil; use sammo\WebUtil;
use function sammo\addTurn; use function sammo\addTurn;
@@ -166,6 +166,7 @@ class Join extends \sammo\BaseAPI
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['year', 'month', 'maxgeneral', 'scenario', 'show_img_level', 'block_general_create', 'turnterm', 'turntime', 'genius', 'npcmode']); $gameStor->cacheValues(['year', 'month', 'maxgeneral', 'scenario', 'show_img_level', 'block_general_create', 'turnterm', 'turntime', 'genius', 'npcmode']);
$clock = GameClock::fromStorage($gameStor);
########## 동일 정보 존재여부 확인. ########## ########## 동일 정보 존재여부 확인. ##########
$block_general_create = $gameStor->getValue('block_general_create'); $block_general_create = $gameStor->getValue('block_general_create');
@@ -223,7 +224,7 @@ class Join extends \sammo\BaseAPI
$userLogger = new UserLogger($userID, $admin['year'], $admin['month'], false); $userLogger = new UserLogger($userID, $admin['year'], $admin['month'], false);
$now = TimeUtil::now(false); $now = $clock->nowTick();
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize( $rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed, UniqueConst::$hiddenSeed,
'MakeGeneral', 'MakeGeneral',
@@ -356,30 +357,19 @@ class Join extends \sammo\BaseAPI
} }
if ($inheritTurntimeZone !== null) { if ($inheritTurntimeZone !== null) {
$turnterm = abs($admin['turnterm']); $inheritTurntime = $inheritTurntimeZone * $admin['turnterm'];
$inheritTurntime = $inheritTurntimeZone * $turnterm; $inheritTurntime += $rng->nextRangeInt(0, Util::clamp($admin['turnterm'] - 1, 0));
$inheritTurntime += $rng->nextRangeInt(0, Util::clamp($turnterm - 1, 0));
$userLogger->push(sprintf("턴 시간 %02d:%02d 로 지정", intdiv($inheritTurntime, 60), $inheritTurntime % 60), "inheritPoint"); $userLogger->push(sprintf("턴 시간 %02d:%02d 로 지정", intdiv($inheritTurntime, 60), $inheritTurntime % 60), "inheritPoint");
$inheritTurntime += $rng->nextRangeInt(0, 999999) / 1000000; $inheritTurnMicrosecond = $rng->nextRangeInt(0, 999999);
$turntime = cutTurn(Util::toInt($admin['turntime']), $admin['turnterm'])
$turntime = new \DateTimeImmutable(cutTurn($admin['turntime'], $turnterm)); + $clock->ticksFromSeconds($inheritTurntime)
$turntime = $turntime->add(TimeUtil::secondsToDateInterval($inheritTurntime)); + intdiv($inheritTurnMicrosecond * $clock->ticksPerSecond(), 1_000_000);
$turntime = TimeUtil::format($turntime, true);
} else { } else {
$turntime = getRandTurn($rng, abs($admin['turnterm']), new \DateTimeImmutable($admin['turntime'])); $turntime = getRandTurn($rng, $admin['turnterm'], Util::toInt($admin['turntime']));
} }
if ($admin['turnterm'] == -60){
$baseObj = VarTurn60::fromDatetime(new \DateTimeImmutable($admin['turntime']));
$userTurnObj = VarTurn60::fromDatetime(new \DateTimeImmutable($turntime));
if($baseObj->turnIdx != $userTurnObj->turnIdx){
$turntime = $userTurnObj->addTurn(-1)->toDateStr();
}
}
$now = TimeUtil::now(true);
if ($now >= $turntime) { if ($now >= $turntime) {
$turntime = addTurn($turntime, $admin['turnterm']); $turntime = addTurn($turntime, $admin['turnterm']);
} }
@@ -444,6 +434,13 @@ class Join extends \sammo\BaseAPI
'specage2' => $specage2, 'specage2' => $specage2,
'special2' => $special2, 'special2' => $special2,
'penalty' => Json::encode($penalty), 'penalty' => Json::encode($penalty),
'aux' => Json::encode([
'prestart_delete_after' => addTurn(
$now,
$admin['turnterm'],
GameConst::$minTurnDieOnPrestart
),
]),
]); ]);
$generalID = $db->insertId(); $generalID = $db->insertId();
$db->insert('general_access_log', [ $db->insert('general_access_log', [
+5 -1
View File
@@ -8,6 +8,8 @@ use DateTimeInterface;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\TurnExecutionHelper; use sammo\TurnExecutionHelper;
use sammo\UniqueConst; use sammo\UniqueConst;
use sammo\GameClock;
use sammo\KVStorage;
class ExecuteEngine extends \sammo\BaseAPI class ExecuteEngine extends \sammo\BaseAPI
{ {
@@ -35,11 +37,13 @@ class ExecuteEngine extends \sammo\BaseAPI
$updated = false; $updated = false;
$locked = false; $locked = false;
$lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked); $lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked);
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
return [ return [
'result' => true, 'result' => true,
'updated' => $updated, 'updated' => $updated,
'locked' => $locked, 'locked' => $locked,
'lastExecuted' => $lastExecuted, 'lastExecutedTick' => $lastExecuted,
'lastExecuted' => $clock->formatTick($lastExecuted, true),
]; ];
} }
} }
+3 -1
View File
@@ -3,6 +3,7 @@
namespace sammo\API\Global; namespace sammo\API\Global;
use sammo\DB; use sammo\DB;
use sammo\GameClock;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Json; use sammo\Json;
use sammo\KVStorage; use sammo\KVStorage;
@@ -145,7 +146,8 @@ class GeneralList extends \sammo\BaseAPI
if (static::$withToken) { if (static::$withToken) {
$now = (new \DateTimeImmutable())->format('Y-m-d H:i:s'); $gameStor = KVStorage::getStorage($db, 'game_env');
$now = GameClock::fromStorage($gameStor)->nowTick();
$tokens = []; $tokens = [];
foreach ($db->query('SELECT * FROM select_npc_token WHERE `valid_until`>=%s', $now) as $token) { foreach ($db->query('SELECT * FROM select_npc_token WHERE `valid_until`>=%s', $now) as $token) {
$validUntil = $token['valid_until']; $validUntil = $token['valid_until'];
@@ -4,6 +4,7 @@ namespace sammo\API\InheritAction;
use sammo\Session; use sammo\Session;
use DateTimeInterface; use DateTimeInterface;
use sammo\CentennialAllStarGrowthService;
use sammo\DB; use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\RankColumn; use sammo\Enums\RankColumn;
@@ -98,6 +99,10 @@ class ResetStat extends \sammo\BaseAPI
return 'NPC는 능력치 초기화를 할 수 없습니다.'; return 'NPC는 능력치 초기화를 할 수 없습니다.';
} }
if (!CentennialAllStarGrowthService::isStatResetAllowed()) {
return '100기 올스타 장수는 능력치 초기화를 사용할 수 없습니다.';
}
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
@@ -62,15 +62,13 @@ class ResetTurnTime extends \sammo\BaseAPI
} }
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = abs($gameStor->getValue('turnterm')); $turnTerm = $gameStor->getValue('turnterm');
$currTurnTime = new DateTimeImmutable($general->getTurnTime());
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize( $rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed, UniqueConst::$hiddenSeed,
'ResetTurnTime', 'ResetTurnTime',
$userID, $userID,
$general->getAuxVar('nextTurnTimeBase') ?? $general->getTurnTime() $general->getAuxVar('nextTurnTimeBase') ?? $general->getTurnTick()
))); )));
$afterTurn = $rng->nextFloat1() * $turnTerm * 60; $afterTurn = $rng->nextFloat1() * $turnTerm * 60;
+6 -2
View File
@@ -8,6 +8,7 @@ use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralLiteQueryMode; use sammo\Enums\GeneralLiteQueryMode;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\General; use sammo\General;
use sammo\GameClock;
use sammo\GeneralLite; use sammo\GeneralLite;
use sammo\Session; use sammo\Session;
use sammo\Util; use sammo\Util;
@@ -264,8 +265,11 @@ class GeneralList extends \sammo\BaseAPI
'honorText' => fn ($rawGeneral) => getHonor($rawGeneral['experience']), 'honorText' => fn ($rawGeneral) => getHonor($rawGeneral['experience']),
'dedLevelText' => fn ($rawGeneral) => getDedLevelText($rawGeneral['dedlevel']), 'dedLevelText' => fn ($rawGeneral) => getDedLevelText($rawGeneral['dedlevel']),
//'0000-00-00 11:23'; //'0000-00-00 11:23';
'turntime' => fn ($rawGeneral) => substr($rawGeneral['turntime'], 0, 19), 'turntime' => fn ($rawGeneral) => GameClock::fromStorage($gameStor)
'recent_war' => fn ($rawGeneral) => substr($rawGeneral['recent_war'], 0, 19), ->formatTick(Util::toInt($rawGeneral['turntime'])),
'recent_war' => fn ($rawGeneral) => $rawGeneral['recent_war'] === null
? null
: GameClock::fromStorage($gameStor)->formatTick(Util::toInt($rawGeneral['recent_war'])),
'bill' => fn ($rawGeneral) => getBillByLevel($rawGeneral['dedlevel']), 'bill' => fn ($rawGeneral) => getBillByLevel($rawGeneral['dedlevel']),
'reservedCommand' => fn ($rawGeneral) => $reservedCommand[$rawGeneral['no']] ?? null, 'reservedCommand' => fn ($rawGeneral) => $reservedCommand[$rawGeneral['no']] ?? null,
'autorun_limit' => fn ($rawGeneral) => ($rawGeneral['aux'] ?? [])['autorun_limit'] ?? 0, 'autorun_limit' => fn ($rawGeneral) => ($rawGeneral['aux'] ?? [])['autorun_limit'] ?? 0,
@@ -8,6 +8,7 @@ use sammo\DB;
use sammo\Enums\APIRecoveryType; use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralQueryMode; use sammo\Enums\GeneralQueryMode;
use sammo\GameConst; use sammo\GameConst;
use sammo\GameClock;
use sammo\General; use sammo\General;
use sammo\Json; use sammo\Json;
use sammo\KVStorage; use sammo\KVStorage;
@@ -51,7 +52,8 @@ class GetReservedCommand extends \sammo\BaseAPI
$nationID = $me['nation']; $nationID = $me['nation'];
$limitState = checkLimit($me['refresh_score']); $limitState = checkLimit($me['refresh_score']);
if ($limitState >= 2) { if ($limitState >= 2) {
return "접속 제한중입니다. 1턴 이내에 너무 많은 갱신을 하셨습니다. (다음 갱신 가능 시각 : {$me['turntime']})"; $limitTime = GameClock::fromStorage($gameStor)->formatTick(Util::toInt($me['turntime']), true);
return "접속 제한중입니다. 1턴 이내에 너무 많은 갱신을 하셨습니다. (다음 갱신 가능 시각 : {$limitTime})";
} }
$permission = checkSecretPermission($me); $permission = checkSecretPermission($me);
+10 -6
View File
@@ -8,6 +8,11 @@ use sammo\Util;
abstract class AbsFromUserPool extends AbsGeneralPool{ abstract class AbsFromUserPool extends AbsGeneralPool{
protected static function getCandidateWeight(array $info, int $owner): int|float
{
return array_sum($info['dex'] ?? []);
}
public function occupyGeneralName(): bool public function occupyGeneralName(): bool
{ {
$generalID = $this->getGeneralBuilder()->getGeneralID(); $generalID = $this->getGeneralBuilder()->getGeneralID();
@@ -25,8 +30,9 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
} }
static public function pickGeneralFromPool(\MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix=null):array{ static public function pickGeneralFromPool(\MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix=null):array{
$oNow = new \DateTimeImmutable(); $gameStor = KVStorage::getStorage($db, 'game_env');
$now = $oNow->format('Y-m-d H:i:s'); $clock = GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$db->update('select_pool', [ $db->update('select_pool', [
'reserved_until'=>null, 'reserved_until'=>null,
@@ -36,17 +42,15 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
$pool = []; $pool = [];
foreach($db->query('SELECT id, unique_name, info FROM select_pool WHERE reserved_until IS NULL AND general_id IS NULL', $pickCnt) as $cand){ foreach($db->query('SELECT id, unique_name, info FROM select_pool WHERE reserved_until IS NULL AND general_id IS NULL', $pickCnt) as $cand){
$cand['info'] = Json::decode($cand['info']); $cand['info'] = Json::decode($cand['info']);
$dexTotal = array_sum($cand['info']['dex']); $pool[] = [$cand, static::getCandidateWeight($cand['info'], $owner)];
$pool[] = [$cand, $dexTotal];
} }
if(count($pool) < $pickCnt){ if(count($pool) < $pickCnt){
throw new \RuntimeException('pool 부족'); throw new \RuntimeException('pool 부족');
} }
$gameStor = KVStorage::getStorage($db, 'game_env');
$result = []; $result = [];
$validUntil = TimeUtil::nowAddMinutes(2 * abs($gameStor->turnterm)); $validUntil = $now + GameClock::TICKS_PER_TURN * 2;
while(count($result) < $pickCnt){ while(count($result) < $pickCnt){
$cand = $rng->choiceUsingWeightPair($pool); $cand = $rng->choiceUsingWeightPair($pool);
$poolID = $cand['id']; $poolID = $cand['id'];
+2 -2
View File
@@ -34,7 +34,7 @@ abstract class AbsGeneralPool{
* specialWar * specialWar
*/ */
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, string $validUntil) public function __construct(\MeekroDB $db, RandUtil $rng, array $info, int $validUntil)
{ {
$this->db = $db; $this->db = $db;
$this->info = $info; $this->info = $info;
@@ -92,7 +92,7 @@ abstract class AbsGeneralPool{
return $this->builder; return $this->builder;
} }
public function getValidUntil():string{ public function getValidUntil():int{
return $this->validUntil; return $this->validUntil;
} }
+59 -58
View File
@@ -146,40 +146,35 @@ abstract class Auction
return $this->info; return $this->info;
} }
public function shrinkCloseDate(?DateTimeInterface $date): ?string public function shrinkCloseTick(?int $tick): ?string
{ {
if ($date === null) {
$date = new DateTimeImmutable();
}
$this->info->closeDate = $date;
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$this->info->closeTick = $tick ?? $clock->nowTick();
$db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id); $db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id);
return null; return null;
} }
public function extendLatestBidCloseDate(?DateTimeInterface $date): ?string public function extendLatestBidCloseTick(?int $tick): ?string
{ {
if ($date === null) { if ($tick === null) {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = abs($gameStor->getValue('turnterm')); $clock = GameClock::fromStorage($gameStor);
$date = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( $turnTerm = $gameStor->getValue('turnterm');
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{ if ($this->info->detail->availableLatestBidCloseTick !== null && $tick < $this->info->detail->availableLatestBidCloseTick) {
$date = DateTimeImmutable::createFromInterface($date);
}
if ($this->info->detail->availableLatestBidCloseDate !== null && $date < $this->info->detail->availableLatestBidCloseDate) {
return '기간보다 짧습니다.'; return '기간보다 짧습니다.';
} }
$this->info->detail->availableLatestBidCloseDate = $date; $this->info->detail->availableLatestBidCloseTick = $tick;
return null; return null;
} }
public function extendCloseDate(DateTimeInterface $date, bool $force = false): ?string public function extendCloseTick(int $tick, bool $force = false): ?string
{ {
if (!$force) { if (!$force) {
if ($this->info->detail->remainCloseDateExtensionCnt === null) { if ($this->info->detail->remainCloseDateExtensionCnt === null) {
@@ -193,12 +188,11 @@ abstract class Auction
} }
} }
if ($date < $this->info->closeDate) { if ($tick < $this->info->closeTick) {
return '종료 기간보다 짧습니다.'; return '종료 기간보다 짧습니다.';
} }
$closeDate = DateTimeImmutable::createFromInterface($date); $this->info->closeTick = $tick;
$this->info->closeDate = $closeDate;
return null; return null;
} }
@@ -245,12 +239,13 @@ abstract class Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게.. //TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다. //TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$msg = new Message( $msg = new Message(
MessageType::private, MessageType::private,
$src, $src,
$dest, $dest,
$reason, $reason,
new DateTime(), DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick())),
new DateTime('9999-12-31'), new DateTime('9999-12-31'),
[] []
); );
@@ -275,7 +270,12 @@ abstract class Auction
$db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id); $db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id);
} }
private function bidInheritPoint(int $amount, \DateTimeImmutable $now, bool $tryExtendCloseDate): ?string private function bidInheritPoint(
int $amount,
int $nowTick,
\DateTimeImmutable $nowDate,
bool $tryExtendCloseDate,
): ?string
{ {
$db = DB::db(); $db = DB::db();
@@ -311,7 +311,7 @@ abstract class Auction
$general->getVar('owner'), $general->getVar('owner'),
$general->getID(), $general->getID(),
$amount, $amount,
$now, $nowDate,
new AuctionBidItemData( new AuctionBidItemData(
$general->getVar('owner_name'), $general->getVar('owner_name'),
$obfuscatedName, $obfuscatedName,
@@ -324,15 +324,16 @@ abstract class Auction
} }
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = abs($gameStor->getValue('turnterm')); $clock = GameClock::fromStorage($gameStor);
$turnTerm = $gameStor->getValue('turnterm');
if ($this->info->detail->availableLatestBidCloseDate !== null) { if ($this->info->detail->availableLatestBidCloseTick !== null) {
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval( $extendedCloseTick = $nowTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60 max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID)
)); );
if ($extendedCloseDate > $this->info->closeDate && $this->info->closeDate < $this->info->detail->availableLatestBidCloseDate) { if ($extendedCloseTick > $this->info->closeTick && $this->info->closeTick < $this->info->detail->availableLatestBidCloseTick) {
$this->extendCloseDate(min($extendedCloseDate, $this->info->detail->availableLatestBidCloseDate), true); $this->extendCloseTick(min($extendedCloseTick, $this->info->detail->availableLatestBidCloseTick), true);
$this->applyDB(); $this->applyDB();
} }
} }
@@ -356,12 +357,16 @@ abstract class Auction
return '경매가 이미 끝났습니다.'; return '경매가 이미 끝났습니다.';
} }
$now = new \DateTimeImmutable(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$nowDate = $clock->tickToDateTime($nowTick);
if ($auctionInfo->closeDate < $now) { if ($auctionInfo->closeTick < $nowTick) {
return '경매가 이미 끝났습니다.'; return '경매가 이미 끝났습니다.';
} }
if ($auctionInfo->openDate > $now) { if ($auctionInfo->openTick > $nowTick) {
return '경매가 아직 시작되지 않았습니다.'; return '경매가 아직 시작되지 않았습니다.';
} }
@@ -377,13 +382,11 @@ abstract class Auction
if ($auctionInfo->reqResource === ResourceType::inheritancePoint) { if ($auctionInfo->reqResource === ResourceType::inheritancePoint) {
return $this->bidInheritPoint($amount, $now, $tryExtendCloseDate); return $this->bidInheritPoint($amount, $nowTick, $nowDate, $tryExtendCloseDate);
} }
//reqResource는 말 그대로 '구매자가 내야하는 자원'이다. //reqResource는 말 그대로 '구매자가 내야하는 자원'이다.
$db = DB::db();
$highestBid = $this->getHighestBid(); $highestBid = $this->getHighestBid();
if (!$auctionInfo->detail->isReverse) { if (!$auctionInfo->detail->isReverse) {
if ($highestBid !== null && $amount <= $highestBid->amount) { if ($highestBid !== null && $amount <= $highestBid->amount) {
@@ -421,7 +424,7 @@ abstract class Auction
$general->getVar('owner'), $general->getVar('owner'),
$general->getID(), $general->getID(),
$amount, $amount,
$now, $nowDate,
new AuctionBidItemData( new AuctionBidItemData(
$general->getVar('owner_name'), $general->getVar('owner_name'),
$general->getName(), $general->getName(),
@@ -436,14 +439,13 @@ abstract class Auction
$general->increaseVar($resType->value, -$morePoint); $general->increaseVar($resType->value, -$morePoint);
$gameStor = KVStorage::getStorage($db, 'game_env'); $turnTerm = $gameStor->getValue('turnterm');
$turnTerm = abs($gameStor->getValue('turnterm')); $extendedCloseTick = $nowTick + $clock->ticksFromMinutes(
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval( max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID)
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60 );
));
if ($extendedCloseDate > $this->info->closeDate) { if ($extendedCloseTick > $this->info->closeTick) {
$this->extendCloseDate($extendedCloseDate, true); $this->extendCloseTick($extendedCloseTick, true);
$this->applyDB(); $this->applyDB();
} }
@@ -456,10 +458,10 @@ abstract class Auction
public function tryFinish(): ?bool public function tryFinish(): ?bool
{ {
$now = new DateTimeImmutable(); $db = DB::db();
if ($now < $this->info->closeDate) { $gameStor = KVStorage::getStorage($db, 'game_env');
return null; $clock = GameClock::fromStorage($gameStor);
} if ($clock->nowTick() < $this->info->closeTick) return null;
//경매를 닫아야한다. //경매를 닫아야한다.
$highestBid = $this->getHighestBid(); $highestBid = $this->getHighestBid();
@@ -469,17 +471,15 @@ abstract class Auction
} }
if ($highestBid->aux->tryExtendCloseDate) { if ($highestBid->aux->tryExtendCloseDate) {
$db = DB::db(); $turnTerm = $gameStor->getValue('turnterm');
$gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = abs($gameStor->getValue('turnterm'));
//연장 요청이 있었다. //연장 요청이 있었다.
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( $extendedCloseTick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60 max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY)
)); );
if ($this->extendCloseDate($extendedCloseDate) === null) { if ($this->extendCloseTick($extendedCloseTick) === null) {
$this->extendLatestBidCloseDate(null); $this->extendLatestBidCloseTick(null);
$this->applyDB(); $this->applyDB();
return false; return false;
} }
@@ -509,12 +509,13 @@ abstract class Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게.. //TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다. //TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$msg = new Message( $msg = new Message(
MessageType::private, MessageType::private,
$src, $src,
$dest, $dest,
$failReason, $failReason,
new \DateTime(), DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick())),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
+10 -14
View File
@@ -56,10 +56,11 @@ abstract class AuctionBasicResource extends Auction
} }
$now = new \DateTimeImmutable();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = abs($gameStor->getValue('turnterm')); $clock = GameClock::fromStorage($gameStor);
$closeDate = $now->add(TimeUtil::secondsToDateInterval($closeTurnCnt * $turnTerm * 60)); $nowTick = $clock->nowTick();
$turnTerm = $gameStor->getValue('turnterm');
$closeTick = $nowTick + GameClock::TICKS_PER_TURN * $closeTurnCnt;
$openResult = static::openAuction(new AuctionInfo( $openResult = static::openAuction(new AuctionInfo(
null, null,
@@ -68,8 +69,8 @@ abstract class AuctionBasicResource extends Auction
"$amount", "$amount",
$general->getId(), $general->getId(),
$bidderRes, $bidderRes,
$now, $nowTick,
$closeDate, $closeTick,
new AuctionInfoDetail( new AuctionInfoDetail(
"{$hostResName} {$amount} 경매", "{$hostResName} {$amount} 경매",
$general->getName(), $general->getName(),
@@ -145,12 +146,13 @@ abstract class AuctionBasicResource extends Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게.. //TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다. //TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$msg = new Message( $msg = new Message(
MessageType::private, MessageType::private,
$src, $src,
$dest, $dest,
"{$this->auctionID}{$hostResName} 경매에 입찰이 없어 취소되었습니다.", "{$this->auctionID}{$hostResName} 경매에 입찰이 없어 취소되었습니다.",
new \DateTime(), \DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick())),
new \DateTime('9999-12-31'), new \DateTime('9999-12-31'),
[] []
); );
@@ -246,14 +248,8 @@ abstract class AuctionBasicResource extends Auction
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm'); $turnTerm = $gameStor->getValue('turnterm');
if($turnTerm == -60){ $clock = GameClock::fromStorage($gameStor);
$date = VarTurn60::fromDatetime(new \DateTimeImmutable())->addTurn(1)->toDateStr(); $this->shrinkCloseTick($clock->nowTick() + GameClock::TICKS_PER_TURN);
}
else{
$date = (new DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($turnTerm * 60));
}
$this->shrinkCloseDate($date);
} }
return null; return null;
+29 -29
View File
@@ -72,17 +72,15 @@ class AuctionUniqueItem extends Auction
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$now = new DateTimeImmutable();
[$turnTerm, $year, $month] = $gameStor->getValuesAsArray(['turnterm', 'year', 'month']); [$turnTerm, $year, $month] = $gameStor->getValuesAsArray(['turnterm', 'year', 'month']);
$turnTerm = abs($turnTerm); $clock = GameClock::fromStorage($gameStor);
$nowTick = $clock->nowTick();
$closeDate = $now->add(TimeUtil::secondsToDateInterval( $closeTick = $nowTick + $clock->ticksFromMinutes(
max(static::MIN_AUCTION_CLOSE_MINUTES, $turnTerm * static::COEFF_AUCTION_CLOSE_MINUTES) * 60 max(static::MIN_AUCTION_CLOSE_MINUTES, $turnTerm * static::COEFF_AUCTION_CLOSE_MINUTES)
)); );
$availableLatestBidCloseDate = $closeDate->add(TimeUtil::secondsToDateInterval( $availableLatestBidCloseTick = $closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID) * 60 max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID)
)); );
$info = new AuctionInfo( $info = new AuctionInfo(
null, null,
@@ -91,8 +89,8 @@ class AuctionUniqueItem extends Auction
$itemKey, $itemKey,
$general->getID(), $general->getID(),
ResourceType::inheritancePoint, ResourceType::inheritancePoint,
$now, $nowTick,
$closeDate, $closeTick,
new AuctionInfoDetail( new AuctionInfoDetail(
"{$item->getName()} 경매", "{$item->getName()} 경매",
static::genObfuscatedName($general->getID()), static::genObfuscatedName($general->getID()),
@@ -101,7 +99,7 @@ class AuctionUniqueItem extends Auction
$startAmount, $startAmount,
null, null,
1, 1,
$availableLatestBidCloseDate, $availableLatestBidCloseTick,
) )
); );
@@ -267,22 +265,23 @@ class AuctionUniqueItem extends Auction
} }
if ($availableEquipUniqueCnt <= 0) { if ($availableEquipUniqueCnt <= 0) {
$turnTerm = abs($gameStor->getValue('turnterm')); $turnTerm = $gameStor->getValue('turnterm');
$clock = GameClock::fromStorage($gameStor);
//제한에 걸렸다면 자동 연장 //제한에 걸렸다면 자동 연장
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( $extendedCloseTick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_UNIQUE_CNT) * 60 max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_UNIQUE_CNT)
)); );
if($bidder->getID() != $this->info->hostGeneralID){ if($bidder->getID() != $this->info->hostGeneralID){
$this->setHostAsNeutral(); $this->setHostAsNeutral();
} }
$this->extendCloseDate($extendedCloseDate, true); $this->extendCloseTick($extendedCloseTick, true);
$this->extendLatestBidCloseDate(null); $this->extendLatestBidCloseTick(null);
$this->applyDB(); $this->applyDB();
return '유니크 아이템 소유 제한 상태입니다. 종료 시간이 연장됩니다.'; return '유니크 아이템 소유 제한 상태입니다. 종료 시간이 연장됩니다.';
} }
$isExtendCloseDateRequired = false; $isExtendCloseTickRequired = false;
foreach (GameConst::$allItems as $itemType => $itemList) { foreach (GameConst::$allItems as $itemType => $itemList) {
//아직은 그런 경우는 없지만 동일 유니크를 여러 부위에 장착할 수 있을지도 모름 //아직은 그런 경우는 없지만 동일 유니크를 여러 부위에 장착할 수 있을지도 모름
if (!key_exists($itemKey, $itemList)) { if (!key_exists($itemKey, $itemList)) {
@@ -292,13 +291,13 @@ class AuctionUniqueItem extends Auction
$ownItem = $general->getItem($itemType); $ownItem = $general->getItem($itemType);
if ($ownItem->getRawClassName() == $itemKey) { if ($ownItem->getRawClassName() == $itemKey) {
//FIXME: 이 경우에는 환불이 되던가 해야함. //FIXME: 이 경우에는 환불이 되던가 해야함.
$isExtendCloseDateRequired = true; $isExtendCloseTickRequired = true;
$reasons[] = '이미 그 유니크를 가지고 있습니다.'; $reasons[] = '이미 그 유니크를 가지고 있습니다.';
continue; continue;
} }
if (!$ownItem->isBuyable()) { if (!$ownItem->isBuyable()) {
$isExtendCloseDateRequired = true; $isExtendCloseTickRequired = true;
$reasons[] = '이미 다른 유니크를 가지고 있습니다.'; $reasons[] = '이미 다른 유니크를 가지고 있습니다.';
continue; continue;
} }
@@ -314,18 +313,19 @@ class AuctionUniqueItem extends Auction
} }
if (!$availableItemTypes) { if (!$availableItemTypes) {
if ($isExtendCloseDateRequired) { if ($isExtendCloseTickRequired) {
$turnTerm = abs($gameStor->getValue('turnterm')); $turnTerm = $gameStor->getValue('turnterm');
$clock = GameClock::fromStorage($gameStor);
//동일 부위 제한에 걸렸다면 자동 연장 //동일 부위 제한에 걸렸다면 자동 연장
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( $extendedCloseTick = $this->info->closeTick + $clock->ticksFromMinutes(
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID) * 60 max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID)
)); );
if($bidder->getID() != $this->info->hostGeneralID){ if($bidder->getID() != $this->info->hostGeneralID){
$this->setHostAsNeutral(); $this->setHostAsNeutral();
} }
$this->extendCloseDate($extendedCloseDate, true); $this->extendCloseTick($extendedCloseTick, true);
$this->extendLatestBidCloseDate(null); $this->extendLatestBidCloseTick(null);
$this->applyDB(); $this->applyDB();
} }
return join(' ', $reasons); return join(' ', $reasons);
@@ -0,0 +1,640 @@
<?php
namespace sammo;
use sammo\Scenario\GeneralBuilder;
final class CentennialAllStarGrowthService
{
public const POOL_CLASS = 'SPoolUnderU100';
public const AUX_KEY = 'event100_allstar';
public const TRAIT_UNLOCK_PROGRESS = 0.4;
public const NPC_PROGRESS_MULTIPLIER = 0.9;
private const STAT_KEYS = ['leadership', 'strength', 'intel'];
private const DEX_KEYS = ['dex1', 'dex2', 'dex3', 'dex4', 'dex5'];
public static function isActive(): bool
{
return GameConst::$targetGeneralPool === self::POOL_CLASS;
}
public static function isStatResetAllowed(): bool
{
return !self::isActive();
}
public static function initialAux(array $targetInfo, ?array $userInitialStats = null): array
{
$granted = array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
if ($userInitialStats !== null) {
foreach (self::STAT_KEYS as $key) {
$initial = (int) ($userInitialStats[$key] ?? GameConst::$defaultStatMin);
$granted[$key] = max(0, $initial - min($initial, GameConst::$defaultStatMin));
}
}
return [
'targetId' => (string) ($targetInfo['uniqueName'] ?? ''),
'granted' => $granted,
'dexConsumed' => array_fill_keys(self::DEX_KEYS, 0),
'dexFloor' => array_fill_keys(self::DEX_KEYS, 0),
'progressMonth' => -1,
'milestone' => 0,
'naturalSpecialDomestic' => null,
'eventSpecialDomestic' => null,
'userInitialStats' => $userInitialStats,
'dexTargetRatio' => 1.0,
];
}
public static function attachInitialTarget(GeneralBuilder $builder, array $targetInfo): void
{
$builder->setAuxVar(self::AUX_KEY, self::initialAux($targetInfo));
}
/**
* Builds an ordinary-user stat total while preserving the selected
* candidate's relative strengths as closely as integer stats allow.
*
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateUserInitialStats(array $targetInfo): array
{
$targets = [];
$bases = [];
foreach (self::STAT_KEYS as $key) {
$target = min(
GameConst::$defaultStatMax,
max(0, (int) ($targetInfo[$key] ?? 0))
);
$targets[$key] = $target;
$bases[$key] = min($target, GameConst::$defaultStatMin);
}
$targetTotal = array_sum($targets);
$desiredTotal = min(GameConst::$defaultStatTotal, $targetTotal);
$baseTotal = array_sum($bases);
$capacityTotal = $targetTotal - $baseTotal;
if ($capacityTotal <= 0 || $desiredTotal <= $baseTotal) {
return $bases;
}
$ratio = ($desiredTotal - $baseTotal) / $capacityTotal;
$result = [];
$fractions = [];
foreach (self::STAT_KEYS as $idx => $key) {
$raw = $bases[$key] + ($targets[$key] - $bases[$key]) * $ratio;
$result[$key] = (int) floor($raw);
$fractions[] = [
'key' => $key,
'fraction' => $raw - $result[$key],
'order' => $idx,
];
}
usort($fractions, static function (array $lhs, array $rhs): int {
$fractionOrder = $rhs['fraction'] <=> $lhs['fraction'];
return $fractionOrder !== 0 ? $fractionOrder : $lhs['order'] <=> $rhs['order'];
});
$remainder = $desiredTotal - array_sum($result);
foreach ($fractions as $fraction) {
if ($remainder <= 0) {
break;
}
$key = $fraction['key'];
if ($result[$key] >= $targets[$key]) {
continue;
}
$result[$key]++;
$remainder--;
}
return $result;
}
/**
* Returns the event stat baseline that a newly selected target receives at
* the supplied game date. Organic growth can still leave the actual stat
* above this baseline.
*
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateUserCurrentTargetStats(
array $targetInfo,
array $env
): array {
$initialStats = self::calculateUserInitialStats($targetInfo);
$progress = self::calculateProgress(
(int) $env['startyear'],
(int) $env['year'],
(int) $env['month']
);
$result = [];
foreach (self::STAT_KEYS as $key) {
$target = min(
GameConst::$maxLevel,
max(0, (int) ($targetInfo[$key] ?? 0))
);
$result[$key] = max(
$initialStats[$key],
CentennialAllStarGrowth::statFloor(
$target,
GameConst::$defaultStatMin,
$progress
)
);
}
return $result;
}
public static function prepareInitialUser(
GeneralBuilder $builder,
array $targetInfo
): void {
$initialStats = self::calculateUserInitialStats($targetInfo);
$builder->setStat(
$initialStats['leadership'],
$initialStats['strength'],
$initialStats['intel']
);
$builder->setAuxVar(
self::AUX_KEY,
self::initialAux($targetInfo, $initialStats)
);
}
/**
* Old 100th-season characters did not distinguish their form-entered
* initial stats from organic growth. Before their first reselection, treat
* the ordinary creation range as the replaceable initial allocation.
*/
public static function prepareLegacyUserReselection(General $general): void
{
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux) || is_array($aux['userInitialStats'] ?? null)) {
return;
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$legacyInitialStats = [];
foreach (self::STAT_KEYS as $key) {
$current = (int) $general->getVar($key);
$granted[$key] = self::calculateLegacyUserGrant(
$current,
(int) ($granted[$key] ?? 0)
);
$beforeEventGrant = max(
0,
$current - max(0, (int) ($aux['granted'][$key] ?? 0))
);
$legacyInitialStats[$key] = min(
$beforeEventGrant,
GameConst::$defaultStatMax
);
}
$aux['granted'] = $granted;
$aux['userInitialStats'] = $legacyInitialStats;
$general->setAuxVar(self::AUX_KEY, $aux);
}
public static function calculateLegacyUserGrant(int $current, int $eventGrant): int
{
$eventGrant = max(0, $eventGrant);
$beforeEventGrant = max(0, $current - $eventGrant);
$replaceableInitialGrant = max(
0,
min($beforeEventGrant, GameConst::$defaultStatMax)
- min($beforeEventGrant, GameConst::$defaultStatMin)
);
return $eventGrant + $replaceableInitialGrant;
}
public static function calculateProgress(
int $startYear,
int $year,
int $month,
float $progressMultiplier = 1.0
): float {
if ($progressMultiplier < 0 || $progressMultiplier > 1) {
throw new \InvalidArgumentException('progress multiplier must be between 0 and 1');
}
return min(
1,
CentennialAllStarGrowth::progress($startYear, $year, $month)
* $progressMultiplier
);
}
public static function calculateDexTargetFloor(
int $target,
array $env,
float $targetRatio = 1.0
): int {
if ($targetRatio < 0 || $targetRatio > 1) {
throw new \InvalidArgumentException('dex target ratio must be between 0 and 1');
}
$target = min(GameConst::$dexLimit, max(0, $target));
$scaledTarget = (int) floor($target * $targetRatio);
$progress = self::calculateProgress(
(int) $env['startyear'],
(int) $env['year'],
(int) $env['month']
);
return CentennialAllStarGrowth::dexFloor($scaledTarget, $progress);
}
/**
* Mutates the General object but leaves persistence to the caller.
*
* @return array{progress:float,milestone:int,previousMilestone:int,targetChanged:bool,changed:bool}
*/
public static function applyTarget(
General $general,
array $targetInfo,
array $env,
float $progressMultiplier = 1.0,
float $dexTargetRatio = 1.0
): array
{
$startYear = (int) $env['startyear'];
$year = (int) $env['year'];
$month = (int) $env['month'];
$progress = self::calculateProgress(
$startYear,
$year,
$month,
$progressMultiplier
);
$progressMonth = (int) floor(
max(0, ($year - $startYear) * 12 + $month - 1)
* $progressMultiplier
);
$targetId = (string) ($targetInfo['uniqueName'] ?? '');
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux)) {
$aux = self::initialAux($targetInfo);
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$targetChanged = ($aux['targetId'] ?? '') !== $targetId;
$dexConsumed = is_array($aux['dexConsumed'] ?? null)
? $aux['dexConsumed']
: array_fill_keys(self::DEX_KEYS, 0);
if ($targetChanged) {
$dexConsumed = array_fill_keys(self::DEX_KEYS, 0);
}
$dexFloor = is_array($aux['dexFloor'] ?? null)
? $aux['dexFloor']
: array_fill_keys(self::DEX_KEYS, 0);
$isUserTarget = is_array($aux['userInitialStats'] ?? null);
$nextUserInitialStats = $targetChanged && $isUserTarget
? self::calculateUserInitialStats($targetInfo)
: ($aux['userInitialStats'] ?? null);
$previousDexTargetRatio = is_numeric($aux['dexTargetRatio'] ?? null)
? (float) $aux['dexTargetRatio']
: 1.0;
$dexTargetRatioChanged = $previousDexTargetRatio !== $dexTargetRatio;
$userCurrentTargetStats = $isUserTarget
? self::calculateUserCurrentTargetStats($targetInfo, $env)
: null;
$changed = $dexTargetRatioChanged;
foreach (self::STAT_KEYS as $key) {
if (!array_key_exists($key, $targetInfo)) {
continue;
}
$target = min(GameConst::$maxLevel, max(0, (int) $targetInfo[$key]));
$floor = CentennialAllStarGrowth::statFloor(
$target,
GameConst::$defaultStatMin,
$progress
);
if ($userCurrentTargetStats !== null) {
$floor = $userCurrentTargetStats[$key];
}
$current = (int) $general->getVar($key);
if ($targetChanged) {
$result = CentennialAllStarGrowth::replaceTarget(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
} else {
$result = CentennialAllStarGrowth::advance(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
}
if ($result['value'] !== $current) {
$general->updateVar($key, $result['value']);
$changed = true;
}
$granted[$key] = $result['granted'];
}
$targetDex = $targetInfo['dex'] ?? [];
foreach (self::DEX_KEYS as $idx => $key) {
if (!array_key_exists($idx, $targetDex)) {
continue;
}
$floor = max(
0,
self::calculateDexTargetFloor(
(int) $targetDex[$idx],
$env,
$dexTargetRatio
) - max(0, (int) ($dexConsumed[$key] ?? 0))
);
$dexFloor[$key] = $floor;
$current = (int) $general->getVar($key);
if ($targetChanged || $dexTargetRatioChanged) {
$result = CentennialAllStarGrowth::replaceTarget(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
} else {
$result = CentennialAllStarGrowth::advance(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
}
if ($result['value'] !== $current) {
$general->updateVar($key, $result['value']);
$changed = true;
}
$granted[$key] = $result['granted'];
}
$oldEventSpecial = $aux['eventSpecialDomestic'] ?? null;
if ($targetChanged && $oldEventSpecial !== null
&& $general->getVar('special') === $oldEventSpecial
) {
$general->updateVar(
'special',
$aux['naturalSpecialDomestic'] ?? GameConst::$defaultSpecialDomestic
);
$changed = true;
$aux['eventSpecialDomestic'] = null;
}
$targetSpecial = $targetInfo['specialDomestic'] ?? null;
if ($progress >= self::TRAIT_UNLOCK_PROGRESS && is_string($targetSpecial) && $targetSpecial !== '') {
if (($aux['naturalSpecialDomestic'] ?? null) === null) {
$aux['naturalSpecialDomestic'] = $general->getVar('special');
}
if ($general->getVar('special') !== $targetSpecial) {
$general->updateVar('special', $targetSpecial);
$changed = true;
}
$aux['eventSpecialDomestic'] = $targetSpecial;
}
$previousMilestone = (int) ($aux['milestone'] ?? 0);
$milestone = min(5, (int) floor($progress * 5 + 0.0000001));
$aux['targetId'] = $targetId;
$aux['granted'] = $granted;
$aux['dexConsumed'] = $dexConsumed;
$aux['dexFloor'] = $dexFloor;
$aux['progressMonth'] = max((int) ($aux['progressMonth'] ?? -1), $progressMonth);
$aux['milestone'] = max($previousMilestone, $milestone);
$aux['userInitialStats'] = $nextUserInitialStats;
$aux['dexTargetRatio'] = $dexTargetRatio;
$general->setAuxVar(self::AUX_KEY, $aux);
return [
'progress' => $progress,
'milestone' => $milestone,
'previousMilestone' => $previousMilestone,
'targetChanged' => $targetChanged,
'changed' => $changed || $targetChanged || $milestone > $previousMilestone,
];
}
public static function progressMultiplierFor(General $general): float
{
return self::progressMultiplierForNPCType($general->getNPCType());
}
public static function progressMultiplierForNPCType(int $npcType): float
{
return in_array($npcType, [3, 4], true)
? self::NPC_PROGRESS_MULTIPLIER
: 1.0;
}
public static function dexTargetRatioForNPCType(int $npcType): float
{
return in_array($npcType, [3, 4], true)
? GameConst::$centennialNpcDexTargetRatio
: 1.0;
}
public static function applyCurrentTargetToBuiltNPC(
\MeekroDB $db,
GeneralBuilder $builder,
array $targetInfo,
array $env
): ?array {
if (!self::isActive()) {
return null;
}
$general = General::createObjFromDB($builder->getGeneralID());
if (!in_array($general->getNPCType(), [3, 4], true)) {
return null;
}
self::initializeGeneratedNPC($general, $targetInfo);
$result = self::applyTarget(
$general,
$targetInfo,
$env,
self::NPC_PROGRESS_MULTIPLIER,
GameConst::$centennialNpcDexTargetRatio
);
$general->applyDB($db);
return $result;
}
/**
* Keeps the ordinary M/G-general stat total while aligning its strong,
* middle, and weak stats with the selected all-star target. The target's
* creation-date growth floor is applied immediately afterwards.
*/
public static function initializeGeneratedNPC(
General $general,
array $targetInfo
): void {
$generatedStats = [];
foreach (self::STAT_KEYS as $key) {
$generatedStats[$key] = (int) $general->getVar($key);
}
$initialStats = self::calculateGeneratedNPCInitialStats(
$targetInfo,
$generatedStats
);
foreach (self::STAT_KEYS as $key) {
$general->updateVar($key, $initialStats[$key]);
}
foreach (self::DEX_KEYS as $key) {
$general->updateVar($key, 0);
}
$general->setAuxVar(self::AUX_KEY, self::initialAux($targetInfo));
}
/**
* @param array<string, mixed> $targetInfo
* @param array{leadership:int,strength:int,intel:int} $generatedStats
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateGeneratedNPCInitialStats(
array $targetInfo,
array $generatedStats
): array {
$targetOrder = self::STAT_KEYS;
$keyOrder = array_flip(self::STAT_KEYS);
usort(
$targetOrder,
static function (string $lhs, string $rhs) use ($targetInfo, $keyOrder): int {
$targetCompare = (int) ($targetInfo[$rhs] ?? 0)
<=> (int) ($targetInfo[$lhs] ?? 0);
return $targetCompare !== 0
? $targetCompare
: $keyOrder[$lhs] <=> $keyOrder[$rhs];
}
);
$generatedValues = array_map(
static fn (string $key): int => (int) ($generatedStats[$key] ?? 0),
self::STAT_KEYS
);
rsort($generatedValues, SORT_NUMERIC);
$result = array_fill_keys(self::STAT_KEYS, 0);
foreach ($targetOrder as $idx => $key) {
$result[$key] = $generatedValues[$idx];
}
return $result;
}
/**
* Moves the event-backed part of a dex conversion with the converted
* value and consumes any guaranteed floor crossed by the source value.
* This keeps the monthly floor from refilling points already converted.
*/
public static function reconcileDexConversion(
General $general,
string $sourceKey,
string $destinationKey,
int $sourceBefore,
int $sourceAfter,
int $destinationBefore,
int $destinationAfter,
float $convertCoeff
): void {
if (!in_array($sourceKey, self::DEX_KEYS, true)
|| !in_array($destinationKey, self::DEX_KEYS, true)
|| $sourceKey === $destinationKey
) {
throw new \InvalidArgumentException('invalid dex conversion keys');
}
if ($convertCoeff < 0 || $convertCoeff > 1) {
throw new \InvalidArgumentException('dex conversion coefficient must be between 0 and 1');
}
$sourceDecrease = max(0, $sourceBefore - $sourceAfter);
$destinationIncrease = max(0, $destinationAfter - $destinationBefore);
if ($sourceDecrease === 0 && $destinationIncrease === 0) {
return;
}
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux)) {
return;
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$dexConsumed = is_array($aux['dexConsumed'] ?? null)
? $aux['dexConsumed']
: array_fill_keys(self::DEX_KEYS, 0);
$dexFloor = is_array($aux['dexFloor'] ?? null)
? $aux['dexFloor']
: [];
$sourceGrantedBefore = min(
max(0, $sourceBefore),
max(0, (int) ($granted[$sourceKey] ?? 0))
);
/*
* A dex value can contain both organic and event-backed points. Split
* the actual command deltas by their share of the source total instead
* of consuming either bucket first. This keeps the combined ownership
* stable when conversion and target reselection are interleaved.
*/
$eventGrantRemoved = $sourceBefore > 0
? intdiv($sourceDecrease * $sourceGrantedBefore, $sourceBefore)
: 0;
$sourceGrantedAfter = max(0, $sourceGrantedBefore - $eventGrantRemoved);
$destinationGrantedBefore = min(
max(0, $destinationBefore),
max(0, (int) ($granted[$destinationKey] ?? 0))
);
$eventGrantTransferred = $sourceBefore > 0
? intdiv($destinationIncrease * $sourceGrantedBefore, $sourceBefore)
: 0;
$eventGrantTransferred = min(
$destinationIncrease,
$eventGrantRemoved,
$eventGrantTransferred
);
$granted[$sourceKey] = $sourceGrantedAfter;
$granted[$destinationKey] = min(
max(0, $destinationAfter),
$destinationGrantedBefore + $eventGrantTransferred
);
$sourceFloor = max(
0,
(int) ($dexFloor[$sourceKey] ?? $sourceBefore)
);
$gapBefore = max(0, $sourceFloor - $sourceBefore);
$gapAfter = max(0, $sourceFloor - $sourceAfter);
$dexConsumed[$sourceKey] = max(
0,
(int) ($dexConsumed[$sourceKey] ?? 0)
+ max(0, $gapAfter - $gapBefore)
);
$aux['granted'] = $granted;
$aux['dexConsumed'] = $dexConsumed;
$general->setAuxVar(self::AUX_KEY, $aux);
}
public static function recordableValue(General $general, string $key): int
{
$aux = $general->getAuxVar(self::AUX_KEY);
$granted = is_array($aux) && is_array($aux['granted'] ?? null)
? (int) ($aux['granted'][$key] ?? 0)
: 0;
return CentennialAllStarGrowth::recordableValue((int) $general->getVar($key), $granted);
}
public static function recordableRawValue(array $general, string $key): int
{
$aux = Json::decode($general['aux'] ?? '{}');
$eventAux = is_array($aux[self::AUX_KEY] ?? null) ? $aux[self::AUX_KEY] : [];
$granted = is_array($eventAux['granted'] ?? null)
? (int) ($eventAux['granted'][$key] ?? 0)
: 0;
return CentennialAllStarGrowth::recordableValue((int) ($general[$key] ?? 0), $granted);
}
}
@@ -14,6 +14,7 @@ use function \sammo\tryUniqueItemLottery;
use \sammo\Constraint\ConstraintHelper; use \sammo\Constraint\ConstraintHelper;
use sammo\StaticEventHandler; use sammo\StaticEventHandler;
use sammo\CentennialAllStarGrowthService;
class che_숙련전환 extends Command\GeneralCommand class che_숙련전환 extends Command\GeneralCommand
{ {
@@ -157,6 +158,7 @@ class che_숙련전환 extends Command\GeneralCommand
$logger = $general->getLogger(); $logger = $general->getLogger();
$srcDex = $general->getVar('dex' . $this->srcArmType); $srcDex = $general->getVar('dex' . $this->srcArmType);
$destDex = $general->getVar('dex' . $this->destArmType);
$cutDex = Util::toInt($srcDex * static::$decreaseCoeff); $cutDex = Util::toInt($srcDex * static::$decreaseCoeff);
$cutDexText = number_format($cutDex); $cutDexText = number_format($cutDex);
$addDex = Util::toInt($cutDex * static::$convertCoeff); $addDex = Util::toInt($cutDex * static::$convertCoeff);
@@ -164,6 +166,19 @@ class che_숙련전환 extends Command\GeneralCommand
$general->increaseVar('dex' . $this->srcArmType, -$cutDex); $general->increaseVar('dex' . $this->srcArmType, -$cutDex);
$general->increaseVar('dex' . $this->destArmType, $addDex); $general->increaseVar('dex' . $this->destArmType, $addDex);
// 100기 이벤트 지급분을 목적 숙련으로 옮기고 소비한 성장 하한은 다시 채우지 않는다.
if (CentennialAllStarGrowthService::isActive()) {
CentennialAllStarGrowthService::reconcileDexConversion(
$general,
'dex' . $this->srcArmType,
'dex' . $this->destArmType,
$srcDex,
$general->getVar('dex' . $this->srcArmType),
$destDex,
$general->getVar('dex' . $this->destArmType),
static::$convertCoeff
);
}
$josaUl = JosaUtil::pick($cutDex, '을'); $josaUl = JosaUtil::pick($cutDex, '을');
$josaRo = JosaUtil::pick($addDex, '로'); $josaRo = JosaUtil::pick($addDex, '로');
@@ -7,6 +7,7 @@ use \sammo\Util;
use \sammo\JosaUtil; use \sammo\JosaUtil;
use \sammo\General; use \sammo\General;
use \sammo\ActionLogger; use \sammo\ActionLogger;
use \sammo\CentennialAllStarGrowthService;
use \sammo\GameConst; use \sammo\GameConst;
use \sammo\LastTurn; use \sammo\LastTurn;
use \sammo\GameUnitConst; use \sammo\GameUnitConst;
@@ -190,6 +191,12 @@ class che_인재탐색 extends Command\GeneralCommand
$newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env); $newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env);
$newNPC->build($this->env); $newNPC->build($this->env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$newNPC,
$pickedNPC->getInfo(),
$this->env
);
$pickedNPC->occupyGeneralName(); $pickedNPC->occupyGeneralName();
$npcName = $newNPC->getGeneralName(); $npcName = $newNPC->getGeneralName();
$josaRa = JosaUtil::pick($npcName, '라'); $josaRa = JosaUtil::pick($npcName, '라');
+1 -1
View File
@@ -159,7 +159,7 @@ class che_발령 extends Command\NationCommand
$destGeneral->getLogger()->pushGeneralActionLog("<Y>{$generalName}</>에 의해 <G><b>{$destCityName}</b></>{$josaRo} 발령됐습니다. <1>$date</>"); $destGeneral->getLogger()->pushGeneralActionLog("<Y>{$generalName}</>에 의해 <G><b>{$destCityName}</b></>{$josaRo} 발령됐습니다. <1>$date</>");
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']); $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
if (cutTurn($general->getTurnTime(), $this->env['turnterm']) != cutTurn($destGeneral->getTurnTime(), $this->env['turnterm'])) { if (cutTurn($general->getTurnTick(), $this->env['turnterm']) != cutTurn($destGeneral->getTurnTick(), $this->env['turnterm'])) {
$yearMonth += 1; $yearMonth += 1;
} }
$destGeneral->setAuxVar('last발령', $yearMonth); $destGeneral->setAuxVar('last발령', $yearMonth);
@@ -200,7 +200,7 @@ class che_불가침제의 extends Command\NationCommand
$now = new \DateTime($date); $now = new \DateTime($date);
$validUntil = new \DateTime($date); $validUntil = new \DateTime($date);
$validMinutes = max(30, abs($env['turnterm']) * 3); $validMinutes = max(30, $env['turnterm'] * 3);
$validUntil->add(new \DateInterval("PT{$validMinutes}M")); $validUntil->add(new \DateInterval("PT{$validMinutes}M"));
$josaWa = JosaUtil::pick($nationName, '와'); $josaWa = JosaUtil::pick($nationName, '와');
@@ -149,7 +149,7 @@ class che_불가침파기제의 extends Command\NationCommand{
$now = new \DateTime($date); $now = new \DateTime($date);
$validUntil = new \DateTime($date); $validUntil = new \DateTime($date);
$validMinutes = max(30, abs($env['turnterm'])*3); $validMinutes = max(30, $env['turnterm']*3);
$validUntil->add(new \DateInterval("PT{$validMinutes}M")); $validUntil->add(new \DateInterval("PT{$validMinutes}M"));
$msg = new DiplomaticMessage( $msg = new DiplomaticMessage(
@@ -7,6 +7,7 @@ use \sammo\Util;
use \sammo\JosaUtil; use \sammo\JosaUtil;
use \sammo\General; use \sammo\General;
use \sammo\ActionLogger; use \sammo\ActionLogger;
use \sammo\CentennialAllStarGrowthService;
use \sammo\GameConst; use \sammo\GameConst;
use \sammo\LastTurn; use \sammo\LastTurn;
use \sammo\GameUnitConst; use \sammo\GameUnitConst;
@@ -160,6 +161,12 @@ class che_의병모집 extends Command\NationCommand
$newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env); $newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env);
$newNPC->build($this->env); $newNPC->build($this->env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$newNPC,
$pickedNPC->getInfo(),
$this->env
);
$pickedNPC->occupyGeneralName(); $pickedNPC->occupyGeneralName();
} }
@@ -147,7 +147,7 @@ class che_종전제의 extends Command\NationCommand{
$now = new \DateTime($date); $now = new \DateTime($date);
$validUntil = new \DateTime($date); $validUntil = new \DateTime($date);
$validMinutes = max(30, abs($env['turnterm'])*3); $validMinutes = max(30, $env['turnterm']*3);
$validUntil->add(new \DateInterval("PT{$validMinutes}M")); $validUntil->add(new \DateInterval("PT{$validMinutes}M"));
$msg = new DiplomaticMessage( $msg = new DiplomaticMessage(
+1 -1
View File
@@ -137,7 +137,7 @@ class che_천도 extends Command\NationCommand
$nationID = $general->getNationID(); $nationID = $general->getNationID();
$nationStor = \sammo\KVStorage::getStorage(DB::db(), $nationID, 'nation_env'); $nationStor = \sammo\KVStorage::getStorage(DB::db(), $nationID, 'nation_env');
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()]; $nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTick()];
if ($lastTurn->getCommand() != $commandName || $lastTurn->getArg() !== $this->arg) { if ($lastTurn->getCommand() != $commandName || $lastTurn->getArg() !== $this->arg) {
$this->setResultTurn(new LastTurn( $this->setResultTurn(new LastTurn(
@@ -1,204 +0,0 @@
<?php
namespace sammo\Command\Nation;
use \sammo\DB;
use \sammo\Util;
use \sammo\JosaUtil;
use \sammo\General;
use \sammo\DummyGeneral;
use \sammo\ActionLogger;
use \sammo\GameConst;
use \sammo\LastTurn;
use \sammo\GameUnitConst;
use \sammo\Command;
use \sammo\Constraint\Constraint;
use \sammo\Constraint\ConstraintHelper;
use sammo\Enums\GeneralQueryMode;
use sammo\StaticEventHandler;
use function sammo\pullGeneralCommand;
use function sammo\pushGeneralCommand;
class che_행동지시 extends Command\NationCommand
{
static protected $actionName = '행동 지시';
static public $reqArg = true;
protected function argTest(): bool
{
if ($this->arg === null) {
return false;
}
if (!key_exists('isPull', $this->arg)) {
return false;
}
if (!key_exists('amount', $this->arg)) {
return false;
}
if (!key_exists('destGeneralID', $this->arg)) {
return false;
}
$isPull = $this->arg['isPull'];
$amount = $this->arg['amount'];
$destGeneralID = $this->arg['destGeneralID'];
if (!is_numeric($amount)) {
return false;
}
if ($amount <= 0) {
return false;
}
if (!is_bool($isPull)) {
return false;
}
if (!is_int($destGeneralID)) {
return false;
}
if ($destGeneralID <= 0) {
return false;
}
$amount = Util::clamp($amount, 1, 5);
$this->arg = [
'isPull' => $isPull,
'amount' => $amount,
'destGeneralID' => $destGeneralID
];
return true;
}
protected function init()
{
$general = $this->generalObj;
$this->setCity();
$this->setNation();
$this->minConditionConstraints = [
ConstraintHelper::NotBeNeutral(),
ConstraintHelper::OccupiedCity(),
ConstraintHelper::BeChief(),
ConstraintHelper::SuppliedCity(),
];
}
protected function initWithArg()
{
$destGeneral = General::createObjFromDB($this->arg['destGeneralID']);
$this->setDestGeneral($destGeneral);
if($this->arg['destGeneralID'] == $this->getGeneral()->getID()){
$this->fullConditionConstraints=[
ConstraintHelper::AlwaysFail('본인입니다')
];
return;
}
$this->fullConditionConstraints = [
ConstraintHelper::NotBeNeutral(),
ConstraintHelper::OccupiedCity(),
ConstraintHelper::BeChief(),
ConstraintHelper::SuppliedCity(),
ConstraintHelper::ExistsDestGeneral(),
ConstraintHelper::FriendlyDestGeneral()
];
}
public function getCost(): array
{
return [0, 0];
}
public function getPreReqTurn(): int
{
return 0;
}
public function getPostReqTurn(): int
{
return 0;
}
public function getBrief(): string
{
$isPull = $this->arg['isPull'];
$amount = $this->arg['amount'];
$actSpecificName = $isPull ? '당기기' : '미루기';
$destGeneral = $this->destGeneralObj;
return "{$destGeneral->getName()}{$amount}{$actSpecificName}";
}
public function run(\Sammo\RandUtil $rng): bool
{
if (!$this->hasFullConditionMet()) {
throw new \RuntimeException('불가능한 커맨드를 강제로 실행 시도');
}
$db = DB::db();
$general = $this->generalObj;
$date = $general->getTurnTime($general::TURNTIME_HM);
$isPull = $this->arg['isPull'];
$amount = $this->arg['amount'];
$destGeneral = $this->destGeneralObj;
if($isPull){
pullGeneralCommand($destGeneral->getID(), $amount);
}
else{
pushGeneralCommand($destGeneral->getID(), $amount);
}
$logger = $general->getLogger();
$actDestText = $isPull ? '당겼습니다.' : '미루었습니다.';
$actText = $isPull ? '당기도록' : '미루도록';
$destGeneral->getLogger()->pushGeneralActionLog("<Y>{$general->getName()}</>의 지시로 <C>{$amount}</>턴을 {$actDestText}", ActionLogger::PLAIN);
$logger->pushGeneralActionLog("<Y>{$destGeneral->getName()}</>에게 <C>{$amount}</>턴을 {$actText} 지시했습니다. <1>$date</>");
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
StaticEventHandler::handleEvent($this->generalObj, $this->destGeneralObj, $this::class, $this->env, $this->arg ?? []);
$general->applyDB($db);
$destGeneral->applyDB($db);
return true;
}
public function exportJSVars(): array
{
$db = DB::db();
$nationID = $this->getNationID();
$troops = Util::convertArrayToDict($db->query('SELECT * FROM troop WHERE nation=%i', $nationID), 'troop_leader');
$destRawGenerals = Util::convertArrayToDict($db->queryAllLists('SELECT no,name,officer_level,npc,gold,rice,leadership,strength,intel,city,crew,train,atmos,troop FROM general WHERE nation = %i ORDER BY npc,binary(name)', $nationID), 0);
if($destRawGenerals){
foreach ($db->queryAllLists(
'SELECT general_id, brief FROM general_turn WHERE general_id IN %li AND turn_idx = 0',
array_keys($destRawGenerals)
) as [$generalID, $brief]) {
if (!key_exists($generalID, $destRawGenerals)) {
continue;
}
$destRawGenerals[$generalID][] = $brief;
}
}
return [
'procRes' => [
'troops' => $troops,
'generals' => array_values($destRawGenerals),
'generalsKey' => ['no', 'name', 'officerLevel', 'npc', 'gold', 'rice', 'leadership', 'strength', 'intel', 'cityID', 'crew', 'train', 'atmos', 'troopID', 'turn0Brief'],
'cities' => \sammo\JSOptionsForCities(),
'amountGuide' => [1, 2, 3, 4, 5],
'minAmount' => 1,
'maxAmount' => 5,
]
];
}
}
+4 -8
View File
@@ -2,11 +2,9 @@
namespace sammo\DTO; namespace sammo\DTO;
use LDTO\Attr\Convert;
use LDTO\Attr\JsonString; use LDTO\Attr\JsonString;
use LDTO\Attr\NullIsUndefined; use LDTO\Attr\NullIsUndefined;
use LDTO\Attr\RawName; use LDTO\Attr\RawName;
use LDTO\Converter\DateTimeConverter;
use sammo\Enums\AuctionType; use sammo\Enums\AuctionType;
use sammo\Enums\ResourceType; use sammo\Enums\ResourceType;
@@ -23,12 +21,10 @@ class AuctionInfo extends \LDTO\DTO
#[RawName('req_resource')] #[RawName('req_resource')]
public ResourceType $reqResource, public ResourceType $reqResource,
#[RawName('open_date')] #[RawName('open_tick')]
#[Convert(DateTimeConverter::class)] public int $openTick,
public \DateTimeImmutable $openDate, #[RawName('close_tick')]
#[RawName('close_date')] public int $closeTick,
#[Convert(DateTimeConverter::class)]
public \DateTimeImmutable $closeDate,
#[JsonString] #[JsonString]
public AuctionInfoDetail $detail, public AuctionInfoDetail $detail,
+1 -4
View File
@@ -2,9 +2,7 @@
namespace sammo\DTO; namespace sammo\DTO;
use LDTO\Attr\Convert;
use LDTO\Attr\NullIsUndefined; use LDTO\Attr\NullIsUndefined;
use LDTO\Converter\DateTimeConverter;
class AuctionInfoDetail extends \LDTO\DTO class AuctionInfoDetail extends \LDTO\DTO
{ {
@@ -21,8 +19,7 @@ class AuctionInfoDetail extends \LDTO\DTO
#[NullIsUndefined] #[NullIsUndefined]
public ?int $remainCloseDateExtensionCnt, public ?int $remainCloseDateExtensionCnt,
#[NullIsUndefined] #[NullIsUndefined]
#[Convert(DateTimeConverter::class)] public ?int $availableLatestBidCloseTick,
public ?\DateTimeImmutable $availableLatestBidCloseDate,
) { ) {
} }
} }
+1 -4
View File
@@ -2,10 +2,8 @@
namespace sammo\DTO; namespace sammo\DTO;
use LDTO\Attr\Convert;
use LDTO\Attr\NullIsUndefined; use LDTO\Attr\NullIsUndefined;
use LDTO\Attr\RawName; use LDTO\Attr\RawName;
use LDTO\Converter\DateTimeConverter;
class GeneralAccessLog extends \LDTO\DTO class GeneralAccessLog extends \LDTO\DTO
{ {
@@ -20,8 +18,7 @@ class GeneralAccessLog extends \LDTO\DTO
public ?int $userID, public ?int $userID,
#[RawName('last_refresh')] #[RawName('last_refresh')]
#[Convert(DateTimeConverter::class)] public ?int $lastRefresh,
public \DateTimeImmutable $lastRefresh,
public int $refresh, public int $refresh,
+1 -1
View File
@@ -49,7 +49,7 @@ class DiplomaticMessage extends Message{
$this->validDiplomacy = false; $this->validDiplomacy = false;
} }
if($this->validUntil < (new \DateTime())){ if($this->validUntil < $this->date){
$this->validDiplomacy = false; $this->validDiplomacy = false;
} }
} }
@@ -0,0 +1,55 @@
<?php
namespace sammo\Event\Action;
use sammo\ActionLogger;
use sammo\CentennialAllStarGrowthService;
use sammo\DB;
use sammo\General;
use sammo\Json;
class AdvanceCentennialAllStar extends \sammo\Event\Action
{
public function run(array $env)
{
if (!CentennialAllStarGrowthService::isActive()) {
return [__CLASS__, 0];
}
$db = DB::db();
$updated = 0;
foreach ($db->query(
'SELECT general.no, select_pool.info
FROM general
JOIN select_pool ON select_pool.general_id = general.no'
) as $row) {
$general = General::createObjFromDB((int) $row['no']);
$targetInfo = Json::decode($row['info']);
$result = CentennialAllStarGrowthService::applyTarget(
$general,
$targetInfo,
$env,
CentennialAllStarGrowthService::progressMultiplierFor($general),
CentennialAllStarGrowthService::dexTargetRatioForNPCType(
$general->getNPCType()
)
);
if ($result['milestone'] > $result['previousMilestone']) {
$percent = $result['milestone'] * 20;
$general->getLogger()->pushGeneralActionLog(
"<L>올스타 동조율</>이 <C>{$percent}%</>에 도달했습니다!",
ActionLogger::PLAIN
);
$general->getLogger()->pushGeneralHistoryLog(
"<L>올스타 동조율 {$percent}% 달성</>"
);
}
if ($general->applyDB($db)) {
$updated++;
}
}
return [__CLASS__, $updated];
}
}
+9 -1
View File
@@ -5,6 +5,7 @@ namespace sammo\Event\Action;
use \sammo\GameConst; use \sammo\GameConst;
use \sammo\Util; use \sammo\Util;
use \sammo\DB; use \sammo\DB;
use sammo\CentennialAllStarGrowthService;
use sammo\LiteHashDRBG; use sammo\LiteHashDRBG;
use sammo\RandUtil; use sammo\RandUtil;
use sammo\UniqueConst; use sammo\UniqueConst;
@@ -35,7 +36,8 @@ class CreateManyNPC extends \sammo\Event\Action
))); )));
$result = []; $result = [];
foreach (pickGeneralFromPool(DB::db(), $rng, 0, $cnt) as $pickedNPC) { $db = DB::db();
foreach (pickGeneralFromPool($db, $rng, 0, $cnt) as $pickedNPC) {
$age = $rng->nextRangeInt(20, 25); $age = $rng->nextRangeInt(20, 25);
$birthYear = $env['year'] - $age; $birthYear = $env['year'] - $age;
$deathYear = $env['year'] + $rng->nextRangeInt(10, 50); $deathYear = $env['year'] + $rng->nextRangeInt(10, 50);
@@ -50,6 +52,12 @@ class CreateManyNPC extends \sammo\Event\Action
} }
$newNPC->fillRemainSpecAsZero($env); $newNPC->fillRemainSpecAsZero($env);
$newNPC->build($env); $newNPC->build($env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$newNPC,
$pickedNPC->getInfo(),
$env
);
$pickedNPC->occupyGeneralName(); $pickedNPC->occupyGeneralName();
$result[] = [ $result[] = [
$newNPC->getGeneralName(), $newNPC->getGeneralID() $newNPC->getGeneralName(), $newNPC->getGeneralID()
+1 -1
View File
@@ -120,7 +120,7 @@ class RaiseInvader extends \sammo\Event\Action
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->setValue('isunited', 1); $gameStor->setValue('isunited', 1);
$turnterm = abs($gameStor->turnterm); $turnterm = $gameStor->turnterm;
$generalCnt = $db->queryFirstField('SELECT count(*) FROM general'); $generalCnt = $db->queryFirstField('SELECT count(*) FROM general');
if ($npcEachCount < 0) { if ($npcEachCount < 0) {
+1 -1
View File
@@ -134,7 +134,7 @@ class UpdateNationLevel extends \sammo\Event\Action
if ($levelDiff) { if ($levelDiff) {
//유니크 아이템 하나 돌리자 //유니크 아이템 하나 돌리자
$targetKillTurn = $env['killturn']; $targetKillTurn = $env['killturn'];
$targetKillTurn -= 24 * 60 / abs($env['turnterm']); $targetKillTurn -= 24 * 60 / $env['turnterm'];
$nationGenIDList = $db->queryFirstColumn( $nationGenIDList = $db->queryFirstColumn(
'SELECT no FROM general WHERE nation = %i AND killturn >= %i AND npc < 2', 'SELECT no FROM general WHERE nation = %i AND killturn >= %i AND npc < 2',
$nation['nation'], $nation['nation'],
+6 -1
View File
@@ -184,6 +184,10 @@ class GameConstBase
/** @var array 선택할 수 없으나 게임 내에 유효한 장수 내정 특기 */ /** @var array 선택할 수 없으나 게임 내에 유효한 장수 내정 특기 */
public static $optionalSpecialDomestic = [ public static $optionalSpecialDomestic = [
'None', 'None',
'che_event_귀병', 'che_event_신산', 'che_event_환술', 'che_event_집중', 'che_event_신중',
'che_event_반계', 'che_event_보병', 'che_event_궁병', 'che_event_기병', 'che_event_공성',
'che_event_돌격', 'che_event_무쌍', 'che_event_견고', 'che_event_위압', 'che_event_저격',
'che_event_필살', 'che_event_징병', 'che_event_의술', 'che_event_격노', 'che_event_척사',
]; ];
/** @var string 기본 전투 특기 */ /** @var string 기본 전투 특기 */
@@ -384,7 +388,6 @@ class GameConstBase
'che_포상', 'che_포상',
'che_몰수', 'che_몰수',
'che_부대탈퇴지시', 'che_부대탈퇴지시',
'che_행동지시',
], ],
'외교' => [ '외교' => [
'che_물자원조', 'che_물자원조',
@@ -417,6 +420,8 @@ class GameConstBase
public static $retirementYear = 80; public static $retirementYear = 80;
public static $targetGeneralPool = 'RandomNameGeneral'; public static $targetGeneralPool = 'RandomNameGeneral';
/** @var float 100기 올스타 NPC의 원본 목표 대비 최종 숙련 비율 */
public static $centennialNpcDexTargetRatio = 0.4;
public static $generalPoolAllowOption = ['stat', 'ego', 'picture']; public static $generalPoolAllowOption = ['stat', 'ego', 'picture'];
public static $randGenFirstName = [ public static $randGenFirstName = [
+66 -29
View File
@@ -281,23 +281,13 @@ class General extends GeneralBase implements iAction
$this->calcCache[$cacheKey] = $result; $this->calcCache[$cacheKey] = $result;
return $result; return $result;
} }
$recwar = new \DateTimeImmutable($this->getVar('recent_war')); $tickDiff = Util::toInt($this->getVar('turntime')) - Util::toInt($this->getVar('recent_war'));
$turnNow = new \DateTimeImmutable($this->getVar('turntime')); if ($tickDiff <= 0) {
$secDiff = TimeUtil::DateIntervalToSeconds($recwar->diff($turnNow));
if ($secDiff <= 0) {
$this->calcCache[$cacheKey] = 0; $this->calcCache[$cacheKey] = 0;
return 0; return 0;
} }
if($turnTerm == -60){ $result = intdiv($tickDiff, GameClock::TICKS_PER_TURN);
$turnDiff = VarTurn60::calcTurnDiff($recwar, $turnNow);
$this->calcCache[$cacheKey] = $turnDiff;
return $turnDiff;
}
$result = intdiv(Util::toInt($secDiff), 60 * $turnTerm);
$this->calcCache[$cacheKey] = $result; $this->calcCache[$cacheKey] = $result;
return $result; return $result;
} }
@@ -1052,18 +1042,64 @@ class General extends GeneralBase implements iAction
/** @var Map<GeneralAccessLog,int>|null */ /** @var Map<GeneralAccessLog,int>|null */
$rawAccessLog = null; $rawAccessLog = null;
$rankColumnValues = array_map(fn (\BackedEnum $e) => $e->value, $rankColumn);
if (!$accessLogColumn) { if (!$accessLogColumn) {
$rawGeneral = $db->queryFirstRow('SELECT %l FROM general WHERE no = %i', Util::formatListOfBackticks($column), $generalID); if ($rankColumn) {
$rawGeneral = $db->queryFirstRow(
'SELECT %l, (
SELECT GROUP_CONCAT(
CONCAT(`type`, CHAR(61), `value`)
ORDER BY `type`
)
FROM rank_data
WHERE rank_data.general_id = general.no
AND `type` IN %ls
) AS `_rank_values`
FROM general WHERE no = %i',
Util::formatListOfBackticks($column),
$rankColumnValues,
$generalID
);
} else {
$rawGeneral = $db->queryFirstRow(
'SELECT %l FROM general WHERE no = %i',
Util::formatListOfBackticks($column),
$generalID
);
}
} else { } else {
$rawGeneral = $db->queryFirstRow( if ($rankColumn) {
'SELECT %l, %l FROM `general` LEFT JOIN general_access_log $rawGeneral = $db->queryFirstRow(
ON general.no = general_access_log.general_id WHERE no = %i', 'SELECT %l, %l, (
Util::formatListOfBackticks($column), SELECT GROUP_CONCAT(
Util::formatListOfBackticks($accessLogColumn), CONCAT(`type`, CHAR(61), `value`)
$generalID ORDER BY `type`
); )
FROM rank_data
WHERE rank_data.general_id = general.no
AND `type` IN %ls
) AS `_rank_values`
FROM `general` LEFT JOIN general_access_log
ON general.no = general_access_log.general_id
WHERE no = %i',
Util::formatListOfBackticks($column),
Util::formatListOfBackticks($accessLogColumn),
$rankColumnValues,
$generalID
);
} else {
$rawGeneral = $db->queryFirstRow(
'SELECT %l, %l FROM `general` LEFT JOIN general_access_log
ON general.no = general_access_log.general_id WHERE no = %i',
Util::formatListOfBackticks($column),
Util::formatListOfBackticks($accessLogColumn),
$generalID
);
}
}
if ($accessLogColumn) {
$rawAccessLog = new Map(); $rawAccessLog = new Map();
foreach ($accessLogColumn as $accessLogKey) { foreach ($accessLogColumn as $accessLogKey) {
if (!key_exists($accessLogKey->value, $rawGeneral)) { if (!key_exists($accessLogKey->value, $rawGeneral)) {
@@ -1083,15 +1119,16 @@ class General extends GeneralBase implements iAction
$rawRankValues = new Map(); $rawRankValues = new Map();
if ($rankColumn) { if ($rankColumn) {
$rawValue = $db->queryAllLists( $rawRankPairs = $rawGeneral['_rank_values'];
'SELECT `type`, `value` FROM rank_data WHERE general_id = %i AND `type` IN %ls', foreach ($rawRankPairs === null || $rawRankPairs === ''
$generalID, ? []
array_map(fn (\BackedEnum $e) => $e->value, $rankColumn) : explode(',', $rawRankPairs) as $rawRankPair
); ) {
foreach ($rawValue as [$rawRankType, $rankValue]) { [$rawRankType, $rankValue] = explode('=', $rawRankPair, 2);
$rankType = RankColumn::tryFrom($rawRankType); $rankType = RankColumn::from($rawRankType);
$rawRankValues->put($rankType, $rankValue); $rawRankValues->put($rankType, (int) $rankValue);
} }
unset($rawGeneral['_rank_values']);
} }
+16 -19
View File
@@ -302,7 +302,7 @@ class GeneralAI
$this->calcWarRoute(); $this->calcWarRoute();
$troopCandidate = []; $troopCandidate = [];
$chiefTurn = cutTurn($this->general->getTurnTime(), $this->env['turnterm']); $chiefTurn = cutTurn($this->general->getTurnTick(), $this->env['turnterm']);
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']); $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
foreach ($this->troopLeaders as $troopLeader) { foreach ($this->troopLeaders as $troopLeader) {
@@ -319,7 +319,7 @@ class GeneralAI
$last발령 = $troopLeader->getAuxVar('last발령'); $last발령 = $troopLeader->getAuxVar('last발령');
if ($last발령) { if ($last발령) {
$leaderTurn = cutTurn($troopLeader->getTurnTime(), $this->env['turnterm']); $leaderTurn = cutTurn($troopLeader->getTurnTick(), $this->env['turnterm']);
$compYearMonth = $yearMonth; $compYearMonth = $yearMonth;
if ($chiefTurn < $leaderTurn) { if ($chiefTurn < $leaderTurn) {
$compYearMonth += 1; $compYearMonth += 1;
@@ -405,7 +405,7 @@ class GeneralAI
return null; return null;
} }
$chiefTurn = cutTurn($this->general->getTurnTime(), $this->env['turnterm']); $chiefTurn = cutTurn($this->general->getTurnTick(), $this->env['turnterm']);
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']); $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
$troopCandidate = []; $troopCandidate = [];
@@ -428,7 +428,7 @@ class GeneralAI
$last발령 = $troopLeader->getAuxVar('last발령'); $last발령 = $troopLeader->getAuxVar('last발령');
if ($last발령) { if ($last발령) {
$leaderTurn = cutTurn($troopLeader->getTurnTime(), $this->env['turnterm']); $leaderTurn = cutTurn($troopLeader->getTurnTick(), $this->env['turnterm']);
$compYearMonth = $yearMonth; $compYearMonth = $yearMonth;
if ($chiefTurn < $leaderTurn) { if ($chiefTurn < $leaderTurn) {
$compYearMonth += 1; $compYearMonth += 1;
@@ -586,8 +586,8 @@ class GeneralAI
continue; continue;
} }
$generalTurnTime = $userGeneral->getTurnTime(); $generalTurnTime = $userGeneral->getTurnTick();
$troopTurnTime = $troopLeader->getTurnTime(); $troopTurnTime = $troopLeader->getTurnTick();
if ($generalTurnTime < $troopTurnTime) { //NOTE: 어차피 수뇌 턴이 제일 빠르다 if ($generalTurnTime < $troopTurnTime) { //NOTE: 어차피 수뇌 턴이 제일 빠르다
$generalCadidates[$generalID] = $userGeneral; $generalCadidates[$generalID] = $userGeneral;
@@ -782,7 +782,7 @@ class GeneralAI
if ( if (
key_exists($troopLeader->getCityID(), $this->supplyCities) && key_exists($troopLeader->getCityID(), $this->supplyCities) &&
$this->troopLeaders[$troopID]->getTurnTime() < $lostGeneral->getTurnTime() $this->troopLeaders[$troopID]->getTurnTick() < $lostGeneral->getTurnTick()
) { ) {
//이미 탈출 가능한 부대를 탔다 //이미 탈출 가능한 부대를 탔다
continue; continue;
@@ -1980,13 +1980,13 @@ class GeneralAI
$db = DB::db(); $db = DB::db();
$nationStor = KVStorage::getStorage($db, $general->getNationID(), 'nation_env'); $nationStor = KVStorage::getStorage($db, $general->getNationID(), 'nation_env');
$turnTerm = abs($this->env['turnterm']); $turnTerm = $this->env['turnterm'];
//천도를 한턴 넣었다면 계속 넣는다. //천도를 한턴 넣었다면 계속 넣는다.
if ($lastTurn->getCommand() === '천도' && $lastTurn->getArg()['destCityID'] != $this->nation['capital']) { if ($lastTurn->getCommand() === '천도' && $lastTurn->getArg()['destCityID'] != $this->nation['capital']) {
$cmd = buildNationCommandClass('che_천도', $this->general, $this->env, $lastTurn, $lastTurn->getArg()); $cmd = buildNationCommandClass('che_천도', $this->general, $this->env, $lastTurn, $lastTurn->getArg());
if ($cmd->hasFullConditionMet()) { if ($cmd->hasFullConditionMet()) {
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()]; $nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTick()];
$this->reqUpdateInstance = true; $this->reqUpdateInstance = true;
return $cmd; return $cmd;
} }
@@ -1995,12 +1995,9 @@ class GeneralAI
$lastTrial = $nationStor->last천도Trial; $lastTrial = $nationStor->last천도Trial;
if ($lastTrial) { if ($lastTrial) {
[$lastTrialLevel, $lastTrialTurnTime] = $lastTrial; [$lastTrialLevel, $lastTrialTurnTime] = $lastTrial;
$timeDiffSeconds = TimeUtil::DateIntervalToSeconds( $timeDiffTick = abs($general->getTurnTick() - Util::toInt($lastTrialTurnTime));
date_create_immutable($lastTrialTurnTime)->diff( if ($timeDiffTick < intdiv(GameClock::TICKS_PER_TURN, 2)
date_create_immutable($general->getTurnTime()) && $lastTrialLevel !== $general->getVar('officer_level')) { //0.5Turn
)
);
if ($timeDiffSeconds < $turnTerm * 30 && $lastTrialLevel !== $general->getVar('officer_level')) { //0.5Turn
return null; return null;
} }
} }
@@ -2109,7 +2106,7 @@ class GeneralAI
} }
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()]; $nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTick()];
$this->reqUpdateInstance = true; $this->reqUpdateInstance = true;
return $cmd; return $cmd;
} }
@@ -3134,7 +3131,7 @@ class GeneralAI
$rawCity = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $this->general->getCityID()); $rawCity = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $this->general->getCityID());
$this->general->setRawCity($rawCity); $this->general->setRawCity($rawCity);
} }
if (!in_array($this->general->getRawCity()['level'], [5, 6])) { if (in_array($this->general->getRawCity()['level'], [5, 6])) {
return null; return null;
} }
} }
@@ -3715,7 +3712,7 @@ class GeneralAI
$this->updateInstance(); $this->updateInstance();
//특별 메세지 있는 경우 출력 //특별 메세지 있는 경우 출력
$term = abs($this->env['turnterm']); $term = $this->env['turnterm'];
if ($general->getVar('npcmsg') && $this->rng->nextBool(GameConst::$npcMessageFreqByDay * $term / (60 * 24))) { if ($general->getVar('npcmsg') && $this->rng->nextBool(GameConst::$npcMessageFreqByDay * $term / (60 * 24))) {
$src = new MessageTarget( $src = new MessageTarget(
$general->getID(), $general->getID(),
@@ -3985,7 +3982,7 @@ class GeneralAI
$userChiefCnt = 0; $userChiefCnt = 0;
$minUserKillturn = $this->env['killturn'] - Util::toInt(240 / abs($this->env['turnterm'])); $minUserKillturn = $this->env['killturn'] - Util::toInt(240 / $this->env['turnterm']);
$minNPCKillturn = 36; $minNPCKillturn = 36;
foreach (Util::range($minChiefLevel, 12) as $chiefLevel) { foreach (Util::range($minChiefLevel, 12) as $chiefLevel) {
+24 -1
View File
@@ -60,6 +60,16 @@ abstract class GeneralBase
return null; return null;
} }
$rawTurnTime = $this->getVar('turntime');
// 비교용 Dummy와 과거 fixture는 문자열을 잠시 허용하되 제품 DB는 tick만 사용합니다.
if (is_string($rawTurnTime) && !ctype_digit(ltrim($rawTurnTime, '-'))) {
$formattedTurnTime = $rawTurnTime;
} else {
$db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$formattedTurnTime = $clock->formatTick(Util::toInt($rawTurnTime), true);
}
return [ return [
self::TURNTIME_FULL_MS => function ($turntime) { self::TURNTIME_FULL_MS => function ($turntime) {
return $turntime; return $turntime;
@@ -73,7 +83,20 @@ abstract class GeneralBase
self::TURNTIME_HM => function ($turntime) { self::TURNTIME_HM => function ($turntime) {
return substr($turntime, 11, 5); return substr($turntime, 11, 5);
}, },
][$short]($this->getVar('turntime')); ][$short]($formattedTurnTime);
}
function getTurnTick(): ?int
{
if (!key_exists('turntime', $this->raw)) {
return null;
}
$rawTurnTime = $this->getVar('turntime');
if (is_string($rawTurnTime) && !ctype_digit(ltrim($rawTurnTime, '-'))) {
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
return $clock->dateTimeToTick(new \DateTimeImmutable($rawTurnTime));
}
return Util::toInt($rawTurnTime);
} }
function getNPCType(): int function getNPCType(): int
File diff suppressed because it is too large Load Diff
+6 -7
View File
@@ -59,7 +59,7 @@ class RandomNameGeneral extends AbsGeneralPool{
'generalName'=>$generalName, 'generalName'=>$generalName,
'imgsvr'=>0, 'imgsvr'=>0,
'picture'=>null 'picture'=>null
], '9999-12-31 12:00:00'); ], PHP_INT_MAX);
} }
static public function pickGeneralFromPool(MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix = null): array static public function pickGeneralFromPool(MeekroDB $db, RandUtil $rng, int $owner, int $pickCnt, ?string $prefix = null): array
@@ -68,26 +68,25 @@ class RandomNameGeneral extends AbsGeneralPool{
$result = []; $result = [];
$dbInsert = []; $dbInsert = [];
$oNow = new \DateTimeImmutable();
for($i=0;$i<$pickCnt;$i++){ for($i=0;$i<$pickCnt;$i++){
$result[] = static::pickGeneral1FromPool($db, $rng, $owner, $prefix); $result[] = static::pickGeneral1FromPool($db, $rng, $owner, $prefix);
} }
if($owner){ if($owner){
$now = $oNow->format('Y-m-d H:i:s'); $gameStor = \sammo\KVStorage::getStorage($db, 'game_env');
$clock = \sammo\GameClock::fromStorage($gameStor);
$now = $clock->nowTick();
$db->delete('select_pool', [ $db->delete('select_pool', [
'reserved_until'=>null, 'reserved_until'=>null,
'owner'=>null, 'owner'=>null,
],'(reserved_until < %s OR reserved_until IS NULL) AND general_id IS null', $now); ],'(reserved_until < %s OR reserved_until IS NULL) AND general_id IS null', $now);
$validUntil = $oNow->add(new \DateInterval(sprintf('PT%dS', 30))); $validUntil = $now + $clock->ticksFromSeconds(30);
foreach($result as $pickedGeneral){ foreach($result as $pickedGeneral){
$dbInsert[] = [ $dbInsert[] = [
'owner'=>$owner, 'owner'=>$owner,
'uniqueName'=>$pickedGeneral->getUniqueName(), 'uniqueName'=>$pickedGeneral->getUniqueName(),
'info'=>$pickedGeneral->getInfo(), 'info'=>$pickedGeneral->getInfo(),
'reserved_until'=>$validUntil->format(('Y-m-d H:i:s')) 'reserved_until'=>$validUntil
]; ];
} }
$db->insert('select_pool', $dbInsert); $db->insert('select_pool', $dbInsert);
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace sammo\GeneralPool;
use sammo\AbsFromUserPool;
use sammo\CentennialAllStarGrowthService;
use sammo\Json;
use sammo\RandUtil;
class SPoolUnderU100 extends AbsFromUserPool
{
private const MIN_DEX_WEIGHT = 100000;
private const STAT_BONUS_MIN_TOTAL = 160;
private const STAT_BONUS_MAX_TOTAL = 190;
private const STAT_BONUS_MAX_MULTIPLIER = 1.5;
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, int $validUntil)
{
$targetInfo = $info;
$initialInfo = $info;
foreach ([
'leadership',
'strength',
'intel',
'experience',
'dedication',
'dex',
'specialDomestic',
'specialWar',
] as $targetKey) {
unset($initialInfo[$targetKey]);
}
parent::__construct($db, $rng, $initialInfo, $validUntil);
$this->info = $targetInfo;
CentennialAllStarGrowthService::attachInitialTarget($this->builder, $targetInfo);
}
public static function getPoolName(): string
{
return '100기 올스타 클래식';
}
protected static function getCandidateWeight(array $info, int $owner): int|float
{
$dexWeight = max(
self::MIN_DEX_WEIGHT,
array_sum($info['dex'] ?? [])
);
if ($owner <= 0) {
return $dexWeight;
}
$statTotal = array_sum([
(int) ($info['leadership'] ?? 0),
(int) ($info['strength'] ?? 0),
(int) ($info['intel'] ?? 0),
]);
$normalizedStat = min(1, max(
0,
($statTotal - self::STAT_BONUS_MIN_TOTAL)
/ (self::STAT_BONUS_MAX_TOTAL - self::STAT_BONUS_MIN_TOTAL)
));
$statMultiplier = 1
+ (self::STAT_BONUS_MAX_MULTIPLIER - 1) * $normalizedStat;
return $dexWeight * $statMultiplier;
}
public static function initPool(\MeekroDB $db)
{
$jsonData = Json::decode(file_get_contents(__DIR__ . '/Pool/UnderS100.json'));
$columns = $jsonData['columns'];
$sqlValues = [];
foreach ($jsonData['data'] as $idx => $rawItem) {
if (count($rawItem) !== count($columns)) {
throw new \RuntimeException(($rawItem[0] ?? (string) $idx) . ' Error');
}
$item = array_combine($columns, $rawItem);
$uniqueName = sprintf('A100%04d', $idx + 1);
$item['uniqueName'] = $uniqueName;
$item['event100Growth'] = true;
$sqlValues[] = [
'unique_name' => $uniqueName,
'info' => Json::encode($item),
];
}
$db->insert('select_pool', $sqlValues);
}
}
+32 -15
View File
@@ -104,6 +104,7 @@ class Message
public static function buildFromArray(array $row) : Message public static function buildFromArray(array $row) : Message
{ {
$clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$dbMessage = Json::decode($row['message']); $dbMessage = Json::decode($row['message']);
$msgType = MessageType::from($row['type']); $msgType = MessageType::from($row['type']);
@@ -116,8 +117,8 @@ class Message
$src, $src,
$dest, $dest,
$dbMessage['text'], $dbMessage['text'],
new \DateTime($row['time']), \DateTime::createFromImmutable($clock->tickToDateTime(Util::toInt($row['time']))),
new \DateTime($row['valid_until']), \DateTime::createFromImmutable($clock->tickToDateTime(Util::toInt($row['valid_until']))),
$option $option
]; ];
@@ -151,9 +152,12 @@ class Message
public static function getMessageByID(int $messageID) : ?Message public static function getMessageByID(int $messageID) : ?Message
{ {
$db = DB::db(); $db = DB::db();
$now = new \DateTime(); $clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$row = $db->queryFirstRow('SELECT * FROM `message` WHERE `id` = %i AND valid_until', $messageID); $row = $db->queryFirstRow(
//FIXME: $now가 들어가야 하는데 안 들어가있는데? 'SELECT * FROM `message` WHERE `id` = %i AND valid_until > %i',
$messageID,
$clock->nowTick(),
);
if (!$row) { if (!$row) {
return null; return null;
} }
@@ -171,12 +175,12 @@ class Message
{ {
$db = DB::db(); $db = DB::db();
$date = (new \DateTime())->format('Y-m-d H:i:s'); $date = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->nowTick();
$where = new \WhereClause('and'); $where = new \WhereClause('and');
$where->add('mailbox = %i', $mailbox); $where->add('mailbox = %i', $mailbox);
$where->add('type = %s', $msgType->value); $where->add('type = %s', $msgType->value);
$where->add('valid_until > %s', $date); $where->add('valid_until > %i', $date);
if ($fromSeq > 0) { if ($fromSeq > 0) {
$where->add('id >= %i', $fromSeq); $where->add('id >= %i', $fromSeq);
} }
@@ -203,12 +207,12 @@ class Message
{ {
$db = DB::db(); $db = DB::db();
$date = (new \DateTime())->format('Y-m-d H:i:s'); $date = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->nowTick();
$where = new \WhereClause('and'); $where = new \WhereClause('and');
$where->add('mailbox = %i', $mailbox); $where->add('mailbox = %i', $mailbox);
$where->add('type = %s', $msgType->value); $where->add('type = %s', $msgType->value);
$where->add('valid_until > %s', $date); $where->add('valid_until > %i', $date);
$where->add('id < %i', $toSeq); $where->add('id < %i', $toSeq);
if ($limit > 0) { if ($limit > 0) {
@@ -236,7 +240,8 @@ class Message
return '시스템 외교 메시지는 삭제할 수 없습니다.'; return '시스템 외교 메시지는 삭제할 수 없습니다.';
} }
$prev5min = new \DateTime(); $clock = GameClock::fromStorage(KVStorage::getStorage(DB::db(), 'game_env'));
$prev5min = \DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick()));
$prev5min->sub(new \DateInterval('PT5M')); $prev5min->sub(new \DateInterval('PT5M'));
if($msgObj->date < $prev5min){ if($msgObj->date < $prev5min){
@@ -265,14 +270,15 @@ class Message
} }
$in1min = new \DateTime(); $now = \DateTime::createFromImmutable($clock->tickToDateTime($clock->nowTick()));
$in1min = clone $now;
$in1min->add(new \DateInterval('PT1M')); $in1min->add(new \DateInterval('PT1M'));
$newMsg = new Message( $newMsg = new Message(
$msgObj->msgType, $msgObj->msgType,
$msgObj->src, $msgObj->src,
$msgObj->dest, $msgObj->dest,
"req_del_msg", "req_del_msg",
new \DateTime(), $now,
$in1min, $in1min,
$msgOption $msgOption
); );
@@ -300,13 +306,23 @@ class Message
$db = DB::db(); $db = DB::db();
$clock = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'));
$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 = $timeTick + $clock->ticksFromSeconds($validitySeconds);
}
$this->date = \DateTime::createFromImmutable($clock->tickToDateTime($timeTick));
$this->validUntil = \DateTime::createFromImmutable($clock->tickToDateTime($validUntilTick));
$db->insert('message', [ $db->insert('message', [
'mailbox' => $mailbox, 'mailbox' => $mailbox,
'type' => $this->msgType->value, 'type' => $this->msgType->value,
'src' => $src_id, 'src' => $src_id,
'dest' => $dest_id, 'dest' => $dest_id,
'time' => $this->date->format('Y-m-d H:i:s'), 'time' => $timeTick,
'valid_until' => $this->validUntil->format('Y-m-d H:i:s'), 'valid_until' => $validUntilTick,
'message' => Json::encode([ 'message' => Json::encode([
'src'=>($this->src)?($this->src->toArray()):[], 'src'=>($this->src)?($this->src->toArray()):[],
'dest'=>($this->dest)?($this->dest->toArray()):[], 'dest'=>($this->dest)?($this->dest->toArray()):[],
@@ -489,7 +505,8 @@ class Message
'text' => $this->msg, 'text' => $this->msg,
'option' => $this->msgOption 'option' => $this->msgOption
]), ]),
'valid_until'=>$this->validUntil->format('Y-m-d H:i:s'), 'valid_until'=>GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))
->dateTimeToTick($this->validUntil),
], 'id=%i', $this->id); ], 'id=%i', $this->id);
} }
+27 -18
View File
@@ -245,23 +245,16 @@ class ResetHelper{
true true
); );
if($turnterm == -60){ $requestedTime = new \DateTimeImmutable($turntime);
[$starttime, $yearPulled, $month] = VarTurn60::fromDatetime(new \DateTimeImmutable($turntime))->cutDay(); if($sync == 0) {
if($yearPulled){
$year = $startyear-1;
}
else{
$year = $startyear;
}
}
else if($sync == 0) {
// 현재 시간을 1월로 맞춤 // 현재 시간을 1월로 맞춤
$starttime = cutTurn($turntime, $turnterm); $baseTime = new \DateTimeImmutable(cutTurnDateTime($turntime, $turnterm));
$month = 1; $month = 1;
$year = $startyear; $year = $startyear;
} else { } else {
// 현재 시간과 동기화 // 현재 시간과 동기화
[$starttime, $yearPulled, $month] = cutDay($turntime, $turnterm); [$baseTimeString, $yearPulled, $month] = cutDay($turntime, $turnterm);
$baseTime = new \DateTimeImmutable($baseTimeString);
if($yearPulled){ if($yearPulled){
$year = $startyear-1; $year = $startyear-1;
} }
@@ -270,7 +263,19 @@ class ResetHelper{
} }
} }
$killturn = 4800 / abs($turnterm); $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); } if($npcmode == 1) { $killturn = intdiv($killturn, 3); }
$develcost = ($year - $startyear + 10) * 2; $develcost = ($year - $startyear + 10) * 2;
@@ -292,10 +297,14 @@ class ResetHelper{
'maxnation'=>GameConst::$defaultMaxNation, 'maxnation'=>GameConst::$defaultMaxNation,
'refreshLimit'=>30000, 'refreshLimit'=>30000,
'develcost'=>$develcost, 'develcost'=>$develcost,
'turntime'=>$turntime, 'turntime'=>$requestedTick,
'starttime'=>$starttime, 'starttime'=>0,
'opentime'=>$turntime, 'opentime'=>$requestedTick,
'turnterm'=>$turnterm, 'turnterm'=>$turnterm,
'clock_base_time'=>TimeUtil::format($baseTime, true),
'clock_tick'=>$currentTick,
'clock_mode'=>GameClock::MODE_REALTIME,
'clock_wall_anchor'=>TimeUtil::format($wallNow, true),
'killturn'=>$killturn, 'killturn'=>$killturn,
'genius'=>GameConst::$defaultMaxGenius, 'genius'=>GameConst::$defaultMaxGenius,
'show_img_level'=>$show_img_level, 'show_img_level'=>$show_img_level,
@@ -317,7 +326,7 @@ class ResetHelper{
'name'=>$admin['name'], 'name'=>$admin['name'],
'picture'=>$admin['picture'], 'picture'=>$admin['picture'],
'imgsvr'=>$admin['imgsvr'], 'imgsvr'=>$admin['imgsvr'],
'turntime'=>$turntime, 'turntime'=>$requestedTick,
'killturn'=>9999, 'killturn'=>9999,
'crewtype'=>GameUnitConst::DEFAULT_CREWTYPE 'crewtype'=>GameUnitConst::DEFAULT_CREWTYPE
]); ]);
@@ -353,7 +362,7 @@ class ResetHelper{
$db->insert('ng_games', [ $db->insert('ng_games', [
'server_id'=>$serverID, 'server_id'=>$serverID,
'date'=>$turntime, 'date'=>TimeUtil::format($requestedTime, false),
'winner_nation'=>null, 'winner_nation'=>null,
'map'=>$scenarioObj->getMapTheme(), 'map'=>$scenarioObj->getMapTheme(),
'season'=>$seasonIdx, 'season'=>$seasonIdx,
+1 -1
View File
@@ -653,7 +653,7 @@ class GeneralBuilder{
$officerLevel = $nationID?1:0; $officerLevel = $nationID?1:0;
} }
$turntime = \sammo\getRandTurn($this->rng, $env['turnterm'], new \DateTimeImmutable($env['turntime'])); $turntime = \sammo\getRandTurn($this->rng, $env['turnterm'], Util::toInt($env['turntime']));
if($this->killturn){ if($this->killturn){
$killturn = $this->killturn; $killturn = $this->killturn;
+3 -3
View File
@@ -35,7 +35,7 @@ class ScoutMessage extends Message
$this->validScout = false; $this->validScout = false;
} }
if ($this->validUntil <= new \DateTime()) { if ($this->validUntil <= $this->date) {
$this->validScout = false; $this->validScout = false;
} }
} }
@@ -122,11 +122,11 @@ class ScoutMessage extends Message
public static function invalidateAll(int $generalID, ?int $exceptMsgID = null) public static function invalidateAll(int $generalID, ?int $exceptMsgID = null)
{ {
$db = DB::db(); $db = DB::db();
$now = TimeUtil::now(); $now = GameClock::fromStorage(KVStorage::getStorage($db, 'game_env'))->nowTick();
//XXX: 뭔가 기존 쿼리가 애매하다. invalid 관련해서 다른 옵션이 가능한가? //XXX: 뭔가 기존 쿼리가 애매하다. invalid 관련해서 다른 옵션이 가능한가?
$rawMsgList = Util::convertArrayToDict($db->query( $rawMsgList = Util::convertArrayToDict($db->query(
'SELECT * FROM `message` WHERE 'SELECT * FROM `message` WHERE
`mailbox` = %i AND `type` = "private" AND `dest` = `mailbox` AND `valid_until` > %s AND `mailbox` = %i AND `type` = "private" AND `dest` = `mailbox` AND `valid_until` > %i AND
JSON_VALUE(message, "$.option.action") = %s', JSON_VALUE(message, "$.option.action") = %s',
$generalID, $generalID,
$now, $now,
+14 -28
View File
@@ -20,17 +20,6 @@ final class ServerTool
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
$admin = $gameStor->getValues(['turntime', 'turnterm', 'year', 'startyear', 'month', 'isunited']); $admin = $gameStor->getValues(['turntime', 'turnterm', 'year', 'startyear', 'month', 'isunited']);
$oldunit = $admin['turnterm'] * 60;
$unit = $turnterm * 60;
if($unit == $oldunit){
return null;
}
if($oldunit < 0 || $unit < 0){
return 'variable_turnterm';
}
$reqGameLock = $admin['isunited'] != 2 && !$ignoreLock; $reqGameLock = $admin['isunited'] != 2 && !$ignoreLock;
$locked = false; $locked = false;
@@ -51,25 +40,22 @@ final class ServerTool
$locked = tryLock(); $locked = tryLock();
} }
if($turnterm == $admin['turnterm']){
$unitDiff = $unit / $oldunit; if($locked){
unlock();
$servTurnTime = new \DateTimeImmutable($admin['turntime']); }
foreach ($db->query('SELECT no,turntime FROM general') as $gen) { return null;
$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'); $oldClock = GameClock::fromStorage($gameStor);
$starttime = cutTurn($starttime, $turnterm, false); $currentTick = $oldClock->nowTick();
$currentDisplay = $oldClock->tickToDateTime($currentTick);
$oldClock->persistTick($gameStor, $currentTick);
$gameStor->turnterm = $turnterm; $gameStor->turnterm = $turnterm;
$gameStor->starttime = $starttime; $gameStor->clock_base_time = TimeUtil::format(
GameClock::baseTimeForProjection($currentDisplay, $currentTick, $turnterm),
true,
);
pushGlobalHistoryLog(["<R>★</>턴시간이 <C>{$turnterm}분</>으로 변경됩니다."]); pushGlobalHistoryLog(["<R>★</>턴시간이 <C>{$turnterm}분</>으로 변경됩니다."]);
if($locked){ if($locked){
+14 -23
View File
@@ -215,31 +215,21 @@ class TurnExecutionHelper
$general->rebirth(); $general->rebirth();
} }
$turntime = addTurn($general->getTurnTime(), $gameStor->turnterm); $turntime = addTurn($general->getTurnTick(), $gameStor->turnterm);
$nextTurnTimeBase = $general->getAuxVar('nextTurnTimeBase'); $nextTurnTimeBase = $general->getAuxVar('nextTurnTimeBase');
if($nextTurnTimeBase !== null){ if($nextTurnTimeBase !== null){
if($gameStor->turnterm == -60){ $turntime = cutTurn($turntime, $gameStor->turnterm);
[$turntime, $nextTurnterm] = VarTurn60::fromDatetime(new \DateTimeImmutable($turntime))->cutTurn(); $clock = GameClock::fromStorage($gameStor);
$turntimeObj = new \DateTimeImmutable($turntime); $turntime += $clock->ticksFromSeconds($nextTurnTimeBase);
$turntimeObj = $turntimeObj->add(TimeUtil::secondsToDateInterval($nextTurnTimeBase * $nextTurnterm / 60)); $general->setAuxVar('nextTurnTimeBase', null);
$turntime = TimeUtil::format($turntimeObj, true);
$general->setAuxVar('nextTurnTimeBase', null);
}
else {
$turntime = cutTurn($turntime, $gameStor->turnterm);
$turntimeObj = new \DateTimeImmutable($turntime);
$turntimeObj = $turntimeObj->add(TimeUtil::secondsToDateInterval($nextTurnTimeBase));
$turntime = TimeUtil::format($turntimeObj, true);
$general->setAuxVar('nextTurnTimeBase', null);
}
} }
$general->setVar('turntime', $turntime); $general->setVar('turntime', $turntime);
} }
static public function executeGeneralCommandUntil(string $date, \DateTimeInterface $limitActionTime, int $year, int $month) static public function executeGeneralCommandUntil(int $date, \DateTimeInterface $limitActionTime, int $year, int $month)
{ {
$db = DB::db(); $db = DB::db();
$generalsTodo = $db->query( $generalsTodo = $db->query(
@@ -253,7 +243,7 @@ class TurnExecutionHelper
$autorun_user = $gameStor->autorun_user; $autorun_user = $gameStor->autorun_user;
foreach ($generalsTodo as $rawGeneral) { foreach ($generalsTodo as $rawGeneral) {
$currActionTime = new \DateTimeImmutable(); $currActionTime = GameClock::readWallTime();
if ($currActionTime > $limitActionTime) { if ($currActionTime > $limitActionTime) {
return [true, $currentTurn]; return [true, $currentTurn];
} }
@@ -359,12 +349,12 @@ class TurnExecutionHelper
pullNationCommand($general->getVar('nation'), $general->getVar('officer_level')); pullNationCommand($general->getVar('nation'), $general->getVar('officer_level'));
pullGeneralCommand($general->getID()); pullGeneralCommand($general->getID());
$currentTurn = $general->getTurnTime(); $currentTurn = $general->getTurnTick();
$general->increaseVarWithLimit('myset', GameConst::$incDefSettingChange, null, GameConst::$maxDefSettingChange); $general->increaseVarWithLimit('myset', GameConst::$incDefSettingChange, null, GameConst::$maxDefSettingChange);
if (($autorun_user['limit_minutes'] ?? false) && $general->getNPCType() < 2 && $hasReservedTurn) { if (($autorun_user['limit_minutes'] ?? false) && $general->getNPCType() < 2 && $hasReservedTurn) {
$autorun_limit = Util::joinYearMonth($year, $month); $autorun_limit = Util::joinYearMonth($year, $month);
$autorun_limit += intdiv($autorun_user['limit_minutes'], abs($turnterm)); $autorun_limit += intdiv($autorun_user['limit_minutes'], $turnterm);
$general->setAuxVar('autorun_limit', $autorun_limit); $general->setAuxVar('autorun_limit', $autorun_limit);
} }
@@ -399,13 +389,14 @@ class TurnExecutionHelper
return true; return true;
} }
static public function executeAllCommand(&$executed = false, &$locked = false): string static public function executeAllCommand(&$executed = false, &$locked = false): int
{ {
$db = DB::db(); $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor = KVStorage::getStorage($db, 'game_env');
if (TimeUtil::now(true) < $gameStor->turntime) { $clock = GameClock::fromStorage($gameStor);
if ($clock->nowTick() < $gameStor->turntime) {
//턴 시각 이전이면 아무것도 하지 않음 //턴 시각 이전이면 아무것도 하지 않음
return $gameStor->turntime; return $gameStor->turntime;
} }
@@ -429,7 +420,7 @@ class TurnExecutionHelper
//접속자 수 따라서 갱신제한 변경 //접속자 수 따라서 갱신제한 변경
CheckOverhead(); CheckOverhead();
$date = TimeUtil::now(true); $date = $clock->nowTick();
// 최종 처리 월턴의 다음 월턴시간 구함 // 최종 처리 월턴의 다음 월턴시간 구함
//$lastExecuted = $gameStor->turntime; //$lastExecuted = $gameStor->turntime;
$prevTurn = cutTurn($gameStor->turntime, $gameStor->turnterm); $prevTurn = cutTurn($gameStor->turntime, $gameStor->turnterm);
@@ -442,7 +433,7 @@ class TurnExecutionHelper
$maxActionTime = max($maxActionTime * 2 / 3, $maxActionTime - 10); $maxActionTime = max($maxActionTime * 2 / 3, $maxActionTime - 10);
} }
$limitActionTime = (new \DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($maxActionTime)); $limitActionTime = GameClock::readWallTime()->add(TimeUtil::secondsToDateInterval($maxActionTime));
// 현재 턴 이전 월턴까지 모두처리. // 현재 턴 이전 월턴까지 모두처리.
//최종 처리 이후 다음 월턴이 현재 시간보다 전이라면 //최종 처리 이후 다음 월턴이 현재 시간보다 전이라면
-191
View File
@@ -1,191 +0,0 @@
<?php
namespace sammo;
class VarTurn60
{
const MonthAdjustOffset = 2;
static array $min30ToTurn = [
[0, 0, 120], //00:00 2
[0, 30, 120], //00:30
[0, 60, 120], //01:00
[0, 90, 120], //01:30
[1, 0, 120], //02:00 3
[1, 30, 120], //02:30
[1, 60, 120], //03:00
[1, 90, 120], //03:30
[2, 0, 120], //04:00 4
[2, 30, 120], //04:30
[2, 60, 120], //05:00
[2, 90, 120], //05:30
[3, 0, 60], //06:00 5
[3, 30, 60], //06:30
[4, 0, 60], //07:00 6
[4, 30, 60], //07:30
[5, 0, 60], //08:00 7
[5, 30, 60], //08:30
[6, 0, 60], //09:00 8
[6, 30, 60], //09:30
[7, 0, 60], //10:00 9
[7, 30, 60], //10:30
[8, 0, 60], //11:00 10
[8, 30, 60], //11:30
[9, 0, 60], //12:00 11
[9, 30, 60], //12:30
[10, 0, 60], //13:00 0(12)
[10, 30, 60], //13:30
[11, 0, 60], //14:00 1(13)
[11, 30, 60], //14:30
[12, 0, 60], //15:00 2(14)
[12, 30, 60], //15:30
[13, 0, 60], //16:00 3(15)
[13, 30, 60], //16:30
[14, 0, 60], //17:00 4(16)
[14, 30, 60], //17:30
[15, 0, 60], //18:00 5(17)
[15, 30, 60], //18:30
[16, 0, 30], //19:00 6(18)
[17, 0, 30], //19:30 7(19)
[18, 0, 30], //20:00 8(20)
[19, 0, 30], //20:30 9(21)
[20, 0, 30], //21:00 10(22)
[21, 0, 30], //21:30 11(23)
[22, 0, 60], //22:00 0
[22, 30, 60], //22:30
[23, 0, 60], //23:00 1
[23, 30, 60], //23:30
//[24, 0, 60], //24:00 2
];
static array $turnToHM = [
[0, 0, 120], //0
[2, 0, 120], //1
[4, 0, 120], //2
[6, 0, 60], //3
[7, 0, 60], //4
[8, 0, 60], //5
[9, 0, 60], //6
[10, 0, 60], //7
[11, 0, 60], //8
[12, 0, 60], //9
[13, 0, 60], //10
[14, 0, 60], //11
[15, 0, 60], //12
[16, 0, 60], //13
[17, 0, 60], //14
[18, 0, 60], //15
[19, 0, 30], //16
[19, 30, 30],//17
[20, 0, 30], //18
[20, 30, 30],//19
[21, 0, 30], //20
[21, 30, 30],//21
[22, 0, 60], //22
[23, 0, 60], //23
//[24, 0, 60], //24(?)
];
function __construct(
public \DateTimeImmutable $baseDay,
public int $turnIdx, //0~23
public float $secOffset, ///turnTerm에 따라 최대치가 30*60, 60*60, 120*60 가변
)
{
}
static function fromDatetime(\DateTimeInterface $date): self {
$baseDay = new \DateTimeImmutable($date->format('Y-m-d'));
$totalSec = $date->getTimestamp() - $baseDay->getTimestamp();
$min30 = intdiv($totalSec, 60 * 30);
assert($min30 < 48);
[$turnIdx, $minOffset, ] = static::$min30ToTurn[$min30];
$secOffset = $totalSec - 60 * 30 * $min30 + $minOffset * 60;
return new static($baseDay, $turnIdx, $secOffset);
}
/// $b - $a 의 턴차
static function calcTurnDiff(\DateTimeInterface $a, \DateTimeInterface $b): int{
$aObj = static::fromDatetime($a);
$bObj = static::fromDatetime($b);
$resDays = $aObj->baseDay->diff($bObj->baseDay)->days; //놀랍게도 절대값
if($aObj->baseDay > $bObj->baseDay){
$resDays *= -1;
}
$res = $resDays * 24 + $bObj->turnIdx - $aObj->turnIdx;
return $res;
}
function cutTurn($withFraction = true): array{
[$hour, $minOffset, $turnTerm] = static::$turnToHM[$this->turnIdx];
$date = $this->baseDay->add(TimeUtil::secondsToDateInterval(($hour * 60 + $minOffset) * 60));
return [TimeUtil::format($date, $withFraction), $turnTerm];
}
function toDateStr($withFraction = true): string{
[$hour, $minOffset, ] = static::$turnToHM[$this->turnIdx];
$date = $this->baseDay->add(TimeUtil::secondsToDateInterval(($hour * 60 + $minOffset) * 60 + $this->secOffset));
return TimeUtil::format($date, $withFraction);
}
function cutDay($withFraction = true): array{
//고정 시간 기준 cutDay()의 이식
//상수 테이블이 이미 13:00에 1월이 되도록 맞춰져 있음.
//다만 20:00인 경우 8월이 아니라 9월이 됨 ^^;
$newMonth = ($this->turnIdx + static::MonthAdjustOffset) % 12 + 1;
$yearPulled = $newMonth > 3;
$obj = $yearPulled ? $this->addTurn(12): $this;
[$date, ] = $obj->addTurn(-($newMonth - 1))->cutTurn($withFraction);
return [$date, $yearPulled, $newMonth];
}
function addTurn(int $moreTurn): self{
$dayDiff = intdiv($moreTurn, 24);
$moreTurn %= 24;
$nextTurnIdx = $this->turnIdx + $moreTurn;
if($nextTurnIdx < 0){
$dayDiff -= 1;
$nextTurnIdx += 24;
}
else if($nextTurnIdx >= 24){
$dayDiff += 1;
$nextTurnIdx -= 24;
}
[, , $oldTurnTerm] = static::$turnToHM[$this->turnIdx];
[, , $nextTurnTerm] = static::$turnToHM[$nextTurnIdx];
$nextSecOffset = $this->secOffset;
if($oldTurnTerm != $nextTurnTerm){
$nextSecOffset *= $nextTurnTerm;
$nextSecOffset /= $oldTurnTerm;
}
if($dayDiff == 0){
return new static($this->baseDay, $nextTurnIdx, $nextSecOffset);
}
if($dayDiff > 0){
$nextBaseDay = $this->baseDay->add(new \DateInterval("P{$dayDiff}D"));
}
else{
$dayAbsDiff = abs($dayDiff);
$nextBaseDay = $this->baseDay->sub(new \DateInterval("P{$dayAbsDiff}D"));
}
return new static($nextBaseDay, $nextTurnIdx, $nextSecOffset);
}
}
+5 -5
View File
@@ -57,16 +57,16 @@ class WarUnitGeneral extends WarUnit
$this->general->increaseRankVar(RankColumn::warnum, 1); $this->general->increaseRankVar(RankColumn::warnum, 1);
if ($this->isAttacker) { if ($this->isAttacker) {
$semiTurn = $general->getTurnTime(); $semiTurn = $general->getTurnTick();
} else if ($oppose !== null) { } else if ($oppose !== null) {
$semiTurn = $oppose->getGeneral()->getTurnTime(); $semiTurn = $oppose->getGeneral()->getTurnTick();
} else { } else {
LogText("WarUnitGeneral::setOppose", "defender인데 oppose가 null {$general->getID()}, {$general->getTurnTime()}"); LogText("WarUnitGeneral::setOppose", "defender인데 oppose가 null {$general->getID()}, {$general->getTurnTime()}");
$semiTurn = $general->getTurnTime(); $semiTurn = $general->getTurnTick();
} }
$phase = $this->getRealPhase(); $phase = $this->getRealPhase();
$semiTurn = substr($semiTurn, 0, strlen($semiTurn) - 2); $semiTurn -= $semiTurn % 100;
$semiTurn .= sprintf("%02d", Util::valueFit($phase, 0, 99)); $semiTurn += Util::valueFit($phase, 0, 99);
$general->setVar('recent_war', $semiTurn); $general->setVar('recent_war', $semiTurn);
} }
-7
View File
@@ -81,13 +81,6 @@
["RaiseNPCNation"], ["RaiseNPCNation"],
["DeleteEvent"] ["DeleteEvent"]
], ],
[
"month", 1000,
true,
["ChangeCity", "all", {
"trade":100
}]
],
[ [
"month", 999, "month", 999,
["Date", "==", 181, 1], ["Date", "==", 181, 1],
+92
View File
@@ -0,0 +1,92 @@
{
"title":"【공백지】 요양 ",
"startYear":180,
"map":{
"mapName":"miniche"
},
"history":[
"<C>●</>180년 1월:<L><b>【이벤트】</b></>쉬었다 가는 깃수입니다."
],
"const": {
"joinRuinedNPCProp":0,
"npcBanMessageProb":0.005,
"defaultMaxGeneral": 800,
"allItems":{
"horse":{
"che_명마_01_노기":0, "che_명마_02_조랑":0, "che_명마_03_노새":0,
"che_명마_04_나귀":0, "che_명마_05_갈색마":0, "che_명마_06_흑색마":0,
"che_명마_07_백마" : 2, "che_명마_07_기주마" : 2, "che_명마_07_오환마" : 2, "che_명마_07_백상" : 2,
"che_명마_08_양주마" : 2, "che_명마_08_흉노마" : 2, "che_명마_09_과하마" : 2, "che_명마_09_의남백마" : 2,
"che_명마_10_대완마" : 2, "che_명마_10_옥추마" : 2, "che_명마_11_서량마" : 2, "che_명마_11_화종마" : 2,
"che_명마_12_사륜거" : 2, "che_명마_12_옥란백용구": 2, "che_명마_13_절영" : 2, "che_명마_13_적로" : 2,
"che_명마_14_적란마" : 2, "che_명마_14_조황비전" : 2, "che_명마_15_한혈마" : 2, "che_명마_15_적토마" : 2
},
"weapon":{
"che_무기_01_단도":0, "che_무기_02_단궁":0, "che_무기_03_단극":0,
"che_무기_04_목검":0, "che_무기_05_죽창":0, "che_무기_06_소부":0,
"che_무기_07_동추":2, "che_무기_07_철편":2, "che_무기_07_철쇄":2, "che_무기_07_맥궁":2,
"che_무기_08_유성추":2, "che_무기_08_철질여골":2, "che_무기_09_쌍철극":2, "che_무기_09_동호비궁":2,
"che_무기_10_삼첨도":2, "che_무기_10_대부":2, "che_무기_11_고정도":2, "che_무기_11_이광궁":2,
"che_무기_12_철척사모":2, "che_무기_12_칠성검":2, "che_무기_13_사모":2, "che_무기_13_양유기궁":2,
"che_무기_14_언월도":2, "che_무기_14_방천화극":2, "che_무기_15_청홍검":2, "che_무기_15_의천검":2
},
"book":{
"che_서적_01_효경전":0, "che_서적_02_회남자":0, "che_서적_03_변도론":0,
"che_서적_04_건상역주":0, "che_서적_05_여씨춘추":0, "che_서적_06_사민월령":0,
"che_서적_07_위료자":2, "che_서적_07_사마법":2, "che_서적_07_한서":2, "che_서적_07_논어":2,
"che_서적_08_전론":2, "che_서적_08_사기":2, "che_서적_09_장자":2, "che_서적_09_역경":2,
"che_서적_10_시경":2, "che_서적_10_구국론":2, "che_서적_11_상군서":2, "che_서적_11_춘추전":2,
"che_서적_12_산해경":2, "che_서적_12_맹덕신서":2, "che_서적_13_관자":2, "che_서적_13_병법24편":2,
"che_서적_14_한비자":2, "che_서적_14_오자병법":2, "che_서적_15_노자":2, "che_서적_15_손자병법":2
},
"item":{
"che_치료_환약":0, "che_저격_수극":0, "che_사기_탁주":0,
"che_훈련_청주":0, "che_계략_이추":0, "che_계략_향낭":0,
"event_전투특기_격노":0, "event_전투특기_견고":0, "event_전투특기_공성":0, "event_전투특기_궁병":0,
"event_전투특기_귀병":0, "event_전투특기_기병":0, "event_전투특기_돌격":0, "event_전투특기_무쌍":0,
"event_전투특기_반계":0, "event_전투특기_보병":0, "event_전투특기_신산":0, "event_전투특기_신중":0,
"event_전투특기_위압":0, "event_전투특기_의술":0, "event_전투특기_저격":0, "event_전투특기_집중":0,
"event_전투특기_징병":0, "event_전투특기_척사":0, "event_전투특기_필살":0, "event_전투특기_환술":0
}
}
},
"events":[
[
"month", 1000,
["or", ["Date", "==", null, 12], ["Date", "==", null, 6]],
["CreateManyNPC", 15, 10],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", 181, 1],
["RaiseNPCNation"],
["DeleteEvent"]
],
[
"month", 999,
["Date", "==", 181, 1],
["OpenNationBetting", 4, 5000],
["OpenNationBetting", 1, 2000],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", null, 1],
["ChangeCity", "all", {
"trade":100
}]
],
[
"month", 1000,
["Date", "==", null, 7],
["ChangeCity", "all", {
"trade":100
}]
]
]
}
+71
View File
@@ -0,0 +1,71 @@
{
"title": "【공백지】 100기 올스타 클래식",
"startYear": 180,
"map": {
"mapName": "miniche",
"targetGeneralPool": "SPoolUnderU100",
"generalPoolAllowOption": ["stat", "ego", "picture"],
"centennialNpcDexTargetRatio": 0.4
},
"history": [
"<C>●</>180년 1월:<L><b>【100기 이벤트】</b></> 역대 장수들이 평범한 능력으로 다시 모여, 지난 전성기의 힘과 서서히 동조하기 시작했다!"
],
"const": {
"npcBanMessageProb":0.005,
"defaultMaxGeneral": 800,
"uniqueTrialCoef": 2
},
"events": [
[
"month", 8000,
true,
["AdvanceCentennialAllStar"]
],
[
"month", 1000,
["Date", "==", null, 12],
["CreateManyNPC", 100, 0],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", 181, 1],
["RaiseNPCNation"],
["DeleteEvent"]
],
[
"destroy_nation", 1000,
["and",
["Date", ">=", 183, 1],
["RemainNation", "==", 1]
],
["BlockScoutAction"],
["DeleteEvent"]
],
[
"month", 999,
["Date", "==", 181, 1],
["OpenNationBetting", 4, 5000],
["OpenNationBetting", 1, 2000],
["DeleteEvent"]
],
[
"month", 999,
["and",
["Date", ">=", 183, 1],
["RemainNation", "<=", 8]
],
["OpenNationBetting", 1, 1000],
["DeleteEvent"]
],
[
"destroy_nation", 1000,
["and",
["Date", ">=", 183, 1],
["RemainNation", "==", 1]
],
["BlockScoutAction"],
["DeleteEvent"]
]
]
}
+87 -30
View File
@@ -19,8 +19,16 @@ if ($admin['npcmode'] != 2) {
$member = RootDB::db()->queryFirstRow("SELECT no,name,picture,imgsvr,grade from member where no= %i", $userID); $member = RootDB::db()->queryFirstRow("SELECT no,name,picture,imgsvr,grade from member where no= %i", $userID);
$generalID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID); $currentGeneral = $db->queryFirstRow(
'SELECT no,picture,imgsvr FROM general WHERE owner = %i',
$userID
);
$generalID = $currentGeneral['no'] ?? null;
$gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2'); $gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2');
$isCentennialAllStar = CentennialAllStarGrowthService::isActive();
$canUseOwnPicture = $admin['show_img_level'] >= 1
&& $member['grade'] >= 1
&& $member['picture'] != "";
$nationList = $db->query('SELECT nation,`name`,color,scout FROM nation'); $nationList = $db->query('SELECT nation,`name`,color,scout FROM nation');
shuffle($nationList); shuffle($nationList);
@@ -54,6 +62,7 @@ foreach (getCharacterList(false) as $id => [$name, $info]) {
<script> <script>
var hasGeneralID = <?= $generalID === null ? 'false' : 'true' ?>; var hasGeneralID = <?= $generalID === null ? 'false' : 'true' ?>;
var isCentennialAllStar = <?= $isCentennialAllStar ? 'true' : 'false' ?>;
var defaultStatTotal = <?= GameConst::$defaultStatTotal ?>; var defaultStatTotal = <?= GameConst::$defaultStatTotal ?>;
var defaultStatMin = <?= GameConst::$defaultStatMin ?>; var defaultStatMin = <?= GameConst::$defaultStatMin ?>;
var defaultStatMax = <?= GameConst::$defaultStatMax ?>; var defaultStatMax = <?= GameConst::$defaultStatMax ?>;
@@ -104,6 +113,27 @@ if ($gencount >= $admin['maxgeneral']) {
<small id="valid_until">(<span id="valid_until_text"></span>까지 유효)</small><small id="outdate_token">- 만료 -</small><br> <small id="valid_until">(<span id="valid_until_text"></span>까지 유효)</small><small id="outdate_token">- 만료 -</small><br>
<form class="card_holder"> <form class="card_holder">
</form> </form>
<?php if ($isCentennialAllStar && $generalID !== null) : ?>
<div id="reselect_picture_plate" class="picture_choice">
<strong>변경 후 전콘</strong>
<label>
<input type="radio" name="reselect_picture_source" value="selected" checked>
새로 선택할 장수 전콘
</label>
<label>
<input type="radio" name="reselect_picture_source" value="current">
<img width="32" height="32" src="<?= GetImageURL($currentGeneral['imgsvr']) ?>/<?= $currentGeneral['picture'] ?>" border="0">
현재 장수 전콘
</label>
<?php if ($canUseOwnPicture) : ?>
<label>
<input type="radio" name="reselect_picture_source" value="own">
<img width="32" height="32" src="<?= GetImageURL($member['imgsvr']) ?>/<?= $member['picture'] ?>" border="0">
내 원래 전콘
</label>
<?php endif; ?>
</div>
<?php endif; ?>
</div> </div>
</div> </div>
@@ -117,7 +147,28 @@ if ($gencount >= $admin['maxgeneral']) {
<form id='custom_form'> <form id='custom_form'>
<table class='tb_layout' style='width:100%;text-align:left;'> <table class='tb_layout' style='width:100%;text-align:left;'>
<?php <?php
if ($admin['show_img_level'] >= 1 && $member['grade'] >= 1 && $member['picture'] != "") { if ($isCentennialAllStar) {
echo "
<tr class='event_picture'>
<td align=right class='bg1'>전콘 선택</td>
<td colspan=2>
<label><input type=radio name=picture_source value=selected checked> 선택한 장수 전콘</label>
";
if ($canUseOwnPicture) {
$imageTemp = GetImageURL($member['imgsvr']);
echo "
<label>
<input type=radio name=picture_source value=own>
<img width='64' height='64' src='{$imageTemp}/{$member['picture']}' border='0'>
내 전콘
</label>
";
}
echo "
</td>
</tr>
";
} elseif ($canUseOwnPicture) {
$imageTemp = GetImageURL($member['imgsvr']); $imageTemp = GetImageURL($member['imgsvr']);
echo " echo "
<tr class='custom_picture'> <tr class='custom_picture'>
@@ -144,36 +195,42 @@ if ($gencount >= $admin['maxgeneral']) {
</select> <span id="charInfoText"></span> </select> <span id="charInfoText"></span>
</td> </td>
</tr> </tr>
<tr class='custom_stat'> <?php if (!$isCentennialAllStar) : ?>
<td align=right class='bg1'>통솔</td> <tr class='custom_stat'>
<td colspan=2><input type="number" name="leadership" id="leadership" value="50"></td> <td align=right class='bg1'>통솔</td>
</tr> <td colspan=2><input type="number" name="leadership" id="leadership" value="50"></td>
<tr class='custom_stat'> </tr>
<td align=right class='bg1'>무력</td> <tr class='custom_stat'>
<td colspan=2><input type="number" name="strength" id="strength" value="50"></td> <td align=right class='bg1'>무력</td>
</tr> <td colspan=2><input type="number" name="strength" id="strength" value="50"></td>
<tr class='custom_stat'> </tr>
<td align=right class='bg1'>지력</td> <tr class='custom_stat'>
<td colspan=2><input type="number" name="intel" id="intel" value="50"></td> <td align=right class='bg1'>지력</td>
</tr> <td colspan=2><input type="number" name="intel" id="intel" value="50"></td>
<tr class='custom_stat'> </tr>
<td align=right class='bg1'>능력치 조정</td> <tr class='custom_stat'>
<td colspan=2> <td align=right class='bg1'>능력치 조정</td>
<input type=button value=랜덤형 onclick=abilityRand()> <td colspan=2>
<input type=button value=통솔무력형 onclick=abilityLeadpow()> <input type=button value=랜덤형 onclick=abilityRand()>
<input type=button value=통솔력형 onclick=abilityLeadint()> <input type=button value=통솔력형 onclick=abilityLeadpow()>
<input type=button value=무력지력형 onclick=abilityPowint()> <input type=button value=통솔지력형 onclick=abilityLeadint()>
</td> <input type=button value=무력지력형 onclick=abilityPowint()>
</tr> </td>
<tr class='custom_stat'> </tr>
<td align=center colspan=3> <tr class='custom_stat'>
<font color=orange>모든 능력치는 ( <?= GameConst::$defaultStatMin ?> <= 능력치 <=<?= GameConst::$defaultStatMax ?> ) 사이로 잡으셔야 합니다.<br> <td align=center colspan=3>
그 외의 능력치는 가입되지 않습니다.</font> <font color=orange>모든 능력치는 ( <?= GameConst::$defaultStatMin ?> <= 능력치 <=<?= GameConst::$defaultStatMax ?> ) 사이로 잡으셔야 합니다.<br>
</td> 그 외의 능력치는 가입되지 않습니다.</font>
</tr> </td>
</tr>
<?php endif; ?>
<tr> <tr>
<td align=center colspan=3> <td align=center colspan=3>
<span class='custom_stat'>능력치의 총합은 <?= GameConst::$defaultStatTotal ?>입니다. 가입후 0~10의 능력치 보너스를 받게 됩니다.<br></span> <?php if ($isCentennialAllStar) : ?>
선택한 장수의 최종 능력치 비율을 반영한 약화 능력치로 시작합니다.<br>
<?php else : ?>
<span class='custom_stat'>능력치의 총합은 <?= GameConst::$defaultStatTotal ?>입니다. 가입후 0~10의 능력치 보너스를 받게 됩니다.<br></span>
<?php endif; ?>
임의의 도시에서 재야로 시작하며 건국과 임관은 게임 내에서 실행합니다. 임의의 도시에서 재야로 시작하며 건국과 임관은 게임 내에서 실행합니다.
</td> </td>
</tr> </tr>
+11 -11
View File
@@ -45,8 +45,8 @@ CREATE TABLE `general` (
`book` VARCHAR(20) NOT NULL DEFAULT 'None', `book` VARCHAR(20) NOT NULL DEFAULT 'None',
`horse` VARCHAR(20) NOT NULL DEFAULT 'None', `horse` VARCHAR(20) NOT NULL DEFAULT 'None',
`item` VARCHAR(20) NOT NULL DEFAULT 'None', `item` VARCHAR(20) NOT NULL DEFAULT 'None',
`turntime` DATETIME(6) NOT NULL, `turntime` BIGINT NOT NULL,
`recent_war` DATETIME(6) NULL DEFAULT NULL, `recent_war` BIGINT NULL DEFAULT NULL,
`makelimit` INT(2) NULL DEFAULT '0', `makelimit` INT(2) NULL DEFAULT '0',
`killturn` INT(3) NULL DEFAULT NULL, `killturn` INT(3) NULL DEFAULT NULL,
`block` INT(1) NULL DEFAULT '0', `block` INT(1) NULL DEFAULT '0',
@@ -97,7 +97,7 @@ CREATE TABLE `general_access_log` (
`id` INT(11) NOT NULL AUTO_INCREMENT, `id` INT(11) NOT NULL AUTO_INCREMENT,
`general_id` INT(11) NOT NULL, `general_id` INT(11) NOT NULL,
`user_id` INT(11) NULL DEFAULT NULL, `user_id` INT(11) NULL DEFAULT NULL,
`last_refresh` DATETIME NULL DEFAULT NULL, `last_refresh` BIGINT NULL DEFAULT NULL,
`refresh` INT(11) NOT NULL DEFAULT '0', `refresh` INT(11) NOT NULL DEFAULT '0',
`refresh_total` INT(11) NOT NULL DEFAULT '0', `refresh_total` INT(11) NOT NULL DEFAULT '0',
`refresh_score` INT(11) NOT NULL DEFAULT '0', `refresh_score` INT(11) NOT NULL DEFAULT '0',
@@ -244,8 +244,8 @@ CREATE TABLE `message` (
`type` ENUM('private', 'national', 'public', 'diplomacy') NOT NULL, `type` ENUM('private', 'national', 'public', 'diplomacy') NOT NULL,
`src` INT(11) NOT NULL, `src` INT(11) NOT NULL,
`dest` INT(11) NOT NULL, `dest` INT(11) NOT NULL,
`time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `time` BIGINT NOT NULL,
`valid_until` DATETIME NOT NULL DEFAULT '9999-12-31 23:59:59', `valid_until` BIGINT NOT NULL,
`message` TEXT NOT NULL COLLATE 'utf8mb4_bin', `message` TEXT NOT NULL COLLATE 'utf8mb4_bin',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
INDEX `by_mailbox` (`mailbox`, `type`, `id`), INDEX `by_mailbox` (`mailbox`, `type`, `id`),
@@ -536,8 +536,8 @@ CREATE TABLE IF NOT EXISTS `reserved_open` (
CREATE TABLE `select_npc_token` ( CREATE TABLE `select_npc_token` (
`id` INT(11) NOT NULL AUTO_INCREMENT, `id` INT(11) NOT NULL AUTO_INCREMENT,
`owner` INT(11) NOT NULL, `owner` INT(11) NOT NULL,
`valid_until` DATETIME NOT NULL, `valid_until` BIGINT NOT NULL,
`pick_more_from` DATETIME NOT NULL, `pick_more_from` BIGINT NOT NULL,
`pick_result` TEXT NOT NULL COLLATE 'utf8mb4_bin', `pick_result` TEXT NOT NULL COLLATE 'utf8mb4_bin',
`nonce` INT(11) NOT NULL, `nonce` INT(11) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@@ -553,7 +553,7 @@ CREATE TABLE `select_pool` (
`unique_name` VARCHAR(20) NOT NULL, `unique_name` VARCHAR(20) NOT NULL,
`owner` INT(11) NULL DEFAULT NULL, `owner` INT(11) NULL DEFAULT NULL,
`general_id` INT(11) NULL DEFAULT NULL, `general_id` INT(11) NULL DEFAULT NULL,
`reserved_until` DATETIME NULL DEFAULT NULL, `reserved_until` BIGINT NULL DEFAULT NULL,
`info` TEXT NOT NULL, `info` TEXT NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE INDEX `unique_name` (`unique_name`), UNIQUE INDEX `unique_name` (`unique_name`),
@@ -694,11 +694,11 @@ CREATE TABLE `ng_auction` (
`target` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_bin', `target` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_bin',
`host_general_id` INT(11) NOT NULL, `host_general_id` INT(11) NOT NULL,
`req_resource` ENUM('gold','rice','inheritPoint') NOT NULL COLLATE 'utf8mb4_bin', `req_resource` ENUM('gold','rice','inheritPoint') NOT NULL COLLATE 'utf8mb4_bin',
`open_date` DATETIME NOT NULL, `open_tick` BIGINT NOT NULL,
`close_date` DATETIME NOT NULL, `close_tick` BIGINT NOT NULL,
`detail` LONGTEXT NOT NULL COLLATE 'utf8mb4_bin', `detail` LONGTEXT NOT NULL COLLATE 'utf8mb4_bin',
PRIMARY KEY (`id`) USING BTREE, PRIMARY KEY (`id`) USING BTREE,
INDEX `by_close` (`finished`, `type`, `close_date`) USING BTREE, INDEX `by_close` (`finished`, `type`, `close_tick`) USING BTREE,
INDEX `by_general_id` (`host_general_id`, `type`, `finished`) USING BTREE, INDEX `by_general_id` (`host_general_id`, `type`, `finished`) USING BTREE,
CONSTRAINT `detail` CHECK (json_valid(`detail`)) CONSTRAINT `detail` CHECK (json_valid(`detail`))
) )
+5 -17
View File
@@ -68,7 +68,7 @@ import { formatTime } from "@/util/formatTime";
import { mb_strwidth } from "@/util/mb_strwidth"; import { mb_strwidth } from "@/util/mb_strwidth";
import { parseTime } from "@/util/parseTime"; import { parseTime } from "@/util/parseTime";
import type { StoredActionsHelper } from "@/util/StoredActionsHelper"; import type { StoredActionsHelper } from "@/util/StoredActionsHelper";
import { addMinutes } from "date-fns/addMinutes"; import addMinutes from "date-fns/esm/addMinutes/index";
import { range } from "lodash-es"; import { range } from "lodash-es";
import { inject, onMounted, ref, type PropType } from "vue"; import { inject, onMounted, ref, type PropType } from "vue";
import VueTypes from "vue-types"; import VueTypes from "vue-types";
@@ -76,7 +76,6 @@ import DragSelect from "@/components/DragSelect.vue";
import { BButton } from "bootstrap-vue-next"; import { BButton } from "bootstrap-vue-next";
import { QueryActionHelper } from "@/util/QueryActionHelper"; import { QueryActionHelper } from "@/util/QueryActionHelper";
import type { ChiefResponse } from "@/defs/API/NationCommand"; import type { ChiefResponse } from "@/defs/API/NationCommand";
import { VarTurn60 } from "@/varTurn60";
const props = defineProps({ const props = defineProps({
style: VueTypes.object.isRequired, style: VueTypes.object.isRequired,
@@ -199,21 +198,10 @@ if (!props.officer || !props.officer.turnTime) {
} }
} else { } else {
const baseTurnTime = parseTime(props.officer.turnTime); const baseTurnTime = parseTime(props.officer.turnTime);
if(props.turnTerm == -60){ for (const idx of range(props.officer.turn.length)) {
const baseTurnTimeObj = VarTurn60.fromDatetime(baseTurnTime); turnTimes.value.push(
for (const idx of range(props.officer.turn.length)) { formatTime(addMinutes(baseTurnTime, idx * props.turnTerm), props.turnTerm >= 5 ? "HH:mm" : "mm:ss")
turnTimes.value.push( );
formatTime(baseTurnTimeObj.addTurn(idx).toDate(), "HH:mm")
);
}
} }
else{
for (const idx of range(props.officer.turn.length)) {
turnTimes.value.push(
formatTime(addMinutes(baseTurnTime, idx * props.turnTerm), props.turnTerm >= 5 ? "HH:mm" : "mm:ss")
);
}
}
} }
</script> </script>
+17 -3
View File
@@ -172,7 +172,7 @@
<BButton class="col-6 offset-6" variant="primary" @click="checkOwner"> 소유자 찾기 </BButton> <BButton class="col-6 offset-6" variant="primary" @click="checkOwner"> 소유자 찾기 </BButton>
</div> </div>
</div> </div>
<div class="col col-lg-4 col-sm-6 col-12 py-2"> <div v-if="canResetStat" class="col col-lg-4 col-sm-6 col-12 py-2">
<div class="row px-4"> <div class="row px-4">
<div class="a-right col-6 align-self-center">능력치 초기화</div> <div class="a-right col-6 align-self-center">능력치 초기화</div>
<div class="col-6"> <div class="col-6">
@@ -222,6 +222,17 @@
<BButton class="col-6 offset-6" variant="primary" @click="resetStat"> 능력치 초기화</BButton> <BButton class="col-6 offset-6" variant="primary" @click="resetStat"> 능력치 초기화</BButton>
</div> </div>
</div> </div>
<div v-else class="col col-lg-4 col-sm-6 col-12 py-2">
<div class="row px-4">
<div class="a-right col-6 align-self-center">능력치 초기화</div>
<div class="col-6 align-self-center">사용 불가</div>
</div>
<div class="a-right">
<small class="form-text text-muted">
100 올스타 장수는 장수 전환 능력치 성장 기록을 보존하기 위해 능력치 초기화를 사용할 없습니다.
</small>
</div>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@@ -297,6 +308,7 @@ declare const staticValues: {
} }
>; >;
availableTargetGeneral: Record<number, string>; availableTargetGeneral: Record<number, string>;
canResetStat: boolean;
currentStat: { currentStat: {
leadership: number; leadership: number;
strength: number; strength: number;
@@ -310,13 +322,14 @@ declare const staticValues: {
import { reactive, ref, watch } from "vue"; import { reactive, ref, watch } from "vue";
import "@scss/game_bg.scss"; import "@scss/game_bg.scss";
import TopBackBar from "@/components/TopBackBar.vue"; import TopBackBar from "@/components/TopBackBar.vue";
import { sum } from "lodash-es"; import _ from "lodash-es";
import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue"; import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue";
import { SammoAPI } from "./SammoAPI"; import { SammoAPI } from "./SammoAPI";
import { InheritResetStat, type inheritBuffType, type InheritPointLogItem } from "./defs/API/InheritAction"; import { InheritResetStat, type inheritBuffType, type InheritPointLogItem } from "./defs/API/InheritAction";
import * as JosaUtil from "@/util/JosaUtil"; import * as JosaUtil from "@/util/JosaUtil";
import { BButton } from "bootstrap-vue-next"; import { BButton } from "bootstrap-vue-next";
import { unwrap } from "./util/unwrap"; import { unwrap } from "./util/unwrap";
import { add as dateAdd } from 'date-fns';
const inheritanceViewText: Record<InheritanceViewType, { title: string; info: string }> = { const inheritanceViewText: Record<InheritanceViewType, { title: string; info: string }> = {
sum: { sum: {
@@ -427,7 +440,7 @@ const title = "유산 관리";
const items = ref( const items = ref(
(() => { (() => {
const totalPoint = Math.floor(sum(Object.values(staticValues.items))); const totalPoint = Math.floor(_.sum(Object.values(staticValues.items)));
const previousPoint = Math.floor(staticValues.items["previous"]); const previousPoint = Math.floor(staticValues.items["previous"]);
const newPoint = Math.floor(totalPoint - previousPoint); const newPoint = Math.floor(totalPoint - previousPoint);
const result: Record<InheritanceViewType, number> = { const result: Record<InheritanceViewType, number> = {
@@ -446,6 +459,7 @@ const {
availableSpecialWar, availableSpecialWar,
availableUnique, availableUnique,
availableTargetGeneral, availableTargetGeneral,
canResetStat,
currentStat currentStat
} = staticValues; } = staticValues;
+1 -1
View File
@@ -485,7 +485,7 @@ watch(inheritCity, (newValue: undefined | number) => {
const inheritTurnTimeZone = ref<number>(); const inheritTurnTimeZone = ref<number>();
const turnTimeZoneList: string[] = (()=>{ const turnTimeZoneList: string[] = (()=>{
const result: string[] = []; const result: string[] = [];
const zoneSec = Math.abs(turnterm); // * 60 / 60 const zoneSec = turnterm; // * 60 / 60
let zoneCur = 0; let zoneCur = 0;
for(const idx of range(60)){ for(const idx of range(60)){
const zoneNext = zoneCur + zoneSec; const zoneNext = zoneCur + zoneSec;
+2 -9
View File
@@ -276,7 +276,7 @@ declare const staticValues: {
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { addMinutes } from "date-fns/addMinutes"; import addMinutes from "date-fns/esm/addMinutes";
import { isString, range, trim } from "lodash-es"; import { isString, range, trim } from "lodash-es";
import queryString from "query-string"; import queryString from "query-string";
import { onMounted, ref, watch } from "vue"; import { onMounted, ref, watch } from "vue";
@@ -297,7 +297,6 @@ import { QueryActionHelper } from "./util/QueryActionHelper";
import SimpleClock from "./components/SimpleClock.vue"; import SimpleClock from "./components/SimpleClock.vue";
import type { ReservedCommandResponse } from "./defs/API/Command"; import type { ReservedCommandResponse } from "./defs/API/Command";
import { unwrap } from "./util/unwrap"; import { unwrap } from "./util/unwrap";
import { VarTurn60 } from "./varTurn60";
defineExpose({ defineExpose({
updateCommandTable, updateCommandTable,
@@ -545,13 +544,7 @@ async function reloadCommandList() {
}); });
yearMonth += 1; yearMonth += 1;
if(result.turnTerm == -60){ nextTurnTime = addMinutes(nextTurnTime, result.turnTerm);
nextTurnTime = VarTurn60.fromDatetime(nextTurnTime).addTurn(1).toDate();
}
else{
nextTurnTime = addMinutes(nextTurnTime, result.turnTerm);
}
} }
serverNow.value = parseTime(result.date); serverNow.value = parseTime(result.date);
+2 -2
View File
@@ -1,6 +1,6 @@
import type { AutoLoginFailed, AutoLoginNonceResponse, AutoLoginResponse, LoginFailed, LoginResponse } from "./defs/API/Login"; import type { AutoLoginFailed, AutoLoginNonceResponse, AutoLoginResponse, LoginFailed, LoginResponse } from "./defs/API/Login";
import { APIPathGen } from "./util/APIPathGen"; import { APIPathGen } from "./util/APIPathGen";
import { callSammoAPI, extractHttpMethod, GET, POST, type APICallT, type APITail, type InvalidResponse, type RawArgType, type ValidResponse } from "./util/callSammoAPI"; import { callSammoAPI, extractHttpMethod, POST, type APICallT, type APITail, type InvalidResponse, type RawArgType, type ValidResponse } from "./util/callSammoAPI";
export type { ValidResponse, InvalidResponse }; export type { ValidResponse, InvalidResponse };
const apiRealPath = { const apiRealPath = {
@@ -18,7 +18,7 @@ const apiRealPath = {
hashedToken: string, hashedToken: string,
token_id: number, token_id: number,
}, AutoLoginResponse, AutoLoginFailed>, }, AutoLoginResponse, AutoLoginFailed>,
ReqNonce: GET as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed> ReqNonce: POST as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed>
}, },
} as const; } as const;
+3 -10
View File
@@ -256,7 +256,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { addMinutes } from "date-fns/addMinutes"; import addMinutes from "date-fns/esm/addMinutes";
import queryString from "query-string"; import queryString from "query-string";
import { onMounted, ref, watch, type PropType, inject, type Ref } from "vue"; import { onMounted, ref, watch, type PropType, inject, type Ref } from "vue";
import { formatTime } from "@util/formatTime"; import { formatTime } from "@util/formatTime";
@@ -282,7 +282,6 @@ import { unwrap_err } from "@/util/unwrap_err";
import type { GameConstStore } from "@/GameConstStore"; import type { GameConstStore } from "@/GameConstStore";
import { postFilterNationCommandGen } from "@/utilGame/postFilterNationCommandGen"; import { postFilterNationCommandGen } from "@/utilGame/postFilterNationCommandGen";
import { unwrap } from "@/util/unwrap"; import { unwrap } from "@/util/unwrap";
import { VarTurn60 } from "@/varTurn60";
const toasts = unwrap(useToast()); const toasts = unwrap(useToast());
@@ -537,19 +536,13 @@ function updateCommandList() {
...obj, ...obj,
year, year,
month, month,
time: formatTime(nextTurnTime, Math.abs(props.turnTerm) >= 5 ? "HH:mm" : "mm:ss"), time: formatTime(nextTurnTime, props.turnTerm >= 5 ? "HH:mm" : "mm:ss"),
tooltip: tooltip.length == 0 ? undefined : tooltip.join("\n"), tooltip: tooltip.length == 0 ? undefined : tooltip.join("\n"),
style, style,
}); });
yearMonth += 1; yearMonth += 1;
nextTurnTime = addMinutes(nextTurnTime, props.turnTerm);
if(props.turnTerm == -60){
nextTurnTime = VarTurn60.fromDatetime(nextTurnTime).addTurn(1).toDate();
}
else{
nextTurnTime = addMinutes(nextTurnTime, props.turnTerm);
}
} }
reservedCommandList.value = _reservedCommandList; reservedCommandList.value = _reservedCommandList;
updated.value = true; updated.value = true;
+1 -1
View File
@@ -23,7 +23,7 @@
<AutorunInfo :autorunMode="globalInfo.autorunUser" /> <AutorunInfo :autorunMode="globalInfo.autorunUser" />
</div> </div>
<div class="s-border-t col py-2 col-8 col-lg-4 subYearMonth"> <div class="s-border-t col py-2 col-8 col-lg-4 subYearMonth">
현재: {{ globalInfo.year }} {{ globalInfo.month }} ({{ globalInfo.turnterm == -60 ? '가변 60' : globalInfo.turnterm }} 서버) 현재: {{ globalInfo.year }} {{ globalInfo.month }} ({{ globalInfo.turnterm }} 서버)
</div> </div>
<div class="s-border-t col py-2 col-4 col-lg-2 subOnlineUserCnt"> <div class="s-border-t col py-2 col-4 col-lg-2 subOnlineUserCnt">
전체 접속자 : {{ (globalInfo.onlineUserCnt ?? 0).toLocaleString() }} 전체 접속자 : {{ (globalInfo.onlineUserCnt ?? 0).toLocaleString() }}
+2 -8
View File
@@ -175,9 +175,8 @@ import { clamp } from "lodash-es";
import { formatCityName } from "@/utilGame/formatCityName"; import { formatCityName } from "@/utilGame/formatCityName";
import { isValidObjKey } from "@/utilGame/isValidObjKey"; import { isValidObjKey } from "@/utilGame/isValidObjKey";
import { calcInjury } from "@/utilGame/calcInjury"; import { calcInjury } from "@/utilGame/calcInjury";
import { addMinutes } from "date-fns/addMinutes"; import { addMinutes } from "date-fns/esm";
import type { GameIActionInfo } from "@/defs/GameObj"; import type { GameIActionInfo } from "@/defs/GameObj";
import { VarTurn60 } from "@/varTurn60";
const imagePath = window.pathConfig.gameImage; const imagePath = window.pathConfig.gameImage;
const gameConstStore = unwrap(inject<Ref<GameConstStore>>("gameConstStore")); const gameConstStore = unwrap(inject<Ref<GameConstStore>>("gameConstStore"));
const props = defineProps<{ const props = defineProps<{
@@ -279,12 +278,7 @@ watch(
() => { () => {
let turnTime = parseTime(general.value.turntime); let turnTime = parseTime(general.value.turntime);
if (turnTime.getTime() < props.lastExecuted.getTime()) { if (turnTime.getTime() < props.lastExecuted.getTime()) {
if(props.turnTerm == -60){ turnTime = addMinutes(turnTime, props.turnTerm);
turnTime = VarTurn60.fromDatetime(turnTime).addTurn(1).toDate();
}
else {
turnTime = addMinutes(turnTime, props.turnTerm);
}
} }
nextExecuteMinute.value = Math.floor(clamp((turnTime.getTime() - props.lastExecuted.getTime()) / 60000, 0, 999)); nextExecuteMinute.value = Math.floor(clamp((turnTime.getTime() - props.lastExecuted.getTime()) / 60000, 0, 999));
}, },
+1 -1
View File
@@ -127,7 +127,7 @@
<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 { parseTime } from "@/util/parseTime";
import { addMinutes, differenceInMilliseconds } from "date-fns"; 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";
+1 -1
View File
@@ -93,7 +93,7 @@ $(function () {
const runAnalysis = async function () { const runAnalysis = async function () {
let realKillturn = killturn; let realKillturn = killturn;
if(autorun_user && autorun_user.limit_minutes){ if(autorun_user && autorun_user.limit_minutes){
realKillturn -= autorun_user.limit_minutes / Math.abs(turnterm); realKillturn -= autorun_user.limit_minutes / turnterm;
} }
const $content = $('#on_mover .content'); const $content = $('#on_mover .content');
try { try {
+47 -32
View File
@@ -37,14 +37,16 @@ function regNextToken(tokenInfo: [number, string]) {
function getToken(): [number, string] | undefined { function getToken(): [number, string] | undefined {
const trialToken = localStorage.getItem(LOGIN_TOKEN_KEY); const trialToken = localStorage.getItem(LOGIN_TOKEN_KEY);
if (!trialToken) { if (!trialToken) {
console.log('no token');
return; return;
} }
const tokenItems = JSON.parse(trialToken) as [number, [number, string], string]; const tokenItems = JSON.parse(trialToken) as [number, [number, string], number];
if (tokenItems[0] != TOKEN_VERSION) { if (tokenItems[0] != TOKEN_VERSION) {
console.log(tokenItems); console.log(tokenItems);
resetToken(); resetToken();
return; return;
} }
console.debug(localStorage.getItem(LOGIN_TOKEN_KEY));
const [, token,] = tokenItems; const [, token,] = tokenItems;
return token; return token;
} }
@@ -62,39 +64,54 @@ async function tryAutoLogin() {
const [tokenID, token] = tokenInfo; const [tokenID, token] = tokenInfo;
const result = await SammoRootAPI.Login.ReqNonce(undefined, true); for (let attempt = 0; attempt < 2; attempt++) {
const reqNonceStartAt = Date.now();
const nonceResult = await SammoRootAPI.Login.ReqNonce(undefined, true);
if (!result) { if (!nonceResult) {
//api 에러. //api 에러.
return; return;
}
if (!result.result) {
resetToken();
return;
}
const nonce = result.loginNonce;
const hashedToken = sha512(token + nonce);
const loginResult = await SammoRootAPI.Login.LoginByToken({
'hashedToken': hashedToken,
'token_id': tokenID,
}, true);
if (!loginResult.result) {
if (!loginResult.silent) {
alert(loginResult.reason);
} }
console.error(loginResult.reason);
if (!nonceResult.result) {
resetToken();
return;
}
const nonce = nonceResult.loginNonce;
console.debug(
'try auto login with token',
tokenID,
`attempt:${attempt + 1}`,
`reqNonceElapsed:${Date.now() - reqNonceStartAt}ms`
);
const hashedToken = sha512(token + nonce);
const loginResult = await SammoRootAPI.Login.LoginByToken({
'hashedToken': hashedToken,
'token_id': tokenID,
}, true);
if (!loginResult.result) {
if (loginResult.reason === '자동 로그인: 절차 오류' && attempt === 0) {
console.warn('auto login failed by procedure error. retrying once.');
await delay(150);
continue;
}
if (!loginResult.silent) {
alert(loginResult.reason);
}
console.error(loginResult.reason);
return;
}
if (loginResult.nextToken) {
regNextToken(loginResult.nextToken);
}
window.location.href = "./";
return; return;
} }
if (loginResult.nextToken) {
regNextToken(loginResult.nextToken);
}
window.location.href = "./";
} }
catch (e) { catch (e) {
if (isString(e)) { if (isString(e)) {
@@ -103,8 +120,6 @@ async function tryAutoLogin() {
console.error(e); console.error(e);
return; return;
} }
} }
function getOAuthToken(mode: string, scope_list?: string[] | string) { function getOAuthToken(mode: string, scope_list?: string[] | string) {
+1 -1
View File
@@ -147,7 +147,7 @@ const descriptor: NamedRules<InstallFormType> = {
turnterm: { turnterm: {
required: true, required: true,
type: "enum", type: "enum",
enum: [1, 2, 5, 10, 20, 30, 60, 120, -60], enum: [1, 2, 5, 10, 20, 30, 60, 120],
transform: parseInt, transform: parseInt,
}, },
sync: { sync: {
+3 -3
View File
@@ -6,7 +6,7 @@ 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, last, trim } from 'lodash-es'; import _, { isError, isString } from 'lodash-es';
import { addMinutes } from 'date-fns'; import { addMinutes } from 'date-fns';
import { parseTime } from '@util/parseTime'; import { parseTime } from '@util/parseTime';
import { formatTime } from '@util/formatTime'; import { formatTime } from '@util/formatTime';
@@ -234,7 +234,7 @@ function redrawMsg(msgResponse: MsgResponse, addFront: boolean): MsgResponse {
if (!msgList || msgList.length == 0) { if (!msgList || msgList.length == 0) {
continue; continue;
} }
const lastMsg = unwrap(last(msgList)); const lastMsg = unwrap(_.last(msgList));
minMsgSeq[msgType] = Math.min(minMsgSeq[msgType], lastMsg.id); minMsgSeq[msgType] = Math.min(minMsgSeq[msgType], lastMsg.id);
} }
return obj; return obj;
@@ -605,7 +605,7 @@ function activateMessageForm() {
$msgSubmit.on('click', async function () { $msgSubmit.on('click', async function () {
const text = trim(unwrap_any<string>($msgInput.val())); const text = _.trim(unwrap_any<string>($msgInput.val()));
$msgInput.val('').trigger('focus'); $msgInput.val('').trigger('focus');
const targetMailbox = unwrap_any<string>($mailboxList.val()); const targetMailbox = unwrap_any<string>($mailboxList.val());

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