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 " - "; - } else { - 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 " + "; + } else { + 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{
선택된 국가에 피장파장을 발동합니다.
지정한 전략을 상대국이 턴 동안 사용할 수 없게됩니다.
+대신 아국은 지정한 전략을getTargetPostReqTurn()?>턴 동안 사용할 수 없습니다.
선포, 전쟁중인 상대국에만 가능합니다.
상대 국가를 목록에서 선택하세요.
배경색은 현재 피장파장 불가능 국가는 붉은색으로 표시됩니다.
@@ -240,16 +284,17 @@ class che_피장파장 extends Command\NationCommand{ style='color:;' >【 +에 + 전략을 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_국호변경', ]