Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4de7ebec17 | ||
|
|
d43dff35fb | ||
|
|
5b13f8bd30 | ||
|
|
e51bc6f711 | ||
|
|
1b08aa0c58 | ||
|
|
f2547fe207 | ||
|
|
cec7726731 | ||
|
|
d679a6fed8 |
+5
-49
@@ -923,13 +923,6 @@ function banner()
|
||||
|
||||
function addTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true)
|
||||
{
|
||||
if($turnterm < 0){
|
||||
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"));
|
||||
@@ -941,13 +934,6 @@ function addTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true)
|
||||
|
||||
function subTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true)
|
||||
{
|
||||
if($turnterm < 0){
|
||||
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"));
|
||||
@@ -959,14 +945,6 @@ function subTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true)
|
||||
|
||||
function cutTurn($date, int $turnterm, bool $withFraction = true)
|
||||
{
|
||||
if($turnterm < 0){
|
||||
if($turnterm == -60){
|
||||
[$baseDate, ] = VarTurn60::fromDatetime(new \DateTimeImmutable(($date)))->cutTurn($withFraction);
|
||||
return $baseDate;
|
||||
}
|
||||
throw new \Exception("InvalidTurnTerm".$turnterm);
|
||||
}
|
||||
|
||||
$date = new \DateTime($date);
|
||||
|
||||
$baseDate = new \DateTime($date->format('Y-m-d'));
|
||||
@@ -985,8 +963,6 @@ function cutTurn($date, int $turnterm, bool $withFraction = true)
|
||||
|
||||
function cutDay($date, int $turnterm, bool $withFraction = true)
|
||||
{
|
||||
assert($turnterm > 0);
|
||||
|
||||
$date = new \DateTime($date);
|
||||
|
||||
$baseDate = new \DateTime($date->format('Y-m-d'));
|
||||
@@ -1071,7 +1047,7 @@ function increaseRefresh($type = "", $cnt = 1)
|
||||
|
||||
$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');
|
||||
|
||||
$logDB->insert('api_log', [
|
||||
@@ -1130,7 +1106,6 @@ function CheckOverhead()
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
[$turnterm, $refreshLimit] = $gameStor->getValuesAsArray(['turnterm', 'refreshLimit']);
|
||||
$turnterm = abs($turnterm);
|
||||
|
||||
$nextRefreshLimit = Util::round(pow($turnterm, 0.6) * 3) * GameConst::$refreshLimitCoef;
|
||||
|
||||
@@ -1178,7 +1153,7 @@ function timeover(): bool
|
||||
list($turnterm, $turntime) = $gameStor->getValuesAsArray(['turnterm', 'turntime']);
|
||||
$diff = (new \DateTime())->getTimestamp() - (new \DateTime($turntime))->getTimestamp();
|
||||
|
||||
$t = min(abs($turnterm), 5);
|
||||
$t = min($turnterm, 5);
|
||||
|
||||
$term = $diff;
|
||||
if ($term >= $t || $term < 0) {
|
||||
@@ -1200,12 +1175,6 @@ function checkDelay()
|
||||
|
||||
// 1턴이상 갱신 없었으면 서버 지연
|
||||
$term = $gameStor->turnterm;
|
||||
|
||||
if($term < 0){
|
||||
//가변 턴에는 수행 안함!
|
||||
return;
|
||||
}
|
||||
|
||||
if ($term >= 20) {
|
||||
$threshold = 1;
|
||||
} else if ($term >= 10) {
|
||||
@@ -1285,14 +1254,10 @@ function turnDate($curtime)
|
||||
$admin = $gameStor->getValues(['startyear', 'starttime', 'turnterm', 'year', 'month']);
|
||||
|
||||
$turn = $admin['starttime'];
|
||||
$curturn = cutTurn($curtime, $admin['turnterm']);
|
||||
$term = $admin['turnterm'];
|
||||
if($term == -60){
|
||||
$num = VarTurn60::calcTurnDiff(new \DateTimeImmutable($turn), new \DateTimeImmutable($curtime));
|
||||
}
|
||||
else{
|
||||
$curturn = cutTurn($curtime, $admin['turnterm']);
|
||||
$num = intdiv((strtotime($curturn) - strtotime($turn)), $term * 60);
|
||||
}
|
||||
|
||||
$num = intdiv((strtotime($curturn) - strtotime($turn)), $term * 60);
|
||||
|
||||
$date = $admin['startyear'] * 12;
|
||||
$date += $num;
|
||||
@@ -2243,10 +2208,6 @@ function getRandTurn(RandUtil $rng, $term, ?\DateTimeInterface $baseDateTime = n
|
||||
throw new MustNotBeReachedException();
|
||||
}
|
||||
|
||||
if($term == -60){
|
||||
[, $term] = VarTurn60::fromDatetime($baseDateTime)->cutTurn();
|
||||
}
|
||||
|
||||
$randSecond = $rng->nextRangeInt(0, 60 * $term - 1);
|
||||
$randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수
|
||||
|
||||
@@ -2262,11 +2223,6 @@ function getRandTurn2(RandUtil $rng, $term, ?\DateTimeInterface $baseDateTime =
|
||||
} else {
|
||||
throw new MustNotBeReachedException();
|
||||
}
|
||||
|
||||
if($term == -60){
|
||||
[, $term] = VarTurn60::fromDatetime($baseDateTime)->cutTurn();
|
||||
}
|
||||
|
||||
$randSecond = $rng->nextRangeInt(0, 60 * $term - 1);
|
||||
$randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수
|
||||
|
||||
|
||||
+1
-6
@@ -181,12 +181,7 @@ function info($type = 0)
|
||||
|
||||
$admin = $gameStor->getValues(['year', 'month', 'turnterm', 'maxgeneral']);
|
||||
|
||||
$turnTermText = $admin['turnterm'].'분';
|
||||
if($admin['turnterm'] < 0){
|
||||
$turnTermText = "가변 {$admin['turnterm']}분";
|
||||
}
|
||||
|
||||
$termtype = "{$turnTermText}분 턴";
|
||||
$termtype = "{$admin['turnterm']}분 턴";
|
||||
|
||||
$gencount = $db->queryFirstField('SELECT count(no) FROM general WHERE npc < 2');
|
||||
$npccount = $db->queryFirstField('SELECT count(no) FROM general WHERE npc >= 2');
|
||||
|
||||
@@ -10,7 +10,6 @@ use sammo\Enums\InheritanceKey;
|
||||
* @return int 토너먼트 초 단위
|
||||
*/
|
||||
function calcTournamentTerm(int $turnTerm): int{
|
||||
$turnTerm = abs($turnTerm);
|
||||
return Util::valueFit($turnTerm, 5, 120);
|
||||
}
|
||||
|
||||
@@ -139,7 +138,6 @@ function processTournament()
|
||||
|
||||
function getTournamentTermText(int $turnTerm)
|
||||
{
|
||||
$turnTerm = abs($turnTerm);
|
||||
$term = calcTournamentTerm($turnTerm);
|
||||
|
||||
if ($term % 60 === 0) {
|
||||
@@ -344,7 +342,7 @@ function startBetting($type)
|
||||
{
|
||||
$db = DB::db();
|
||||
$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([
|
||||
"<S>◆</>{$year}년 {$month}월:<B><b>【대회】</b></>우승자를 예상하는 <C>내기</>가 진행중입니다! 호사가의 참여를 기다립니다!"
|
||||
], $year, $month);
|
||||
|
||||
@@ -54,7 +54,6 @@ if ($session->userGrade < 5 && !$allowReset) {
|
||||
<label for="turnterm" class="col-sm-3 col-form-label">턴 시간(분)</label>
|
||||
<div class="col-sm-9">
|
||||
<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_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>
|
||||
|
||||
@@ -36,7 +36,6 @@ list(
|
||||
$turnterm,
|
||||
$npcmode
|
||||
) = $gameStor->getValuesAsArray(['maxgeneral', 'turnterm', 'npcmode']);
|
||||
$turnterm = abs($turnterm);
|
||||
|
||||
if($npcmode!=1){
|
||||
Json::die([
|
||||
|
||||
@@ -121,7 +121,7 @@ $builder->setOwner($userID);
|
||||
$builder->setOwnerName($userNick);
|
||||
$builder->setKillturn(5);
|
||||
$builder->setNPCType(0);
|
||||
$builder->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * abs($env['turnterm'])));
|
||||
$builder->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $env['turnterm']));
|
||||
$builder->fillRemainSpecAsZero($env);
|
||||
$builder->build($env);
|
||||
$generalID = $builder->getGeneralID();
|
||||
|
||||
@@ -60,7 +60,7 @@ $me->setAuxVar('use_auto_nation_turn', $use_auto_nation_turn);
|
||||
$me->setVar('tnmt', $tnmt);
|
||||
|
||||
if ($me->getNPCType() == 1 && $detachNPC) {
|
||||
$turnterm = abs($gameStor->turnterm);
|
||||
$turnterm = $gameStor->turnterm;
|
||||
|
||||
if ($turnterm < 10) {
|
||||
$targetKillTurn = 30 / $turnterm;
|
||||
|
||||
@@ -39,7 +39,6 @@ list(
|
||||
$npcmode,
|
||||
$turnterm
|
||||
) = $gameStor->getValuesAsArray(['year', 'month', 'maxgeneral', 'npcmode', 'turnterm']);
|
||||
$turnterm = abs($turnterm);
|
||||
|
||||
if($npcmode!=2){
|
||||
Json::die([
|
||||
|
||||
@@ -28,7 +28,6 @@ use sammo\UniqueConst;
|
||||
use sammo\UserLogger;
|
||||
use sammo\Util;
|
||||
use sammo\Validator;
|
||||
use sammo\VarTurn60;
|
||||
use sammo\WebUtil;
|
||||
|
||||
use function sammo\addTurn;
|
||||
@@ -356,28 +355,20 @@ class Join extends \sammo\BaseAPI
|
||||
}
|
||||
|
||||
if ($inheritTurntimeZone !== null) {
|
||||
$turnterm = abs($admin['turnterm']);
|
||||
$inheritTurntime = $inheritTurntimeZone * $turnterm;
|
||||
$inheritTurntime += $rng->nextRangeInt(0, Util::clamp($turnterm - 1, 0));
|
||||
$inheritTurntime = $inheritTurntimeZone * $admin['turnterm'];
|
||||
$inheritTurntime += $rng->nextRangeInt(0, Util::clamp($admin['turnterm'] - 1, 0));
|
||||
|
||||
$userLogger->push(sprintf("턴 시간 %02d:%02d 로 지정", intdiv($inheritTurntime, 60), $inheritTurntime % 60), "inheritPoint");
|
||||
|
||||
$inheritTurntime += $rng->nextRangeInt(0, 999999) / 1000000;
|
||||
|
||||
$turntime = new \DateTimeImmutable(cutTurn($admin['turntime'], $turnterm));
|
||||
$turntime = new \DateTimeImmutable(cutTurn($admin['turntime'], $admin['turnterm']));
|
||||
$turntime = $turntime->add(TimeUtil::secondsToDateInterval($inheritTurntime));
|
||||
$turntime = TimeUtil::format($turntime, true);
|
||||
} else {
|
||||
$turntime = getRandTurn($rng, abs($admin['turnterm']), new \DateTimeImmutable($admin['turntime']));
|
||||
$turntime = getRandTurn($rng, $admin['turnterm'], new \DateTimeImmutable($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) {
|
||||
|
||||
@@ -62,7 +62,7 @@ class ResetTurnTime extends \sammo\BaseAPI
|
||||
}
|
||||
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = abs($gameStor->getValue('turnterm'));
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
|
||||
$currTurnTime = new DateTimeImmutable($general->getTurnTime());
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
|
||||
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$result = [];
|
||||
$validUntil = TimeUtil::nowAddMinutes(2 * abs($gameStor->turnterm));
|
||||
$validUntil = TimeUtil::nowAddMinutes(2 * $gameStor->turnterm);
|
||||
while(count($result) < $pickCnt){
|
||||
$cand = $rng->choiceUsingWeightPair($pool);
|
||||
$poolID = $cand['id'];
|
||||
|
||||
@@ -164,7 +164,7 @@ abstract class Auction
|
||||
if ($date === null) {
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = abs($gameStor->getValue('turnterm'));
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
$date = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60
|
||||
));
|
||||
@@ -324,7 +324,7 @@ abstract class Auction
|
||||
}
|
||||
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = abs($gameStor->getValue('turnterm'));
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
|
||||
if ($this->info->detail->availableLatestBidCloseDate !== null) {
|
||||
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval(
|
||||
@@ -437,7 +437,7 @@ abstract class Auction
|
||||
$general->increaseVar($resType->value, -$morePoint);
|
||||
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = abs($gameStor->getValue('turnterm'));
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60
|
||||
));
|
||||
@@ -471,7 +471,7 @@ abstract class Auction
|
||||
if ($highestBid->aux->tryExtendCloseDate) {
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = abs($gameStor->getValue('turnterm'));
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
|
||||
//연장 요청이 있었다.
|
||||
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
|
||||
|
||||
@@ -58,7 +58,7 @@ abstract class AuctionBasicResource extends Auction
|
||||
|
||||
$now = new \DateTimeImmutable();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = abs($gameStor->getValue('turnterm'));
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
$closeDate = $now->add(TimeUtil::secondsToDateInterval($closeTurnCnt * $turnTerm * 60));
|
||||
|
||||
$openResult = static::openAuction(new AuctionInfo(
|
||||
@@ -246,13 +246,7 @@ abstract class AuctionBasicResource extends Auction
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
if($turnTerm == -60){
|
||||
$date = VarTurn60::fromDatetime(new \DateTimeImmutable())->addTurn(1)->toDateStr();
|
||||
}
|
||||
else{
|
||||
$date = (new DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($turnTerm * 60));
|
||||
}
|
||||
|
||||
$date = (new DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($turnTerm * 60));
|
||||
$this->shrinkCloseDate($date);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ class AuctionUniqueItem extends Auction
|
||||
$now = new DateTimeImmutable();
|
||||
|
||||
[$turnTerm, $year, $month] = $gameStor->getValuesAsArray(['turnterm', 'year', 'month']);
|
||||
$turnTerm = abs($turnTerm);
|
||||
|
||||
$closeDate = $now->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_AUCTION_CLOSE_MINUTES, $turnTerm * static::COEFF_AUCTION_CLOSE_MINUTES) * 60
|
||||
@@ -267,7 +266,7 @@ class AuctionUniqueItem extends Auction
|
||||
}
|
||||
|
||||
if ($availableEquipUniqueCnt <= 0) {
|
||||
$turnTerm = abs($gameStor->getValue('turnterm'));
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
//제한에 걸렸다면 자동 연장
|
||||
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_UNIQUE_CNT) * 60
|
||||
@@ -315,7 +314,7 @@ class AuctionUniqueItem extends Auction
|
||||
|
||||
if (!$availableItemTypes) {
|
||||
if ($isExtendCloseDateRequired) {
|
||||
$turnTerm = abs($gameStor->getValue('turnterm'));
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
//동일 부위 제한에 걸렸다면 자동 연장
|
||||
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID) * 60
|
||||
|
||||
@@ -200,7 +200,7 @@ class che_불가침제의 extends Command\NationCommand
|
||||
|
||||
$now = 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"));
|
||||
|
||||
$josaWa = JosaUtil::pick($nationName, '와');
|
||||
|
||||
@@ -149,7 +149,7 @@ class che_불가침파기제의 extends Command\NationCommand{
|
||||
|
||||
$now = 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"));
|
||||
|
||||
$msg = new DiplomaticMessage(
|
||||
|
||||
@@ -147,7 +147,7 @@ class che_종전제의 extends Command\NationCommand{
|
||||
|
||||
$now = 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"));
|
||||
|
||||
$msg = new DiplomaticMessage(
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ class RaiseInvader extends \sammo\Event\Action
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$gameStor->setValue('isunited', 1);
|
||||
|
||||
$turnterm = abs($gameStor->turnterm);
|
||||
$turnterm = $gameStor->turnterm;
|
||||
$generalCnt = $db->queryFirstField('SELECT count(*) FROM general');
|
||||
|
||||
if ($npcEachCount < 0) {
|
||||
|
||||
@@ -134,7 +134,7 @@ class UpdateNationLevel extends \sammo\Event\Action
|
||||
if ($levelDiff) {
|
||||
//유니크 아이템 하나 돌리자
|
||||
$targetKillTurn = $env['killturn'];
|
||||
$targetKillTurn -= 24 * 60 / abs($env['turnterm']);
|
||||
$targetKillTurn -= 24 * 60 / $env['turnterm'];
|
||||
$nationGenIDList = $db->queryFirstColumn(
|
||||
'SELECT no FROM general WHERE nation = %i AND killturn >= %i AND npc < 2',
|
||||
$nation['nation'],
|
||||
|
||||
@@ -384,7 +384,6 @@ class GameConstBase
|
||||
'che_포상',
|
||||
'che_몰수',
|
||||
'che_부대탈퇴지시',
|
||||
'che_행동지시',
|
||||
],
|
||||
'외교' => [
|
||||
'che_물자원조',
|
||||
|
||||
@@ -283,7 +283,6 @@ class General extends GeneralBase implements iAction
|
||||
}
|
||||
$recwar = new \DateTimeImmutable($this->getVar('recent_war'));
|
||||
$turnNow = new \DateTimeImmutable($this->getVar('turntime'));
|
||||
|
||||
$secDiff = TimeUtil::DateIntervalToSeconds($recwar->diff($turnNow));
|
||||
|
||||
if ($secDiff <= 0) {
|
||||
@@ -291,12 +290,6 @@ class General extends GeneralBase implements iAction
|
||||
return 0;
|
||||
}
|
||||
|
||||
if($turnTerm == -60){
|
||||
$turnDiff = VarTurn60::calcTurnDiff($recwar, $turnNow);
|
||||
$this->calcCache[$cacheKey] = $turnDiff;
|
||||
return $turnDiff;
|
||||
}
|
||||
|
||||
$result = intdiv(Util::toInt($secDiff), 60 * $turnTerm);
|
||||
$this->calcCache[$cacheKey] = $result;
|
||||
return $result;
|
||||
|
||||
@@ -1980,7 +1980,7 @@ class GeneralAI
|
||||
$db = DB::db();
|
||||
|
||||
$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']) {
|
||||
@@ -3134,7 +3134,7 @@ class GeneralAI
|
||||
$rawCity = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $this->general->getCityID());
|
||||
$this->general->setRawCity($rawCity);
|
||||
}
|
||||
if (!in_array($this->general->getRawCity()['level'], [5, 6])) {
|
||||
if (in_array($this->general->getRawCity()['level'], [5, 6])) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3715,7 +3715,7 @@ class GeneralAI
|
||||
$this->updateInstance();
|
||||
|
||||
//특별 메세지 있는 경우 출력
|
||||
$term = abs($this->env['turnterm']);
|
||||
$term = $this->env['turnterm'];
|
||||
if ($general->getVar('npcmsg') && $this->rng->nextBool(GameConst::$npcMessageFreqByDay * $term / (60 * 24))) {
|
||||
$src = new MessageTarget(
|
||||
$general->getID(),
|
||||
@@ -3985,7 +3985,7 @@ class GeneralAI
|
||||
|
||||
$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;
|
||||
|
||||
foreach (Util::range($minChiefLevel, 12) as $chiefLevel) {
|
||||
|
||||
@@ -245,16 +245,7 @@ class ResetHelper{
|
||||
true
|
||||
);
|
||||
|
||||
if($turnterm == -60){
|
||||
[$starttime, $yearPulled, $month] = VarTurn60::fromDatetime(new \DateTimeImmutable($turntime))->cutDay();
|
||||
if($yearPulled){
|
||||
$year = $startyear-1;
|
||||
}
|
||||
else{
|
||||
$year = $startyear;
|
||||
}
|
||||
}
|
||||
else if($sync == 0) {
|
||||
if($sync == 0) {
|
||||
// 현재 시간을 1월로 맞춤
|
||||
$starttime = cutTurn($turntime, $turnterm);
|
||||
$month = 1;
|
||||
@@ -270,7 +261,7 @@ class ResetHelper{
|
||||
}
|
||||
}
|
||||
|
||||
$killturn = 4800 / abs($turnterm);
|
||||
$killturn = 4800 / $turnterm;
|
||||
if($npcmode == 1) { $killturn = intdiv($killturn, 3); }
|
||||
|
||||
$develcost = ($year - $startyear + 10) * 2;
|
||||
|
||||
+10
-12
@@ -14,23 +14,12 @@ final class ServerTool
|
||||
if ((120 % $turnterm) != 0) {
|
||||
return 'invalid minute';
|
||||
}
|
||||
|
||||
|
||||
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$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;
|
||||
|
||||
$locked = false;
|
||||
@@ -51,6 +40,15 @@ final class ServerTool
|
||||
$locked = tryLock();
|
||||
}
|
||||
|
||||
$oldunit = $admin['turnterm'] * 60;
|
||||
$unit = $turnterm * 60;
|
||||
|
||||
if($unit == $oldunit){
|
||||
if($locked){
|
||||
unlock();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$unitDiff = $unit / $oldunit;
|
||||
|
||||
|
||||
@@ -219,20 +219,11 @@ class TurnExecutionHelper
|
||||
|
||||
$nextTurnTimeBase = $general->getAuxVar('nextTurnTimeBase');
|
||||
if($nextTurnTimeBase !== null){
|
||||
if($gameStor->turnterm == -60){
|
||||
[$turntime, $nextTurnterm] = VarTurn60::fromDatetime(new \DateTimeImmutable($turntime))->cutTurn();
|
||||
$turntimeObj = new \DateTimeImmutable($turntime);
|
||||
$turntimeObj = $turntimeObj->add(TimeUtil::secondsToDateInterval($nextTurnTimeBase * $nextTurnterm / 60));
|
||||
$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);
|
||||
}
|
||||
$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);
|
||||
@@ -364,7 +355,7 @@ class TurnExecutionHelper
|
||||
|
||||
if (($autorun_user['limit_minutes'] ?? false) && $general->getNPCType() < 2 && $hasReservedTurn) {
|
||||
$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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -81,13 +81,6 @@
|
||||
["RaiseNPCNation"],
|
||||
["DeleteEvent"]
|
||||
],
|
||||
[
|
||||
"month", 1000,
|
||||
true,
|
||||
["ChangeCity", "all", {
|
||||
"trade":100
|
||||
}]
|
||||
],
|
||||
[
|
||||
"month", 999,
|
||||
["Date", "==", 181, 1],
|
||||
|
||||
@@ -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
|
||||
}]
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -68,7 +68,7 @@ import { formatTime } from "@/util/formatTime";
|
||||
import { mb_strwidth } from "@/util/mb_strwidth";
|
||||
import { parseTime } from "@/util/parseTime";
|
||||
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 { inject, onMounted, ref, type PropType } from "vue";
|
||||
import VueTypes from "vue-types";
|
||||
@@ -76,7 +76,6 @@ import DragSelect from "@/components/DragSelect.vue";
|
||||
import { BButton } from "bootstrap-vue-next";
|
||||
import { QueryActionHelper } from "@/util/QueryActionHelper";
|
||||
import type { ChiefResponse } from "@/defs/API/NationCommand";
|
||||
import { VarTurn60 } from "@/varTurn60";
|
||||
|
||||
const props = defineProps({
|
||||
style: VueTypes.object.isRequired,
|
||||
@@ -199,21 +198,10 @@ if (!props.officer || !props.officer.turnTime) {
|
||||
}
|
||||
} else {
|
||||
const baseTurnTime = parseTime(props.officer.turnTime);
|
||||
if(props.turnTerm == -60){
|
||||
const baseTurnTimeObj = VarTurn60.fromDatetime(baseTurnTime);
|
||||
for (const idx of range(props.officer.turn.length)) {
|
||||
turnTimes.value.push(
|
||||
formatTime(baseTurnTimeObj.addTurn(idx).toDate(), "HH:mm")
|
||||
);
|
||||
}
|
||||
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")
|
||||
);
|
||||
}
|
||||
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>
|
||||
|
||||
@@ -310,13 +310,14 @@ declare const staticValues: {
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import "@scss/game_bg.scss";
|
||||
import TopBackBar from "@/components/TopBackBar.vue";
|
||||
import { sum } from "lodash-es";
|
||||
import _ from "lodash-es";
|
||||
import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue";
|
||||
import { SammoAPI } from "./SammoAPI";
|
||||
import { InheritResetStat, type inheritBuffType, type InheritPointLogItem } from "./defs/API/InheritAction";
|
||||
import * as JosaUtil from "@/util/JosaUtil";
|
||||
import { BButton } from "bootstrap-vue-next";
|
||||
import { unwrap } from "./util/unwrap";
|
||||
import { add as dateAdd } from 'date-fns';
|
||||
|
||||
const inheritanceViewText: Record<InheritanceViewType, { title: string; info: string }> = {
|
||||
sum: {
|
||||
@@ -427,7 +428,7 @@ const title = "유산 관리";
|
||||
|
||||
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 newPoint = Math.floor(totalPoint - previousPoint);
|
||||
const result: Record<InheritanceViewType, number> = {
|
||||
|
||||
+1
-1
@@ -485,7 +485,7 @@ watch(inheritCity, (newValue: undefined | number) => {
|
||||
const inheritTurnTimeZone = ref<number>();
|
||||
const turnTimeZoneList: string[] = (()=>{
|
||||
const result: string[] = [];
|
||||
const zoneSec = Math.abs(turnterm); // * 60 / 60
|
||||
const zoneSec = turnterm; // * 60 / 60
|
||||
let zoneCur = 0;
|
||||
for(const idx of range(60)){
|
||||
const zoneNext = zoneCur + zoneSec;
|
||||
|
||||
@@ -276,7 +276,7 @@ declare const staticValues: {
|
||||
</script>
|
||||
|
||||
<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 queryString from "query-string";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
@@ -297,7 +297,6 @@ import { QueryActionHelper } from "./util/QueryActionHelper";
|
||||
import SimpleClock from "./components/SimpleClock.vue";
|
||||
import type { ReservedCommandResponse } from "./defs/API/Command";
|
||||
import { unwrap } from "./util/unwrap";
|
||||
import { VarTurn60 } from "./varTurn60";
|
||||
|
||||
defineExpose({
|
||||
updateCommandTable,
|
||||
@@ -545,13 +544,7 @@ async function reloadCommandList() {
|
||||
});
|
||||
|
||||
yearMonth += 1;
|
||||
if(result.turnTerm == -60){
|
||||
nextTurnTime = VarTurn60.fromDatetime(nextTurnTime).addTurn(1).toDate();
|
||||
}
|
||||
else{
|
||||
nextTurnTime = addMinutes(nextTurnTime, result.turnTerm);
|
||||
}
|
||||
|
||||
nextTurnTime = addMinutes(nextTurnTime, result.turnTerm);
|
||||
}
|
||||
|
||||
serverNow.value = parseTime(result.date);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AutoLoginFailed, AutoLoginNonceResponse, AutoLoginResponse, LoginFailed, LoginResponse } from "./defs/API/Login";
|
||||
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 };
|
||||
|
||||
const apiRealPath = {
|
||||
@@ -18,7 +18,7 @@ const apiRealPath = {
|
||||
hashedToken: string,
|
||||
token_id: number,
|
||||
}, AutoLoginResponse, AutoLoginFailed>,
|
||||
ReqNonce: GET as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed>
|
||||
ReqNonce: POST as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed>
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -30,4 +30,4 @@ export const SammoRootAPI = APIPathGen(apiRealPath, (path: string[], tail: APITa
|
||||
}
|
||||
return callSammoAPI(method, path.join('/'), args, pathParam);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { addMinutes } from "date-fns/addMinutes";
|
||||
import addMinutes from "date-fns/esm/addMinutes";
|
||||
import queryString from "query-string";
|
||||
import { onMounted, ref, watch, type PropType, inject, type Ref } from "vue";
|
||||
import { formatTime } from "@util/formatTime";
|
||||
@@ -282,7 +282,6 @@ import { unwrap_err } from "@/util/unwrap_err";
|
||||
import type { GameConstStore } from "@/GameConstStore";
|
||||
import { postFilterNationCommandGen } from "@/utilGame/postFilterNationCommandGen";
|
||||
import { unwrap } from "@/util/unwrap";
|
||||
import { VarTurn60 } from "@/varTurn60";
|
||||
|
||||
const toasts = unwrap(useToast());
|
||||
|
||||
@@ -537,19 +536,13 @@ function updateCommandList() {
|
||||
...obj,
|
||||
year,
|
||||
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"),
|
||||
style,
|
||||
});
|
||||
|
||||
yearMonth += 1;
|
||||
|
||||
if(props.turnTerm == -60){
|
||||
nextTurnTime = VarTurn60.fromDatetime(nextTurnTime).addTurn(1).toDate();
|
||||
}
|
||||
else{
|
||||
nextTurnTime = addMinutes(nextTurnTime, props.turnTerm);
|
||||
}
|
||||
nextTurnTime = addMinutes(nextTurnTime, props.turnTerm);
|
||||
}
|
||||
reservedCommandList.value = _reservedCommandList;
|
||||
updated.value = true;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<AutorunInfo :autorunMode="globalInfo.autorunUser" />
|
||||
</div>
|
||||
<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 class="s-border-t col py-2 col-4 col-lg-2 subOnlineUserCnt">
|
||||
전체 접속자 수: {{ (globalInfo.onlineUserCnt ?? 0).toLocaleString() }}명
|
||||
|
||||
@@ -175,9 +175,8 @@ import { clamp } from "lodash-es";
|
||||
import { formatCityName } from "@/utilGame/formatCityName";
|
||||
import { isValidObjKey } from "@/utilGame/isValidObjKey";
|
||||
import { calcInjury } from "@/utilGame/calcInjury";
|
||||
import { addMinutes } from "date-fns/addMinutes";
|
||||
import { addMinutes } from "date-fns/esm";
|
||||
import type { GameIActionInfo } from "@/defs/GameObj";
|
||||
import { VarTurn60 } from "@/varTurn60";
|
||||
const imagePath = window.pathConfig.gameImage;
|
||||
const gameConstStore = unwrap(inject<Ref<GameConstStore>>("gameConstStore"));
|
||||
const props = defineProps<{
|
||||
@@ -279,12 +278,7 @@ watch(
|
||||
() => {
|
||||
let turnTime = parseTime(general.value.turntime);
|
||||
if (turnTime.getTime() < props.lastExecuted.getTime()) {
|
||||
if(props.turnTerm == -60){
|
||||
turnTime = VarTurn60.fromDatetime(turnTime).addTurn(1).toDate();
|
||||
}
|
||||
else {
|
||||
turnTime = addMinutes(turnTime, props.turnTerm);
|
||||
}
|
||||
turnTime = addMinutes(turnTime, props.turnTerm);
|
||||
}
|
||||
nextExecuteMinute.value = Math.floor(clamp((turnTime.getTime() - props.lastExecuted.getTime()) / 60000, 0, 999));
|
||||
},
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { MsgItem, MsgTarget, MsgType } from "@/defs/API/Message";
|
||||
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 linkifyStr from "linkify-string";
|
||||
import { SammoAPI } from "@/SammoAPI";
|
||||
|
||||
@@ -93,7 +93,7 @@ $(function () {
|
||||
const runAnalysis = async function () {
|
||||
let realKillturn = killturn;
|
||||
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');
|
||||
try {
|
||||
|
||||
+70
-55
@@ -37,14 +37,16 @@ function regNextToken(tokenInfo: [number, string]) {
|
||||
function getToken(): [number, string] | undefined {
|
||||
const trialToken = localStorage.getItem(LOGIN_TOKEN_KEY);
|
||||
if (!trialToken) {
|
||||
console.log('no token');
|
||||
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) {
|
||||
console.log(tokenItems);
|
||||
resetToken();
|
||||
return;
|
||||
}
|
||||
console.debug(localStorage.getItem(LOGIN_TOKEN_KEY));
|
||||
const [, token,] = tokenItems;
|
||||
return token;
|
||||
}
|
||||
@@ -53,60 +55,73 @@ function resetToken() {
|
||||
localStorage.removeItem(LOGIN_TOKEN_KEY);
|
||||
}
|
||||
|
||||
async function tryAutoLogin() {
|
||||
try {
|
||||
const tokenInfo = getToken();
|
||||
if (!tokenInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [tokenID, token] = tokenInfo;
|
||||
|
||||
const result = await SammoRootAPI.Login.ReqNonce(undefined, true);
|
||||
|
||||
if (!result) {
|
||||
//api 에러.
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginResult.nextToken) {
|
||||
regNextToken(loginResult.nextToken);
|
||||
}
|
||||
window.location.href = "./";
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
if (isString(e)) {
|
||||
alert(e);
|
||||
}
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function tryAutoLogin() {
|
||||
try {
|
||||
const tokenInfo = getToken();
|
||||
if (!tokenInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [tokenID, token] = tokenInfo;
|
||||
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
const reqNonceStartAt = Date.now();
|
||||
const nonceResult = await SammoRootAPI.Login.ReqNonce(undefined, true);
|
||||
|
||||
if (!nonceResult) {
|
||||
//api 에러.
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (isString(e)) {
|
||||
alert(e);
|
||||
}
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function getOAuthToken(mode: string, scope_list?: string[] | string) {
|
||||
if (mode === undefined) {
|
||||
mode = 'login';
|
||||
|
||||
+1
-1
@@ -147,7 +147,7 @@ const descriptor: NamedRules<InstallFormType> = {
|
||||
turnterm: {
|
||||
required: true,
|
||||
type: "enum",
|
||||
enum: [1, 2, 5, 10, 20, 30, 60, 120, -60],
|
||||
enum: [1, 2, 5, 10, 20, 30, 60, 120],
|
||||
transform: parseInt,
|
||||
},
|
||||
sync: {
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ import axios from 'axios';
|
||||
import { convertFormData } from '@util/convertFormData';
|
||||
import { isBrightColor } from "@util/isBrightColor";
|
||||
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 { parseTime } from '@util/parseTime';
|
||||
import { formatTime } from '@util/formatTime';
|
||||
@@ -234,7 +234,7 @@ function redrawMsg(msgResponse: MsgResponse, addFront: boolean): MsgResponse {
|
||||
if (!msgList || msgList.length == 0) {
|
||||
continue;
|
||||
}
|
||||
const lastMsg = unwrap(last(msgList));
|
||||
const lastMsg = unwrap(_.last(msgList));
|
||||
minMsgSeq[msgType] = Math.min(minMsgSeq[msgType], lastMsg.id);
|
||||
}
|
||||
return obj;
|
||||
@@ -605,7 +605,7 @@ function activateMessageForm() {
|
||||
|
||||
$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');
|
||||
|
||||
const targetMailbox = unwrap_any<string>($mailboxList.val());
|
||||
|
||||
@@ -10,6 +10,7 @@ import { default as che_장수대상임관 } from "./che_장수대상임관.vue"
|
||||
import { default as che_징병 } from "./che_징병.vue";
|
||||
import { default as che_헌납 } from "./che_헌납.vue";
|
||||
import { default as cr_건국 } from "./cr_건국.vue";
|
||||
|
||||
import { default as ProcessCity } from "../ProcessCity.vue";
|
||||
import { default as ProcessGeneralAmount } from "../ProcessGeneralAmount.vue";
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
<template>
|
||||
<TopBackBar v-model:searchable="searchable" :title="commandName" :type="procEntryMode" />
|
||||
<div class="bg0">
|
||||
<div>장수에게 턴을 당기거라 미루라고 지시합니다.</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-5">
|
||||
장수 :
|
||||
<SelectGeneral
|
||||
v-model="selectedGeneralID"
|
||||
:cities="citiesMap"
|
||||
:generals="generalList"
|
||||
:textHelper="textHelpGeneral"
|
||||
:searchable="searchable"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-2 col-lg-2">
|
||||
턴 :
|
||||
<b-button-group>
|
||||
<b-button :pressed="isPull" @click="isPull = true"> 당기기 </b-button>
|
||||
<b-button :pressed="!isPull" @click="isPull = false"> 미루기 </b-button>
|
||||
</b-button-group>
|
||||
</div>
|
||||
<div class="col-7 col-lg-3">
|
||||
<SelectAmount v-model="amount" :amountGuide="amountGuide" :maxAmount="maxAmount" :minAmount="minAmount" />
|
||||
</div>
|
||||
<div class="col-3 col-lg-2 d-grid">
|
||||
<b-button variant="primary" @click="submit">
|
||||
{{ commandName }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BottomBar :title="commandName" :type="procEntryMode" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
declare const procRes: {
|
||||
distanceList: Record<number, number[]>;
|
||||
cities: [number, string][];
|
||||
generals: procGeneralRawItemList;
|
||||
generalsKey: procGeneralKey[];
|
||||
minAmount: number;
|
||||
maxAmount: number;
|
||||
amountGuide: number[];
|
||||
};
|
||||
|
||||
declare const staticValues: {
|
||||
commandName: string;
|
||||
entryInfo: ["General" | "Nation", unknown];
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import SelectGeneral from "@/processing/SelectGeneral.vue";
|
||||
import SelectAmount from "@/processing/SelectAmount.vue";
|
||||
import { ref } from "vue";
|
||||
import { unwrap } from "@/util/unwrap";
|
||||
import type { Args } from "@/processing/args";
|
||||
import TopBackBar from "@/components/TopBackBar.vue";
|
||||
import BottomBar from "@/components/BottomBar.vue";
|
||||
import {
|
||||
convertGeneralList,
|
||||
getProcSearchable,
|
||||
type procGeneralItem,
|
||||
type procGeneralKey,
|
||||
type procGeneralRawItemList,
|
||||
} from "@/processing/processingRes";
|
||||
import { getNPCColor } from "@/utilGame";
|
||||
|
||||
const citiesMap = ref(
|
||||
new Map<
|
||||
number,
|
||||
{
|
||||
name: string;
|
||||
info?: string;
|
||||
}
|
||||
>()
|
||||
);
|
||||
for (const [id, name] of procRes.cities) {
|
||||
citiesMap.value.set(id, { name });
|
||||
}
|
||||
|
||||
const generalList = convertGeneralList(procRes.generalsKey, procRes.generals);
|
||||
const amount = ref(1);
|
||||
const isPull = ref(true);
|
||||
|
||||
const selectedGeneralID = ref(generalList[0].no);
|
||||
|
||||
type procGeneralItemWithTurn0Brief = procGeneralItem & {
|
||||
turn0Brief: string;
|
||||
};
|
||||
|
||||
function textHelpGeneral(_gen: procGeneralItem): string {
|
||||
const gen = _gen as procGeneralItemWithTurn0Brief;
|
||||
const nameColor = getNPCColor(gen.npc);
|
||||
const name = nameColor ? `<span style="color:${nameColor}">${gen.name}</span>` : gen.name;
|
||||
return `${name} [${citiesMap.value.get(unwrap(gen.cityID))?.name}] (${gen.leadership}/${gen.strength}/${
|
||||
gen.intel
|
||||
}) (${gen.turn0Brief})`;
|
||||
}
|
||||
|
||||
async function submit(e: Event) {
|
||||
const event = new CustomEvent<Args>("customSubmit", {
|
||||
detail: {
|
||||
amount: amount.value,
|
||||
isPull: isPull.value,
|
||||
destGeneralID: selectedGeneralID.value,
|
||||
},
|
||||
});
|
||||
unwrap(e.target).dispatchEvent(event);
|
||||
}
|
||||
|
||||
const { commandName, entryInfo } = staticValues;
|
||||
const searchable = getProcSearchable();
|
||||
|
||||
const procEntryMode: "chief" | "normal" = entryInfo[0] == "Nation" ? "chief" : "normal";
|
||||
|
||||
const { minAmount, maxAmount, amountGuide } = procRes;
|
||||
</script>
|
||||
@@ -3,7 +3,6 @@ import { default as che_국호변경 } from "./che_국호변경.vue";
|
||||
import { default as che_물자원조 } from "./che_물자원조.vue";
|
||||
import { default as che_불가침제의 } from "./che_불가침제의.vue";
|
||||
import { default as che_피장파장 } from "./che_피장파장.vue";
|
||||
import { default as che_행동지시 } from "./che_행동지시.vue";
|
||||
|
||||
import { default as cr_인구이동 } from "./cr_인구이동.vue";
|
||||
|
||||
@@ -34,7 +33,6 @@ export const commandMap: Record<string, typeof ProcessNation | typeof ProcessCit
|
||||
che_피장파장,
|
||||
che_허보: ProcessCity,
|
||||
cr_인구이동,
|
||||
che_행동지시,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -13,7 +13,7 @@ const intArgs = [
|
||||
] as const;
|
||||
|
||||
const booleanArgs = [
|
||||
'isGold', 'buyRice', 'isPull',
|
||||
'isGold', 'buyRice',
|
||||
] as const;
|
||||
|
||||
const integerArrayArgs = [
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {parseISO} from 'date-fns';
|
||||
|
||||
export function parseTime(dateString: string): Date{
|
||||
if(dateString === null){
|
||||
console.warn('why null');
|
||||
console.trace();
|
||||
return new Date();
|
||||
}
|
||||
const tmp = parseISO(dateString);
|
||||
return tmp;
|
||||
return parseISO(dateString);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { clamp } from 'lodash-es';
|
||||
export function calcTournamentTerm(turnTerm: number): number{
|
||||
turnTerm = Math.abs(turnTerm);
|
||||
return clamp(turnTerm, 5, 120);
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
import {
|
||||
addSeconds,
|
||||
addDays,
|
||||
differenceInCalendarDays,
|
||||
} from 'date-fns';
|
||||
import {
|
||||
toZonedTime,
|
||||
fromZonedTime,
|
||||
} from 'date-fns-tz';
|
||||
import { formatTime } from './util/formatTime';
|
||||
|
||||
import { parse as parseDF, isValid } from 'date-fns';
|
||||
|
||||
export const SEOUL_TZ = 'Asia/Seoul';
|
||||
|
||||
// 문자열이 타임존/오프셋을 포함하는지 단순 판별 (ISO 형태 위주)
|
||||
const TZ_OFFSET_RE = /([zZ]|[+-]\d{2}:?\d{2})$/;
|
||||
|
||||
function tryParseWithFormats(s: string): Date | null {
|
||||
const fmts = [
|
||||
"yyyy-MM-dd'T'HH:mm:ss", // 2025-10-31T09:30:00
|
||||
'yyyy-MM-dd HH:mm:ss', // 2025-10-31 09:30:00
|
||||
"yyyy-MM-dd'T'HH:mm", // 2025-10-31T09:30
|
||||
'yyyy-MM-dd HH:mm', // 2025-10-31 09:30
|
||||
'yyyy-MM-dd', // 2025-10-31
|
||||
];
|
||||
for (const fmt of fmts) {
|
||||
const d = parseDF(s, fmt, new Date(0));
|
||||
if (isValid(d)) return d;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 문자열/Date/number 입력을 UTC Date로 정규화
|
||||
* - 문자열에 오프셋/타임존이 없으면 Asia/Seoul 기준 “벽시계 시간”으로 간주
|
||||
* - number는 epoch ms로 간주 (epoch s면 직접 *1000 해서 넘겨줘)
|
||||
*/
|
||||
export function toUTC(input: string | Date | number): Date {
|
||||
if (input instanceof Date) {
|
||||
return new Date(input.getTime()); // 이미 절대시간
|
||||
}
|
||||
if (typeof input === 'number') {
|
||||
return new Date(input); // epoch ms
|
||||
}
|
||||
const s = input.trim();
|
||||
|
||||
// 오프셋/타임존이 명시된 ISO라면 기본 파서로 (절대시간 유지)
|
||||
if (TZ_OFFSET_RE.test(s)) {
|
||||
const d = new Date(s);
|
||||
if (isValid(d)) return d;
|
||||
}
|
||||
|
||||
// 오프셋이 없는 문자열 → 서울 벽시계로 파싱해서 UTC로 변환
|
||||
const parsedLocal = tryParseWithFormats(s) ?? new Date(s);
|
||||
if (!isValid(parsedLocal)) {
|
||||
throw new Error(`Unparsable date string: "${input}"`);
|
||||
}
|
||||
// parsedLocal는 "그 문자열 그대로의 Y/M/D h:m:s"를 들고 있음(타임존 無)
|
||||
// 이를 Asia/Seoul 로컬로 간주해 UTC로 변환
|
||||
const utc = fromZonedTime(parsedLocal, SEOUL_TZ);
|
||||
return utc;
|
||||
}
|
||||
|
||||
export class VarTurn60 {
|
||||
static readonly TZ = 'Asia/Seoul';
|
||||
static readonly MonthAdjustOffset = 2;
|
||||
|
||||
// [turnIdx, minOffset, turnTermMinutes]
|
||||
static readonly min30ToTurn: Array<[number, number, number]> = [
|
||||
[0, 0, 120],
|
||||
[0, 30, 120],
|
||||
[0, 60, 120],
|
||||
[0, 90, 120],
|
||||
[1, 0, 120],
|
||||
[1, 30, 120],
|
||||
[1, 60, 120],
|
||||
[1, 90, 120],
|
||||
[2, 0, 120],
|
||||
[2, 30, 120],
|
||||
[2, 60, 120],
|
||||
[2, 90, 120],
|
||||
[3, 0, 60],
|
||||
[3, 30, 60],
|
||||
[4, 0, 60],
|
||||
[4, 30, 60],
|
||||
[5, 0, 60],
|
||||
[5, 30, 60],
|
||||
[6, 0, 60],
|
||||
[6, 30, 60],
|
||||
[7, 0, 60],
|
||||
[7, 30, 60],
|
||||
[8, 0, 60],
|
||||
[8, 30, 60],
|
||||
[9, 0, 60],
|
||||
[9, 30, 60],
|
||||
[10, 0, 60],
|
||||
[10, 30, 60],
|
||||
[11, 0, 60],
|
||||
[11, 30, 60],
|
||||
[12, 0, 60],
|
||||
[12, 30, 60],
|
||||
[13, 0, 60],
|
||||
[13, 30, 60],
|
||||
[14, 0, 60],
|
||||
[14, 30, 60],
|
||||
[15, 0, 60],
|
||||
[15, 30, 60],
|
||||
[16, 0, 30],
|
||||
[17, 0, 30],
|
||||
[18, 0, 30],
|
||||
[19, 0, 30],
|
||||
[20, 0, 30],
|
||||
[21, 0, 30],
|
||||
[22, 0, 60],
|
||||
[22, 30, 60],
|
||||
[23, 0, 60],
|
||||
[23, 30, 60],
|
||||
];
|
||||
|
||||
// [hour, minOffset, turnTermMinutes] for turnIdx 0..23
|
||||
static readonly turnToHM: Array<[number, number, number]> = [
|
||||
[0, 0, 120],
|
||||
[2, 0, 120],
|
||||
[4, 0, 120],
|
||||
[6, 0, 60],
|
||||
[7, 0, 60],
|
||||
[8, 0, 60],
|
||||
[9, 0, 60],
|
||||
[10, 0, 60],
|
||||
[11, 0, 60],
|
||||
[12, 0, 60],
|
||||
[13, 0, 60],
|
||||
[14, 0, 60],
|
||||
[15, 0, 60],
|
||||
[16, 0, 60],
|
||||
[17, 0, 60],
|
||||
[18, 0, 60],
|
||||
[19, 0, 30],
|
||||
[19, 30, 30],
|
||||
[20, 0, 30],
|
||||
[20, 30, 30],
|
||||
[21, 0, 30],
|
||||
[21, 30, 30],
|
||||
[22, 0, 60],
|
||||
[23, 0, 60],
|
||||
];
|
||||
|
||||
constructor(
|
||||
public readonly baseDayUTC: Date, // UTC 기준 하루의 00:00 (Seoul 기준)
|
||||
public readonly turnIdx: number, // 0..23
|
||||
public readonly secOffset: number // seconds within the turn
|
||||
) { }
|
||||
|
||||
// ----- Utilities -----
|
||||
|
||||
/** date → 서울 자정 (UTC 기준 Date) */
|
||||
private static toSeoulMidnightUTC(dateUTC: Date): Date {
|
||||
const zoned = toZonedTime(dateUTC, this.TZ);
|
||||
const midnightInSeoul = new Date(
|
||||
zoned.getFullYear(), zoned.getMonth(), zoned.getDate(), 0, 0, 0, 0
|
||||
);
|
||||
return fromZonedTime(midnightInSeoul, this.TZ);
|
||||
}
|
||||
|
||||
private static format(dateUTC: Date, withFraction = true): string {
|
||||
const zoned = toZonedTime(dateUTC, this.TZ);
|
||||
return formatTime(zoned, withFraction);
|
||||
}
|
||||
|
||||
// ----- Core Logic -----
|
||||
|
||||
|
||||
static fromDatetime(input: string | Date | number): VarTurn60 {
|
||||
const utc = toUTC(input);
|
||||
const baseDayUTC = this.toSeoulMidnightUTC(utc);
|
||||
const zoned = toZonedTime(utc, this.TZ);
|
||||
|
||||
const totalSec = zoned.getHours() * 3600 + zoned.getMinutes() * 60 + zoned.getSeconds() + zoned.getMilliseconds() / 1000;
|
||||
const min30 = Math.floor(totalSec / 1800);
|
||||
if (min30 < 0 || min30 >= 48) throw new RangeError('half-hour index out of range');
|
||||
const [turnIdx, minOffset] = this.min30ToTurn[min30];
|
||||
const secOffset = totalSec - 1800 * min30 + minOffset * 60;
|
||||
|
||||
return new VarTurn60(baseDayUTC, turnIdx, secOffset);
|
||||
}
|
||||
|
||||
static calcTurnDiff(a: string | Date | number, b: string | Date | number): number {
|
||||
const aObj = this.fromDatetime(a);
|
||||
const bObj = this.fromDatetime(b);
|
||||
const dayDiff = differenceInCalendarDays(bObj.baseDayUTC, aObj.baseDayUTC);
|
||||
return dayDiff * 24 + (bObj.turnIdx - aObj.turnIdx);
|
||||
}
|
||||
|
||||
cutTurn(withFraction = true): [string, number] {
|
||||
const [hour, minOffset, turnTerm] = VarTurn60.turnToHM[this.turnIdx];
|
||||
const sec = (hour * 60 + minOffset) * 60;
|
||||
const date = addSeconds(this.baseDayUTC, sec);
|
||||
return [VarTurn60.format(date, withFraction), turnTerm];
|
||||
}
|
||||
|
||||
toDate(): Date {
|
||||
const [hour, minOffset] = VarTurn60.turnToHM[this.turnIdx];
|
||||
const sec = (hour * 60 + minOffset) * 60 + this.secOffset;
|
||||
const date = toZonedTime(addSeconds(this.baseDayUTC, sec), SEOUL_TZ);
|
||||
return date;
|
||||
}
|
||||
|
||||
toDateStr(withFraction = true): string {
|
||||
const [hour, minOffset] = VarTurn60.turnToHM[this.turnIdx];
|
||||
const sec = (hour * 60 + minOffset) * 60 + this.secOffset;
|
||||
const date = addSeconds(this.baseDayUTC, sec);
|
||||
return VarTurn60.format(date, withFraction);
|
||||
}
|
||||
|
||||
cutDay(withFraction = true): [string, boolean, number] {
|
||||
const newMonth = ((this.turnIdx + VarTurn60.MonthAdjustOffset) % 12) + 1;
|
||||
const moved = this.addTurn(-(newMonth - 1));
|
||||
const [dateStr] = moved.cutTurn(withFraction);
|
||||
const yearPulled = newMonth > 3;
|
||||
return [dateStr, yearPulled, newMonth];
|
||||
}
|
||||
|
||||
addTurn(moreTurn: number): VarTurn60 {
|
||||
let dayDiff = Math.trunc(moreTurn / 24);
|
||||
moreTurn %= 24;
|
||||
|
||||
let nextTurnIdx = this.turnIdx + moreTurn;
|
||||
if (nextTurnIdx < 0) {
|
||||
dayDiff -= 1;
|
||||
nextTurnIdx += 24;
|
||||
} else if (nextTurnIdx >= 24) {
|
||||
dayDiff += 1;
|
||||
nextTurnIdx -= 24;
|
||||
}
|
||||
|
||||
const [, , oldTurnTerm] = VarTurn60.turnToHM[this.turnIdx];
|
||||
const [, , nextTurnTerm] = VarTurn60.turnToHM[nextTurnIdx];
|
||||
|
||||
let nextSecOffset = this.secOffset;
|
||||
if (oldTurnTerm !== nextTurnTerm) {
|
||||
nextSecOffset = (nextSecOffset * nextTurnTerm) / oldTurnTerm;
|
||||
const cap = nextTurnTerm * 60 - 1;
|
||||
if (nextSecOffset < 0) nextSecOffset = 0;
|
||||
if (nextSecOffset > cap) nextSecOffset = cap;
|
||||
}
|
||||
|
||||
const nextBaseDayUTC =
|
||||
dayDiff === 0 ? this.baseDayUTC : addDays(this.baseDayUTC, dayDiff);
|
||||
|
||||
return new VarTurn60(nextBaseDayUTC, nextTurnIdx, nextSecOffset);
|
||||
}
|
||||
}
|
||||
Generated
+12
-26
@@ -52,8 +52,7 @@
|
||||
"css-color-names": "^1.0.1",
|
||||
"css-loader": "^6.7.3",
|
||||
"cssnano": "^5.1.15",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"denque": "^2.1.0",
|
||||
"detect-it": "^4.0.1",
|
||||
"downloadjs": "^1.4.7",
|
||||
@@ -3536,22 +3535,15 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
|
||||
"engines": {
|
||||
"node": ">=0.11"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns-tz": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
|
||||
"integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"date-fns": "^3.0.0 || ^4.0.0"
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/date-fns"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
@@ -12097,15 +12089,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="
|
||||
},
|
||||
"date-fns-tz": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
|
||||
"integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==",
|
||||
"requires": {}
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
|
||||
+1
-2
@@ -65,8 +65,7 @@
|
||||
"css-color-names": "^1.0.1",
|
||||
"css-loader": "^6.7.3",
|
||||
"cssnano": "^5.1.15",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"denque": "^2.1.0",
|
||||
"detect-it": "^4.0.1",
|
||||
"downloadjs": "^1.4.7",
|
||||
|
||||
+73
-8
@@ -36,6 +36,57 @@ type ProcResult = {
|
||||
lastExecuted?: string;
|
||||
};
|
||||
|
||||
class CookieJar {
|
||||
#cookies = new Map<string, string>();
|
||||
|
||||
getCookieHeader(): string | undefined {
|
||||
if (this.#cookies.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return [...this.#cookies.entries()].map(([key, value]) => `${key}=${value}`).join("; ");
|
||||
}
|
||||
|
||||
ingestSetCookie(setCookieHeaders: string[]): void {
|
||||
for (const setCookie of setCookieHeaders) {
|
||||
const cookiePart = setCookie.split(";", 1)[0]?.trim();
|
||||
if (!cookiePart) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const eqPos = cookiePart.indexOf("=");
|
||||
if (eqPos <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = cookiePart.slice(0, eqPos).trim();
|
||||
const value = cookiePart.slice(eqPos + 1).trim();
|
||||
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value === "") {
|
||||
this.#cookies.delete(key);
|
||||
continue;
|
||||
}
|
||||
this.#cookies.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSetCookieHeaders(headers: Headers): string[] {
|
||||
const headerObj = headers as unknown as { getSetCookie?: () => string[] };
|
||||
if (typeof headerObj.getSetCookie === "function") {
|
||||
return headerObj.getSetCookie();
|
||||
}
|
||||
|
||||
const fallback = headers.get("set-cookie");
|
||||
if (!fallback) {
|
||||
return [];
|
||||
}
|
||||
return [fallback];
|
||||
}
|
||||
|
||||
function nowStr() {
|
||||
const d = new Date();
|
||||
const pad = (n: number) => n.toString().padStart(2, "0");
|
||||
@@ -131,13 +182,19 @@ async function scanOnce(): Promise<ServerEntry[]> {
|
||||
}
|
||||
|
||||
// ============ 공개 접근 체크 ============
|
||||
async function isPublicReachable(signal?: AbortSignal): Promise<boolean> {
|
||||
async function isPublicReachable(signal?: AbortSignal, cookieJar?: CookieJar): Promise<boolean> {
|
||||
if (!REQUIRE_PUBLIC) return true;
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), Math.min(5_000, FETCH_TIMEOUT_MS));
|
||||
const composite = anySignal([ctrl.signal, signal].filter(Boolean) as AbortSignal[]);
|
||||
try {
|
||||
const res = await fetch(webBase, { method: "GET", signal: composite });
|
||||
const cookieHeader = cookieJar?.getCookieHeader();
|
||||
const res = await fetch(webBase, {
|
||||
method: "GET",
|
||||
signal: composite,
|
||||
headers: cookieHeader ? { cookie: cookieHeader } : undefined,
|
||||
});
|
||||
cookieJar?.ingestSetCookie(getSetCookieHeaders(res.headers));
|
||||
if (!res.ok) { log(`[PUBLIC] 응답 ${res.status} → 비공개로 간주`); return false; }
|
||||
return true;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -150,14 +207,20 @@ async function isPublicReachable(signal?: AbortSignal): Promise<boolean> {
|
||||
}
|
||||
|
||||
// ============ HTTP ============
|
||||
async function httpGetJson<T extends Json>(url: string, outerSignal?: AbortSignal): Promise<T | null> {
|
||||
async function httpGetJson<T extends Json>(url: string, outerSignal?: AbortSignal, cookieJar?: CookieJar): Promise<T | null> {
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
||||
const signal = anySignal([ctrl.signal, outerSignal].filter(Boolean) as AbortSignal[]);
|
||||
|
||||
const t0 = performance.now();
|
||||
try {
|
||||
const res = await fetch(url, { signal, cache: "no-store" });
|
||||
const cookieHeader = cookieJar?.getCookieHeader();
|
||||
const res = await fetch(url, {
|
||||
signal,
|
||||
cache: "no-store",
|
||||
headers: cookieHeader ? { cookie: cookieHeader } : undefined,
|
||||
});
|
||||
cookieJar?.ingestSetCookie(getSetCookieHeaders(res.headers));
|
||||
const dt = Math.round(performance.now() - t0);
|
||||
if (!res.ok) { log("HTTPError:", res.status, url); return null; }
|
||||
try {
|
||||
@@ -195,6 +258,7 @@ class ServerRunner {
|
||||
#stopCtrl = new AbortController();
|
||||
#stopped = false;
|
||||
#lastAutoResetAt = 0;
|
||||
#cookieJar = new CookieJar();
|
||||
|
||||
constructor(entry: ServerEntry) {
|
||||
this.#entry = entry;
|
||||
@@ -222,7 +286,7 @@ class ServerRunner {
|
||||
while (!this.#stopCtrl.signal.aborted) {
|
||||
try {
|
||||
// 공개 접근 체크
|
||||
const pub = await isPublicReachable(this.#stopCtrl.signal);
|
||||
const pub = await isPublicReachable(this.#stopCtrl.signal, publicCookieJar);
|
||||
if (!pub) { await delay(15_000, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ }); continue; }
|
||||
|
||||
const hidden = await this.#entry.isHidden();
|
||||
@@ -236,7 +300,7 @@ class ServerRunner {
|
||||
if (this.#stopCtrl.signal.aborted) break;
|
||||
}
|
||||
log(`[${this.name()}] 닫힘 - (정렬) autoreset 호출 @ mm:00${AUTO_RESET_JITTER_MS ? `+${AUTO_RESET_JITTER_MS}ms` : ""}`);
|
||||
await httpGetJson<Json>(this.#entry.autoresetUrl, this.#stopCtrl.signal);
|
||||
await httpGetJson<Json>(this.#entry.autoresetUrl, this.#stopCtrl.signal, this.#cookieJar);
|
||||
// 같은 분에 중복 호출 방지: 0초 직후 잠깐 쉬고 다음 루프는 자연스럽게 다음 분으로 정렬됨
|
||||
await delay(200, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
|
||||
continue;
|
||||
@@ -244,7 +308,7 @@ class ServerRunner {
|
||||
|
||||
|
||||
// 열림: proc
|
||||
const data = await httpGetJson<ProcResult>(this.#entry.procUrl, this.#stopCtrl.signal);
|
||||
const data = await httpGetJson<ProcResult>(this.#entry.procUrl, this.#stopCtrl.signal, this.#cookieJar);
|
||||
if (!data) {
|
||||
await delay(IF_LOCKED_WAIT_MS, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
|
||||
continue;
|
||||
@@ -348,6 +412,7 @@ class DaemonManager {
|
||||
|
||||
// ============ 메인 ============
|
||||
const manager = new DaemonManager();
|
||||
const publicCookieJar = new CookieJar();
|
||||
|
||||
async function main() {
|
||||
// 신호 처리: SIGINT/SIGTERM → graceful shutdown
|
||||
@@ -372,4 +437,4 @@ async function main() {
|
||||
main().catch((e) => {
|
||||
log("치명적 예외:", e);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,8 @@ use sammo\TimeUtil;
|
||||
use sammo\Util;
|
||||
use sammo\Validator;
|
||||
|
||||
use function sammo\logError;
|
||||
|
||||
class LoginByToken extends LoginByID
|
||||
{
|
||||
static array $sensitiveArgs = ['hashedToken'];
|
||||
|
||||
@@ -73,7 +73,7 @@ class APIHelper
|
||||
$logPath = "{$rootPath}/d_log/{$realYearMonth}_api_log.db";
|
||||
}
|
||||
$logDB = FileDB::db($logPath, __DIR__ . '/../../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');
|
||||
|
||||
//NOTE: array_merge([], {})의 상황이 가능함.
|
||||
|
||||
@@ -272,7 +272,7 @@ class Session
|
||||
return $this;
|
||||
}
|
||||
|
||||
$turnterm = abs($gameStor->turnterm);
|
||||
$turnterm = $gameStor->turnterm;
|
||||
$isUnited = $gameStor->isunited != 0;
|
||||
|
||||
$generalID = $general['no'];
|
||||
|
||||
+1
-19
@@ -947,31 +947,13 @@ class Util
|
||||
return $flattened;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IP address of the client.
|
||||
*
|
||||
* @param boolean $trust_proxy_headers Whether or not to trust the
|
||||
* proxy headers HTTP_CLIENT_IP
|
||||
* and HTTP_X_FORWARDED_FOR. ONLY
|
||||
* use if your server is behind a
|
||||
* proxy that sets these values
|
||||
* @return string
|
||||
*/
|
||||
public static function get_client_ip($trust_proxy_headers = false)
|
||||
{
|
||||
if (!$trust_proxy_headers) {
|
||||
return $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
|
||||
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
||||
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
||||
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
} else {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
|
||||
return $ip;
|
||||
return $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'local';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user