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 $refreshScore 장수의 벌점 */ function checkLimit($refreshScore = null) { $session = Session::getInstance(); if ($session->userGrade >= 4) { return 0; } $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); if ($refreshScore === null) { $refreshScore = $db->queryFirstField('SELECT refresh_score FROM general_access_log WHERE `general_id`=%i', Session::getGeneralID()); } $refreshLimit = $gameStor->refreshLimit; if ($refreshScore > $refreshLimit) { return 2; //접속제한 90%이면 경고문구 } elseif ($refreshScore > $refreshLimit * 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(RandUtil $rng) { $firstname = $rng->choice(GameConst::$randGenFirstName); $middlename = $rng->choice(GameConst::$randGenMiddleName); $lastname = $rng->choice(GameConst::$randGenLastName); return "{$firstname}{$middlename}{$lastname}"; } function cityInfo(GeneralLite $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']] = formatName($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) ? formatName($topChiefs[12]['name'], $topChiefs[12]['npc']) : '-'; $level11Name = key_exists(11, $topChiefs) ? formatName($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:14px;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 commandGroup($typename, $type = 0) { if ($type == 0) { echo " "; } else { echo " "; } } function getCommandTable(General $general) { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor->turnOnCache(); $env = $gameStor->getAll(); $result = []; foreach (GameConst::$availableGeneralCommand as $commandCategory => $commandList) { $subList = []; foreach ($commandList as $commandClassName) { $commandObj = buildGeneralCommandClass($commandClassName, $general, $env); if (!$commandObj->canDisplay()) { continue; } $subList[] = [ 'value' => Util::getClassNameFromObj($commandObj), 'compensation' => $commandObj->getCompensationStyle(), 'possible' => $commandObj->hasMinConditionMet(), 'title' => $commandObj->getCommandDetailTitle(), 'simpleName' => $commandObj->getName(), 'reqArg' => $commandObj::$reqArg, ]; } $result[] = [ 'category' => $commandCategory, 'values' => $subList ]; } return $result; } function getChiefCommandTable(General $general) { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $gameStor->turnOnCache(); $env = $gameStor->getAll(); $result = []; foreach (GameConst::$availableChiefCommand as $commandCategory => $commandList) { $subList = []; foreach ($commandList as $commandClassName) { $commandObj = buildNationCommandClass($commandClassName, $general, $env, new LastTurn()); if (!$commandObj->canDisplay()) { continue; } $subList[] = [ 'value' => Util::getClassNameFromObj($commandObj), 'compensation' => $commandObj->getCompensationStyle(), 'possible' => $commandObj->hasMinConditionMet(), 'title' => $commandObj->getCommandDetailTitle(), 'simpleName' => $commandObj->getName(), 'reqArg' => $commandObj::$reqArg, ]; } $result[] = [ 'category' => $commandCategory, 'values' => $subList ]; } return $result; } function chiefCommandTable(General $generalObj) { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $userID = Session::getUserID(); $gameStor->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} 세"; } $refreshScoreTotal = round($generalObj->getAccessLogVar(GeneralAccessLogColumn::refreshScoreTotal) ?? 0, -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} 벌점 " . getRefreshScoreText($refreshScoreTotal) . " {$refreshScoreTotal}({$generalObj->getAccessLogVar(GeneralAccessLogColumn::refreshScore)})
"; } function generalInfo2(General $generalObj) { $general = $generalObj->getRaw(); $winRate = round($generalObj->getRankVar(RankColumn::killnum) / max($generalObj->getRankVar(RankColumn::warnum), 1) * 100, 2); $killRate = round($generalObj->getRankVar(RankColumn::killcrew) / max($generalObj->getRankVar(RankColumn::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(RankColumn::warnum)} 계략 {$generalObj->getRankVar(RankColumn::firenum)} 사관 {$general['belong']}년
승률 {$winRate} % 승리 {$generalObj->getRankVar(RankColumn::killnum)} 패배 {$generalObj->getRankVar(RankColumn::deathnum)}
살상률 {$killRate} % 사살 {$generalObj->getRankVar(RankColumn::killcrew)} 피살 {$generalObj->getRankVar(RankColumn::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(): int { return KVStorage::getStorage(DB::db(), 'game_env')->getValue('online_user_cnt') ?? 0; } 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->nationNotice['msg'] ?? ''; } function banner() { return sprintf( '%s %s / %s', GameConst::$title, VersionGit::getVersion(), 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; $dateObj = new \DateTimeImmutable(); $date = TimeUtil::format($dateObj, false); $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $isunited = $gameStor->isunited; $opentime = $gameStor->opentime; if ($userGrade == 6) { return; } if ($isunited == 2) { return; } if (!$generalID) { return; } if ($opentime > $date) { return; } $gameStor->refresh = $gameStor->refresh + $cnt; //TODO: +로 증가하는 값은 별도로 분리 $db->insertUpdate('general_access_log', [ GeneralAccessLogColumn::generalID->value => $generalID, GeneralAccessLogColumn::userID->value => $userID, GeneralAccessLogColumn::lastRefresh->value => $date, GeneralAccessLogColumn::refreshTotal->value => $cnt, GeneralAccessLogColumn::refresh->value => $cnt, GeneralAccessLogColumn::refreshScore->value => $cnt, GeneralAccessLogColumn::refreshScoreTotal->value => $cnt, ], [ GeneralAccessLogColumn::lastRefresh->value => $date, GeneralAccessLogColumn::refreshTotal->value => $db->sqleval('%b + %i', GeneralAccessLogColumn::refreshTotal->value, $cnt), GeneralAccessLogColumn::refresh->value => $db->sqleval('%b + %i', GeneralAccessLogColumn::refresh->value, $cnt), GeneralAccessLogColumn::refreshScore->value => $db->sqleval('%b + %i', GeneralAccessLogColumn::refreshScore->value, $cnt), GeneralAccessLogColumn::refreshScoreTotal->value => $db->sqleval('%b + %i', GeneralAccessLogColumn::refreshScoreTotal->value, $cnt), ]); $serverPath = __DIR__; $serverID = UniqueConst::$serverID; $logPath = "{$serverPath}/logs/{$serverID}/api_log.db"; $logDB = FileDB::db($logPath, $serverPath . '/../f_install/sql/api_log.sql'); $ip = $_SERVER['REMOTE_ADDR'] ?? 'local'; $date = date('Y-m-d H:i:s'); $logDB->insert('api_log', [ 'user_id' => $session->userID, 'ip' => $ip, 'date' => $date, 'path' => "refresh", 'arg' => JSON::encode([ 'type' => $type, ]), 'aux' => JSON::encode([ 'generalID' => $generalID, 'generalName' => $session->generalName, 'userName' => $session->userName, ]), ]); } function updateTraffic() { $online = getOnlineNum(); $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $admin = $gameStor->getValues(['year', 'month', 'refresh', 'maxonline', 'maxrefresh', 'recentTraffic']); /** @var array{year:int,month:int,refresh:int,maxonline:int,maxrefresh:int} $admin */ 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']; $recentTraffic = $admin['recentTraffic'] ?? []; if(count($recentTraffic) >= 5){ array_shift($recentTraffic); } $recentTraffic[] = [ 'year'=>$admin['year'], 'month' => $admin['month'], 'refresh' => $admin['refresh'], 'online' => $online, 'date' => TimeUtil::now(), ]; $gameStor->recentTraffic = $recentTraffic; $db->update('general_access_log', ['refresh' => 0], true); } function CheckOverhead() { //서버정보 $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); [$turnterm, $refreshLimit] = $gameStor->getValuesAsArray(['turnterm', 'refreshLimit']); $nextRefreshLimit = Util::round(pow($turnterm, 0.6) * 3) * 10; if ($nextRefreshLimit != $refreshLimit) { $gameStor->refreshLimit = $nextRefreshLimit; } } function isLock() { return DB::db()->queryFirstField("SELECT plock from plock WHERE `type`='GAME'") != 0; } function tryLock(): bool { //NOTE: 게임 로직과 관련한 모든 insert, update 함수들은 lock을 거칠것을 권장함. $db = DB::db(); // 잠금 $db->update('plock', [ 'plock' => 1, 'locktime' => TimeUtil::now(true) ], 'plock=0 AND type="GAME"'); return $db->affectedRows() > 0; } function unlock(): bool { // 풀림 $db = DB::db(); $db->update('plock', [ 'plock' => 0 ], 'type="GAME"'); 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('ng_auction', [ 'close_date' => $db->sqleval('DATE_ADD(close_date, INTERVAL %i MINUTE)', $minute) ], 'finished = 0'); } } function updateOnline() { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $nationname = ["재야"]; //국가별 이름 매핑 foreach (getAllNationStaticInfo() as $nation) { $nationname[$nation['nation']] = $nation['name']; } //동접수 $startTurn = cutTurn($gameStor->turntime, $gameStor->turnterm, false); $onlineUser = $db->query( 'SELECT %b, %b, %b FROM `general_access_log` as `log` INNER JOIN `general` ON `log`.%b = `general`.%b WHERE %b >= %s', GeneralColumn::no->value, GeneralColumn::name->value, GeneralColumn::nation->value, GeneralAccessLogColumn::generalID->value, GeneralColumn::no->value, GeneralAccessLogColumn::lastRefresh->value, $startTurn ); $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 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(RandUtil $rng) { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); [$tournament, $tnmt_trig, $tnmt_pattern] = $gameStor->getValuesAsArray(['tournament', 'tnmt_trig', 'tnmt_pattern']); //현재 토너먼트 없고, 자동개시 걸려있을때, 40%확률 if ($tournament != 0) { return; } if (!$tnmt_trig) { return; } if (!$rng->nextBool(0.4)) { return; } if (!$tnmt_pattern) { // 0 : 전력전, 1 : 통솔전, 2 : 일기토, 3 : 설전 //전력전 40%, 통, 일, 설 각 20% $tnmt_pattern = [0, 0, 1, 2, 3]; shuffle($tnmt_pattern); } $tnmt_type = array_pop($tnmt_pattern); $gameStor->setValue('tnmt_pattern', $tnmt_pattern); startTournament($tnmt_type); } function CheckHall($no) { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); //TODO: Enum타입을 두종류로 넣고, raw string이면 calcVar에서 받아오면? $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::createObjFromDB($no); $ttw = $generalObj->getRankVar(RankColumn::ttw); $ttd = $generalObj->getRankVar(RankColumn::ttd); $ttl = $generalObj->getRankVar(RankColumn::ttl); $tlw = $generalObj->getRankVar(RankColumn::tlw); $tld = $generalObj->getRankVar(RankColumn::tld); $tll = $generalObj->getRankVar(RankColumn::tll); $tsw = $generalObj->getRankVar(RankColumn::tsw); $tsd = $generalObj->getRankVar(RankColumn::tsd); $tsl = $generalObj->getRankVar(RankColumn::tsl); $tiw = $generalObj->getRankVar(RankColumn::tiw); $tid = $generalObj->getRankVar(RankColumn::tid); $til = $generalObj->getRankVar(RankColumn::til); $betWinGold = $generalObj->getRankVar(RankColumn::betwingold); $betGold = Util::valueFit($generalObj->getRankVar(RankColumn::betgold), 1); $win = $generalObj->getRankVar(RankColumn::killnum); $war = Util::valueFit($generalObj->getRankVar(RankColumn::warnum), 1); $kill = $generalObj->getRankVar(RankColumn::killcrew); $death = Util::valueFit($generalObj->getRankVar(RankColumn::deathcrew), 1); $killPerson = $generalObj->getRankVar(RankColumn::killcrew_person); $deathPerson = Util::valueFit($generalObj->getRankVar(RankColumn::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(RankColumn::from($typeName)); } else if ($valueType === 'calc') { $value = $calcVar[$typeName]; } //승률,살상률인데 10회 미만 전투시 스킵 if (($typeName === 'winrate' || $typeName === 'killrate') && $generalObj->getRankVar(RankColumn::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(RankColumn::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(RandUtil $rng, General $general, string $acquireType): bool { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); //아이템 습득 상황 $availableUnique = []; //TODO: 너무 바보 같다. 장기적으로는 유니크 아이템 테이블 같은게 필요하지 않을까? //일단은 '획득' 시에만 동작하므로 이대로 사용하기로... $occupiedUnique = []; $invalidItemType = []; foreach (array_keys(GameConst::$allItems) as $itemType) { $ownItem = $general->getItems()[$itemType] ?? null; if ($ownItem !== null && !$ownItem->isBuyable()) { $invalidItemType[$itemType] = true; } } foreach (array_keys(GameConst::$allItems) as $itemType) { if (key_exists($itemType, $invalidItemType)) { continue; } 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; } } $auctionItems = $db->queryFirstColumn( 'SELECT `target` FROM `ng_auction` WHERE `type` = %s AND `finished` = 0', AuctionType::UniqueItem->value ); foreach ($auctionItems as $itemCode) { if (key_exists($itemCode, $occupiedUnique)) { $occupiedUnique[$itemCode]++; } else { $occupiedUnique[$itemCode] = 1; } } foreach ($db->queryAllLists('SELECT namespace, count(*) as cnt FROM `storage` WHERE namespace LIKE "ut_%" GROUP BY namespace') as [$uniqueNS, $cnt]) { $itemCode = substr($uniqueNS, 3); $itemClass = buildItemClass($itemCode); if (!$itemClass) { continue; } if ($itemClass->isBuyable()) { continue; } $occupiedUnique[$itemCode] = ($occupiedUnique[$itemCode] ?? 0) + $cnt; } foreach (GameConst::$allItems as $itemType => $itemCategories) { if (key_exists($itemType, $invalidItemType)) { continue; } foreach ($itemCategories as $itemCode => $cnt) { if ($cnt == 0) { continue; } if (!key_exists($itemCode, $occupiedUnique)) { $availableUnique[] = [[$itemType, $itemCode], $cnt]; continue; } $remain = $cnt - $occupiedUnique[$itemCode]; if ($remain > 0) { $availableUnique[] = [[$itemType, $itemCode], $remain]; } } } if (!$availableUnique) { if ($general->getAuxVar('inheritRandomUnique')) { $general->setAuxVar('inheritRandomUnique', null); $general->increaseInheritancePoint(InheritanceKey::previous, GameConst::$inheritItemRandomPoint); $general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, -GameConst::$inheritItemRandomPoint); $userLogger = new UserLogger($general->getVar('owner')); $userLogger->push(sprintf("얻을 유니크가 없어 %d 포인트 반환", GameConst::$inheritItemRandomPoint), "inheritPoint"); } return false; } if ($general->getAuxVar('inheritRandomUnique')) { [$year, $month, $initYear, $initMonth] = $gameStor->getValuesAsArray(['year', 'month', 'init_year', 'init_month']); $relMonthByInit = Util::joinYearMonth($year, $month) - Util::joinYearMonth($initYear, $initMonth); $availableBuyUnique = $relMonthByInit >= GameConst::$minMonthToAllowInheritItem; if ($availableBuyUnique) { $general->setAuxVar('inheritRandomUnique', null); } } [$itemType, $itemCode] = $rng->choiceUsingWeightPair($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(RandUtil $rng, General $general, string $acquireType = '아이템'): bool { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); if ($general->getNPCType() >= 2) { return false; } $itemTypeCnt = count(GameConst::$allItems); [$startYear, $year, $month, $initYear, $initMonth] = $gameStor->getValuesAsArray(['startyear', 'year', 'month', 'init_year', 'init_month']); $relYear = $year - $startYear; $maxTrialCountByYear = 1; foreach (GameConst::$maxUniqueItemLimit as $tmpVals) { [$targetYear, $targetTrialCnt] = $tmpVals; if ($relYear < $targetYear) { break; } $maxTrialCountByYear = $targetTrialCnt; } $trialCnt = Util::valueFit($itemTypeCnt, null, $maxTrialCountByYear); $maxCnt = $itemTypeCnt; $relMonthByInit = Util::joinYearMonth($year, $month) - Util::joinYearMonth($initYear, $initMonth); $availableBuyUnique = $relMonthByInit >= GameConst::$minMonthToAllowInheritItem; foreach ($general->getItems() as $item) { if (!$item->isBuyable()) { $trialCnt -= 1; $maxCnt -= 1; } } if ($trialCnt <= 0) { LogText("{$general->getName()}, {$general->getID()} 모든 아이템", $trialCnt); if ($general->getAuxVar('inheritRandomUnique')) { $general->setAuxVar('inheritRandomUnique', null); $general->increaseInheritancePoint(InheritanceKey::previous, GameConst::$inheritItemRandomPoint); $general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, -GameConst::$inheritItemRandomPoint); $userLogger = new UserLogger($general->getVar('owner')); $userLogger->push(sprintf("유니크를 얻을 공간이 없어 %d 포인트 반환", GameConst::$inheritItemRandomPoint), "inheritPoint"); } return false; } $scenario = $gameStor->scenario; $genCount = $db->queryFirstField('SELECT count(*) FROM general WHERE npc<2'); if ($scenario < 100) { $prob = 1 / ($genCount * 3 * $itemTypeCnt); // 3~4개월에 하나씩 등장 } else { $prob = 1 / ($genCount * $itemTypeCnt); // 1~2개월에 하나씩 등장 } if ($acquireType == '설문조사') { $prob = 1 / ($genCount * $itemTypeCnt * 0.7 / 3); // 투표율 70%, 설문조사 한번에 2~3개 등장 } else if ($acquireType == '랜덤 임관') { $prob = 1 / ($genCount * $itemTypeCnt / 10 / 2); // 랜임시 2개(10%) 등장(200명중 20명 랜임시도?) } else if ($acquireType == '건국') { $prob = 1 / ($genCount * $itemTypeCnt / 10 / 4); // 건국시 4개(20%) 등장(200명시 20국 정도 됨) } $prob *= GameConst::$uniqueTrialCoef; $prob = Util::valueFit($prob, null, GameConst::$maxUniqueTrialProb); //최대치 감소 $result = false; $prob /= sqrt(7); $moreProb = pow(10, 1 / 4); if ($general->getAuxVar('inheritRandomUnique') && $availableBuyUnique) { //포인트로 랜덤 유니크 획득 $prob = 1; } foreach (Util::range($maxCnt) as $_idx) { if ($rng->nextBool($prob)) { $result = true; break; } $prob *= $moreProb; } if (!$result) { LogText("{$general->getName()}, {$general->getID()} 유니크 실패 {$maxCnt}", $prob); return false; } LogText("{$general->getName()}, {$general->getID()} 유니크 성공 {$maxCnt}", $prob); return giveRandomUniqueItem($rng, $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::createObjListFromDB( $db->queryFirstColumn( 'SELECT `no` FROM general WHERE nation=%i AND no != %i', $nationID, $lordID ), ['npc', 'owner', 'gold', 'rice', 'experience', 'explevel', 'dedication', 'dedlevel', 'belong', 'aux'], ); $nationGeneralList[$lordID] = $lord; $nation['generals'] = array_keys($nationGeneralList); $nation['aux'] = Json::decode($nation['aux']); $nation['msg'] = $nationStor->nationNotice['msg'] ?? ''; $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) { if ($general->getNPCType() < 2) { $general->setAuxVar( InheritanceKey::max_belong->value, max( $general->getVar('belong'), $general->getAuxVar(InheritanceKey::max_belong->value) ?? 0 ) ); } $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, $fiction] = $gameStor->getValuesAsArray(['year', 'month', 'fiction']); $nation = $general->getStaticNation(); $nationName = $nation['name']; $nationID = $nation['nation']; $candidate = null; //npc or npc유저인 경우 후계 찾기 if (!$fiction && $general->getNPCType() > 0) { $rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize( UniqueConst::$hiddenSeed, 'NextNPCRuler', $year, $month, $general->getID(), ))); $rawCandidates = $db->query( '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 asc', $general->getVar('affinity'), $general->getVar('affinity'), $general->getVar('affinity'), $nationID ); if ($rawCandidates) { $candidates = []; $minNPCMatch = $rawCandidates[0]['npcmatch2']; foreach ($rawCandidates as $candidate) { if (!$candidate['npcmatch2'] == $minNPCMatch) { break; } $candidates[] = $candidate; } $candidate = $rng->choice($candidates); } } 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(RandUtil $rng, array $cityGeneralList, string $reason): int { $injuryCount = 0; $josaRo = JosaUtil::pick($reason, '로'); $text = "{$reason}{$josaRo} 인해 부상을 당했습니다."; $db = DB::db(); foreach ($cityGeneralList as $general) { /** @var General $general */ $injuryProb = 0.3; $injuryProb = $general->onCalcStat($general, 'injuryProb', $injuryProb); if (!$rng->nextBool($injuryProb)) { continue; } $general->getLogger()->pushGeneralActionLog($text); $general->increaseVarWithLimit('injury', $rng->nextRangeInt(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(RandUtil $rng, $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 = $rng->nextRangeInt(0, 60 * $term - 1); $randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수 return TimeUtil::format($baseDateTime->add(TimeUtil::secondsToDateInterval($randSecond + $randFraction)), true); } function getRandTurn2(RandUtil $rng, $term, ?\DateTimeInterface $baseDateTime = null) { if ($baseDateTime === null) { $baseDateTime = new \DateTimeImmutable(); } else if ($baseDateTime instanceof \DateTime) { $baseDateTime = \DateTimeImmutable::createFromMutable($baseDateTime); } else { throw new MustNotBeReachedException(); } $randSecond = $rng->nextRangeInt(0, 60 * $term - 1); $randFraction = $rng->nextRangeInt(0, 999999) / 1000000; //6자리 소수 return $baseDateTime->sub(TimeUtil::secondsToDateInterval($randSecond + $randFraction))->format('Y-m-d H:i:s.u'); }