diff --git a/hwe/func.php b/hwe/func.php
index 54dcce78..4c1f7815 100644
--- a/hwe/func.php
+++ b/hwe/func.php
@@ -1,2112 +1,2137 @@
- 0,
- 'name' => '재야',
- 'color' => '#000000',
- 'type' => GameConst::$neutralNationType,
- 'level' => 0,
- 'capital' => 0,
- 'gold' => 0,
- 'rice' => 2000,
- 'tech' => 0,
- 'gennum' => 1,
- 'power' => 1
- ];
- }
-
- if ($nationList === null) {
- $nationAll = DB::db()->query("select nation, name, color, type, level, capital, gennum, power from nation");
- $nationList = Util::convertArrayToDict($nationAll, "nation");
- }
-
- if ($nationID === -1) {
- return $nationList;
- }
-
- if (isset($nationList[$nationID])) {
- return $nationList[$nationID];
- }
- return null;
-}
-
-/**
- * getNationStaticInfo() 함수의 국가 캐시를 초기화
- */
-function refreshNationStaticInfo()
-{
- getNationStaticInfo(null, true);
-}
-
-/**
- * getNationStaticInfo(-1) 의 단축형
- */
-function getAllNationStaticInfo()
-{
- return getNationStaticInfo(-1);
-}
-
-function GetImageURL($imgsvr, $filepath = '')
-{
- if ($imgsvr == 0) {
- return ServConfig::getSharedIconPath($filepath);
- } else {
- return AppConf::getUserIconPathWeb($filepath);
- }
-}
-
-/**
- * @param null|int $con 장수의 벌점
- * @param null|int $conlimit 최대 벌점
- */
-function checkLimit($con = null)
-{
- $session = Session::getInstance();
- if ($session->userGrade >= 4) {
- return 0;
- }
-
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
- if ($con === null) {
- $con = $db->queryFirstField('SELECT con FROM general WHERE `owner`=%i', Session::getUserID());
- }
- $conlimit = $gameStor->conlimit;
-
- if ($con > $conlimit) {
- return 2;
- //접속제한 90%이면 경고문구
- } elseif ($con > $conlimit * 0.9) {
- return 1;
- } else {
- return 0;
- }
-}
-
-function getBlockLevel()
-{
- return DB::db()->queryFirstField('select block from general where no = %i', Session::getInstance()->generalID);
-}
-
-function getRandGenName()
-{
- $firstname = Util::choiceRandom(GameConst::$randGenFirstName);
- $middlename = Util::choiceRandom(GameConst::$randGenMiddleName);
- $lastname = Util::choiceRandom(GameConst::$randGenLastName);
-
- return "{$firstname}{$middlename}{$lastname}";
-}
-
-
-
-function cityInfo(General $generalObj)
-{
- $db = DB::db();
-
- // 도시 정보
- $city = $generalObj->getRawCity();
- $cityID = $city['city'];
-
- $nation = getNationStaticInfo($city['nation']);
-
- if (!$nation) {
- $nation = getNationStaticInfo(0);
- }
-
- $city['nationName'] = $nation['name'];
- $city['nationTextColor'] = newColor($nation['color']);
- $city['nationColor'] = $nation['color'];
- $city['region'] = CityConst::$regionMap[$city['region']];
- $city['levelText'] = CityConst::$levelMap[$city['level']];
-
- $officerName = [
- 2 => '-',
- 3 => '-',
- 4 => '-'
- ];
-
- foreach ($db->query('SELECT `officer_level`, `name`, npc, no FROM general WHERE officer_city = %i', $cityID) as $officer) {
- $officerName[$officer['officer_level']] = getColoredName($officer['name'], $officer['npc']);
- }
-
- $city['officerName'] = $officerName;
-
- $templates = new \League\Plates\Engine('templates');
- $templates->registerFunction('bar', '\sammo\bar');
- return $templates->render('mainCityInfo', $city);
-}
-
-function myNationInfo(General $generalObj)
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
- $admin = $gameStor->getValues(['startyear', 'year']);
-
- $nationID = $generalObj->getNationID();
- $nation = $db->queryFirstRow('SELECT * FROM nation WHERE nation = %i', $nationID)??getNationStaticInfo(0);
- $city = $db->queryFirstRow(
- 'SELECT COUNT(*) as cnt, SUM(pop) as totpop, SUM(pop_max) as maxpop from city where nation=%i',
- $nationID
- );
- $general = $db->queryFirstRow('SELECT COUNT(*) as cnt, SUM(crew) as totcrew,SUM(leadership)*100 as maxcrew from general where nation=%i', $nationID);
-
- $topChiefs = Util::convertArrayToDict($db->query('SELECT officer_level, no, name, npc FROM general WHERE nation = %i AND officer_level >= 11', $nationID), 'officer_level');
-
- $level12Name = key_exists(12, $topChiefs)?getColoredName($topChiefs[12]['name'], $topChiefs[12]['npc']):'-';
- $level11Name = key_exists(11, $topChiefs)?getColoredName($topChiefs[11]['name'], $topChiefs[11]['npc']):'-';
-
- echo "
-
- | 【재 야】";
- } else {
- echo "style='color:" . newColor($nation['color']) . ";background-color:{$nation['color']};font-weight:bold;font-size:13px;text-align:center'>국가【 {$nation['name']} 】";
- }
-
- echo "
- |
-
-
- | 성 향 |
- " . getNationType($nation['type']) . " (" . getNationType2($nation['type']) . ") |
-
-
-
- | " . getOfficerLevelText(12, $nation['level']) . " |
- {$level12Name} |
- " . getOfficerLevelText(11, $nation['level']) . " |
- {$level11Name} |
-
-
- | 총주민 |
- ";
- echo $nationID===0 ? "해당 없음" : "{$city['totpop']}/{$city['maxpop']}";
- echo " |
- 총병사 |
- ";
- echo $nationID===0 ? "해당 없음" : "{$general['totcrew']}/{$general['maxcrew']}";
- echo " |
-
-
-
- | 국 고 |
- ";
- echo $nationID===0 ? "해당 없음" : "{$nation['gold']}";
- echo " |
- 병 량 |
- ";
- echo $nationID===0 ? "해당 없음" : "{$nation['rice']}";
- echo " |
-
-
- | 지급률 |
- ";
- if ($nationID===0) {
- echo "해당 없음";
- } else {
- echo $nation['bill'] == 0 ? "0 %" : "{$nation['bill']} %";
- }
- echo "
- |
- 세 율 |
- ";
- if ($nationID===0) {
- echo "해당 없음";
- } else {
- echo $nation['rate'] == 0 ? "0 %" : "{$nation['rate']} %";
- }
-
- $techCall = getTechCall($nation['tech']);
-
- if (TechLimit($admin['startyear'], $admin['year'], $nation['tech'])) {
- $nation['tech'] = "" . floor($nation['tech']) . "";
- } else {
- $nation['tech'] = "" . floor($nation['tech']) . "";
- }
-
- $nation['tech'] = "$techCall / {$nation['tech']}";
-
- if ($nationID===0) {
- $nation['strategic_cmd_limit'] = "해당 없음";
- $nation['surlimit'] = "해당 없음";
- $nation['scout'] = "해당 없음";
- $nation['war'] = "해당 없음";
- $nation['power'] = "해당 없음";
- } else {
- if ($nation['strategic_cmd_limit'] != 0) {
- $nation['strategic_cmd_limit'] = "{$nation['strategic_cmd_limit']}턴";
- } else {
- $nation['strategic_cmd_limit'] = "가 능";
- }
-
- if ($nation['surlimit'] != 0) {
- $nation['surlimit'] = "{$nation['surlimit']}턴";
- } else {
- $nation['surlimit'] = "가 능";
- }
-
- if ($nation['scout'] != 0) {
- $nation['scout'] = "금 지";
- } else {
- $nation['scout'] = "허 가";
- }
-
- if ($nation['war'] != 0) {
- $nation['war'] = "금 지";
- } else {
- $nation['war'] = "허 가";
- }
- }
-
- echo "
- |
-
-
- | 속 령 |
- ";
- echo $nationID===0 ? "-" : "{$city['cnt']}";
- echo " |
- 장 수 |
- ";
- echo $nationID===0 ? "-" : "{$general['cnt']}";
- echo " |
-
-
- | 국 력 |
- {$nation['power']} |
- 기술력 |
- ";
- echo $nationID===0 ? "-" : "{$nation['tech']}";
- echo " |
-
-
- | 전 략 |
- {$nation['strategic_cmd_limit']} |
- 외 교 |
- {$nation['surlimit']} |
-
-
- | 임 관 |
- {$nation['scout']} |
- 전 쟁 |
- {$nation['war']} |
-
-
-";
-}
-
-function checkSecretMaxPermission($penalty)
-{
- $secretMax = 4;
- if ($penalty['noTopSecret'] ?? false) {
- $secretMax = 1;
- } else if ($penalty['noChief'] ?? false) {
- $secretMax = 1;
- } else if ($penalty['noAmbassador'] ?? false) {
- $secretMax = 2;
- }
- return $secretMax;
-}
-
-function checkSecretPermission(array $me, $checkSecretLimit = true)
-{
- if (!key_exists('penalty', $me) || !key_exists('permission', $me)) {
- trigger_error('canAccessSecret() 함수에 필요한 인자가 부족');
- }
- $penalty = Json::decode($me['penalty']) ?? [];
- $permission = $me['permission'];
-
- if (!$me['nation']) {
- return -1;
- }
-
- if ($me['officer_level'] == 0) {
- return -1;
- }
-
-
- if ($penalty['noSecret'] ?? false) {
- return 0;
- }
-
- $secretMin = 0;
- $secretMax = checkSecretMaxPermission($me, $penalty);
-
-
- if ($me['officer_level'] == 12) {
- $secretMin = 4;
- } else if ($me['permission'] == 'ambassador') {
- $secretMin = 4;
- } else if ($me['permission'] == 'auditor') {
- $secretMin = 3;
- } else if ($me['officer_level'] >= 5) {
- $secretMin = 2;
- } else if ($me['officer_level'] > 1) {
- $secretMin = 1;
- } else if ($checkSecretLimit) {
- $db = DB::db();
- $secretLimit = $db->queryFirstField('SELECT secretlimit FROM nation WHERE nation = %i', $me['nation']);
- if ($me['belong'] >= $secretLimit) {
- $secretMin = 1;
- }
- }
-
- return min($secretMin, $secretMax);
-}
-
-function addCommand($typename, $value, $valid = 1, $color = 0)
-{
- if ($valid == 1) {
- switch ($color) {
- case 0:
- echo "
- ";
- break;
- case 1:
- echo "
- ";
- break;
- case 2:
- echo "
- ";
- break;
- }
- } else {
- echo "
- ";
- }
-}
-
-function commandGroup($typename, $type = 0)
-{
- if ($type == 0) {
- echo "
- ";
- }
-}
-
-function printCommandTable(General $generalObj)
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
- $userID = Session::getUserID();
-
- $gameStor->turnOnCache();
- $env = $gameStor->getAll();
-
-?>
-
-turnOnCache();
- $env = $gameStor->getAll();
-
-?>
-
-show_img_level;
-
-
- $nation = getNationStaticInfo($generalObj->getNationID());
-
- $lbonus = calcLeadershipBonus($generalObj->getVar('officer_level'), $nation['level']);
- if ($lbonus > 0) {
- $lbonus = "+{$lbonus}";
- } else {
- $lbonus = "";
- }
-
- if ($generalObj->getVar('troop') == 0) {
- $troopInfo = '-';
- } else {
- $troopCity = $db->queryFirstField('SELECT city FROM general WHERE no=%i', $generalObj->getVar('troop'));
- $troopTurn = $db->queryFirstField('SELECT `brief` FROM general_turn WHERE general_id = %i AND turn_idx = 0', $generalObj->getVar('troop'));
- $troopInfo = $db->queryFirstField('SELECT name FROM troop WHERE troop_leader = %i', $generalObj->getVar('troop'));
-
- if ($troopTurn !== '집합') {
- $troopInfo = "{$troopInfo}";
- } else if ($troopCity != $generalObj->getCityID()) {
- $troopCityName = CityConst::byID($troopCity)->name;
- $troopInfo = "{$troopInfo}({$troopCityName})";
- }
- }
-
- $officerLevel = $generalObj->getVar('officer_level');
- $officerLevelText = getOfficerLevelText($officerLevel, $nation['level']);
-
- if (2 <= $officerLevel && $officerLevel <= 4) {
- $cityOfficerName = CityConst::byID($generalObj->getVar('officer_city'))->name;
- $officerLevelText = "{$cityOfficerName} {$officerLevelText}";
- }
-
- $call = getCall(...$generalObj->getVars('leadership', 'strength', 'intel'));
- $crewTypeInfo = displayiActionObjInfo($generalObj->getCrewTypeObj());
- $weaponInfo = displayiActionObjInfo($generalObj->getItem('weapon'));
- $bookInfo = displayiActionObjInfo($generalObj->getItem('book'));
- $horseInfo = displayiActionObjInfo($generalObj->getItem('horse'));
- $itemInfo = displayiActionObjInfo($generalObj->getItem('item'));
-
- $leadership = $generalObj->getLeadership(true, false, false);
- $strength = $generalObj->getStrength(true, false, false);
- $intel = $generalObj->getIntel(true, false, false);
-
-
- $injury = $generalObj->getVar('injury');
- if ($injury > 60) {
- $color = "";
- $injury = "위독";
- } elseif ($injury > 40) {
- $color = "";
- $injury = "심각";
- } elseif ($injury > 20) {
- $color = "";
- $injury = "중상";
- } elseif ($injury > 0) {
- $color = "";
- $injury = "경상";
- } else {
- $color = "";
- $injury = "건강";
- }
-
- $remaining = (new \DateTimeImmutable($generalObj->getTurnTime()))->diff(new \DateTimeImmutable())->i;
-
- if ($nation['color'] == "") {
- $nation['color'] = "#000000";
- }
-
- $age = $generalObj->getVar('age');
- if ($age < GameConst::$retirementYear * 0.75) {
- $age = "{$age} 세";
- } elseif ($age < GameConst::$retirementYear) {
- $age = "{$age} 세";
- } else {
- $age = "{$age} 세";
- }
-
- $connectCnt = round($generalObj->getVar('connect'), -1);
- $specialDomestic = $generalObj->getVar('special') === GameConst::$defaultSpecialDomestic
- ? "{$generalObj->getVar('specage')}세"
- : "" . displayiActionObjInfo($generalObj->getSpecialDomestic()) . "";
- $specialWar = $generalObj->getVar('special2') === GameConst::$defaultSpecialDomestic
- ? "{$generalObj->getVar('specage2')}세"
- : "" . displayiActionObjInfo($generalObj->getSpecialWar()) . "";
-
- $atmos = $generalObj->getVar('atmos');
- $atmosBonus = $generalObj->onCalcStat($generalObj, 'bonusAtmos', $atmos) - $atmos;
- if ($atmosBonus > 0) {
- $atmos = "{$atmos} (+{$atmosBonus})";
- } else if ($atmosBonus < 0) {
- $atmos = "{$atmos} ({$atmosBonus})";
- } else {
- $atmos = "$atmos";
- }
-
- $train = $generalObj->getVar('train');
- $trainBonus = $generalObj->onCalcStat($generalObj, 'bonusTrain', $train) - $train;
- if ($trainBonus > 0) {
- $train = "{$train} (+{$trainBonus})";
- } else if ($trainBonus < 0) {
- $train = "{$train} ({$trainBonus})";
- } else {
- $train = "$train";
- }
-
- if ($generalObj->getVar('defence_train') === 999) {
- $defenceTrain = "수비 안함";
- } else {
- $defenceTrain = "수비 함(훈사{$generalObj->getVar('defence_train')})";
- }
-
- $crewType = $generalObj->getCrewTypeObj();
-
- $weapImage = ServConfig::$gameImagePath . "/crewtype{$crewType->id}.png";
- if ($show_img_level < 2) {
- $weapImage = ServConfig::$sharedIconPath . "/default.jpg";
- };
- $imagePath = GetImageURL(...$generalObj->getVars('imgsvr', 'picture'));
- echo "
-
- | |
- {$generalObj->getName()} 【 {$officerLevelText} | {$call} | {$color}{$injury} 】 " . $generalObj->getTurnTime($generalObj::TURNTIME_HMS) . " |
-
-
- | 통솔 |
- {$color}{$leadership}{$lbonus} |
- " . bar(expStatus($generalObj->getVar('leadership_exp')), 20) . " |
- 무력 |
- {$color}{$strength} |
- " . bar(expStatus($generalObj->getVar('strength_exp')), 20) . " |
- 지력 |
- {$color}{$intel} |
- " . bar(expStatus($generalObj->getVar('intel_exp')), 20) . " |
-
-
- | 명마 |
- $horseInfo |
- 무기 |
- $weaponInfo |
- 서적 |
- $bookInfo |
-
-
- |
- 자금 |
- {$generalObj->getVar('gold')} |
- 군량 |
- {$generalObj->getVar('rice')} |
- 도구 |
- $itemInfo |
-
-
- | 병종 |
- {$crewTypeInfo} |
- 병사 |
- {$generalObj->getVar('crew')} |
- 성격 |
- " . displayiActionObjInfo($generalObj->getPersonality()) . " |
-
-
- | 훈련 |
- $train |
- 사기 |
- $atmos |
- 특기 |
- $specialDomestic / $specialWar |
-
-
- | Lv |
- {$generalObj->getVar('explevel')} |
- " . bar(getLevelPer(...$generalObj->getVars('experience', 'explevel')), 20) . " |
- 연령 |
- {$age} |
-
-
- | 수비 |
- {$defenceTrain} |
- 삭턴 |
- {$generalObj->getVar('killturn')} 턴 |
- 실행 |
- $remaining 분 남음 |
-
-
- | 부대 |
- {$troopInfo} |
- 벌점 |
- " . getConnect($connectCnt) . " {$connectCnt}({$generalObj->getVar('con')}) |
-
-
";
-}
-
-function generalInfo2(General $generalObj)
-{
- $general = $generalObj->getRaw();
-
- $winRate = round($generalObj->getRankVar('killnum') / max($generalObj->getRankVar('warnum'), 1) * 100, 2);
- $killRate = round($generalObj->getRankVar('killcrew') / max($generalObj->getRankVar('deathcrew'), 1) * 100, 2);
-
- $experienceBonus = $generalObj->onCalcStat($generalObj, 'experience', 10000) - 10000;
- if ($experienceBonus > 0) {
- $experience = "" . getHonor($general['experience']) . " ({$general['experience']})";
- } else if ($experienceBonus < 0) {
- $experience = "" . getHonor($general['experience']) . " ({$general['experience']})";
- } else {
- $experience = getHonor($general['experience']) . " ({$general['experience']})";
- }
-
- $dedicationBonus = $generalObj->onCalcStat($generalObj, 'dedication', 10000) - 10000;
- if ($dedicationBonus > 0) {
- $dedication = "" . getHonor($general['dedication']) . " ({$general['dedication']})";
- } else if ($dedicationBonus < 0) {
- $dedication = "" . getHonor($general['dedication']) . " ({$general['dedication']})";
- } else {
- $dedication = getHonor($general['dedication']) . " ({$general['dedication']})";
- }
-
- $dex1 = $general['dex1'] / GameConst::$dexLimit * 100;
- $dex2 = $general['dex2'] / GameConst::$dexLimit * 100;
- $dex3 = $general['dex3'] / GameConst::$dexLimit * 100;
- $dex4 = $general['dex4'] / GameConst::$dexLimit * 100;
- $dex5 = $general['dex5'] / GameConst::$dexLimit * 100;
-
- if ($dex1 > 100) {
- $dex1 = 100;
- }
- if ($dex2 > 100) {
- $dex2 = 100;
- }
- if ($dex3 > 100) {
- $dex3 = 100;
- }
- if ($dex4 > 100) {
- $dex4 = 100;
- }
- if ($dex5 > 100) {
- $dex5 = 100;
- }
-
- $general['dex1_text'] = getDexCall($general['dex1']);
- $general['dex2_text'] = getDexCall($general['dex2']);
- $general['dex3_text'] = getDexCall($general['dex3']);
- $general['dex4_text'] = getDexCall($general['dex4']);
- $general['dex5_text'] = getDexCall($general['dex5']);
-
- $general['dex1_short'] = sprintf('%.1fK', $general['dex1'] / 1000);
- $general['dex2_short'] = sprintf('%.1fK', $general['dex2'] / 1000);
- $general['dex3_short'] = sprintf('%.1fK', $general['dex3'] / 1000);
- $general['dex4_short'] = sprintf('%.1fK', $general['dex4'] / 1000);
- $general['dex5_short'] = sprintf('%.1fK', $general['dex5'] / 1000);
-
- echo "
- | 추 가 정 보 |
-
- | 명성 |
- $experience |
- 계급 |
- $dedication |
-
-
- | 전투 |
- {$generalObj->getRankVar('warnum')} |
- 계략 |
- {$generalObj->getRankVar('firenum')} |
- 사관 |
- {$general['belong']}년 |
-
-
- | 승률 |
- {$winRate} % |
- 승리 |
- {$generalObj->getRankVar('killnum')} |
- 패배 |
- {$generalObj->getRankVar('deathnum')} |
-
-
- | 살상률 |
- {$killRate} % |
- 사살 |
- {$generalObj->getRankVar('killcrew')} |
- 피살 |
- {$generalObj->getRankVar('deathcrew')} |
-
-
-
- | 숙 련 도 |
-
- | 보병 |
- {$general['dex1_text']} |
- {$general['dex1_short']} |
- " . bar($dex1, 16) . " |
-
-
- | 궁병 |
- {$general['dex2_text']} |
- {$general['dex2_short']} |
- " . bar($dex2, 16) . " |
-
-
- | 기병 |
- {$general['dex3_text']} |
- {$general['dex3_short']} |
- " . bar($dex3, 16) . " |
-
-
- | 귀병 |
- {$general['dex4_text']} |
- {$general['dex4_short']} |
- " . bar($dex4, 16) . " |
-
-
- | 차병 |
- {$general['dex5_text']} |
- {$general['dex5_short']} |
- " . bar($dex5, 16) . " |
-
-
";
-}
-
-function getOnlineNum()
-{
- return KVStorage::getStorage(DB::db(), 'game_env')->online;
-}
-
-function onlinegen(General $general)
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
- $nationID = $general->getNationID();
- $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
- $onlinegen = $nationStor->online_genenerals;
- return $onlinegen;
-}
-
-function nationMsg(General $general)
-{
- $db = DB::db();
- $nationID = $general->getNationID();
- $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
-
- return $nationStor->notice??'';
-}
-
-function banner()
-{
-
- return sprintf(
- '%s %s / %s',
- GameConst::$title,
- VersionGit::$version,
- GameConst::$banner
- );
-}
-
-function addTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true)
-{
- $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)
-{
- $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)
-{
- $date = new \DateTime($date);
-
- $baseDate = new \DateTime($date->format('Y-m-d'));
- $baseDate->sub(new \DateInterval("P1D"));
- $baseDate->add(new \DateInterval("PT1H"));
-
- $diffMin = intdiv($date->getTimeStamp() - $baseDate->getTimeStamp(), 60);
- $diffMin -= $diffMin % $turnterm;
-
- $baseDate->add(new \DateInterval("PT{$diffMin}M"));
- if ($withFraction) {
- return $baseDate->format('Y-m-d H:i:s.u');
- }
- return $baseDate->format('Y-m-d H:i:s');
-}
-
-function cutDay($date, int $turnterm, bool $withFraction = true)
-{
- $date = new \DateTime($date);
-
- $baseDate = new \DateTime($date->format('Y-m-d'));
- $baseDate->sub(new \DateInterval("P1D"));
- $baseDate->add(new \DateInterval("PT1H"));
-
- $baseGap = 12 * $turnterm;
-
- $diffMin = intdiv($date->getTimeStamp() - $baseDate->getTimeStamp(), 60);
-
- $timeAdjust = $diffMin % $baseGap;
- $newMonth = intdiv($timeAdjust, $turnterm) + 1;
-
- $yearPulled = false;
- if ($newMonth > 3) { //3월 이후일때는
- $yearPulled = true;
- $diffMin += $baseGap;
- }
- $diffMin -= $timeAdjust;
-
- $baseDate->add(new \DateInterval("PT{$diffMin}M"));
- if ($withFraction) {
- $dateTimeString = $baseDate->format('Y-m-d H:i:s.u');
- } else {
- $dateTimeString = $baseDate->format('Y-m-d H:i:s');
- }
-
- return [$dateTimeString, $yearPulled, $newMonth];
-}
-
-function increaseRefresh($type = "", $cnt = 1)
-{
- //FIXME: 로그인, 비로그인 시 처리가 명확하지 않음
- $session = Session::getInstance();
- $userID = $session->userID;
- $generalID = $session->generalID;
- $userGrade = $session->userGrade;
-
- $date = TimeUtil::now();
-
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
- $gameStor->refresh = $gameStor->refresh + $cnt; //TODO: +로 증가하는 값은 별도로 분리
- $isunited = $gameStor->isunited;
- $opentime = $gameStor->opentime;
-
- if ($isunited != 2 && $generalID && $userGrade < 6 && $opentime <= TimeUtil::DatetimeNow()) {
- $db->update('general', [
- 'lastrefresh' => $date,
- 'con' => $db->sqleval('con + %i', $cnt),
- 'connect' => $db->sqleval('connect + %i', $cnt),
- 'refcnt' => $db->sqleval('refcnt + %i', $cnt),
- 'refresh' => $db->sqleval('refresh + %i', $cnt)
- ], 'owner=%i', $userID);
- }
-
- $date = date('Y_m_d H:i:s');
- $date2 = substr($date, 0, 10);
- $online = getOnlineNum();
- file_put_contents(
- __DIR__ . "/logs/" . UniqueConst::$serverID . "/_{$date2}_refresh.txt",
- sprintf(
- "%s, %s, %s, %s, %s, %d\n",
- $date,
- $session->userName,
- $session->generalName,
- $session->ip,
- $type,
- $online
- ),
- FILE_APPEND
- );
-
- $proxy_headers = array(
- 'HTTP_VIA',
- 'HTTP_X_FORWARDED_FOR',
- 'HTTP_FORWARDED_FOR',
- 'HTTP_X_FORWARDED',
- 'HTTP_FORWARDED',
- 'HTTP_CLIENT_IP',
- 'HTTP_FORWARDED_FOR_IP',
- 'VIA',
- 'X_FORWARDED_FOR',
- 'FORWARDED_FOR',
- 'X_FORWARDED',
- 'FORWARDED',
- 'CLIENT_IP',
- 'FORWARDED_FOR_IP',
- 'HTTP_PROXY_CONNECTION'
- );
-
- $str = "";
- foreach ($proxy_headers as $x) {
- if (isset($_SERVER[$x])) $str .= "//{$x}:{$_SERVER[$x]}";
- }
- if ($str != "") {
- file_put_contents(
- __DIR__ . "/logs/" . UniqueConst::$serverID . "/_{$date2}_ipcheck.txt",
- sprintf(
- "%s, %s, %s%s\n",
- $session->userName,
- $session->generalName,
- $_SERVER['REMOTE_ADDR'],
- $str
- ),
- FILE_APPEND
- );
- }
-}
-
-function updateTraffic()
-{
- $online = getOnlineNum();
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
- $admin = $gameStor->getValues(['year', 'month', 'refresh', 'maxonline', 'maxrefresh']);
-
- //최다갱신자
- $user = $db->queryFirstRow('select name,refresh from general order by refresh desc limit 1');
-
- if ($admin['maxrefresh'] < $admin['refresh']) {
- $admin['maxrefresh'] = $admin['refresh'];
- }
- if ($admin['maxonline'] < $online) {
- $admin['maxonline'] = $online;
- }
- $gameStor->refresh = 0;
- $gameStor->maxrefresh = $admin['maxrefresh'];
- $gameStor->maxonline = $admin['maxonline'];
-
- $db->update('general', ['refresh' => 0], true);
-
- $date = TimeUtil::now();
- //일시|년|월|총갱신|접속자|최다갱신자
- file_put_contents(
- __DIR__ . "/logs/" . UniqueConst::$serverID . "/_traffic.txt",
- Json::encode([
- $date,
- $admin['year'],
- $admin['month'],
- $admin['refresh'],
- $online,
- $user['name'] . "(" . $user['refresh'] . ")"
- ]) . "\n",
- FILE_APPEND
- );
-}
-
-function CheckOverhead()
-{
- //서버정보
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
- list($turnterm, $conlimit) = $gameStor->getValuesAsArray(['turnterm', 'conlimit']);
-
- $con = Util::round(pow($turnterm, 0.6) * 3) * 10;
-
-
- if ($con != $conlimit) {
- $gameStor->conlimit = $con;
- }
-}
-
-function isLock()
-{
- return DB::db()->queryFirstField("SELECT plock from plock limit 1") != 0;
-}
-
-function tryLock(): bool
-{
- //NOTE: 게임 로직과 관련한 모든 insert, update 함수들은 lock을 거칠것을 권장함.
- $db = DB::db();
-
- // 잠금
- $db->update('plock', [
- 'plock' => 1,
- 'locktime' => TimeUtil::now(true)
- ], 'plock=0');
-
- return $db->affectedRows() > 0;
-}
-
-function unlock(): bool
-{
- // 풀림
- $db = DB::db();
- $db->update('plock', [
- 'plock' => 0
- ], true);
-
- return $db->affectedRows() > 0;
-}
-
-function timeover(): bool
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
- list($turnterm, $turntime) = $gameStor->getValuesAsArray(['turnterm', 'turntime']);
- $diff = (new \DateTime())->getTimestamp() - (new \DateTime($turntime))->getTimestamp();
-
- $t = min($turnterm, 5);
-
- $term = $diff;
- if ($term >= $t || $term < 0) {
- return true;
- } else {
- return false;
- }
-}
-
-function checkDelay()
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
- //서버정보
- $now = new \DateTimeImmutable();
- $turntime = new \DateTimeImmutable($gameStor->turntime);
- $timeMinDiff = intdiv($now->getTimestamp() - $turntime->getTimestamp(), 60);
-
- // 1턴이상 갱신 없었으면 서버 지연
- $term = $gameStor->turnterm;
- if ($term >= 20) {
- $threshold = 1;
- } else if ($term >= 10) {
- $threshold = 3;
- } else {
- $threshold = 6;
- }
- //지연 해야할 밀린 턴 횟수
- $iter = intdiv($timeMinDiff, $term);
- if ($iter > $threshold) {
- $minute = $iter * $term;
- $newTurntime = $turntime->add(new \DateInterval("PT{$minute}M"));
- $newNextTurntime = $turntime->add(new \DateInterval("PT{$term}M"));
- $gameStor->turntime = $newTurntime->format('Y-m-d H:i:s');
- $gameStor->starttime = (new \DateTimeImmutable($gameStor->starttime))
- ->add(new \DateInterval("PT{$minute}M"))
- ->format('Y-m-d H:i:s');
-
- $db->update('general', [
- 'turntime' => $db->sqleval('DATE_ADD(turntime, INTERVAL %i MINUTE)', $minute)
- ], 'turntime<=DATE_ADD(turntime, INTERVAL %i MINUTE)', $term);
- $db->update('auction', [
- 'expire' => $db->sqleval('DATE_ADD(expire, INTERVAL %i MINUTE)', $minute)
- ], true);
- }
-}
-
-function updateOnline()
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
- $nationname = ["재야"];
-
- //국가별 이름 매핑
- foreach (getAllNationStaticInfo() as $nation) {
- $nationname[$nation['nation']] = $nation['name'];
- }
-
-
- //동접수
- $before5Min = TimeUtil::nowAddMinutes(-5);
- $onlineUser = $db->query('SELECT no,name,nation FROM general WHERE lastrefresh > %s', $before5Min);
- $onlineNum = count($onlineUser);
- $onlineNationUsers = Util::arrayGroupBy($onlineUser, 'nation');
-
- uasort($onlineNationUsers, function(array $lhs, array $rhs){
- return -(count($lhs)<=>count($rhs));
- });
-
- $onlineNation = [];
-
- foreach($onlineNationUsers as $nationID=>$rawOnlineUser){
- $nationName = getNationStaticInfo($nationID)['name'];
- $onlineNation[] = "【{$nationName}】";
- $userList = join(', ', Util::squeezeFromArray($rawOnlineUser, 'name'));
- $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
- $nationStor->online_genenerals = $userList;
- }
-
- //접속중인 국가
- $gameStor->online_user_cnt = $onlineNum;
- $gameStor->online_nation = join(', ', $onlineNation);
-}
-
-function addAge()
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
-
- //나이와 호봉 증가
- $db->update('general', [
- 'age' => $db->sqleval('age+1'),
- 'belong' => $db->sqleval('belong+1')
- ], true);
-
- [$startYear, $year, $month] = $gameStor->getValuesAsArray(['startyear', 'year', 'month']);
-
- if ($year >= $startYear + 3) {
- foreach ($db->query('SELECT no,name,nation,leadership,strength,intel,aux from general where specage<=age and special=%s', GameConst::$defaultSpecialDomestic) as $general) {
- $generalID = $general['no'];
- $special = SpecialityHelper::pickSpecialDomestic(
- $general,
- (Json::decode($general['aux'])['prev_types_special'])??[]
- );
- $specialClass = buildGeneralSpecialDomesticClass($special);
- $specialText = $specialClass->getName();
- $db->update('general', [
- 'special' => $special
- ], 'no=%i', $generalID);
-
- $logger = new ActionLogger($generalID, $general['nation'], $year, $month);
-
- $josaUl = JosaUtil::pick($specialText, '을');
- $logger->pushGeneralActionLog("특기 【{$specialText}>】{$josaUl} 익혔습니다!", ActionLogger::PLAIN);
- $logger->pushGeneralHistoryLog("특기 【{$specialText}>】{$josaUl} 습득");
- }
-
- foreach ($db->query('SELECT no,name,nation,leadership,strength,intel,npc,dex1,dex2,dex3,dex4,dex5,aux from general where specage2<=age and special2=%s', GameConst::$defaultSpecialWar) as $general) {
- $generalID = $general['no'];
- $special2 = SpecialityHelper::pickSpecialWar(
- $general,
- (Json::decode($general['aux'])['prev_types_special2'])??[]
- );
- $specialClass = buildGeneralSpecialWarClass($special2);
- $specialText = $specialClass->getName();
-
- $db->update('general', [
- 'special2' => $special2
- ], 'no=%i', $general['no']);
-
- $logger = new ActionLogger($generalID, $general['nation'], $year, $month);
-
- $josaUl = JosaUtil::pick($specialText, '을');
- $logger->pushGeneralActionLog("특기 【{$specialText}>】{$josaUl} 익혔습니다!", ActionLogger::PLAIN);
- $logger->pushGeneralHistoryLog("특기 【{$specialText}>】{$josaUl} 습득");
- }
- }
-}
-
-function turnDate($curtime)
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
- $admin = $gameStor->getValues(['startyear', 'starttime', 'turnterm', 'year', 'month']);
-
- $turn = $admin['starttime'];
- $curturn = cutTurn($curtime, $admin['turnterm']);
- $term = $admin['turnterm'];
-
- $num = intdiv((strtotime($curturn) - strtotime($turn)), $term * 60);
-
- $date = $admin['startyear'] * 12;
- $date += $num;
-
- $year = intdiv($date, 12);
- $month = 1 + $date % 12;
-
- // 바뀐 경우만 업데이트
- if ($admin['month'] != $month || $admin['year'] != $year) {
- $gameStor->year = $year;
- $gameStor->month = $month;
- }
-
- return [$year, $month];
-}
-
-
-function triggerTournament()
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
- $admin = $gameStor->getValues(['tournament', 'tnmt_trig']);
-
- //현재 토너먼트 없고, 자동개시 걸려있을때, 40%확률
- if ($admin['tournament'] == 0 && $admin['tnmt_trig'] > 0 && rand() % 100 < 40) {
- $type = rand() % 5; // 0 : 전력전, 1 : 통솔전, 2 : 일기토, 3 : 설전
- //전력전 40%, 통, 일, 설 각 20%
- if ($type > 3) {
- $type = 0;
- }
- startTournament($admin['tnmt_trig'], $type);
- }
-}
-
-function CheckHall($no)
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
- $types = [
- ["experience", 'natural'],
- ["dedication", 'natural'],
- ["firenum", 'rank'],
- ["warnum", 'rank'],
- ["killnum", 'rank'],
- ["winrate", 'calc'],
- ["occupied", 'rank'],
- ["killcrew", 'rank'],
- ["killrate", 'calc'],
- ["killcrew_person", 'rank'],
- ["killrate_person", 'calc'],
- ["dex1", 'natural'],
- ["dex2", 'natural'],
- ["dex3", 'natural'],
- ["dex4", 'natural'],
- ["dex5", 'natural'],
- ["ttrate", 'calc'],
- ["tlrate", 'calc'],
- ["tsrate", 'calc'],
- ["tirate", 'calc'],
- ["betgold", 'rank'],
- ["betwin", 'rank'],
- ["betwingold", 'rank'],
- ["betrate", 'calc'],
- ];
-
- $generalObj = General::createGeneralObjFromDB($no, null, 2);
-
- $ttw = $generalObj->getRankVar('ttw');
- $ttd = $generalObj->getRankVar('ttd');
- $ttl = $generalObj->getRankVar('ttl');
-
- $tlw = $generalObj->getRankVar('tlw');
- $tld = $generalObj->getRankVar('tld');
- $tll = $generalObj->getRankVar('tll');
-
- $tsw = $generalObj->getRankVar('tsw');
- $tsd = $generalObj->getRankVar('tsd');
- $tsl = $generalObj->getRankVar('tsl');
-
- $tiw = $generalObj->getRankVar('tiw');
- $tid = $generalObj->getRankVar('tid');
- $til = $generalObj->getRankVar('til');
-
- $betWinGold = $generalObj->getRankVar('betwingold');
- $betGold = Util::valueFit($generalObj->getRankVar('betgold'), 1);
-
- $win = $generalObj->getRankVar('killnum');
- $war = Util::valueFit($generalObj->getRankVar('warnum'), 1);
-
- $kill = $generalObj->getRankVar('killcrew');
- $death = Util::valueFit($generalObj->getRankVar('deathcrew'), 1);
-
- $killPerson = $generalObj->getRankVar('killcrew_person');
- $deathPerson = Util::valueFit($generalObj->getRankVar('deathcrew_person'), 1);
-
- $tt = Util::valueFit($ttw + $ttd + $ttl, 1);
- $tl = Util::valueFit($tlw + $tld + $tll, 1);
- $ts = Util::valueFit($tsw + $tsd + $tsl, 1);
- $ti = Util::valueFit($tiw + $tid + $til, 1);
-
- $calcVar = [];
- $calcVar['ttrate'] = $ttw / $tt;
- $calcVar['tlrate'] = $tlw / $tl;
- $calcVar['tsrate'] = $tsw / $ts;
- $calcVar['tirate'] = $tiw / $ti;
- $calcVar['betrate'] = $betWinGold / $betGold;
- $calcVar['winrate'] = $win / $war;
- $calcVar['killrate'] = $kill / $death;
- $calcVar['killrate_person'] = $killPerson / $deathPerson;
-
- if ($generalObj instanceof DummyGeneral) {
- return;
- }
-
- $unitedDate = TimeUtil::now();
- $nation = $generalObj->getStaticNation();
-
- $serverCnt = $db->queryFirstField('SELECT count(*) FROM ng_games');
-
- [$scenarioIdx, $scenarioName, $startTime] = $gameStor->getValuesAsArray(['scenario', 'scenario_text', 'starttime']);
-
- $ownerName = $generalObj->getVar('owner_name');
- if ($generalObj->getVar('owner')) {
- $ownerName = RootDB::db()->queryFirstField('SELECT name FROM member WHERE no = %i', $generalObj->getVar('owner'));
- }
-
- foreach ($types as [$typeName, $valueType]) {
-
- if ($valueType === 'natural') {
- $value = $generalObj->getVar($typeName);
- } else if ($valueType === 'rank') {
- $value = $generalObj->getRankVar($typeName);
- } else if ($valueType === 'calc') {
- $value = $calcVar[$typeName];
- }
-
- //승률,살상률인데 10회 미만 전투시 스킵
- if (($typeName === 'winrate' || $typeName === 'killrate') && $generalObj->getRankVar('warnum') < 10) {
- continue;
- }
- //토너승률인데 50회 미만시 스킵
- if ($typeName === 'ttrate' && $tt < 50) {
- continue;
- }
- //토너승률인데 50회 미만시 스킵
- if ($typeName === 'tlrate' && $tl < 50) {
- continue;
- }
- //토너승률인데 50회 미만시 스킵
- if ($typeName === 'tsrate' && $ts < 50) {
- continue;
- }
- //토너승률인데 50회 미만시 스킵
- if ($typeName === 'tirate' && $ti < 50) {
- continue;
- }
- //수익률인데 1000미만시 스킵
- if ($typeName === 'betrate' && $generalObj->getRankVar('betgold') < 1000) {
- continue;
- }
-
- if ($value <= 0) {
- continue;
- }
-
- $aux = [
- 'name' => $generalObj->getName(),
- 'nationName' => $nation['name'],
- 'bgColor' => $nation['color'],
- 'fgColor' => newColor($nation['color']),
- 'picture' => $generalObj->getVar('picture'),
- 'imgsvr' => $generalObj->getVar('imgsvr'),
- 'startTime' => $startTime,
- 'unitedTime' => $unitedDate,
- 'ownerName' => $ownerName,
- 'serverID' => UniqueConst::$serverID,
- 'serverIdx' => $serverCnt,
- 'serverName' => UniqueConst::$serverName,
- 'scenarioName' => $scenarioName,
- ];
- $jsonAux = Json::encode($aux);
-
- $db->insertIgnore('hall', [
- 'server_id' => UniqueConst::$serverID,
- 'season' => UniqueConst::$seasonIdx,
- 'scenario' => $scenarioIdx,
- 'general_no' => $no,
- 'type' => $typeName,
- 'value' => $value,
- 'owner' => $generalObj->getVar('owner'),
- 'aux' => $jsonAux
- ]);
-
- if ($db->affectedRows() == 0) {
- $db->update(
- 'hall',
- [
- 'value' => $value,
- 'aux' => $jsonAux
- ],
- 'server_id = %s AND scenario = %i AND general_no = %i AND type = %s AND value < %d',
- UniqueConst::$serverID,
- $scenarioIdx,
- $no,
- $typeName,
- $value
- );
- }
- }
-}
-
-function giveRandomUniqueItem(General $general, string $acquireType): bool
-{
- $db = DB::db();
- //아이템 습득 상황
- $availableUnique = [];
-
- //TODO: 너무 바보 같다. 장기적으로는 유니크 아이템 테이블 같은게 필요하지 않을까?
- //일단은 '획득' 시에만 동작하므로 이대로 사용하기로...
- $occupiedUnique = [];
-
- foreach (array_keys(GameConst::$allItems) as $itemType) {
- foreach ($db->queryAllLists('SELECT %b, count(*) as cnt FROM general GROUP BY %b', $itemType, $itemType) as [$itemCode, $cnt]) {
- $itemClass = buildItemClass($itemCode);
- if (!$itemClass) {
- continue;
- }
- if ($itemClass->isBuyable()) {
- continue;
- }
- $occupiedUnique[$itemCode] = $cnt;
- }
- }
-
- foreach (GameConst::$allItems as $itemType => $itemCategories) {
- foreach ($itemCategories as $itemCode => $cnt) {
- if (!key_exists($itemCode, $occupiedUnique)) {
- $availableUnique[] = [[$itemType, $itemCode], $cnt];
- continue;
- }
-
- $remain = $cnt - $occupiedUnique[$itemCode];
- if ($remain > 0) {
- $availableUnique[] = [[$itemType, $itemCode], $remain];
- }
- }
- }
-
- if (!$availableUnique) {
- return false;
- }
-
- [$itemType, $itemCode] = Util::choiceRandomUsingWeightPair($availableUnique);
-
- $nationName = $general->getStaticNation()['name'];
- $generalName = $general->getName();
- $josaYi = JosaUtil::pick($generalName, '이');
- $itemObj = buildItemClass($itemCode);
- $itemName = $itemObj->getName();
- $itemRawName = $itemObj->getRawName();
- $josaUl = JosaUtil::pick($itemRawName, '을');
-
-
- $general->setVar($itemType, $itemCode);
-
- $logger = $general->getLogger();
-
- $logger->pushGeneralActionLog("{$itemName}>{$josaUl} 습득했습니다!");
- $logger->pushGeneralHistoryLog("{$itemName}>{$josaUl} 습득");
- $logger->pushGlobalActionLog("{$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
- $logger->pushGlobalHistoryLog("【{$acquireType}】>{$nationName}>의 {$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
-
- return true;
-}
-
-function tryUniqueItemLottery(General $general, string $acquireType = '아이템'): bool
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
- if ($general->getNPCType() >= 2) {
- return false;
- }
-
- if ($general->getNPCType() > 6) {
- return false;
- }
-
- foreach ($general->getItems() as $item) {
- if (!$item) {
- continue;
- }
- if (!$item->isBuyable()) {
- return false;
- }
- }
-
- $scenario = $gameStor->scenario;
- $genCount = $db->queryFirstField('SELECT count(*) FROM general WHERE npc<2');
-
- if ($scenario < 100) {
- $prob = 1 / ($genCount * 5); // 5~6개월에 하나씩 등장
- } else {
- $prob = 1 / $genCount; // 1~2개월에 하나씩 등장
- }
-
- if ($acquireType == '설문조사') {
- $prob = 1 / ($genCount * 0.7 / 3); // 투표율 70%, 설문조사 한번에 2~3개 등장
- } else if ($acquireType == '랜덤 임관') {
- $prob = 1 / ($genCount / 10 / 2); // 랜임시 2개(10%) 등장(200명중 20명 랜임시도?)
- } else if ($acquireType == '건국') {
- $prob = 1 / ($genCount / 10 / 4); // 건국시 4개(20%) 등장(200명시 20국 정도 됨)
- }
-
- $prob = Util::valueFit($prob, null, 1 / 3);
-
- if (!Util::randBool($prob)) {
- return false;
- }
-
- return giveRandomUniqueItem($general, $acquireType);
-}
-
-function getAdmin()
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
- return $gameStor->getAll();
-}
-
-/** @return General[] */
-function deleteNation(General $lord, bool $applyDB):array
-{
- $lordID = $lord->getID();
- $nationID = $lord->getNationID();
-
- DeleteConflict($nationID);
-
- $db = DB::db();
- $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
-
- $nation = $db->queryFirstRow('SELECT * FROM nation WHERE nation=%i', $nationID);
- $nationName = $nation['name'];
-
- $logger = $lord->getLogger();
-
- $josaUn = JosaUtil::pick($nationName, '은');
- $logger->pushGlobalHistoryLog("【멸망】>{$nationName}>{$josaUn} 멸망>했습니다.");
-
-
- $nationGeneralList = General::createGeneralObjListFromDB(
- $db->queryFirstColumn(
- 'SELECT `no` FROM general WHERE nation=%i AND no != %i',
- $nationID,
- $lordID
- ),
- ['npc', 'gold', 'rice', 'experience', 'explevel', 'dedication', 'dedlevel', 'aux'], 1
- );
- $nationGeneralList[$lordID] = $lord;
-
- $nation['generals'] = array_keys($nationGeneralList);
- $nation['aux'] = Json::decode($nation['aux']);
- $nation['msg'] = $nationStor->notice;
- $nation['scout_msg'] = $nationStor->scout_msg;
- $nation['aux'] += $nationStor->max_power;
- $nation['history'] = getNationHistoryLogAll($nationID);
-
- $josaYi = JosaUtil::pick($nationName, '이');
- $destroyLog = "{$nationName}>{$josaYi} 멸망>했습니다.";
- $destroyHistoryLog = "{$nationName}>{$josaYi} 멸망>";
-
- // 전 장수 재야로
- foreach($nationGeneralList as $general){
- $general->setVar('belong', 0);
- $general->setVar('troop', 0);
- $general->setVar('officer_level', 0);
- $general->setVar('officer_city', 0);
- $general->setVar('nation', 0);
- $general->setVar('permission', 'normal');
- $logger = $general->getLogger();
- $logger->pushGeneralActionLog($destroyLog, ActionLogger::PLAIN);
- $logger->pushGeneralHistoryLog($destroyHistoryLog);
-
- if($applyDB){
- $general->applyDB($db);
- }
- }
-
- // 도시 공백지로
- $db->update('city', [
- 'nation' => 0,
- 'front' => 0,
- ], 'nation=%i', $nationID);
- // 부대 삭제
- $db->delete('troop', 'nation=%i', $nationID);
-
- // 국가 삭제
- $db->insert('ng_old_nations', [
- 'server_id' => UniqueConst::$serverID,
- 'nation' => $nationID,
- 'data' => Json::encode($nation)
- ]);
- $db->delete('nation', 'nation=%i', $nationID);
- $db->delete('nation_turn', 'nation_id=%i', $nationID);
- // 외교 삭제
- $db->delete('diplomacy', 'me = %i OR you = %i', $nationID, $nationID);
-
- refreshNationStaticInfo();
-
- $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
- $nationStor->resetValues();
-
- return $nationGeneralList;
-}
-
-function nextRuler(General $general)
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
- [$year, $month] = $gameStor->getValuesAsArray(['year', 'month']);
- $nation = $general->getStaticNation();
- $nationName = $nation['name'];
- $nationID = $nation['nation'];
-
- $candidate = null;
-
- //npc or npc유저인 경우 후계 찾기
- if ($general->getNPCType() > 0) {
- $candidate = $db->queryFirstRow(
- 'SELECT no,name,officer_level,IF(ABS(affinity-%i)>75,150-ABS(affinity-%i),ABS(affinity-%i)) as npcmatch2 from general where nation=%i and officer_level!=12 and 1 <= npc and npc<=3 order by npcmatch2,rand() LIMIT 1',
- $general->getVar('affinity'),
- $general->getVar('affinity'),
- $general->getVar('affinity'),
- $nationID
- );
- }
- if (!$candidate) {
- $candidate = $db->queryFirstRow(
- 'SELECT no,name,npc,officer_level FROM general WHERE nation=%i and officer_level!= 12 AND officer_level >= 9 and npc != 5 ORDER BY officer_level DESC LIMIT 1',
- $nationID
- );
- }
- if (!$candidate) {
- $candidate = $db->queryFirstRow(
- 'SELECT no,name,npc,officer_level FROM general WHERE nation=%i and officer_level!= 12 and npc != 5 ORDER BY dedication DESC LIMIT 1',
- $nationID
- );
- }
-
-
- if (!$candidate) {
- deleteNation($general, true);
- return;
- }
-
- $nextRulerID = $candidate['no'];
- $nextRulerName = $candidate['name'];
-
- $general->setVar('officer_level', 1);
- $general->setVar('officer_city', 0);
-
- $db->update('general', [
- 'officer_level' => 12,
- 'officer_city' => 0,
- ], 'no=%i AND nation=%i', $nextRulerID, $nationID);
- if ($db->affectedRows() == 0) {
- throw new \RuntimeException('선양되지 않음');
- }
-
- $josaYi = JosaUtil::pick($nextRulerName, '이');
-
- $nextRulerLogger = new ActionLogger($nextRulerID, $nationID, $year, $month);
- $nextRulerLogger->pushGlobalHistoryLog("【유지】>{$nextRulerName}>{$josaYi} {$nationName}>의 유지를 이어 받았습니다");
- $nextRulerLogger->pushGeneralHistoryLog("【유지】>{$nextRulerName}>{$josaYi} {$nationName}>의 유지를 이어 받음.");
- $nextRulerLogger->flush();
- // 장수 삭제 및 부대처리는 checkTurn에서
-}
-
-/**
- * $maxDist 이내의 도시를 검색하는 함수
- * @param $from 기준 도시 코드
- * @param $maxDist 검색하고자 하는 최대 거리
- * @param $distForm 리턴 타입. true일 경우 $result[$dist] = [...$city] 이며, false일 경우 $result[$city] = $dist 임
- */
-function searchDistance(int $from, int $maxDist = 99, bool $distForm = false)
-{
- $queue = new \SplQueue();
-
- $cities = [];
- $distanceList = [];
-
- $queue->enqueue([$from, 0]);
-
- while (!$queue->isEmpty()) {
- list($cityID, $dist) = $queue->dequeue();
- if (key_exists($cityID, $cities)) {
- continue;
- }
-
- if (!key_exists($dist, $distanceList)) {
- $distanceList[$dist] = [];
- }
- $distanceList[$dist][] = $cityID;
-
- $cities[$cityID] = $dist;
- if ($dist >= $maxDist) {
- continue;
- }
-
- foreach (array_keys(CityConst::byID($cityID)->path) as $connCityID) {
- if (key_exists($connCityID, $cities)) {
- continue;
- }
- $queue->enqueue([$connCityID, $dist + 1]);
- }
- }
-
- if ($distForm) {
- unset($distanceList[0]);
- return $distanceList;
- } else {
- return $cities;
- }
-}
-
-/**
- * $from 으로 지정한 도시부터 $to 도시까지의 최단 거리를 계산해 줌
- * @param $from 기준 도시 코드
- * @param $to 대상 도시 코드
- * @param array|null $linkNationList 경로에 해당하는 국가 리스트, null인 경우 제한 없음
- * @return null|int 거리.
- */
-function calcCityDistance(int $from, int $to, ?array $linkNationList): ?int
-{
- $queue = new \SplQueue();
-
- $cities = [];
-
- if ($linkNationList === []) {
- return null;
- }
-
- if ($linkNationList !== null) {
- $db = DB::db();
- //TODO: 도시-국가 캐싱이 있으면 쓸모 있지 않을까
- $allowedCityList = [];
- foreach ($db->queryFirstColumn(
- 'SELECT city FROM city WHERE nation IN %li',
- $linkNationList
- ) as $cityID) {
- $allowedCityList[$cityID] = $cityID;
- }
- } else {
- $allowedCityList = CityConst::all();
- }
-
- if (!key_exists($to, $allowedCityList)) {
- return null;
- }
-
- if ($from === $to) {
- return 0;
- }
-
- $queue->enqueue([$from, 0]);
-
- while (!$queue->isEmpty()) {
- list($cityID, $dist) = $queue->dequeue();
- if (key_exists($cityID, $cities)) {
- continue;
- }
-
- $cities[$cityID] = $dist;
-
- if ($cityID === $to) {
- return $dist;
- }
-
- foreach (array_keys(CityConst::byID($cityID)->path) as $connCityID) {
- if (!key_exists($connCityID, $allowedCityList)) {
- continue;
- }
- if (key_exists($connCityID, $cities)) {
- continue;
- }
- $queue->enqueue([$connCityID, $dist + 1]);
- }
- }
-
- //길이 없음
- return null;
-}
-/**
- * $from 으로 지정한 도시의 인접 도시와 $to 도시의 최단 거리를 계산해 줌
- * @param $from 기준 도시 코드
- * @param $to 대상 도시 코드
- * @param $linkNationList 경로에 해당하는 국가 리스트
- * @return array $dist=>[cityID, $nation] 배열 가장 앞이 가장 가까움
- */
-function searchDistanceListToDest(int $from, int $to, array $linkNationList)
-{
- $queue = new \SplQueue();
-
- $cities = [];
-
- $db = DB::db();
-
- //TODO: 도시-국가 캐싱이 있으면 쓸모 있지 않을까
- $allowedCityList = [];
- foreach ($db->queryAllLists(
- 'SELECT city, nation FROM city WHERE nation IN %li',
- $linkNationList
- ) as [$cityID, $nationID]) {
- $allowedCityList[$cityID] = $nationID;
- }
-
- $remainFromCities = [];
- foreach (array_keys(CityConst::byID($from)->path) as $fromCityID) {
- if (key_exists($fromCityID, $allowedCityList)) {
- $remainFromCities[$fromCityID] = true;
- }
- }
-
- if (!key_exists($to, $allowedCityList)) {
- return [];
- }
-
- $result = [];
- $queue->enqueue([$to, 0]);
-
- while (!empty($remainFromCities) && !$queue->isEmpty()) {
- list($cityID, $dist) = $queue->dequeue();
- if (key_exists($cityID, $cities)) {
- continue;
- }
-
- $cities[$cityID] = $dist;
-
- if (key_exists($cityID, $remainFromCities)) {
- unset($remainFromCities[$cityID]);
- $result[$dist][] = [$cityID, $allowedCityList[$cityID]];
- }
-
- foreach (array_keys(CityConst::byID($cityID)->path) as $connCityID) {
- if ($allowedCityList !== null && !key_exists($connCityID, $allowedCityList)) {
- continue;
- }
- if (key_exists($connCityID, $cities)) {
- continue;
- }
- $queue->enqueue([$connCityID, $dist + 1]);
- }
- }
-
- return $result;
-}
-
-/**
- * 지정된 도시 내의 최단 거리를 계산해 줌(Floyd-Warshall)
- * @param $cityIDList 이동 가능한 도시 리스트.
- * @return array [from][to] 로 표시되는 거리값
- */
-function searchAllDistanceByCityList(array $cityIDList): array
-{
- if (!$cityIDList) {
- return [];
- }
- $cityList = [];
- foreach ($cityIDList as $cityID) {
- $cityList[$cityID] = $cityID;
- }
-
- $distanceList = [];
- foreach ($cityIDList as $cityID) {
- $nearList = [$cityID => 0];
- foreach (array_keys(CityConst::byID($cityID)->path) as $nextCityID) {
- if (!key_exists($nextCityID, $cityList)) {
- continue;
- }
- $nearList[$nextCityID] = 1;
- }
- $distanceList[$cityID] = $nearList;
- }
-
- //Floyd-Warshall
- foreach ($cityList as $cityStop) {
- foreach ($cityList as $cityFrom) {
- foreach ($cityList as $cityTo) {
- if (!key_exists($cityStop, $distanceList[$cityFrom])) {
- continue;
- }
- if (!key_exists($cityTo, $distanceList[$cityStop])) {
- continue;
- }
-
- if (!key_exists($cityTo, $distanceList[$cityFrom])) {
- $distanceList[$cityFrom][$cityTo] = $distanceList[$cityFrom][$cityStop] + $distanceList[$cityStop][$cityTo];
- continue;
- }
-
- $distanceList[$cityFrom][$cityTo] = min($distanceList[$cityFrom][$cityStop] + $distanceList[$cityStop][$cityTo], $distanceList[$cityFrom][$cityTo]);
- }
- }
- }
-
- return $distanceList;
-}
-
-/**
- * 지정된 국가 내의 모든 도시들의 최단 거리를 계산해 줌(Floyd-Warshall)
- * @param $linkNationList 경로에 해당하는 국가 리스트
- * @param $suppliedCityOnly 보급된 도시만을 이용해 검색
- * @return array [from][to] 로 표시되는 거리값
- */
-function searchAllDistanceByNationList(array $linkNationList, bool $suppliedCityOnly = false): array
-{
- if (!$linkNationList) {
- return [];
- }
- $db = DB::db();
- if ($suppliedCityOnly) {
- $cityIDList = $db->queryFirstColumn('SELECT city FROM city WHERE nation IN %li AND supply=1', $linkNationList);
- } else {
- $cityIDList = $db->queryFirstColumn('SELECT city FROM city WHERE nation IN %li', $linkNationList);
- }
- return searchAllDistanceByCityList($cityIDList);
-}
-
-function isNeighbor(int $nation1, int $nation2, bool $includeNoSupply = true)
-{
- if ($nation1 === $nation2) {
- return false;
- }
- $db = DB::db();
-
- $nation1Cities = [];
-
- if ($includeNoSupply) {
- $supplySql = '';
- } else {
- $supplySql = 'AND supply = 1';
- }
-
- foreach ($db->queryFirstColumn('SELECT city FROM city WHERE nation = %i %l', $nation1, $supplySql) as $city) {
- $nation1Cities[$city] = $city;
- }
-
- foreach ($db->queryFirstColumn('SELECT city FROM city WHERE nation = %i %l', $nation2, $supplySql) as $city) {
- foreach (array_keys(CityConst::byID($city)->path) as $adjCity) {
- if (key_exists($adjCity, $nation1Cities)) {
- return true;
- }
- }
- }
-
- return false;
-}
-
-function SabotageInjury(array $cityGeneralList, string $reason): int
-{
- $injuryCount = 0;
- $josaRo = JosaUtil::pick($reason, '로');
- $text = "{$reason}>{$josaRo} 인해 부상>을 당했습니다.";
-
- $db = DB::db();
-
- foreach ($cityGeneralList as $general) {
- /** @var General $general */
- if (!Util::randBool(0.3)) {
- continue;
- }
- $general->getLogger()->pushGeneralActionLog($text);
-
- $general->increaseVarWithLimit('injury', Util::randRangeInt(1, 16), 0, 80);
- $general->multiplyVar('crew', 0.98);
- $general->multiplyVar('atmos', 0.98);
- $general->multiplyVar('train', 0.98);
-
- $general->applyDB($db);
-
- $injuryCount += 1;
- }
-
- return $injuryCount;
-}
-
-function getRandTurn($term, ?\DateTimeInterface $baseDateTime = null)
-{
- if ($baseDateTime === null) {
- $baseDateTime = new \DateTimeImmutable();
- } else if ($baseDateTime instanceof \DateTime) {
- $baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime);
- } else if ($baseDateTime instanceof \DateTimeImmutable) {
- //do Nothing
- } else {
- throw new MustNotBeReachedException();
- }
-
- $randSecond = Util::randRangeInt(0, 60 * $term - 1);
- $randFraction = Util::randRangeInt(0, 999999) / 1000000; //6자리 소수
-
- return $baseDateTime->add(TimeUtil::secondsToDateInterval($randSecond + $randFraction))->format('Y-m-d H:i:s');
-}
-
-function getRandTurn2($term, ?\DateTimeInterface $baseDateTime = null)
-{
- if ($baseDateTime === null) {
- $baseDateTime = new \DateTimeImmutable();
- } else if ($baseDateTime instanceof \DateTime) {
- $baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime);
- } else {
- throw new MustNotBeReachedException();
- }
- $randSecond = Util::randRangeInt(0, 60 * $term - 1);
- $randFraction = Util::randRangeInt(0, 999999) / 1000000; //6자리 소수
-
- return $baseDateTime->sub(TimeUtil::secondsToDateInterval($randSecond + $randFraction))->format('Y-m-d H:i:s');
-}
+ 0,
+ 'name' => '재야',
+ 'color' => '#000000',
+ 'type' => GameConst::$neutralNationType,
+ 'level' => 0,
+ 'capital' => 0,
+ 'gold' => 0,
+ 'rice' => 2000,
+ 'tech' => 0,
+ 'gennum' => 1,
+ 'power' => 1
+ ];
+ }
+
+ if ($nationList === null) {
+ $nationAll = DB::db()->query("select nation, name, color, type, level, capital, gennum, power from nation");
+ $nationList = Util::convertArrayToDict($nationAll, "nation");
+ }
+
+ if ($nationID === -1) {
+ return $nationList;
+ }
+
+ if (isset($nationList[$nationID])) {
+ return $nationList[$nationID];
+ }
+ return null;
+}
+
+/**
+ * getNationStaticInfo() 함수의 국가 캐시를 초기화
+ */
+function refreshNationStaticInfo()
+{
+ getNationStaticInfo(null, true);
+}
+
+/**
+ * getNationStaticInfo(-1) 의 단축형
+ */
+function getAllNationStaticInfo()
+{
+ return getNationStaticInfo(-1);
+}
+
+function GetImageURL($imgsvr, $filepath = '')
+{
+ if ($imgsvr == 0) {
+ return ServConfig::getSharedIconPath($filepath);
+ } else {
+ return AppConf::getUserIconPathWeb($filepath);
+ }
+}
+
+/**
+ * @param null|int $con 장수의 벌점
+ * @param null|int $conlimit 최대 벌점
+ */
+function checkLimit($con = null)
+{
+ $session = Session::getInstance();
+ if ($session->userGrade >= 4) {
+ return 0;
+ }
+
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ if ($con === null) {
+ $con = $db->queryFirstField('SELECT con FROM general WHERE `owner`=%i', Session::getUserID());
+ }
+ $conlimit = $gameStor->conlimit;
+
+ if ($con > $conlimit) {
+ return 2;
+ //접속제한 90%이면 경고문구
+ } elseif ($con > $conlimit * 0.9) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+function getBlockLevel()
+{
+ return DB::db()->queryFirstField('select block from general where no = %i', Session::getInstance()->generalID);
+}
+
+function getRandGenName()
+{
+ $firstname = Util::choiceRandom(GameConst::$randGenFirstName);
+ $middlename = Util::choiceRandom(GameConst::$randGenMiddleName);
+ $lastname = Util::choiceRandom(GameConst::$randGenLastName);
+
+ return "{$firstname}{$middlename}{$lastname}";
+}
+
+
+
+function cityInfo(General $generalObj)
+{
+ $db = DB::db();
+
+ // 도시 정보
+ $city = $generalObj->getRawCity();
+ $cityID = $city['city'];
+
+ $nation = getNationStaticInfo($city['nation']);
+
+ if (!$nation) {
+ $nation = getNationStaticInfo(0);
+ }
+
+ $city['nationName'] = $nation['name'];
+ $city['nationTextColor'] = newColor($nation['color']);
+ $city['nationColor'] = $nation['color'];
+ $city['region'] = CityConst::$regionMap[$city['region']];
+ $city['levelText'] = CityConst::$levelMap[$city['level']];
+
+ $officerName = [
+ 2 => '-',
+ 3 => '-',
+ 4 => '-'
+ ];
+
+ foreach ($db->query('SELECT `officer_level`, `name`, npc, no FROM general WHERE officer_city = %i', $cityID) as $officer) {
+ $officerName[$officer['officer_level']] = getColoredName($officer['name'], $officer['npc']);
+ }
+
+ $city['officerName'] = $officerName;
+
+ $templates = new \League\Plates\Engine('templates');
+ $templates->registerFunction('bar', '\sammo\bar');
+ return $templates->render('mainCityInfo', $city);
+}
+
+function myNationInfo(General $generalObj)
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ $admin = $gameStor->getValues(['startyear', 'year', 'month']);
+ $templates = new \League\Plates\Engine(__DIR__ . '/templates');
+
+ $nationID = $generalObj->getNationID();
+ $nation = $db->queryFirstRow('SELECT * FROM nation WHERE nation = %i', $nationID)??getNationStaticInfo(0);
+ $city = $db->queryFirstRow(
+ 'SELECT COUNT(*) as cnt, SUM(pop) as totpop, SUM(pop_max) as maxpop from city where nation=%i',
+ $nationID
+ );
+ $general = $db->queryFirstRow('SELECT COUNT(*) as cnt, SUM(crew) as totcrew,SUM(leadership)*100 as maxcrew from general where nation=%i', $nationID);
+
+ $topChiefs = Util::convertArrayToDict($db->query('SELECT officer_level, no, name, npc FROM general WHERE nation = %i AND officer_level >= 11', $nationID), 'officer_level');
+
+ $level12Name = key_exists(12, $topChiefs)?getColoredName($topChiefs[12]['name'], $topChiefs[12]['npc']):'-';
+ $level11Name = key_exists(11, $topChiefs)?getColoredName($topChiefs[11]['name'], $topChiefs[11]['npc']):'-';
+
+ $impossibleStrategicCommandLists = [];
+ $strategicCommandLists = GameConst::$availableChiefCommand['전략'];
+ $yearMonth = Util::joinYearMonth($admin['year'], $admin['month']);
+ foreach($strategicCommandLists as $command){
+ $cmd = buildNationCommandClass($command, $generalObj, $admin, new LastTurn());
+ $nextAvailableTurn = $cmd->getNextAvailableTurn();
+ if($nextAvailableTurn > $yearMonth){
+ $impossibleStrategicCommandLists[] = [$cmd->getName(), $nextAvailableTurn - $yearMonth];
+ }
+ }
+
+ echo "
+
+ | 【재 야】";
+ } else {
+ echo "style='color:" . newColor($nation['color']) . ";background-color:{$nation['color']};font-weight:bold;font-size:13px;text-align:center'>국가【 {$nation['name']} 】";
+ }
+
+ echo "
+ |
+
+
+ | 성 향 |
+ " . getNationType($nation['type']) . " (" . getNationType2($nation['type']) . ") |
+
+
+
+ | " . getOfficerLevelText(12, $nation['level']) . " |
+ {$level12Name} |
+ " . getOfficerLevelText(11, $nation['level']) . " |
+ {$level11Name} |
+
+
+ | 총주민 |
+ ";
+ echo $nationID===0 ? "해당 없음" : "{$city['totpop']}/{$city['maxpop']}";
+ echo " |
+ 총병사 |
+ ";
+ echo $nationID===0 ? "해당 없음" : "{$general['totcrew']}/{$general['maxcrew']}";
+ echo " |
+
+
+
+ | 국 고 |
+ ";
+ echo $nationID===0 ? "해당 없음" : "{$nation['gold']}";
+ echo " |
+ 병 량 |
+ ";
+ echo $nationID===0 ? "해당 없음" : "{$nation['rice']}";
+ echo " |
+
+
+ | 지급률 |
+ ";
+ if ($nationID===0) {
+ echo "해당 없음";
+ } else {
+ echo $nation['bill'] == 0 ? "0 %" : "{$nation['bill']} %";
+ }
+ echo "
+ |
+ 세 율 |
+ ";
+ if ($nationID===0) {
+ echo "해당 없음";
+ } else {
+ echo $nation['rate'] == 0 ? "0 %" : "{$nation['rate']} %";
+ }
+
+ $techCall = getTechCall($nation['tech']);
+
+ if (TechLimit($admin['startyear'], $admin['year'], $nation['tech'])) {
+ $nation['tech'] = "" . floor($nation['tech']) . "";
+ } else {
+ $nation['tech'] = "" . floor($nation['tech']) . "";
+ }
+
+ $nation['tech'] = "$techCall / {$nation['tech']}";
+
+ if ($nationID===0) {
+ $nation['strategic_cmd_limit'] = "해당 없음";
+ $nation['surlimit'] = "해당 없음";
+ $nation['scout'] = "해당 없음";
+ $nation['war'] = "해당 없음";
+ $nation['power'] = "해당 없음";
+ } else {
+ if ($nation['strategic_cmd_limit'] != 0) {
+ $nation['strategic_cmd_limit'] = "{$nation['strategic_cmd_limit']}턴";
+ } else if($impossibleStrategicCommandLists) {
+ $nation['strategic_cmd_limit'] = "가 능";
+ } else{
+ $nation['strategic_cmd_limit'] = "가 능";
+ }
+
+ if($impossibleStrategicCommandLists){
+ $text = [];
+ foreach($impossibleStrategicCommandLists as [$cmdName, $remainTurn]){
+ $text[] = "{$cmdName}: {$remainTurn}턴 뒤";
+ }
+ $nation['strategic_cmd_limit'] = $templates->render('tooltip', [
+ 'text'=>$nation['strategic_cmd_limit'],
+ 'info'=>''.join(' ', $text).'',
+ ]);
+ }
+
+ if ($nation['surlimit'] != 0) {
+ $nation['surlimit'] = "{$nation['surlimit']}턴";
+ } else {
+ $nation['surlimit'] = "가 능";
+ }
+
+ if ($nation['scout'] != 0) {
+ $nation['scout'] = "금 지";
+ } else {
+ $nation['scout'] = "허 가";
+ }
+
+ if ($nation['war'] != 0) {
+ $nation['war'] = "금 지";
+ } else {
+ $nation['war'] = "허 가";
+ }
+ }
+
+ echo "
+ |
+
+
+ | 속 령 |
+ ";
+ echo $nationID===0 ? "-" : "{$city['cnt']}";
+ echo " |
+ 장 수 |
+ ";
+ echo $nationID===0 ? "-" : "{$general['cnt']}";
+ echo " |
+
+
+ | 국 력 |
+ {$nation['power']} |
+ 기술력 |
+ ";
+ echo $nationID===0 ? "-" : "{$nation['tech']}";
+ echo " |
+
+
+ | 전 략 |
+ {$nation['strategic_cmd_limit']} |
+ 외 교 |
+ {$nation['surlimit']} |
+
+
+ | 임 관 |
+ {$nation['scout']} |
+ 전 쟁 |
+ {$nation['war']} |
+
+
+";
+}
+
+function checkSecretMaxPermission($penalty)
+{
+ $secretMax = 4;
+ if ($penalty['noTopSecret'] ?? false) {
+ $secretMax = 1;
+ } else if ($penalty['noChief'] ?? false) {
+ $secretMax = 1;
+ } else if ($penalty['noAmbassador'] ?? false) {
+ $secretMax = 2;
+ }
+ return $secretMax;
+}
+
+function checkSecretPermission(array $me, $checkSecretLimit = true)
+{
+ if (!key_exists('penalty', $me) || !key_exists('permission', $me)) {
+ trigger_error('canAccessSecret() 함수에 필요한 인자가 부족');
+ }
+ $penalty = Json::decode($me['penalty']) ?? [];
+ $permission = $me['permission'];
+
+ if (!$me['nation']) {
+ return -1;
+ }
+
+ if ($me['officer_level'] == 0) {
+ return -1;
+ }
+
+
+ if ($penalty['noSecret'] ?? false) {
+ return 0;
+ }
+
+ $secretMin = 0;
+ $secretMax = checkSecretMaxPermission($me, $penalty);
+
+
+ if ($me['officer_level'] == 12) {
+ $secretMin = 4;
+ } else if ($me['permission'] == 'ambassador') {
+ $secretMin = 4;
+ } else if ($me['permission'] == 'auditor') {
+ $secretMin = 3;
+ } else if ($me['officer_level'] >= 5) {
+ $secretMin = 2;
+ } else if ($me['officer_level'] > 1) {
+ $secretMin = 1;
+ } else if ($checkSecretLimit) {
+ $db = DB::db();
+ $secretLimit = $db->queryFirstField('SELECT secretlimit FROM nation WHERE nation = %i', $me['nation']);
+ if ($me['belong'] >= $secretLimit) {
+ $secretMin = 1;
+ }
+ }
+
+ return min($secretMin, $secretMax);
+}
+
+function addCommand($typename, $value, $valid = 1, $color = 0)
+{
+ if ($valid == 1) {
+ switch ($color) {
+ case 0:
+ echo "
+ ";
+ break;
+ case 1:
+ echo "
+ ";
+ break;
+ case 2:
+ echo "
+ ";
+ break;
+ }
+ } else {
+ echo "
+ ";
+ }
+}
+
+function commandGroup($typename, $type = 0)
+{
+ if ($type == 0) {
+ echo "
+ ";
+ }
+}
+
+function printCommandTable(General $generalObj)
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ $userID = Session::getUserID();
+
+ $gameStor->turnOnCache();
+ $env = $gameStor->getAll();
+
+?>
+
+turnOnCache();
+ $env = $gameStor->getAll();
+
+?>
+
+show_img_level;
+
+
+ $nation = getNationStaticInfo($generalObj->getNationID());
+
+ $lbonus = calcLeadershipBonus($generalObj->getVar('officer_level'), $nation['level']);
+ if ($lbonus > 0) {
+ $lbonus = "+{$lbonus}";
+ } else {
+ $lbonus = "";
+ }
+
+ if ($generalObj->getVar('troop') == 0) {
+ $troopInfo = '-';
+ } else {
+ $troopCity = $db->queryFirstField('SELECT city FROM general WHERE no=%i', $generalObj->getVar('troop'));
+ $troopTurn = $db->queryFirstField('SELECT `brief` FROM general_turn WHERE general_id = %i AND turn_idx = 0', $generalObj->getVar('troop'));
+ $troopInfo = $db->queryFirstField('SELECT name FROM troop WHERE troop_leader = %i', $generalObj->getVar('troop'));
+
+ if ($troopTurn !== '집합') {
+ $troopInfo = "{$troopInfo}";
+ } else if ($troopCity != $generalObj->getCityID()) {
+ $troopCityName = CityConst::byID($troopCity)->name;
+ $troopInfo = "{$troopInfo}({$troopCityName})";
+ }
+ }
+
+ $officerLevel = $generalObj->getVar('officer_level');
+ $officerLevelText = getOfficerLevelText($officerLevel, $nation['level']);
+
+ if (2 <= $officerLevel && $officerLevel <= 4) {
+ $cityOfficerName = CityConst::byID($generalObj->getVar('officer_city'))->name;
+ $officerLevelText = "{$cityOfficerName} {$officerLevelText}";
+ }
+
+ $call = getCall(...$generalObj->getVars('leadership', 'strength', 'intel'));
+ $crewTypeInfo = displayiActionObjInfo($generalObj->getCrewTypeObj());
+ $weaponInfo = displayiActionObjInfo($generalObj->getItem('weapon'));
+ $bookInfo = displayiActionObjInfo($generalObj->getItem('book'));
+ $horseInfo = displayiActionObjInfo($generalObj->getItem('horse'));
+ $itemInfo = displayiActionObjInfo($generalObj->getItem('item'));
+
+ $leadership = $generalObj->getLeadership(true, false, false);
+ $strength = $generalObj->getStrength(true, false, false);
+ $intel = $generalObj->getIntel(true, false, false);
+
+
+ $injury = $generalObj->getVar('injury');
+ if ($injury > 60) {
+ $color = "";
+ $injury = "위독";
+ } elseif ($injury > 40) {
+ $color = "";
+ $injury = "심각";
+ } elseif ($injury > 20) {
+ $color = "";
+ $injury = "중상";
+ } elseif ($injury > 0) {
+ $color = "";
+ $injury = "경상";
+ } else {
+ $color = "";
+ $injury = "건강";
+ }
+
+ $remaining = (new \DateTimeImmutable($generalObj->getTurnTime()))->diff(new \DateTimeImmutable())->i;
+
+ if ($nation['color'] == "") {
+ $nation['color'] = "#000000";
+ }
+
+ $age = $generalObj->getVar('age');
+ if ($age < GameConst::$retirementYear * 0.75) {
+ $age = "{$age} 세";
+ } elseif ($age < GameConst::$retirementYear) {
+ $age = "{$age} 세";
+ } else {
+ $age = "{$age} 세";
+ }
+
+ $connectCnt = round($generalObj->getVar('connect'), -1);
+ $specialDomestic = $generalObj->getVar('special') === GameConst::$defaultSpecialDomestic
+ ? "{$generalObj->getVar('specage')}세"
+ : "" . displayiActionObjInfo($generalObj->getSpecialDomestic()) . "";
+ $specialWar = $generalObj->getVar('special2') === GameConst::$defaultSpecialDomestic
+ ? "{$generalObj->getVar('specage2')}세"
+ : "" . displayiActionObjInfo($generalObj->getSpecialWar()) . "";
+
+ $atmos = $generalObj->getVar('atmos');
+ $atmosBonus = $generalObj->onCalcStat($generalObj, 'bonusAtmos', $atmos) - $atmos;
+ if ($atmosBonus > 0) {
+ $atmos = "{$atmos} (+{$atmosBonus})";
+ } else if ($atmosBonus < 0) {
+ $atmos = "{$atmos} ({$atmosBonus})";
+ } else {
+ $atmos = "$atmos";
+ }
+
+ $train = $generalObj->getVar('train');
+ $trainBonus = $generalObj->onCalcStat($generalObj, 'bonusTrain', $train) - $train;
+ if ($trainBonus > 0) {
+ $train = "{$train} (+{$trainBonus})";
+ } else if ($trainBonus < 0) {
+ $train = "{$train} ({$trainBonus})";
+ } else {
+ $train = "$train";
+ }
+
+ if ($generalObj->getVar('defence_train') === 999) {
+ $defenceTrain = "수비 안함";
+ } else {
+ $defenceTrain = "수비 함(훈사{$generalObj->getVar('defence_train')})";
+ }
+
+ $crewType = $generalObj->getCrewTypeObj();
+
+ $weapImage = ServConfig::$gameImagePath . "/crewtype{$crewType->id}.png";
+ if ($show_img_level < 2) {
+ $weapImage = ServConfig::$sharedIconPath . "/default.jpg";
+ };
+ $imagePath = GetImageURL(...$generalObj->getVars('imgsvr', 'picture'));
+ echo "
+
+ | |
+ {$generalObj->getName()} 【 {$officerLevelText} | {$call} | {$color}{$injury} 】 " . $generalObj->getTurnTime($generalObj::TURNTIME_HMS) . " |
+
+
+ | 통솔 |
+ {$color}{$leadership}{$lbonus} |
+ " . bar(expStatus($generalObj->getVar('leadership_exp')), 20) . " |
+ 무력 |
+ {$color}{$strength} |
+ " . bar(expStatus($generalObj->getVar('strength_exp')), 20) . " |
+ 지력 |
+ {$color}{$intel} |
+ " . bar(expStatus($generalObj->getVar('intel_exp')), 20) . " |
+
+
+ | 명마 |
+ $horseInfo |
+ 무기 |
+ $weaponInfo |
+ 서적 |
+ $bookInfo |
+
+
+ |
+ 자금 |
+ {$generalObj->getVar('gold')} |
+ 군량 |
+ {$generalObj->getVar('rice')} |
+ 도구 |
+ $itemInfo |
+
+
+ | 병종 |
+ {$crewTypeInfo} |
+ 병사 |
+ {$generalObj->getVar('crew')} |
+ 성격 |
+ " . displayiActionObjInfo($generalObj->getPersonality()) . " |
+
+
+ | 훈련 |
+ $train |
+ 사기 |
+ $atmos |
+ 특기 |
+ $specialDomestic / $specialWar |
+
+
+ | Lv |
+ {$generalObj->getVar('explevel')} |
+ " . bar(getLevelPer(...$generalObj->getVars('experience', 'explevel')), 20) . " |
+ 연령 |
+ {$age} |
+
+
+ | 수비 |
+ {$defenceTrain} |
+ 삭턴 |
+ {$generalObj->getVar('killturn')} 턴 |
+ 실행 |
+ $remaining 분 남음 |
+
+
+ | 부대 |
+ {$troopInfo} |
+ 벌점 |
+ " . getConnect($connectCnt) . " {$connectCnt}({$generalObj->getVar('con')}) |
+
+
";
+}
+
+function generalInfo2(General $generalObj)
+{
+ $general = $generalObj->getRaw();
+
+ $winRate = round($generalObj->getRankVar('killnum') / max($generalObj->getRankVar('warnum'), 1) * 100, 2);
+ $killRate = round($generalObj->getRankVar('killcrew') / max($generalObj->getRankVar('deathcrew'), 1) * 100, 2);
+
+ $experienceBonus = $generalObj->onCalcStat($generalObj, 'experience', 10000) - 10000;
+ if ($experienceBonus > 0) {
+ $experience = "" . getHonor($general['experience']) . " ({$general['experience']})";
+ } else if ($experienceBonus < 0) {
+ $experience = "" . getHonor($general['experience']) . " ({$general['experience']})";
+ } else {
+ $experience = getHonor($general['experience']) . " ({$general['experience']})";
+ }
+
+ $dedicationBonus = $generalObj->onCalcStat($generalObj, 'dedication', 10000) - 10000;
+ if ($dedicationBonus > 0) {
+ $dedication = "" . getHonor($general['dedication']) . " ({$general['dedication']})";
+ } else if ($dedicationBonus < 0) {
+ $dedication = "" . getHonor($general['dedication']) . " ({$general['dedication']})";
+ } else {
+ $dedication = getHonor($general['dedication']) . " ({$general['dedication']})";
+ }
+
+ $dex1 = $general['dex1'] / GameConst::$dexLimit * 100;
+ $dex2 = $general['dex2'] / GameConst::$dexLimit * 100;
+ $dex3 = $general['dex3'] / GameConst::$dexLimit * 100;
+ $dex4 = $general['dex4'] / GameConst::$dexLimit * 100;
+ $dex5 = $general['dex5'] / GameConst::$dexLimit * 100;
+
+ if ($dex1 > 100) {
+ $dex1 = 100;
+ }
+ if ($dex2 > 100) {
+ $dex2 = 100;
+ }
+ if ($dex3 > 100) {
+ $dex3 = 100;
+ }
+ if ($dex4 > 100) {
+ $dex4 = 100;
+ }
+ if ($dex5 > 100) {
+ $dex5 = 100;
+ }
+
+ $general['dex1_text'] = getDexCall($general['dex1']);
+ $general['dex2_text'] = getDexCall($general['dex2']);
+ $general['dex3_text'] = getDexCall($general['dex3']);
+ $general['dex4_text'] = getDexCall($general['dex4']);
+ $general['dex5_text'] = getDexCall($general['dex5']);
+
+ $general['dex1_short'] = sprintf('%.1fK', $general['dex1'] / 1000);
+ $general['dex2_short'] = sprintf('%.1fK', $general['dex2'] / 1000);
+ $general['dex3_short'] = sprintf('%.1fK', $general['dex3'] / 1000);
+ $general['dex4_short'] = sprintf('%.1fK', $general['dex4'] / 1000);
+ $general['dex5_short'] = sprintf('%.1fK', $general['dex5'] / 1000);
+
+ echo "
+ | 추 가 정 보 |
+
+ | 명성 |
+ $experience |
+ 계급 |
+ $dedication |
+
+
+ | 전투 |
+ {$generalObj->getRankVar('warnum')} |
+ 계략 |
+ {$generalObj->getRankVar('firenum')} |
+ 사관 |
+ {$general['belong']}년 |
+
+
+ | 승률 |
+ {$winRate} % |
+ 승리 |
+ {$generalObj->getRankVar('killnum')} |
+ 패배 |
+ {$generalObj->getRankVar('deathnum')} |
+
+
+ | 살상률 |
+ {$killRate} % |
+ 사살 |
+ {$generalObj->getRankVar('killcrew')} |
+ 피살 |
+ {$generalObj->getRankVar('deathcrew')} |
+
+
+
+ | 숙 련 도 |
+
+ | 보병 |
+ {$general['dex1_text']} |
+ {$general['dex1_short']} |
+ " . bar($dex1, 16) . " |
+
+
+ | 궁병 |
+ {$general['dex2_text']} |
+ {$general['dex2_short']} |
+ " . bar($dex2, 16) . " |
+
+
+ | 기병 |
+ {$general['dex3_text']} |
+ {$general['dex3_short']} |
+ " . bar($dex3, 16) . " |
+
+
+ | 귀병 |
+ {$general['dex4_text']} |
+ {$general['dex4_short']} |
+ " . bar($dex4, 16) . " |
+
+
+ | 차병 |
+ {$general['dex5_text']} |
+ {$general['dex5_short']} |
+ " . bar($dex5, 16) . " |
+
+
";
+}
+
+function getOnlineNum()
+{
+ return KVStorage::getStorage(DB::db(), 'game_env')->online;
+}
+
+function onlinegen(General $general)
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ $nationID = $general->getNationID();
+ $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
+ $onlinegen = $nationStor->online_genenerals;
+ return $onlinegen;
+}
+
+function nationMsg(General $general)
+{
+ $db = DB::db();
+ $nationID = $general->getNationID();
+ $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
+
+ return $nationStor->notice??'';
+}
+
+function banner()
+{
+
+ return sprintf(
+ '%s %s / %s',
+ GameConst::$title,
+ VersionGit::$version,
+ GameConst::$banner
+ );
+}
+
+function addTurn($date, int $turnterm, int $turn = 1, bool $withFraction = true)
+{
+ $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)
+{
+ $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)
+{
+ $date = new \DateTime($date);
+
+ $baseDate = new \DateTime($date->format('Y-m-d'));
+ $baseDate->sub(new \DateInterval("P1D"));
+ $baseDate->add(new \DateInterval("PT1H"));
+
+ $diffMin = intdiv($date->getTimeStamp() - $baseDate->getTimeStamp(), 60);
+ $diffMin -= $diffMin % $turnterm;
+
+ $baseDate->add(new \DateInterval("PT{$diffMin}M"));
+ if ($withFraction) {
+ return $baseDate->format('Y-m-d H:i:s.u');
+ }
+ return $baseDate->format('Y-m-d H:i:s');
+}
+
+function cutDay($date, int $turnterm, bool $withFraction = true)
+{
+ $date = new \DateTime($date);
+
+ $baseDate = new \DateTime($date->format('Y-m-d'));
+ $baseDate->sub(new \DateInterval("P1D"));
+ $baseDate->add(new \DateInterval("PT1H"));
+
+ $baseGap = 12 * $turnterm;
+
+ $diffMin = intdiv($date->getTimeStamp() - $baseDate->getTimeStamp(), 60);
+
+ $timeAdjust = $diffMin % $baseGap;
+ $newMonth = intdiv($timeAdjust, $turnterm) + 1;
+
+ $yearPulled = false;
+ if ($newMonth > 3) { //3월 이후일때는
+ $yearPulled = true;
+ $diffMin += $baseGap;
+ }
+ $diffMin -= $timeAdjust;
+
+ $baseDate->add(new \DateInterval("PT{$diffMin}M"));
+ if ($withFraction) {
+ $dateTimeString = $baseDate->format('Y-m-d H:i:s.u');
+ } else {
+ $dateTimeString = $baseDate->format('Y-m-d H:i:s');
+ }
+
+ return [$dateTimeString, $yearPulled, $newMonth];
+}
+
+function increaseRefresh($type = "", $cnt = 1)
+{
+ //FIXME: 로그인, 비로그인 시 처리가 명확하지 않음
+ $session = Session::getInstance();
+ $userID = $session->userID;
+ $generalID = $session->generalID;
+ $userGrade = $session->userGrade;
+
+ $date = TimeUtil::now();
+
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ $gameStor->refresh = $gameStor->refresh + $cnt; //TODO: +로 증가하는 값은 별도로 분리
+ $isunited = $gameStor->isunited;
+ $opentime = $gameStor->opentime;
+
+ if ($isunited != 2 && $generalID && $userGrade < 6 && $opentime <= TimeUtil::DatetimeNow()) {
+ $db->update('general', [
+ 'lastrefresh' => $date,
+ 'con' => $db->sqleval('con + %i', $cnt),
+ 'connect' => $db->sqleval('connect + %i', $cnt),
+ 'refcnt' => $db->sqleval('refcnt + %i', $cnt),
+ 'refresh' => $db->sqleval('refresh + %i', $cnt)
+ ], 'owner=%i', $userID);
+ }
+
+ $date = date('Y_m_d H:i:s');
+ $date2 = substr($date, 0, 10);
+ $online = getOnlineNum();
+ file_put_contents(
+ __DIR__ . "/logs/" . UniqueConst::$serverID . "/_{$date2}_refresh.txt",
+ sprintf(
+ "%s, %s, %s, %s, %s, %d\n",
+ $date,
+ $session->userName,
+ $session->generalName,
+ $session->ip,
+ $type,
+ $online
+ ),
+ FILE_APPEND
+ );
+
+ $proxy_headers = array(
+ 'HTTP_VIA',
+ 'HTTP_X_FORWARDED_FOR',
+ 'HTTP_FORWARDED_FOR',
+ 'HTTP_X_FORWARDED',
+ 'HTTP_FORWARDED',
+ 'HTTP_CLIENT_IP',
+ 'HTTP_FORWARDED_FOR_IP',
+ 'VIA',
+ 'X_FORWARDED_FOR',
+ 'FORWARDED_FOR',
+ 'X_FORWARDED',
+ 'FORWARDED',
+ 'CLIENT_IP',
+ 'FORWARDED_FOR_IP',
+ 'HTTP_PROXY_CONNECTION'
+ );
+
+ $str = "";
+ foreach ($proxy_headers as $x) {
+ if (isset($_SERVER[$x])) $str .= "//{$x}:{$_SERVER[$x]}";
+ }
+ if ($str != "") {
+ file_put_contents(
+ __DIR__ . "/logs/" . UniqueConst::$serverID . "/_{$date2}_ipcheck.txt",
+ sprintf(
+ "%s, %s, %s%s\n",
+ $session->userName,
+ $session->generalName,
+ $_SERVER['REMOTE_ADDR'],
+ $str
+ ),
+ FILE_APPEND
+ );
+ }
+}
+
+function updateTraffic()
+{
+ $online = getOnlineNum();
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ $admin = $gameStor->getValues(['year', 'month', 'refresh', 'maxonline', 'maxrefresh']);
+
+ //최다갱신자
+ $user = $db->queryFirstRow('select name,refresh from general order by refresh desc limit 1');
+
+ if ($admin['maxrefresh'] < $admin['refresh']) {
+ $admin['maxrefresh'] = $admin['refresh'];
+ }
+ if ($admin['maxonline'] < $online) {
+ $admin['maxonline'] = $online;
+ }
+ $gameStor->refresh = 0;
+ $gameStor->maxrefresh = $admin['maxrefresh'];
+ $gameStor->maxonline = $admin['maxonline'];
+
+ $db->update('general', ['refresh' => 0], true);
+
+ $date = TimeUtil::now();
+ //일시|년|월|총갱신|접속자|최다갱신자
+ file_put_contents(
+ __DIR__ . "/logs/" . UniqueConst::$serverID . "/_traffic.txt",
+ Json::encode([
+ $date,
+ $admin['year'],
+ $admin['month'],
+ $admin['refresh'],
+ $online,
+ $user['name'] . "(" . $user['refresh'] . ")"
+ ]) . "\n",
+ FILE_APPEND
+ );
+}
+
+function CheckOverhead()
+{
+ //서버정보
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ list($turnterm, $conlimit) = $gameStor->getValuesAsArray(['turnterm', 'conlimit']);
+
+ $con = Util::round(pow($turnterm, 0.6) * 3) * 10;
+
+
+ if ($con != $conlimit) {
+ $gameStor->conlimit = $con;
+ }
+}
+
+function isLock()
+{
+ return DB::db()->queryFirstField("SELECT plock from plock limit 1") != 0;
+}
+
+function tryLock(): bool
+{
+ //NOTE: 게임 로직과 관련한 모든 insert, update 함수들은 lock을 거칠것을 권장함.
+ $db = DB::db();
+
+ // 잠금
+ $db->update('plock', [
+ 'plock' => 1,
+ 'locktime' => TimeUtil::now(true)
+ ], 'plock=0');
+
+ return $db->affectedRows() > 0;
+}
+
+function unlock(): bool
+{
+ // 풀림
+ $db = DB::db();
+ $db->update('plock', [
+ 'plock' => 0
+ ], true);
+
+ return $db->affectedRows() > 0;
+}
+
+function timeover(): bool
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ list($turnterm, $turntime) = $gameStor->getValuesAsArray(['turnterm', 'turntime']);
+ $diff = (new \DateTime())->getTimestamp() - (new \DateTime($turntime))->getTimestamp();
+
+ $t = min($turnterm, 5);
+
+ $term = $diff;
+ if ($term >= $t || $term < 0) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+function checkDelay()
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ //서버정보
+ $now = new \DateTimeImmutable();
+ $turntime = new \DateTimeImmutable($gameStor->turntime);
+ $timeMinDiff = intdiv($now->getTimestamp() - $turntime->getTimestamp(), 60);
+
+ // 1턴이상 갱신 없었으면 서버 지연
+ $term = $gameStor->turnterm;
+ if ($term >= 20) {
+ $threshold = 1;
+ } else if ($term >= 10) {
+ $threshold = 3;
+ } else {
+ $threshold = 6;
+ }
+ //지연 해야할 밀린 턴 횟수
+ $iter = intdiv($timeMinDiff, $term);
+ if ($iter > $threshold) {
+ $minute = $iter * $term;
+ $newTurntime = $turntime->add(new \DateInterval("PT{$minute}M"));
+ $newNextTurntime = $turntime->add(new \DateInterval("PT{$term}M"));
+ $gameStor->turntime = $newTurntime->format('Y-m-d H:i:s');
+ $gameStor->starttime = (new \DateTimeImmutable($gameStor->starttime))
+ ->add(new \DateInterval("PT{$minute}M"))
+ ->format('Y-m-d H:i:s');
+
+ $db->update('general', [
+ 'turntime' => $db->sqleval('DATE_ADD(turntime, INTERVAL %i MINUTE)', $minute)
+ ], 'turntime<=DATE_ADD(turntime, INTERVAL %i MINUTE)', $term);
+ $db->update('auction', [
+ 'expire' => $db->sqleval('DATE_ADD(expire, INTERVAL %i MINUTE)', $minute)
+ ], true);
+ }
+}
+
+function updateOnline()
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ $nationname = ["재야"];
+
+ //국가별 이름 매핑
+ foreach (getAllNationStaticInfo() as $nation) {
+ $nationname[$nation['nation']] = $nation['name'];
+ }
+
+
+ //동접수
+ $before5Min = TimeUtil::nowAddMinutes(-5);
+ $onlineUser = $db->query('SELECT no,name,nation FROM general WHERE lastrefresh > %s', $before5Min);
+ $onlineNum = count($onlineUser);
+ $onlineNationUsers = Util::arrayGroupBy($onlineUser, 'nation');
+
+ uasort($onlineNationUsers, function(array $lhs, array $rhs){
+ return -(count($lhs)<=>count($rhs));
+ });
+
+ $onlineNation = [];
+
+ foreach($onlineNationUsers as $nationID=>$rawOnlineUser){
+ $nationName = getNationStaticInfo($nationID)['name'];
+ $onlineNation[] = "【{$nationName}】";
+ $userList = join(', ', Util::squeezeFromArray($rawOnlineUser, 'name'));
+ $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
+ $nationStor->online_genenerals = $userList;
+ }
+
+ //접속중인 국가
+ $gameStor->online_user_cnt = $onlineNum;
+ $gameStor->online_nation = join(', ', $onlineNation);
+}
+
+function addAge()
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+
+ //나이와 호봉 증가
+ $db->update('general', [
+ 'age' => $db->sqleval('age+1'),
+ 'belong' => $db->sqleval('belong+1')
+ ], true);
+
+ [$startYear, $year, $month] = $gameStor->getValuesAsArray(['startyear', 'year', 'month']);
+
+ if ($year >= $startYear + 3) {
+ foreach ($db->query('SELECT no,name,nation,leadership,strength,intel,aux from general where specage<=age and special=%s', GameConst::$defaultSpecialDomestic) as $general) {
+ $generalID = $general['no'];
+ $special = SpecialityHelper::pickSpecialDomestic(
+ $general,
+ (Json::decode($general['aux'])['prev_types_special'])??[]
+ );
+ $specialClass = buildGeneralSpecialDomesticClass($special);
+ $specialText = $specialClass->getName();
+ $db->update('general', [
+ 'special' => $special
+ ], 'no=%i', $generalID);
+
+ $logger = new ActionLogger($generalID, $general['nation'], $year, $month);
+
+ $josaUl = JosaUtil::pick($specialText, '을');
+ $logger->pushGeneralActionLog("특기 【{$specialText}>】{$josaUl} 익혔습니다!", ActionLogger::PLAIN);
+ $logger->pushGeneralHistoryLog("특기 【{$specialText}>】{$josaUl} 습득");
+ }
+
+ foreach ($db->query('SELECT no,name,nation,leadership,strength,intel,npc,dex1,dex2,dex3,dex4,dex5,aux from general where specage2<=age and special2=%s', GameConst::$defaultSpecialWar) as $general) {
+ $generalID = $general['no'];
+ $special2 = SpecialityHelper::pickSpecialWar(
+ $general,
+ (Json::decode($general['aux'])['prev_types_special2'])??[]
+ );
+ $specialClass = buildGeneralSpecialWarClass($special2);
+ $specialText = $specialClass->getName();
+
+ $db->update('general', [
+ 'special2' => $special2
+ ], 'no=%i', $general['no']);
+
+ $logger = new ActionLogger($generalID, $general['nation'], $year, $month);
+
+ $josaUl = JosaUtil::pick($specialText, '을');
+ $logger->pushGeneralActionLog("특기 【{$specialText}>】{$josaUl} 익혔습니다!", ActionLogger::PLAIN);
+ $logger->pushGeneralHistoryLog("특기 【{$specialText}>】{$josaUl} 습득");
+ }
+ }
+}
+
+function turnDate($curtime)
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ $admin = $gameStor->getValues(['startyear', 'starttime', 'turnterm', 'year', 'month']);
+
+ $turn = $admin['starttime'];
+ $curturn = cutTurn($curtime, $admin['turnterm']);
+ $term = $admin['turnterm'];
+
+ $num = intdiv((strtotime($curturn) - strtotime($turn)), $term * 60);
+
+ $date = $admin['startyear'] * 12;
+ $date += $num;
+
+ $year = intdiv($date, 12);
+ $month = 1 + $date % 12;
+
+ // 바뀐 경우만 업데이트
+ if ($admin['month'] != $month || $admin['year'] != $year) {
+ $gameStor->year = $year;
+ $gameStor->month = $month;
+ }
+
+ return [$year, $month];
+}
+
+
+function triggerTournament()
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ $admin = $gameStor->getValues(['tournament', 'tnmt_trig']);
+
+ //현재 토너먼트 없고, 자동개시 걸려있을때, 40%확률
+ if ($admin['tournament'] == 0 && $admin['tnmt_trig'] > 0 && rand() % 100 < 40) {
+ $type = rand() % 5; // 0 : 전력전, 1 : 통솔전, 2 : 일기토, 3 : 설전
+ //전력전 40%, 통, 일, 설 각 20%
+ if ($type > 3) {
+ $type = 0;
+ }
+ startTournament($admin['tnmt_trig'], $type);
+ }
+}
+
+function CheckHall($no)
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ $types = [
+ ["experience", 'natural'],
+ ["dedication", 'natural'],
+ ["firenum", 'rank'],
+ ["warnum", 'rank'],
+ ["killnum", 'rank'],
+ ["winrate", 'calc'],
+ ["occupied", 'rank'],
+ ["killcrew", 'rank'],
+ ["killrate", 'calc'],
+ ["killcrew_person", 'rank'],
+ ["killrate_person", 'calc'],
+ ["dex1", 'natural'],
+ ["dex2", 'natural'],
+ ["dex3", 'natural'],
+ ["dex4", 'natural'],
+ ["dex5", 'natural'],
+ ["ttrate", 'calc'],
+ ["tlrate", 'calc'],
+ ["tsrate", 'calc'],
+ ["tirate", 'calc'],
+ ["betgold", 'rank'],
+ ["betwin", 'rank'],
+ ["betwingold", 'rank'],
+ ["betrate", 'calc'],
+ ];
+
+ $generalObj = General::createGeneralObjFromDB($no, null, 2);
+
+ $ttw = $generalObj->getRankVar('ttw');
+ $ttd = $generalObj->getRankVar('ttd');
+ $ttl = $generalObj->getRankVar('ttl');
+
+ $tlw = $generalObj->getRankVar('tlw');
+ $tld = $generalObj->getRankVar('tld');
+ $tll = $generalObj->getRankVar('tll');
+
+ $tsw = $generalObj->getRankVar('tsw');
+ $tsd = $generalObj->getRankVar('tsd');
+ $tsl = $generalObj->getRankVar('tsl');
+
+ $tiw = $generalObj->getRankVar('tiw');
+ $tid = $generalObj->getRankVar('tid');
+ $til = $generalObj->getRankVar('til');
+
+ $betWinGold = $generalObj->getRankVar('betwingold');
+ $betGold = Util::valueFit($generalObj->getRankVar('betgold'), 1);
+
+ $win = $generalObj->getRankVar('killnum');
+ $war = Util::valueFit($generalObj->getRankVar('warnum'), 1);
+
+ $kill = $generalObj->getRankVar('killcrew');
+ $death = Util::valueFit($generalObj->getRankVar('deathcrew'), 1);
+
+ $killPerson = $generalObj->getRankVar('killcrew_person');
+ $deathPerson = Util::valueFit($generalObj->getRankVar('deathcrew_person'), 1);
+
+ $tt = Util::valueFit($ttw + $ttd + $ttl, 1);
+ $tl = Util::valueFit($tlw + $tld + $tll, 1);
+ $ts = Util::valueFit($tsw + $tsd + $tsl, 1);
+ $ti = Util::valueFit($tiw + $tid + $til, 1);
+
+ $calcVar = [];
+ $calcVar['ttrate'] = $ttw / $tt;
+ $calcVar['tlrate'] = $tlw / $tl;
+ $calcVar['tsrate'] = $tsw / $ts;
+ $calcVar['tirate'] = $tiw / $ti;
+ $calcVar['betrate'] = $betWinGold / $betGold;
+ $calcVar['winrate'] = $win / $war;
+ $calcVar['killrate'] = $kill / $death;
+ $calcVar['killrate_person'] = $killPerson / $deathPerson;
+
+ if ($generalObj instanceof DummyGeneral) {
+ return;
+ }
+
+ $unitedDate = TimeUtil::now();
+ $nation = $generalObj->getStaticNation();
+
+ $serverCnt = $db->queryFirstField('SELECT count(*) FROM ng_games');
+
+ [$scenarioIdx, $scenarioName, $startTime] = $gameStor->getValuesAsArray(['scenario', 'scenario_text', 'starttime']);
+
+ $ownerName = $generalObj->getVar('owner_name');
+ if ($generalObj->getVar('owner')) {
+ $ownerName = RootDB::db()->queryFirstField('SELECT name FROM member WHERE no = %i', $generalObj->getVar('owner'));
+ }
+
+ foreach ($types as [$typeName, $valueType]) {
+
+ if ($valueType === 'natural') {
+ $value = $generalObj->getVar($typeName);
+ } else if ($valueType === 'rank') {
+ $value = $generalObj->getRankVar($typeName);
+ } else if ($valueType === 'calc') {
+ $value = $calcVar[$typeName];
+ }
+
+ //승률,살상률인데 10회 미만 전투시 스킵
+ if (($typeName === 'winrate' || $typeName === 'killrate') && $generalObj->getRankVar('warnum') < 10) {
+ continue;
+ }
+ //토너승률인데 50회 미만시 스킵
+ if ($typeName === 'ttrate' && $tt < 50) {
+ continue;
+ }
+ //토너승률인데 50회 미만시 스킵
+ if ($typeName === 'tlrate' && $tl < 50) {
+ continue;
+ }
+ //토너승률인데 50회 미만시 스킵
+ if ($typeName === 'tsrate' && $ts < 50) {
+ continue;
+ }
+ //토너승률인데 50회 미만시 스킵
+ if ($typeName === 'tirate' && $ti < 50) {
+ continue;
+ }
+ //수익률인데 1000미만시 스킵
+ if ($typeName === 'betrate' && $generalObj->getRankVar('betgold') < 1000) {
+ continue;
+ }
+
+ if ($value <= 0) {
+ continue;
+ }
+
+ $aux = [
+ 'name' => $generalObj->getName(),
+ 'nationName' => $nation['name'],
+ 'bgColor' => $nation['color'],
+ 'fgColor' => newColor($nation['color']),
+ 'picture' => $generalObj->getVar('picture'),
+ 'imgsvr' => $generalObj->getVar('imgsvr'),
+ 'startTime' => $startTime,
+ 'unitedTime' => $unitedDate,
+ 'ownerName' => $ownerName,
+ 'serverID' => UniqueConst::$serverID,
+ 'serverIdx' => $serverCnt,
+ 'serverName' => UniqueConst::$serverName,
+ 'scenarioName' => $scenarioName,
+ ];
+ $jsonAux = Json::encode($aux);
+
+ $db->insertIgnore('hall', [
+ 'server_id' => UniqueConst::$serverID,
+ 'season' => UniqueConst::$seasonIdx,
+ 'scenario' => $scenarioIdx,
+ 'general_no' => $no,
+ 'type' => $typeName,
+ 'value' => $value,
+ 'owner' => $generalObj->getVar('owner'),
+ 'aux' => $jsonAux
+ ]);
+
+ if ($db->affectedRows() == 0) {
+ $db->update(
+ 'hall',
+ [
+ 'value' => $value,
+ 'aux' => $jsonAux
+ ],
+ 'server_id = %s AND scenario = %i AND general_no = %i AND type = %s AND value < %d',
+ UniqueConst::$serverID,
+ $scenarioIdx,
+ $no,
+ $typeName,
+ $value
+ );
+ }
+ }
+}
+
+function giveRandomUniqueItem(General $general, string $acquireType): bool
+{
+ $db = DB::db();
+ //아이템 습득 상황
+ $availableUnique = [];
+
+ //TODO: 너무 바보 같다. 장기적으로는 유니크 아이템 테이블 같은게 필요하지 않을까?
+ //일단은 '획득' 시에만 동작하므로 이대로 사용하기로...
+ $occupiedUnique = [];
+
+ foreach (array_keys(GameConst::$allItems) as $itemType) {
+ foreach ($db->queryAllLists('SELECT %b, count(*) as cnt FROM general GROUP BY %b', $itemType, $itemType) as [$itemCode, $cnt]) {
+ $itemClass = buildItemClass($itemCode);
+ if (!$itemClass) {
+ continue;
+ }
+ if ($itemClass->isBuyable()) {
+ continue;
+ }
+ $occupiedUnique[$itemCode] = $cnt;
+ }
+ }
+
+ foreach (GameConst::$allItems as $itemType => $itemCategories) {
+ foreach ($itemCategories as $itemCode => $cnt) {
+ if (!key_exists($itemCode, $occupiedUnique)) {
+ $availableUnique[] = [[$itemType, $itemCode], $cnt];
+ continue;
+ }
+
+ $remain = $cnt - $occupiedUnique[$itemCode];
+ if ($remain > 0) {
+ $availableUnique[] = [[$itemType, $itemCode], $remain];
+ }
+ }
+ }
+
+ if (!$availableUnique) {
+ return false;
+ }
+
+ [$itemType, $itemCode] = Util::choiceRandomUsingWeightPair($availableUnique);
+
+ $nationName = $general->getStaticNation()['name'];
+ $generalName = $general->getName();
+ $josaYi = JosaUtil::pick($generalName, '이');
+ $itemObj = buildItemClass($itemCode);
+ $itemName = $itemObj->getName();
+ $itemRawName = $itemObj->getRawName();
+ $josaUl = JosaUtil::pick($itemRawName, '을');
+
+
+ $general->setVar($itemType, $itemCode);
+
+ $logger = $general->getLogger();
+
+ $logger->pushGeneralActionLog("{$itemName}>{$josaUl} 습득했습니다!");
+ $logger->pushGeneralHistoryLog("{$itemName}>{$josaUl} 습득");
+ $logger->pushGlobalActionLog("{$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
+ $logger->pushGlobalHistoryLog("【{$acquireType}】>{$nationName}>의 {$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
+
+ return true;
+}
+
+function tryUniqueItemLottery(General $general, string $acquireType = '아이템'): bool
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ if ($general->getNPCType() >= 2) {
+ return false;
+ }
+
+ if ($general->getNPCType() > 6) {
+ return false;
+ }
+
+ foreach ($general->getItems() as $item) {
+ if (!$item) {
+ continue;
+ }
+ if (!$item->isBuyable()) {
+ return false;
+ }
+ }
+
+ $scenario = $gameStor->scenario;
+ $genCount = $db->queryFirstField('SELECT count(*) FROM general WHERE npc<2');
+
+ if ($scenario < 100) {
+ $prob = 1 / ($genCount * 5); // 5~6개월에 하나씩 등장
+ } else {
+ $prob = 1 / $genCount; // 1~2개월에 하나씩 등장
+ }
+
+ if ($acquireType == '설문조사') {
+ $prob = 1 / ($genCount * 0.7 / 3); // 투표율 70%, 설문조사 한번에 2~3개 등장
+ } else if ($acquireType == '랜덤 임관') {
+ $prob = 1 / ($genCount / 10 / 2); // 랜임시 2개(10%) 등장(200명중 20명 랜임시도?)
+ } else if ($acquireType == '건국') {
+ $prob = 1 / ($genCount / 10 / 4); // 건국시 4개(20%) 등장(200명시 20국 정도 됨)
+ }
+
+ $prob = Util::valueFit($prob, null, 1 / 3);
+
+ if (!Util::randBool($prob)) {
+ return false;
+ }
+
+ return giveRandomUniqueItem($general, $acquireType);
+}
+
+function getAdmin()
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ return $gameStor->getAll();
+}
+
+/** @return General[] */
+function deleteNation(General $lord, bool $applyDB):array
+{
+ $lordID = $lord->getID();
+ $nationID = $lord->getNationID();
+
+ DeleteConflict($nationID);
+
+ $db = DB::db();
+ $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
+
+ $nation = $db->queryFirstRow('SELECT * FROM nation WHERE nation=%i', $nationID);
+ $nationName = $nation['name'];
+
+ $logger = $lord->getLogger();
+
+ $josaUn = JosaUtil::pick($nationName, '은');
+ $logger->pushGlobalHistoryLog("【멸망】>{$nationName}>{$josaUn} 멸망>했습니다.");
+
+
+ $nationGeneralList = General::createGeneralObjListFromDB(
+ $db->queryFirstColumn(
+ 'SELECT `no` FROM general WHERE nation=%i AND no != %i',
+ $nationID,
+ $lordID
+ ),
+ ['npc', 'gold', 'rice', 'experience', 'explevel', 'dedication', 'dedlevel', 'aux'], 1
+ );
+ $nationGeneralList[$lordID] = $lord;
+
+ $nation['generals'] = array_keys($nationGeneralList);
+ $nation['aux'] = Json::decode($nation['aux']);
+ $nation['msg'] = $nationStor->notice;
+ $nation['scout_msg'] = $nationStor->scout_msg;
+ $nation['aux'] += $nationStor->max_power;
+ $nation['history'] = getNationHistoryLogAll($nationID);
+
+ $josaYi = JosaUtil::pick($nationName, '이');
+ $destroyLog = "{$nationName}>{$josaYi} 멸망>했습니다.";
+ $destroyHistoryLog = "{$nationName}>{$josaYi} 멸망>";
+
+ // 전 장수 재야로
+ foreach($nationGeneralList as $general){
+ $general->setVar('belong', 0);
+ $general->setVar('troop', 0);
+ $general->setVar('officer_level', 0);
+ $general->setVar('officer_city', 0);
+ $general->setVar('nation', 0);
+ $general->setVar('permission', 'normal');
+ $logger = $general->getLogger();
+ $logger->pushGeneralActionLog($destroyLog, ActionLogger::PLAIN);
+ $logger->pushGeneralHistoryLog($destroyHistoryLog);
+
+ if($applyDB){
+ $general->applyDB($db);
+ }
+ }
+
+ // 도시 공백지로
+ $db->update('city', [
+ 'nation' => 0,
+ 'front' => 0,
+ ], 'nation=%i', $nationID);
+ // 부대 삭제
+ $db->delete('troop', 'nation=%i', $nationID);
+
+ // 국가 삭제
+ $db->insert('ng_old_nations', [
+ 'server_id' => UniqueConst::$serverID,
+ 'nation' => $nationID,
+ 'data' => Json::encode($nation)
+ ]);
+ $db->delete('nation', 'nation=%i', $nationID);
+ $db->delete('nation_turn', 'nation_id=%i', $nationID);
+ // 외교 삭제
+ $db->delete('diplomacy', 'me = %i OR you = %i', $nationID, $nationID);
+
+ refreshNationStaticInfo();
+
+ $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
+ $nationStor->resetValues();
+
+ return $nationGeneralList;
+}
+
+function nextRuler(General $general)
+{
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ [$year, $month] = $gameStor->getValuesAsArray(['year', 'month']);
+ $nation = $general->getStaticNation();
+ $nationName = $nation['name'];
+ $nationID = $nation['nation'];
+
+ $candidate = null;
+
+ //npc or npc유저인 경우 후계 찾기
+ if ($general->getNPCType() > 0) {
+ $candidate = $db->queryFirstRow(
+ 'SELECT no,name,officer_level,IF(ABS(affinity-%i)>75,150-ABS(affinity-%i),ABS(affinity-%i)) as npcmatch2 from general where nation=%i and officer_level!=12 and 1 <= npc and npc<=3 order by npcmatch2,rand() LIMIT 1',
+ $general->getVar('affinity'),
+ $general->getVar('affinity'),
+ $general->getVar('affinity'),
+ $nationID
+ );
+ }
+ if (!$candidate) {
+ $candidate = $db->queryFirstRow(
+ 'SELECT no,name,npc,officer_level FROM general WHERE nation=%i and officer_level!= 12 AND officer_level >= 9 and npc != 5 ORDER BY officer_level DESC LIMIT 1',
+ $nationID
+ );
+ }
+ if (!$candidate) {
+ $candidate = $db->queryFirstRow(
+ 'SELECT no,name,npc,officer_level FROM general WHERE nation=%i and officer_level!= 12 and npc != 5 ORDER BY dedication DESC LIMIT 1',
+ $nationID
+ );
+ }
+
+
+ if (!$candidate) {
+ deleteNation($general, true);
+ return;
+ }
+
+ $nextRulerID = $candidate['no'];
+ $nextRulerName = $candidate['name'];
+
+ $general->setVar('officer_level', 1);
+ $general->setVar('officer_city', 0);
+
+ $db->update('general', [
+ 'officer_level' => 12,
+ 'officer_city' => 0,
+ ], 'no=%i AND nation=%i', $nextRulerID, $nationID);
+ if ($db->affectedRows() == 0) {
+ throw new \RuntimeException('선양되지 않음');
+ }
+
+ $josaYi = JosaUtil::pick($nextRulerName, '이');
+
+ $nextRulerLogger = new ActionLogger($nextRulerID, $nationID, $year, $month);
+ $nextRulerLogger->pushGlobalHistoryLog("【유지】>{$nextRulerName}>{$josaYi} {$nationName}>의 유지를 이어 받았습니다");
+ $nextRulerLogger->pushGeneralHistoryLog("【유지】>{$nextRulerName}>{$josaYi} {$nationName}>의 유지를 이어 받음.");
+ $nextRulerLogger->flush();
+ // 장수 삭제 및 부대처리는 checkTurn에서
+}
+
+/**
+ * $maxDist 이내의 도시를 검색하는 함수
+ * @param $from 기준 도시 코드
+ * @param $maxDist 검색하고자 하는 최대 거리
+ * @param $distForm 리턴 타입. true일 경우 $result[$dist] = [...$city] 이며, false일 경우 $result[$city] = $dist 임
+ */
+function searchDistance(int $from, int $maxDist = 99, bool $distForm = false)
+{
+ $queue = new \SplQueue();
+
+ $cities = [];
+ $distanceList = [];
+
+ $queue->enqueue([$from, 0]);
+
+ while (!$queue->isEmpty()) {
+ list($cityID, $dist) = $queue->dequeue();
+ if (key_exists($cityID, $cities)) {
+ continue;
+ }
+
+ if (!key_exists($dist, $distanceList)) {
+ $distanceList[$dist] = [];
+ }
+ $distanceList[$dist][] = $cityID;
+
+ $cities[$cityID] = $dist;
+ if ($dist >= $maxDist) {
+ continue;
+ }
+
+ foreach (array_keys(CityConst::byID($cityID)->path) as $connCityID) {
+ if (key_exists($connCityID, $cities)) {
+ continue;
+ }
+ $queue->enqueue([$connCityID, $dist + 1]);
+ }
+ }
+
+ if ($distForm) {
+ unset($distanceList[0]);
+ return $distanceList;
+ } else {
+ return $cities;
+ }
+}
+
+/**
+ * $from 으로 지정한 도시부터 $to 도시까지의 최단 거리를 계산해 줌
+ * @param $from 기준 도시 코드
+ * @param $to 대상 도시 코드
+ * @param array|null $linkNationList 경로에 해당하는 국가 리스트, null인 경우 제한 없음
+ * @return null|int 거리.
+ */
+function calcCityDistance(int $from, int $to, ?array $linkNationList): ?int
+{
+ $queue = new \SplQueue();
+
+ $cities = [];
+
+ if ($linkNationList === []) {
+ return null;
+ }
+
+ if ($linkNationList !== null) {
+ $db = DB::db();
+ //TODO: 도시-국가 캐싱이 있으면 쓸모 있지 않을까
+ $allowedCityList = [];
+ foreach ($db->queryFirstColumn(
+ 'SELECT city FROM city WHERE nation IN %li',
+ $linkNationList
+ ) as $cityID) {
+ $allowedCityList[$cityID] = $cityID;
+ }
+ } else {
+ $allowedCityList = CityConst::all();
+ }
+
+ if (!key_exists($to, $allowedCityList)) {
+ return null;
+ }
+
+ if ($from === $to) {
+ return 0;
+ }
+
+ $queue->enqueue([$from, 0]);
+
+ while (!$queue->isEmpty()) {
+ list($cityID, $dist) = $queue->dequeue();
+ if (key_exists($cityID, $cities)) {
+ continue;
+ }
+
+ $cities[$cityID] = $dist;
+
+ if ($cityID === $to) {
+ return $dist;
+ }
+
+ foreach (array_keys(CityConst::byID($cityID)->path) as $connCityID) {
+ if (!key_exists($connCityID, $allowedCityList)) {
+ continue;
+ }
+ if (key_exists($connCityID, $cities)) {
+ continue;
+ }
+ $queue->enqueue([$connCityID, $dist + 1]);
+ }
+ }
+
+ //길이 없음
+ return null;
+}
+/**
+ * $from 으로 지정한 도시의 인접 도시와 $to 도시의 최단 거리를 계산해 줌
+ * @param $from 기준 도시 코드
+ * @param $to 대상 도시 코드
+ * @param $linkNationList 경로에 해당하는 국가 리스트
+ * @return array $dist=>[cityID, $nation] 배열 가장 앞이 가장 가까움
+ */
+function searchDistanceListToDest(int $from, int $to, array $linkNationList)
+{
+ $queue = new \SplQueue();
+
+ $cities = [];
+
+ $db = DB::db();
+
+ //TODO: 도시-국가 캐싱이 있으면 쓸모 있지 않을까
+ $allowedCityList = [];
+ foreach ($db->queryAllLists(
+ 'SELECT city, nation FROM city WHERE nation IN %li',
+ $linkNationList
+ ) as [$cityID, $nationID]) {
+ $allowedCityList[$cityID] = $nationID;
+ }
+
+ $remainFromCities = [];
+ foreach (array_keys(CityConst::byID($from)->path) as $fromCityID) {
+ if (key_exists($fromCityID, $allowedCityList)) {
+ $remainFromCities[$fromCityID] = true;
+ }
+ }
+
+ if (!key_exists($to, $allowedCityList)) {
+ return [];
+ }
+
+ $result = [];
+ $queue->enqueue([$to, 0]);
+
+ while (!empty($remainFromCities) && !$queue->isEmpty()) {
+ list($cityID, $dist) = $queue->dequeue();
+ if (key_exists($cityID, $cities)) {
+ continue;
+ }
+
+ $cities[$cityID] = $dist;
+
+ if (key_exists($cityID, $remainFromCities)) {
+ unset($remainFromCities[$cityID]);
+ $result[$dist][] = [$cityID, $allowedCityList[$cityID]];
+ }
+
+ foreach (array_keys(CityConst::byID($cityID)->path) as $connCityID) {
+ if ($allowedCityList !== null && !key_exists($connCityID, $allowedCityList)) {
+ continue;
+ }
+ if (key_exists($connCityID, $cities)) {
+ continue;
+ }
+ $queue->enqueue([$connCityID, $dist + 1]);
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * 지정된 도시 내의 최단 거리를 계산해 줌(Floyd-Warshall)
+ * @param $cityIDList 이동 가능한 도시 리스트.
+ * @return array [from][to] 로 표시되는 거리값
+ */
+function searchAllDistanceByCityList(array $cityIDList): array
+{
+ if (!$cityIDList) {
+ return [];
+ }
+ $cityList = [];
+ foreach ($cityIDList as $cityID) {
+ $cityList[$cityID] = $cityID;
+ }
+
+ $distanceList = [];
+ foreach ($cityIDList as $cityID) {
+ $nearList = [$cityID => 0];
+ foreach (array_keys(CityConst::byID($cityID)->path) as $nextCityID) {
+ if (!key_exists($nextCityID, $cityList)) {
+ continue;
+ }
+ $nearList[$nextCityID] = 1;
+ }
+ $distanceList[$cityID] = $nearList;
+ }
+
+ //Floyd-Warshall
+ foreach ($cityList as $cityStop) {
+ foreach ($cityList as $cityFrom) {
+ foreach ($cityList as $cityTo) {
+ if (!key_exists($cityStop, $distanceList[$cityFrom])) {
+ continue;
+ }
+ if (!key_exists($cityTo, $distanceList[$cityStop])) {
+ continue;
+ }
+
+ if (!key_exists($cityTo, $distanceList[$cityFrom])) {
+ $distanceList[$cityFrom][$cityTo] = $distanceList[$cityFrom][$cityStop] + $distanceList[$cityStop][$cityTo];
+ continue;
+ }
+
+ $distanceList[$cityFrom][$cityTo] = min($distanceList[$cityFrom][$cityStop] + $distanceList[$cityStop][$cityTo], $distanceList[$cityFrom][$cityTo]);
+ }
+ }
+ }
+
+ return $distanceList;
+}
+
+/**
+ * 지정된 국가 내의 모든 도시들의 최단 거리를 계산해 줌(Floyd-Warshall)
+ * @param $linkNationList 경로에 해당하는 국가 리스트
+ * @param $suppliedCityOnly 보급된 도시만을 이용해 검색
+ * @return array [from][to] 로 표시되는 거리값
+ */
+function searchAllDistanceByNationList(array $linkNationList, bool $suppliedCityOnly = false): array
+{
+ if (!$linkNationList) {
+ return [];
+ }
+ $db = DB::db();
+ if ($suppliedCityOnly) {
+ $cityIDList = $db->queryFirstColumn('SELECT city FROM city WHERE nation IN %li AND supply=1', $linkNationList);
+ } else {
+ $cityIDList = $db->queryFirstColumn('SELECT city FROM city WHERE nation IN %li', $linkNationList);
+ }
+ return searchAllDistanceByCityList($cityIDList);
+}
+
+function isNeighbor(int $nation1, int $nation2, bool $includeNoSupply = true)
+{
+ if ($nation1 === $nation2) {
+ return false;
+ }
+ $db = DB::db();
+
+ $nation1Cities = [];
+
+ if ($includeNoSupply) {
+ $supplySql = '';
+ } else {
+ $supplySql = 'AND supply = 1';
+ }
+
+ foreach ($db->queryFirstColumn('SELECT city FROM city WHERE nation = %i %l', $nation1, $supplySql) as $city) {
+ $nation1Cities[$city] = $city;
+ }
+
+ foreach ($db->queryFirstColumn('SELECT city FROM city WHERE nation = %i %l', $nation2, $supplySql) as $city) {
+ foreach (array_keys(CityConst::byID($city)->path) as $adjCity) {
+ if (key_exists($adjCity, $nation1Cities)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+function SabotageInjury(array $cityGeneralList, string $reason): int
+{
+ $injuryCount = 0;
+ $josaRo = JosaUtil::pick($reason, '로');
+ $text = "{$reason}>{$josaRo} 인해 부상>을 당했습니다.";
+
+ $db = DB::db();
+
+ foreach ($cityGeneralList as $general) {
+ /** @var General $general */
+ if (!Util::randBool(0.3)) {
+ continue;
+ }
+ $general->getLogger()->pushGeneralActionLog($text);
+
+ $general->increaseVarWithLimit('injury', Util::randRangeInt(1, 16), 0, 80);
+ $general->multiplyVar('crew', 0.98);
+ $general->multiplyVar('atmos', 0.98);
+ $general->multiplyVar('train', 0.98);
+
+ $general->applyDB($db);
+
+ $injuryCount += 1;
+ }
+
+ return $injuryCount;
+}
+
+function getRandTurn($term, ?\DateTimeInterface $baseDateTime = null)
+{
+ if ($baseDateTime === null) {
+ $baseDateTime = new \DateTimeImmutable();
+ } else if ($baseDateTime instanceof \DateTime) {
+ $baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime);
+ } else if ($baseDateTime instanceof \DateTimeImmutable) {
+ //do Nothing
+ } else {
+ throw new MustNotBeReachedException();
+ }
+
+ $randSecond = Util::randRangeInt(0, 60 * $term - 1);
+ $randFraction = Util::randRangeInt(0, 999999) / 1000000; //6자리 소수
+
+ return $baseDateTime->add(TimeUtil::secondsToDateInterval($randSecond + $randFraction))->format('Y-m-d H:i:s');
+}
+
+function getRandTurn2($term, ?\DateTimeInterface $baseDateTime = null)
+{
+ if ($baseDateTime === null) {
+ $baseDateTime = new \DateTimeImmutable();
+ } else if ($baseDateTime instanceof \DateTime) {
+ $baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime);
+ } else {
+ throw new MustNotBeReachedException();
+ }
+ $randSecond = Util::randRangeInt(0, 60 * $term - 1);
+ $randFraction = Util::randRangeInt(0, 999999) / 1000000; //6자리 소수
+
+ return $baseDateTime->sub(TimeUtil::secondsToDateInterval($randSecond + $randFraction))->format('Y-m-d H:i:s');
+}
diff --git a/hwe/index.php b/hwe/index.php
index 07426573..266502cb 100644
--- a/hwe/index.php
+++ b/hwe/index.php
@@ -61,7 +61,7 @@ $scenario = $gameStor->scenario_text;
$nationID = $generalObj->getNationID();
if($nationID){
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
- $nationStor->cacheValues(['notice', 'online_genenerals']);
+ $nationStor->cacheAll();
}
$valid = 0;
diff --git a/hwe/sammo/Command/BaseCommand.php b/hwe/sammo/Command/BaseCommand.php
index d811a757..fbde7f17 100644
--- a/hwe/sammo/Command/BaseCommand.php
+++ b/hwe/sammo/Command/BaseCommand.php
@@ -271,8 +271,8 @@ abstract class BaseCommand{
return $this->logger;
}
- abstract protected function getNextExecuteKey():string;
- abstract public function getNextAvailable():?int;
+ abstract public function getNextExecuteKey():string;
+ abstract public function getNextAvailableTurn():?int;
abstract public function setNextAvailable(?int $yearMonth=null);
protected function testPostReqTurn():?array{
@@ -280,13 +280,13 @@ abstract class BaseCommand{
return null;
}
- $nextAvailable = $this->getNextAvailable();
- if($nextAvailable === null){
+ $nextAvailableTurn = $this->getNextAvailableTurn();
+ if($nextAvailableTurn === null){
return null;
}
$yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
- $remainTurn = $nextAvailable - $yearMonth - $this->getPreReqTurn();
+ $remainTurn = $nextAvailableTurn - $yearMonth;
if($remainTurn <= 0){
return null;
}
diff --git a/hwe/sammo/Command/GeneralCommand.php b/hwe/sammo/Command/GeneralCommand.php
index fe40b8b7..fac41229 100644
--- a/hwe/sammo/Command/GeneralCommand.php
+++ b/hwe/sammo/Command/GeneralCommand.php
@@ -4,14 +4,14 @@ use \sammo\Util;
abstract class GeneralCommand extends BaseCommand{
- protected function getNextExecuteKey():string{
+ public function getNextExecuteKey():string{
$turnKey = static::$actionName;
$generalID = $this->getGeneral()->getID();
$executeKey = "next_execute_{$generalID}_{$turnKey}";
return $executeKey;
}
- public function getNextAvailable():?int{
+ public function getNextAvailableTurn():?int{
if($this->isArgValid && !$this->getPostReqTurn()){
return null;
}
@@ -25,7 +25,8 @@ abstract class GeneralCommand extends BaseCommand{
return;
}
if($yearMonth === null){
- $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']) + $this->getPostReqTurn();
+ $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month'])
+ + $this->getPostReqTurn() - $this->getPreReqTurn();
}
$db = \sammo\DB::db();
diff --git a/hwe/sammo/Command/Nation/che_급습.php b/hwe/sammo/Command/Nation/che_급습.php
index d43096ac..575deb5c 100644
--- a/hwe/sammo/Command/Nation/che_급습.php
+++ b/hwe/sammo/Command/Nation/che_급습.php
@@ -99,7 +99,7 @@ class che_급습 extends Command\NationCommand
$reqTurn = $this->getPreReqTurn() + 1;
$postReqTurn = $this->getPostReqTurn();
- return "{$name}/{$reqTurn}턴(전략$postReqTurn)";
+ return "{$name}/{$reqTurn}턴(재사용 대기 $postReqTurn)";
}
public function getCost(): array
diff --git a/hwe/sammo/Command/Nation/che_물자원조.php b/hwe/sammo/Command/Nation/che_물자원조.php
index 5ed2ab44..9bae2441 100644
--- a/hwe/sammo/Command/Nation/che_물자원조.php
+++ b/hwe/sammo/Command/Nation/che_물자원조.php
@@ -128,7 +128,7 @@ class che_물자원조 extends Command\NationCommand{
return 12;
}
- public function getNextAvailable():?int{
+ public function getNextAvailableTurn():?int{
return null;
}
diff --git a/hwe/sammo/Command/Nation/che_백성동원.php b/hwe/sammo/Command/Nation/che_백성동원.php
index 1a7d704b..9d2d83ff 100644
--- a/hwe/sammo/Command/Nation/che_백성동원.php
+++ b/hwe/sammo/Command/Nation/che_백성동원.php
@@ -82,7 +82,7 @@ class che_백성동원 extends Command\NationCommand{
$reqTurn = $this->getPreReqTurn()+1;
$postReqTurn = $this->getPostReqTurn();
- return "{$name}/{$reqTurn}턴(전략$postReqTurn)";
+ return "{$name}/{$reqTurn}턴(재사용 대기 $postReqTurn)";
}
public function getCost():array{
diff --git a/hwe/sammo/Command/Nation/che_수몰.php b/hwe/sammo/Command/Nation/che_수몰.php
index 4c37c6ed..6722b82f 100644
--- a/hwe/sammo/Command/Nation/che_수몰.php
+++ b/hwe/sammo/Command/Nation/che_수몰.php
@@ -84,7 +84,7 @@ class che_수몰 extends Command\NationCommand{
$reqTurn = $this->getPreReqTurn()+1;
$postReqTurn = $this->getPostReqTurn();
- return "{$name}/{$reqTurn}턴(전략$postReqTurn)";
+ return "{$name}/{$reqTurn}턴(재사용 대기 $postReqTurn)";
}
public function getCost():array{
diff --git a/hwe/sammo/Command/Nation/che_의병모집.php b/hwe/sammo/Command/Nation/che_의병모집.php
index 5ea6c162..a8e21357 100644
--- a/hwe/sammo/Command/Nation/che_의병모집.php
+++ b/hwe/sammo/Command/Nation/che_의병모집.php
@@ -61,7 +61,7 @@ class che_의병모집 extends Command\NationCommand
$reqTurn = $this->getPreReqTurn() + 1;
$postReqTurn = $this->getPostReqTurn();
- return "{$name}/{$reqTurn}턴(전략$postReqTurn)";
+ return "{$name}/{$reqTurn}턴(재사용 대기 $postReqTurn)";
}
public function getCost(): array
diff --git a/hwe/sammo/Command/Nation/che_이호경식.php b/hwe/sammo/Command/Nation/che_이호경식.php
index 0eb2ba26..e377bc3d 100644
--- a/hwe/sammo/Command/Nation/che_이호경식.php
+++ b/hwe/sammo/Command/Nation/che_이호경식.php
@@ -96,7 +96,7 @@ class che_이호경식 extends Command\NationCommand
$reqTurn = $this->getPreReqTurn() + 1;
$postReqTurn = $this->getPostReqTurn();
- return "{$name}/{$reqTurn}턴(전략$postReqTurn)";
+ return "{$name}/{$reqTurn}턴(재사용 대기 $postReqTurn)";
}
public function getCost(): array
diff --git a/hwe/sammo/Command/Nation/che_초토화.php b/hwe/sammo/Command/Nation/che_초토화.php
index e9c60b3c..cb94a282 100644
--- a/hwe/sammo/Command/Nation/che_초토화.php
+++ b/hwe/sammo/Command/Nation/che_초토화.php
@@ -99,7 +99,7 @@ class che_초토화 extends Command\NationCommand{
return 24;
}
- public function getNextAvailable():?int{
+ public function getNextAvailableTurn():?int{
return null;
}
diff --git a/hwe/sammo/Command/Nation/che_피장파장.php b/hwe/sammo/Command/Nation/che_피장파장.php
index 1e8af5d4..fb5fb3e1 100644
--- a/hwe/sammo/Command/Nation/che_피장파장.php
+++ b/hwe/sammo/Command/Nation/che_피장파장.php
@@ -9,6 +9,7 @@ use \sammo\{
LastTurn,
GameUnitConst,
Command,
+ KVStorage,
Message, MessageTarget
};
@@ -39,6 +40,7 @@ class che_피장파장 extends Command\NationCommand{
return false;
}
$destNationID = $this->arg['destNationID'];
+ $commandType = $this->arg['commandType'];
if(!is_int($destNationID)){
return false;
@@ -47,8 +49,17 @@ class che_피장파장 extends Command\NationCommand{
return false;
}
+ if(!is_string($commandType)){
+ return false;
+ }
+ if(!in_array($commandType, GameConst::$availableChiefCommand['전략'])){
+ return false;
+ }
+
+
$this->arg = [
- 'destNationID'=>$destNationID
+ 'destNationID'=>$destNationID,
+ 'commandType'=>$commandType
];
return true;
}
@@ -71,6 +82,24 @@ class che_피장파장 extends Command\NationCommand{
{
$this->setDestNation($this->arg['destNationID'], null);
+ if($this->getNationID() == 0){
+ $this->fullConditionConstraints=[
+ ConstraintHelper::OccupiedCity()
+ ];
+ return;
+ }
+
+ $cmd = buildNationCommandClass($this->arg['commandType'], $this->generalObj, $this->env, new LastTurn());
+
+ $currYearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
+ $nextAvailableTurn = $cmd->getNextAvailableTurn();
+ if($currYearMonth < $nextAvailableTurn){
+ $this->fullConditionConstraints=[
+ ConstraintHelper::AlwaysFail('해당 전략을 아직 사용할 수 없습니다')
+ ];
+ return;
+ }
+
$this->fullConditionConstraints=[
ConstraintHelper::OccupiedCity(),
ConstraintHelper::BeChief(),
@@ -85,9 +114,8 @@ class che_피장파장 extends Command\NationCommand{
public function getCommandDetailTitle():string{
$name = $this->getName();
$reqTurn = $this->getPreReqTurn()+1;
- $postReqTurn = $this->getPostReqTurn();
- return "{$name}/{$reqTurn}턴";
+ return "{$name}/{$reqTurn}턴(대상 재사용 대기 {$this->getTargetPostReqTurn()})";
}
public function getCost():array{
@@ -102,10 +130,20 @@ class che_피장파장 extends Command\NationCommand{
return 0;
}
+ public function getTargetPostReqTurn():int{
+ $genCount = Util::valueFit($this->nation['gennum'], GameConst::$initialNationGenLimit);
+ $nextTerm = Util::round(sqrt($genCount*2)*10);
+
+ $nextTerm = $this->generalObj->onCalcStrategic($this->getName(), 'delay', $nextTerm);
+ return $nextTerm;
+ }
+
public function getBrief():string{
$commandName = $this->getName();
+ $cmd = buildNationCommandClass($this->arg['commandType'], $this->generalObj, $this->env, new LastTurn());
+ $targetCommandName = $cmd->getName();
$destNationName = getNationStaticInfo($this->arg['destNationID'])['name'];
- return "【{$destNationName}】에 {$commandName}";
+ return "【{$destNationName}】에 【{$targetCommandName}】 {$commandName}";
}
@@ -139,13 +177,16 @@ class che_피장파장 extends Command\NationCommand{
$commandName = $this->getName();
$josaUl = JosaUtil::pick($commandName, '을');
+ $cmd = buildNationCommandClass($this->arg['commandType'], $this->generalObj, $this->env, new LastTurn());
+
+
$logger = $general->getLogger();
- $logger->pushGeneralActionLog("{$commandName} 발동! <1>$date>");
+ $logger->pushGeneralActionLog("{$cmd->getName()}> 전략의 {$commandName} 발동! <1>$date>");
$general->addExperience(5 * ($this->getPreReqTurn() + 1));
$general->addDedication(5 * ($this->getPreReqTurn() + 1));
-
- $broadcastMessage = "{$generalName}>{$josaYi} {$destNationName}>에 {$commandName}>{$josaUl} 발동하였습니다.";
+
+ $broadcastMessage = "{$generalName}>{$josaYi} {$destNationName}>에 {$cmd->getName()}> 전략의 {$commandName}>{$josaUl} 발동하였습니다.";
$nationGeneralList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND no != %i', $nationID, $generalID);
foreach($nationGeneralList as $nationGeneralID){
@@ -156,7 +197,7 @@ class che_피장파장 extends Command\NationCommand{
$josaYiCommand = JosaUtil::pick($commandName, '이');
- $broadcastMessage = "아국에 {$commandName}>{$josaYiCommand} 발동되었습니다.";
+ $broadcastMessage = "아국에 {$cmd->getName()}> 전략의 {$commandName}>{$josaYiCommand} 발동되었습니다.";
$destNationGeneralList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND no != %i', $nationID, $generalID);
foreach($destNationGeneralList as $destNationGeneralID){
@@ -166,12 +207,17 @@ class che_피장파장 extends Command\NationCommand{
}
$destNationLogger = new ActionLogger(0, $destNationID, $year, $month);
- $destNationLogger->pushNationalHistoryLog("{$nationName}>의 {$generalName}>{$josaYi} 아국에 {$commandName}>{$josaUl} 발동");
+ $destNationLogger->pushNationalHistoryLog("{$nationName}>의 {$generalName}>{$josaYi} 아국에 {$cmd->getName()}> {$commandName}>{$josaUl} 발동");
$destNationLogger->flush();
- $logger->pushNationalHistoryLog("{$generalName}>{$josaYi} {$destNationName}>에 {$commandName}>{$josaUl} 발동");
-
+ $logger->pushNationalHistoryLog("{$generalName}>{$josaYi} {$destNationName}>에 {$cmd->getName()}> {$commandName}>{$josaUl} 발동");
+ $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
+ $destNationStor = KVStorage::getStorage($db, $destNationID, 'nation_env');
+
+ $yearMonth = Util::joinYearMonth($env['year'], $env['month']);
+ $nationStor->setValue($cmd->getNextExecuteKey(), $yearMonth + $this->getTargetPostReqTurn());
+ $destNationStor->setValue($cmd->getNextExecuteKey(), $yearMonth + static::$delayCnt);
$general->applyDB($db);
@@ -212,17 +258,14 @@ class che_피장파장 extends Command\NationCommand{
$currYearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']);
foreach(GameConst::$availableChiefCommand['전략'] as $commandType){
$cmd = buildNationCommandClass($commandType, $generalObj, $this->env, new LastTurn());
- if($cmd->getName() == $this->getName()){
- continue;
- }
$cmdName = $cmd->getName();
- $available = true;
- $nextAvailable = $cmd->getNextAvailable();
+ $remainTurn = 0;
+ $nextAvailableTurn = $cmd->getNextAvailableTurn();
- if($nextAvailable !== null && $currYearMonth < $cmd->getNextAvailable() - $cmd->getPreReqTurn()){
- $available = false;
+ if($nextAvailableTurn !== null && $currYearMonth < $nextAvailableTurn){
+ $remainTurn = $nextAvailableTurn - $currYearMonth;
}
- $availableCommandTypeList[$commandType] = [$cmdName, $available];
+ $availableCommandTypeList[$commandType] = [$cmdName, $remainTurn];
}
ob_start();
@@ -230,6 +273,7 @@ class che_피장파장 extends Command\NationCommand{
=\sammo\getMapHtml()?>
선택된 국가에 피장파장을 발동합니다.
지정한 전략을 상대국이 =static::$delayCnt?>턴 동안 사용할 수 없게됩니다.
+대신 아국은 지정한 전략을=$this->getTargetPostReqTurn()?>턴 동안 사용할 수 없습니다.
선포, 전쟁중인 상대국에만 가능합니다.
상대 국가를 목록에서 선택하세요.
배경색은 현재 피장파장 불가능 국가는 붉은색으로 표시됩니다.
@@ -240,16 +284,17 @@ class che_피장파장 extends Command\NationCommand{
style='color:=$nation['color']?>;=$nation['availableCommand']?'':'background-color:red;'?>'
>【=$nation['name']?> 】
+에
+ 전략을
getPreReqTurn()+1;
$postReqTurn = $this->getPostReqTurn();
- return "{$name}/{$reqTurn}턴(전략$postReqTurn)";
+ return "{$name}/{$reqTurn}턴(재사용 대기 $postReqTurn)";
}
public function getCost():array{
diff --git a/hwe/sammo/Command/Nation/che_허보.php b/hwe/sammo/Command/Nation/che_허보.php
index f219c9d3..1ac8ddc7 100644
--- a/hwe/sammo/Command/Nation/che_허보.php
+++ b/hwe/sammo/Command/Nation/che_허보.php
@@ -95,7 +95,7 @@ class che_허보 extends Command\NationCommand
$reqTurn = $this->getPreReqTurn() + 1;
$postReqTurn = $this->getPostReqTurn();
- return "{$name}/{$reqTurn}턴(전략$postReqTurn)";
+ return "{$name}/{$reqTurn}턴(재사용 대기 $postReqTurn)";
}
public function getCost(): array
diff --git a/hwe/sammo/Command/NationCommand.php b/hwe/sammo/Command/NationCommand.php
index 15a5ef30..e1e48075 100644
--- a/hwe/sammo/Command/NationCommand.php
+++ b/hwe/sammo/Command/NationCommand.php
@@ -27,13 +27,13 @@ abstract class NationCommand extends BaseCommand{
return $this->resultTurn;
}
- protected function getNextExecuteKey():string{
+ public function getNextExecuteKey():string{
$turnKey = static::$actionName;
$executeKey = "next_execute_{$turnKey}";
return $executeKey;
}
- public function getNextAvailable():?int{
+ public function getNextAvailableTurn():?int{
if($this->isArgValid && !$this->getPostReqTurn()){
return null;
}
@@ -47,7 +47,8 @@ abstract class NationCommand extends BaseCommand{
return;
}
if($yearMonth === null){
- $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']) + $this->getPostReqTurn();
+ $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month'])
+ + $this->getPostReqTurn() - $this->getPreReqTurn();
}
$db = \sammo\DB::db();
diff --git a/hwe/sammo/GameConstBase.php b/hwe/sammo/GameConstBase.php
index 387eaa52..0ce1ea56 100644
--- a/hwe/sammo/GameConstBase.php
+++ b/hwe/sammo/GameConstBase.php
@@ -321,7 +321,6 @@ class GameConstBase
'che_감축',
],
'전략'=>[
- 'che_피장파장',
'che_필사즉생',
'che_백성동원',
'che_수몰',
@@ -331,6 +330,7 @@ class GameConstBase
'che_급습',
],
'기타'=>[
+ 'che_피장파장',
'che_국기변경',
'che_국호변경',
]