Compare commits

...
Author SHA1 Message Date
Hide_D 32d2f7b0d1 feat: extend S100 dex sync through year 205 2026-08-09 10:54:06 +00:00
Hide_D 4ffd7b5bc0 fix: preserve S100 generated NPC stat floor 2026-07-31 05:06:15 +00:00
Hide_D 1e30659bdd test: verify S100 final-growth reselection 2026-07-31 01:19:23 +00:00
Hide_D 162f198f08 fix: stabilize S100 cooldowns and generated NPC growth 2026-07-31 01:11:47 +00:00
Hide_D 191abf83c0 test: isolate S100 inheritance browser logs 2026-07-29 08:51:51 +00:00
Hide_D edfeae5f0f fix: block S100 inheritance stat resets 2026-07-29 08:42:38 +00:00
Hide_D 424184a51a fix: define mixed S100 dex conversion ownership 2026-07-29 07:29:43 +00:00
Hide_D 03a321f95d fix: prevent S100 dex conversion refills 2026-07-29 06:36:33 +00:00
Hide_D e3615fa6bb fix: cap S100 NPC dex growth at forty percent 2026-07-29 05:20:53 +00:00
Hide_D 32991f736d fix: expose S100 event traits to frontend 2026-07-29 05:11:19 +00:00
Hide_D 8b2748d3d4 feat: 재선택 현재 능력치 기준 표시 2026-07-28 16:56:02 +00:00
Hide_D 883080e275 feat: 100기 NPC 동조 및 후보 추첨 기능 개선과 초기 능력치 계산 로직 추가 2026-07-28 16:28:13 +00:00
Hide_D ce807e2564 유니크 획득 확률 조금 더 증가 2026-07-28 16:11:24 +00:00
Hide_D 9a2ac82373 시나리오 문제점 수정 2026-07-28 16:07:21 +00:00
Hide_D 3c35539c16 CreateManyNPC 추가 2026-07-28 16:04:50 +00:00
Hide_D 5ab08403c6 feat: 100기 NPC 동조와 후보 추첨 보완 2026-07-28 16:02:14 +00:00
Hide_D c1ce478d22 feat: exclude prior event seasons from all-star pool 2026-07-28 00:43:46 +00:00
Hide_D d8bf8ba922 feat: expand 100th-season pool through phase 99 2026-07-28 00:29:25 +00:00
Hide_D 95f66e4c39 feat: add 100th-season all-star growth event 2026-07-25 10:46:23 +00:00
Hide_D 4de7ebec17 요양 깃수 추가 2026-03-04 14:16:08 +00:00
Hide_D d43dff35fb fix: 디버그 코드 복구 2026-02-18 18:54:46 +00:00
Hide_D 5b13f8bd30 ip 기록 2026-02-17 22:31:08 +09:00
Hide_D e51bc6f711 fix: 턴 데몬에 cookie jar 적용 2026-02-17 20:35:15 +09:00
Hide_D 1b08aa0c58 wip: 디버그 코드 추가 2026-02-17 19:12:28 +09:00
Hide_D f2547fe207 dbg 2026-02-17 18:32:04 +09:00
류화영 cec7726731 fix: 방랑군이 빈번하게 해산하는 문제 해결 시도 2026-01-19 22:50:11 +09:00
Hide_D d679a6fed8 fix: InheritancePointManager 버그 수정 2025-11-01 17:38:02 +00:00
Hide_D 6fe156eb7c fix: composer uninstall 누락 2025-10-29 13:00:46 +00:00
Hide_D 663ddbe76a fix: inheritance_result 작성 문제 2025-10-28 20:24:25 +00:00
Hide_D 30297454f7 refac: inheritance_result로 데이터 이관 2025-10-28 20:15:05 +00:00
Hide_D bc602722bf feat: schema에 inheritance_result 추가
- storage에 의미없이 쌓이는 데이터 이관
2025-10-28 20:09:30 +00:00
Hide_D be8f147216 dep: brandonwamboldt/utilphp 제거 2025-10-28 19:56:02 +00:00
Hide_D b9495fb64a refac: 턴 데몬 전체를 async로 변경경 2025-10-07 20:05:55 +09:00
Hide_D 07b217b290 fix: 서버 오픈 핸들러를 00초에 맞추도록 변경경 2025-10-07 19:54:24 +09:00
Hide_D 42fac46062 refac: brandonwamboldt/utilphp 의존 코드 수정
- 실제 의존성 제거는 이후에에
2025-10-07 19:26:22 +09:00
Hide_D 24bc8985d0 __filename 2025-10-04 11:39:35 +00:00
Hide_D 76a2d5ff63 daemon typescript 버전 추가 2025-10-04 11:29:03 +00:00
Hide_D 148f32f78b fix: execute engine updated 2025-10-04 11:04:36 +00:00
Hide_D 62f5b6597e proc.php 일부 수정
- api.php 미사용 경로 대비
2025-10-04 10:59:56 +00:00
Hide_D 7e810b0279 LogText 끄기 2025-09-07 06:01:04 +00:00
Hide_D 2336b29b65 fix: ?? 2025-08-30 06:15:25 +00:00
Hide_D 185c0e16a0 fix: AI 일부 key 접근 문제 수정 2025-08-30 06:11:37 +00:00
Hide_D 656baeb9ee fix: 토너먼트 분노 공격 확률 오류 수정 (#263)
Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/263
2025-08-23 10:08:13 +09:00
류화영 3e06304dc7 fix: 토너먼트 분노 공격 확률 오류 수정 2025-08-23 09:23:00 +09:00
Hide_D 4bb940c8c5 fix: ?? 연산자 우선순위 2025-08-20 12:59:40 +00:00
Hide_D 2b376b6aab fix: logError 버그 수정 2025-07-06 06:15:18 +00:00
Hide_D 04df26ff83 misc: 군웅할거 손견 국명 손책으로 변경
- 서버 시작 시점에서 손견은 맞으나, 땅따 시작점에는 손책임
2025-06-28 12:37:16 +00:00
Hide_D aca4240b92 game: 강서유서 월드로 명칭 변경 2025-06-26 18:37:42 +00:00
Hide_D faf26fa8a2 game: 강서유서 소설 월드(비세력) 수정 2025-06-26 18:36:19 +00:00
Hide_D 030ab1e7ea game: 군웅축록 연도 수정 2025-06-26 18:25:14 +00:00
Hide_D 7f2c1d4782 game: 역사모드4-2 황제 원술
- 제작자: 강서유서
- 유비가 서주를 가지고 있어야 원술을 치지 않나?
2025-06-26 18:23:36 +00:00
Hide_D 9132138a99 game: 군웅축록 외교 2025-06-26 17:54:32 +00:00
Hide_D 84875d60bd game: 역사모드2-2 반동탁연합 결성(정사)
- 제작자: 강서유서
2025-06-26 17:51:39 +00:00
Hide_D 3cbaf76626 game: 역사모드3-2 군웅축록
- 제작자: 강서유서
2025-06-26 17:26:26 +00:00
Hide_D f4846d0d66 game: 무한대흥 NPC 능력치 감소 2025-06-25 15:07:52 +00:00
Hide_D f183dcc72b frontend: 기술 제한 표기 수정 2025-06-25 14:16:19 +00:00
Hide_D 63cc5d8e88 game: 무한대흥 초기 성수 3천 2025-06-25 13:58:47 +00:00
Hide_D f858c32fdf feat: 점령 후 일반 도시 성벽 수치 조정 가능 2025-06-25 13:58:29 +00:00
Hide_D 60eb127d71 game: 천지비급 상인 등장 2025-06-25 13:54:47 +00:00
Hide_D ede33f3a37 game: ban 메시지 빈도 감소 2025-06-25 13:53:36 +00:00
Hide_D ffd53a2390 game: 무한대흥 스탯 2025-06-25 13:20:21 +00:00
Hide_D 2591fbde67 game: 무한대흥 시나리오 2025-06-25 13:12:41 +00:00
Hide_D 922d3f1938 feat: 기술 등급 제한 수치 유연화 2025-06-25 13:12:32 +00:00
Hide_D 64522ba8a6 fix: ChangeCity에서 _max 키 입력 가능하게 2025-06-25 13:09:24 +00:00
Hide_D 7b1eedb574 feat: ChangeCity에 최대 내정 수치도 연산자로 바꿀 수 있도록 2025-06-25 12:31:29 +00:00
Hide_D 7ec6ca605f run_daemon timeout 2025-05-28 13:55:30 +00:00
Hide_D 5a5c613814 game: 랜임 확률 보정 완화
- 강국의 디버프 감소소
2025-03-05 17:35:08 +00:00
102 changed files with 124151 additions and 407 deletions
-1
View File
@@ -12,7 +12,6 @@
}
},
"require": {
"brandonwamboldt/utilphp": "^1.1",
"sergeytsalkov/meekrodb": "^2.5",
"league/plates": "^3.5",
"vlucas/valitron": "^1.4",
Generated
+2 -52
View File
@@ -4,58 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "deed9afdc95bc42f84784df67d295223",
"content-hash": "7edae9b6be35e66ffd0d180f5fac6679",
"packages": [
{
"name": "brandonwamboldt/utilphp",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/brandonwamboldt/utilphp.git",
"reference": "36c32efc4f0679c05163464a550f45c8d83fe683"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brandonwamboldt/utilphp/zipball/36c32efc4f0679c05163464a550f45c8d83fe683",
"reference": "36c32efc4f0679c05163464a550f45c8d83fe683",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"satooshi/php-coveralls": "dev-master"
},
"type": "library",
"autoload": {
"psr-0": {
"utilphp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brandon Wamboldt",
"email": "brandon.wamboldt@gmail.com"
}
],
"description": "util.php is a collection of useful functions and snippets that you need or could use every day, designed to avoid conflicts with existing projects",
"homepage": "https://github.com/brandonwamboldt/utilphp",
"keywords": [
"collection",
"helpers",
"php",
"utility"
],
"support": {
"issues": "https://github.com/brandonwamboldt/utilphp/issues",
"source": "https://github.com/brandonwamboldt/utilphp/tree/master"
},
"time": "2015-02-02T17:56:14+00:00"
},
{
"name": "brick/math",
"version": "0.12.1",
@@ -5721,5 +5671,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}
+4 -1
View File
@@ -99,7 +99,10 @@ function logError(string $err, string $errstr, string $errpath, array $trace)
$err = str_replace(ROOT, '{ROOT}', $err);
$errstr = str_replace(ROOT, '{ROOT}', $errstr);
$errpath = str_replace(ROOT, '{ROOT}', $errpath);
$trace = array_map(function (string $text) {
$trace = array_map(function (string|array $text) {
if (is_array($text)) {
$text = Json::encode($text);
}
return str_replace(ROOT, '{ROOT}', $text);
}, $trace);
+1 -1
View File
@@ -31,7 +31,7 @@ $queryMap = [
];
if ($reqQueryType === null || !key_exists($reqQueryType, $queryMap)) {
$reqQueryType = Util::array_first_key($queryMap);
$reqQueryType = array_key_first($queryMap);
}
//로그인 검사
+5 -2
View File
@@ -243,11 +243,14 @@ $templates = new \League\Plates\Engine(__DIR__ . '/templates');
"SELECT nation,no,name,owner_name as ownerName, owner, picture, imgsvr,
experience, dedication,
dex1, dex2, dex3, dex4, dex5,
horse, weapon, book, item
horse, weapon, book, item, aux
FROM general WHERE %l",
$btn == "NPC 보기" ? "npc>=2" : "npc<2"
) as $general) {
$generalID = $general['no'];
foreach (['dex1', 'dex2', 'dex3', 'dex4', 'dex5'] as $dexKey) {
$general[$dexKey] = CentennialAllStarGrowthService::recordableRawValue($general, $dexKey);
}
$general['bgColor'] = $nationColor[$general['nation']] ?? GameConst::$basecolor4;
$general['fgColor'] = newColor($general['bgColor']);
$general['nationName'] = $nationName[$general['nation']];
@@ -427,4 +430,4 @@ $templates = new \League\Plates\Engine(__DIR__ . '/templates');
</div>
</body>
</html>
</html>
+22 -8
View File
@@ -22,8 +22,6 @@ $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['turntime', 'opentime', 'autorun_user', 'npcmode']);
increaseRefresh("내정보", 1);
$me = General::createObjFromDB($generalID, null, GeneralQueryMode::FullWithAccessLog);
$myset = $me->getVar('myset');
@@ -41,10 +39,26 @@ $lastRefresh = $db->queryFirstField(
$generalID
);
$targetTime = addTurn($lastRefresh, $gameStor->turnterm, GameConst::$minTurnDieOnPrestart);
if ($gameStor->turntime <= $gameStor->opentime) {
//서버 가오픈시 할 수 있는 행동
if ($me->getNPCType() == 0 && $me->getNationID() == 0) {
$nextChange = $me->getAuxVar('next_change');
if (!is_string($nextChange) || $nextChange === '') {
$nextChange = null;
}
increaseRefresh("내정보", 1);
if ($gameStor->turntime <= $gameStor->opentime) {
$targetTime = $me->getAuxVar('prestart_delete_after');
if (!is_string($targetTime) || $targetTime === '') {
$targetTime = addTurn(
$lastRefresh ?: TimeUtil::now(),
$gameStor->turnterm,
GameConst::$minTurnDieOnPrestart
);
$me->setAuxVar('prestart_delete_after', $targetTime);
$me->applyDB($db);
}
//서버 가오픈시 할 수 있는 행동
if ($me->getNPCType() == 0 && $me->getNationID() == 0) {
$showDieOnPrestartBtn = true;
if ($targetTime <= TimeUtil::now()) {
$availableDieOnPrestart = true;
@@ -174,7 +188,7 @@ $changeDefence999Atmos = $me->onCalcDomestic('changeDefenceTrain', "atmos999", $
<?php endif; ?>
<?php if ($gameStor->npcmode == 2 && $me->getNPCType() == 0) : ?>
다른 장수 선택 (<?= substr($me->getAuxVar('next_change') ?? TimeUtil::now(), 0, 19) ?> 부터)<br>
다른 장수 선택 (<?= $nextChange ? substr($nextChange, 0, 19) : '지금' ?>부터)<br>
<a href="select_general_from_pool.php" id='select_general_from_pool'><button type="button" style=background-color:<?= GameConst::$basecolor2 ?>;color:white;width:160px;height:30px;font-size:14px;>다른 장수 선택</button></a><br><br>
<?php endif; ?>
@@ -272,4 +286,4 @@ $changeDefence999Atmos = $me->onCalcDomestic('changeDefenceTrain', "atmos999", $
</div>
</body>
</html>
</html>
+17 -1
View File
@@ -51,6 +51,22 @@
display: none;
}
.picture_choice {
margin: 8px auto;
}
.picture_choice label,
.event_picture label {
display: inline-flex;
align-items: center;
gap: 4px;
margin: 4px 8px;
}
.picture_choice img {
object-fit: cover;
}
.custom_picture {
display: none;
}
@@ -61,4 +77,4 @@
.custom_stat {
display: none;
}
}
+6 -3
View File
@@ -1047,7 +1047,7 @@ function increaseRefresh($type = "", $cnt = 1)
$logDB = FileDB::db($logPath, $serverPath . '/../f_install/sql/api_log.sql');
$ip = $_SERVER['REMOTE_ADDR'] ?? 'local';
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'local';
$date = date('Y-m-d H:i:s');
$logDB->insert('api_log', [
@@ -1401,8 +1401,11 @@ function CheckHall($no)
foreach ($types as [$typeName, $valueType]) {
if ($valueType === 'natural') {
$value = $generalObj->getVar($typeName);
if ($valueType === 'natural') {
$value = $generalObj->getVar($typeName);
if (in_array($typeName, ['dex1', 'dex2', 'dex3', 'dex4', 'dex5'], true)) {
$value = CentennialAllStarGrowthService::recordableValue($generalObj, $typeName);
}
} else if ($valueType === 'rank') {
$value = $generalObj->getRankVar(RankColumn::from($typeName));
} else if ($valueType === 'calc') {
+1 -1
View File
@@ -686,7 +686,7 @@ function TechLimit($startYear, $year, $tech) : bool {
$relYear = $year - $startYear;
$relMaxTech = Util::valueFit(
floor($relYear / 5) + 1,
floor($relYear / GameConst::$techLevelIncYear) + GameConst::$initialAllowedTechLevel,
1,
GameConst::$maxTechLevel
);
+1 -1
View File
@@ -559,7 +559,7 @@ function checkStatistic()
$nationHist = '';
foreach (GameConst::$availableNationType as $nationType) {
if (!Util::array_get($nationHists[$nationType])) {
if (!($nationHists[$nationType] ?? null)) {
$nationHists[$nationType] = '-';
}
$nationHist .= getNationType($nationType) . "({$nationHists[$nationType]}), ";
+2 -2
View File
@@ -138,7 +138,7 @@ function JSCitiesBasedOnDistance(int $cityNo, int $maxDistance = 1): array{
$distanceList = searchDistance($cityNo, $maxDistance, true);
$result = [];
for ($dist = 1; $dist <= $maxDistance; $dist++) {
$result[$dist] = Util::array_get($distanceList[$dist], []);
$result[$dist] = $distanceList[$dist] ?? [];
}
return $result;
}
@@ -152,7 +152,7 @@ function printCitiesBasedOnDistance(int $cityNo, int $maxDistance = 1): string
for ($dist = 1; $dist <= $maxDistance; $dist++) {
$cityList = array_map(function ($cityID) {
return CityConst::byID($cityID)->name;
}, Util::array_get($distanceList[$dist], []));
}, $distanceList[$dist] ?? []);
$cityStr = join(', ', $cityList);
+7 -3
View File
@@ -1153,21 +1153,25 @@ function fight($tnmt_type, $tnmt, $phs, $group, $g1, $g2, $type)
];
//막판 분노
$factor1 = 1.0;
$factor2 = 1.0;
$ratio = rand() % 300;
if ($e1 / 5 > $energy1 && $damage1 > $damage2 && $gen1[$tp] >= $ratio) {
$damage2 *= Util::round((rand() % 301 + 200) / 100); // 200 ~ 500%
$factor2 = Util::round((rand() % 301 + 200) / 100); // 200 ~ 500%
$critical1 = 1;
$str = Util::choiceRandom($crticialSkillMap[$tnmt_type]);
$log[] = "<S>●</> <Y>{$gen1['name']}</>의 분노의 <M>{$str}</> 공격!";
}
$ratio = rand() % 300;
if ($e2 / 5 > $energy2 && $damage2 > $damage1 && $gen2[$tp] >= $ratio) {
$damage1 *= Util::round((rand() % 301 + 200) / 100); // 200 ~ 500%
$factor1 = Util::round((rand() % 301 + 200) / 100); // 200 ~ 500%
$critical2 = 1;
$str = Util::choiceRandom($crticialSkillMap[$tnmt_type]);
$log[] = "<S>●</> <Y>{$gen2['name']}</>의 분노의 <M>{$str}</> 공격!";
}
$damage1 *= $factor1;
$damage2 *= $factor2;
//1합 승부
if ($phase == 1) {
+24 -6
View File
@@ -10,7 +10,23 @@ function sortTokens(&$tokens){
});
}
function putInfoText(&$info){
function putInfoText(&$info, ?array $currentTargetEnv){
if (($info['event100Growth'] ?? false) === true) {
if ($currentTargetEnv === null) {
$displayStats = CentennialAllStarGrowthService::calculateUserInitialStats($info);
$info['selectionStatLabel'] = '시작 능력치';
} else {
$displayStats = CentennialAllStarGrowthService::calculateUserCurrentTargetStats(
$info,
$currentTargetEnv
);
$info['selectionStatLabel'] = '현재 변경 기준 능력치';
}
$info['selectionLeadership'] = $displayStats['leadership'];
$info['selectionStrength'] = $displayStats['strength'];
$info['selectionIntel'] = $displayStats['intel'];
}
if(key_exists('specialDomestic', $info)){
$class = buildGeneralSpecialDomesticClass($info['specialDomestic']);
$info['specialDomesticName'] = $class->getName();
@@ -34,7 +50,8 @@ $now = $oNow->format('Y-m-d H:i:s');
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$npcmode = $gameStor->getValue('npcmode');
$eventEnv = $gameStor->getValues(['npcmode', 'startyear', 'year', 'month']);
$npcmode = $eventEnv['npcmode'];
if($npcmode!=2){
Json::die([
'result'=>false,
@@ -42,7 +59,8 @@ if($npcmode!=2){
]);
}
$rawGeneral = $db->queryFirstRow('SELECT no, aux FROM general WHERE `owner` = %i', $userID);
$rawGeneral = $db->queryFirstRow('SELECT no, aux FROM general WHERE `owner` = %i', $userID);
$currentTargetEnv = $rawGeneral ? $eventEnv : null;
if($rawGeneral){
$generalAux = Json::decode($rawGeneral['aux']);
if(key_exists('next_change', $generalAux)&& $generalAux['next_change'] > $now){
@@ -62,7 +80,7 @@ if($tokens){
foreach($tokens as $token){
$valid_until = $token['reserved_until'];
$info = Json::decode($token['info']);
putInfoText($info);
putInfoText($info, $currentTargetEnv);
$info['uniqueName'] = $token['unique_name'];
$pick[] = $info;
}
@@ -83,7 +101,7 @@ $valid_until = null;
foreach(pickGeneralFromPool($db, $rng, $userID, 14) as $pickObj){
$valid_until = $pickObj->getValidUntil();
$info = $pickObj->getInfo();
putInfoText($info);
putInfoText($info, $currentTargetEnv);
$pick[] = $info;
}
sortTokens($pick);//좀 무식하지만..
@@ -91,4 +109,4 @@ Json::die([
'result'=>true,
'pick'=>$pick,
'validUntil'=>$valid_until
]);
]);
+1 -1
View File
@@ -144,7 +144,7 @@ function do도시임명(General $general, int $cityID, int $targetOfficerLevel):
return '올바르지 않은 도시입니다';
}
if ($general->getVar(GeneralColumn::officer_level) >= 4 && $myPenalty[PenaltyKey::NoChiefChange->value] ?? false) {
if ($general->getVar(GeneralColumn::officer_level) >= 4 && ($myPenalty[PenaltyKey::NoChiefChange->value] ?? false)) {
return '수뇌인 장수를 변경할 수 없는 상태입니다.';
}
+1 -1
View File
@@ -41,7 +41,7 @@ $penaltyInfo = array_merge($memberPenalty['any'] ?? [], $memberPenalty[DB::prefi
$penalty = [];
foreach($penaltyInfo as $penaltyKey => $penaltyValue){
if($penaltyValue['expire'] ?? 0 > TimeUtil::now()){
if(($penaltyValue['expire'] ?? 0) > TimeUtil::now()){
$penalty[$penaltyKey] = $penaltyValue['value'];
}
}
+92 -16
View File
@@ -8,10 +8,20 @@ WebUtil::requireAJAX();
$pick = Util::getPost('pick');
$leadership = Util::getPost('leadership', 'int', GameConst::$defaultStatMin);
$strength = Util::getPost('leadership', 'int', GameConst::$defaultStatMin);
$intel = Util::getPost('leadership', 'int', GameConst::$defaultStatMin);
$isCentennialAllStar = CentennialAllStarGrowthService::isActive();
$strength = Util::getPost(
$isCentennialAllStar ? 'strength' : 'leadership',
'int',
GameConst::$defaultStatMin
);
$intel = Util::getPost(
$isCentennialAllStar ? 'intel' : 'leadership',
'int',
GameConst::$defaultStatMin
);
$personal = Util::getPost('personal', 'string', null);
$use_own_picture = Util::getPost('use_own_picture', 'bool', false);
$pictureSource = Util::getPost('picture_source', 'string', 'selected');
if(!$pick){
@@ -60,16 +70,39 @@ if(!$selectInfo){
}
$selectInfo = Json::decode($selectInfo);
$ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID);
if(!$ownerInfo){
$ownerInfo = RootDB::db()->queryFirstRow(
'SELECT `name`,`picture`,`imgsvr`,`grade` FROM member WHERE `NO`=%i',
$userID
);
if(!$ownerInfo){
Json::die([
'result'=>false,
'reason'=>'멤버 정보를 가져오지 못했습니다.'
]);
}
$gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2');
]);
}
if ($isCentennialAllStar) {
if (!in_array($pictureSource, ['selected', 'own'], true)) {
Json::die([
'result' => false,
'reason' => '올바르지 않은 전콘 선택입니다.',
]);
}
if ($pictureSource === 'own') {
$canUseOwnPicture = in_array('picture', GameConst::$generalPoolAllowOption, true)
&& $env['show_img_level'] >= 1
&& $ownerInfo['grade'] >= 1
&& $ownerInfo['picture'] !== '';
if (!$canUseOwnPicture) {
Json::die([
'result' => false,
'reason' => '사용할 수 있는 내 전콘이 없습니다.',
]);
}
}
}
$gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2');
if ($gencount >= $maxgeneral) {
Json::die([
@@ -80,12 +113,25 @@ if ($gencount >= $maxgeneral) {
$poolClass = getGeneralPoolClass(GameConst::$targetGeneralPool);
/** @var AbsGeneralPool */
$pickedGeneral = new $poolClass($db, $selectInfo, $now);
if ($isCentennialAllStar) {
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'selectPickedGeneral',
$userID,
$pick
)));
$pickedGeneral = new $poolClass($db, $rng, $selectInfo, $now);
} else {
$pickedGeneral = new $poolClass($db, $selectInfo, $now);
}
$builder = $pickedGeneral->getGeneralBuilder();
if ($isCentennialAllStar) {
CentennialAllStarGrowthService::prepareInitialUser($builder, $selectInfo);
}
foreach(GameConst::$generalPoolAllowOption as $allowOption){
if($allowOption == 'stat'){
if($allowOption == 'stat' && !$isCentennialAllStar){
$leadership = Util::valueFit($leadership, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
$strength = Util::valueFit($strength, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
$intel = Util::valueFit($intel, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
@@ -98,14 +144,23 @@ foreach(GameConst::$generalPoolAllowOption as $allowOption){
}
$builder->setStat($leadership, $strength, $intel);
}
else if($allowOption == 'picture' && $use_own_picture){
$builder->setPicture($ownerInfo['imgsvr'], $ownerInfo['picture']);
else if(
$allowOption == 'picture'
&& (
(!$isCentennialAllStar && $use_own_picture)
|| ($isCentennialAllStar && $pictureSource === 'own')
)
){
$builder->setPicture($ownerInfo['imgsvr'], $ownerInfo['picture']);
}
else if($allowOption == 'ego'){
if(!$personal || $personal == 'Random'){
$personal = Util::choiceRandom(GameConst::$availablePersonality);
}
if(!array_search($personal, GameConst::$availablePersonality)){
$invalidPersonal = $isCentennialAllStar
? !in_array($personal, GameConst::$availablePersonality, true)
: !array_search($personal, GameConst::$availablePersonality);
if($invalidPersonal){
Json::die([
'result'=>false,
'reason'=>'올바르지 않은 성격입니다.'
@@ -113,7 +168,7 @@ foreach(GameConst::$generalPoolAllowOption as $allowOption){
}
$builder->setEgo($personal);
}
}
}
$userNick = $ownerInfo['name'];
@@ -122,7 +177,28 @@ $builder->setOwnerName($userNick);
$builder->setKillturn(5);
$builder->setNPCType(0);
$builder->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $env['turnterm']));
$builder->setAuxVar(
'prestart_delete_after',
addTurn($now, $env['turnterm'], GameConst::$minTurnDieOnPrestart)
);
$builder->fillRemainSpecAsZero($env);
if ($isCentennialAllStar) {
$candidateCities = $db->queryFirstColumn(
'SELECT city FROM city WHERE level >= 5 AND level <= 6 AND nation = 0'
);
if (!$candidateCities) {
$candidateCities = $db->queryFirstColumn(
'SELECT city FROM city WHERE level >= 5 AND level <= 6'
);
}
if (!$candidateCities) {
Json::die([
'result' => false,
'reason' => '장수를 생성할 소·중성이 없습니다.',
]);
}
$builder->setCityID($rng->choice($candidateCities));
}
$builder->build($env);
$generalID = $builder->getGeneralID();
if(!$generalID){
@@ -167,4 +243,4 @@ $rootDB->insert('member_log', [
Json::die([
'result'=>true,
'reason'=>'success'
]);
]);
+1 -1
View File
@@ -88,7 +88,7 @@ $penaltyInfo = array_merge($memberPenalty['any'] ?? [], $memberPenalty[DB::prefi
$penalty = [];
foreach ($penaltyInfo as $penaltyKey => $penaltyValue) {
if ($penaltyValue['expire'] ?? 0 > TimeUtil::now()) {
if (($penaltyValue['expire'] ?? 0) > TimeUtil::now()) {
$penalty[$penaltyKey] = $penaltyValue['value'];
}
}
+77 -25
View File
@@ -7,6 +7,7 @@ include "func.php";
WebUtil::requireAJAX();
$pick = Util::getPost('pick');
$pictureSource = Util::getPost('picture_source', 'string', 'selected');
if(!$pick){
Json::die([
@@ -35,10 +36,20 @@ if(!$generalID){
list(
$year,
$month,
$startYear,
$maxgeneral,
$npcmode,
$turnterm
) = $gameStor->getValuesAsArray(['year', 'month', 'maxgeneral', 'npcmode', 'turnterm']);
$turnterm,
$showImgLevel
) = $gameStor->getValuesAsArray([
'year',
'month',
'startyear',
'maxgeneral',
'npcmode',
'turnterm',
'show_img_level',
]);
if($npcmode!=2){
Json::die([
@@ -55,7 +66,10 @@ if(!$info){
]);
}
$ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID);
$ownerInfo = RootDB::db()->queryFirstRow(
'SELECT `name`,`picture`,`imgsvr`,`grade` FROM member WHERE `NO`=%i',
$userID
);
if(!$ownerInfo){
Json::die([
'result'=>false,
@@ -64,6 +78,27 @@ if(!$ownerInfo){
}
$info = Json::decode($info);
$isCentennialAllStar = CentennialAllStarGrowthService::isActive();
if ($isCentennialAllStar) {
if (!in_array($pictureSource, ['current', 'own', 'selected'], true)) {
Json::die([
'result' => false,
'reason' => '올바르지 않은 전콘 선택입니다.',
]);
}
if ($pictureSource === 'own') {
$canUseOwnPicture = in_array('picture', GameConst::$generalPoolAllowOption, true)
&& $showImgLevel >= 1
&& $ownerInfo['grade'] >= 1
&& $ownerInfo['picture'] !== '';
if (!$canUseOwnPicture) {
Json::die([
'result' => false,
'reason' => '사용할 수 있는 내 전콘이 없습니다.',
]);
}
}
}
$generalObj = General::createObjFromDB($generalID);
@@ -101,34 +136,51 @@ $db->update('select_pool',[
'reserved_until'=>null,
], '(owner=%i or reserved_until < %s) AND general_id is NULL', $userID, $now);
if(key_exists('leadership', $info)){
$generalObj->updateVar('leadership', $info['leadership']);
$generalObj->updateVar('strength', $info['strength']);
$generalObj->updateVar('intel', $info['intel']);
if ($isCentennialAllStar) {
CentennialAllStarGrowthService::prepareLegacyUserReselection($generalObj);
CentennialAllStarGrowthService::applyTarget($generalObj, $info, [
'startyear' => $startYear,
'year' => $year,
'month' => $month,
]);
} else {
if(key_exists('leadership', $info)){
$generalObj->updateVar('leadership', $info['leadership']);
$generalObj->updateVar('strength', $info['strength']);
$generalObj->updateVar('intel', $info['intel']);
}
if(key_exists('dex', $info)){
$generalObj->updateVar('dex1', $info['dex'][0]);
$generalObj->updateVar('dex2', $info['dex'][1]);
$generalObj->updateVar('dex3', $info['dex'][2]);
$generalObj->updateVar('dex4', $info['dex'][3]);
$generalObj->updateVar('dex5', $info['dex'][4]);
}
if(key_exists('ego', $info)){
$generalObj->updateVar('personal', $info['ego']);
}
if(key_exists('specialDomestic', $info)){
$generalObj->updateVar('special', $info['specialDomestic']);
}
if(key_exists('specialWar', $info)){
$generalObj->updateVar('special2', $info['specialWar']);
}
}
if(key_exists('picture', $info)){
if ($isCentennialAllStar) {
if ($pictureSource === 'own') {
$generalObj->updateVar('imgsvr', $ownerInfo['imgsvr']);
$generalObj->updateVar('picture', $ownerInfo['picture']);
} elseif ($pictureSource === 'selected' && key_exists('picture', $info)) {
$generalObj->updateVar('imgsvr', $info['imgsvr']);
$generalObj->updateVar('picture', $info['picture']);
}
} elseif(key_exists('picture', $info)){
$generalObj->updateVar('imgsvr', $info['imgsvr']);
$generalObj->updateVar('picture', $info['picture']);
}
if(key_exists('generalName', $info)){
$generalObj->updateVar('name', $info['generalName']);
}
if(key_exists('dex', $info)){
$generalObj->updateVar('dex1', $info['dex'][0]);
$generalObj->updateVar('dex2', $info['dex'][1]);
$generalObj->updateVar('dex3', $info['dex'][2]);
$generalObj->updateVar('dex4', $info['dex'][3]);
$generalObj->updateVar('dex5', $info['dex'][4]);
}
if(key_exists('ego', $info)){
$generalObj->updateVar('personal', $info['ego']);
}
if(key_exists('specialDomestic', $info)){
$generalObj->updateVar('special', $info['specialDomestic']);
}
if(key_exists('specialWar', $info)){
$generalObj->updateVar('special2', $info['specialWar']);
}
$generalObj->setAuxVar('next_change', TimeUtil::nowAddMinutes(12 * $turnterm));
$userNick = $ownerInfo['name'];
@@ -148,4 +200,4 @@ $generalObj->applyDB($db);
Json::die([
'result'=>true,
'reason'=>'success'
]);
]);
+3 -2
View File
@@ -51,8 +51,9 @@ function MessageBox($str)
function LogText($prefix, $variable)
{
$text = sprintf('%s : %s'."\r\n", $prefix, TVarDumper::dump($variable));
file_put_contents(ROOT.'/d_log/'.UniqueConst::$serverName.'_dbg_logs.txt', $text, FILE_APPEND);
return;
//$text = sprintf('%s : %s'."\r\n", $prefix, TVarDumper::dump($variable));
//file_put_contents(ROOT.'/d_log/'.UniqueConst::$serverName.'_dbg_logs.txt', $text, FILE_APPEND);
}
function prepareDir(string $dirPath, bool $forceCreate=true):bool{
+9 -1
View File
@@ -8,4 +8,12 @@ $session = Session::getInstance()->setReadOnly();
$db = DB::db();
TurnExecutionHelper::executeAllCommand();
$updated = false;
$locked = false;
$lastExecuted = TurnExecutionHelper::executeAllCommand($updated, $locked);
Json::die([
'result' => true,
'updated' => $updated,
'locked' => $locked,
'lastExecuted' => $lastExecuted,
]);
+3 -3
View File
@@ -526,7 +526,7 @@ function DeleteConflict($nation)
function getConquerNation($city): int
{
$conflict = Json::decode($city['conflict']);
return Util::array_first_key($conflict);
return array_key_first($conflict);
}
function ConquerCity(array $admin, General $general, array $city, array $defenderCityGeneralList)
@@ -785,8 +785,8 @@ function ConquerCity(array $admin, General $general, array $city, array $defende
'officer_set' => 0,
];
if ($city['level'] > 3) {
$query['def'] = 1000;
$query['wall'] = 1000;
$query['def'] = GameConst::$defaultCityWall;
$query['wall'] = GameConst::$defaultCityWall;
} else {
$query['def'] = $db->sqleval('def_max/2');
$query['wall'] = $db->sqleval('wall_max/2');
+18 -8
View File
@@ -39,6 +39,10 @@ class DieOnPrestart extends \sammo\BaseAPI
$gameStor->cacheValues(['turnterm', 'opentime', 'turntime', 'year', 'month']);
$general = $db->queryFirstRow('SELECT no,name,nation,owner_name,npc FROM general WHERE owner=%i AND npc = 0', $userID);
if (!$general) {
return '장수가 없습니다';
}
$lastRefresh = $db->queryFirstField(
'SELECT %b FROM general_access_log WHERE %b = %i',
GeneralAccessLogColumn::lastRefresh->value,
@@ -46,8 +50,9 @@ class DieOnPrestart extends \sammo\BaseAPI
$general['no']
);
if (!$general) {
return '장수가 없습니다';
$generalObj = General::createObjFromDB($general['no']);
if ($generalObj instanceof DummyGeneral) {
trigger_error("올바르지 않은 삭제 프로세스 $userID", E_USER_WARNING);
}
increaseRefresh("장수 삭제", 1);
@@ -61,18 +66,23 @@ class DieOnPrestart extends \sammo\BaseAPI
return '이미 국가에 소속되어있습니다.';
}
$targetTime = $generalObj->getAuxVar('prestart_delete_after');
if (!is_string($targetTime) || $targetTime === '') {
$targetTime = addTurn(
$lastRefresh ?: TimeUtil::now(),
$gameStor->turnterm,
GameConst::$minTurnDieOnPrestart
);
$generalObj->setAuxVar('prestart_delete_after', $targetTime);
$generalObj->applyDB($db);
}
//서버 가오픈시 할 수 있는 행동
$targetTime = addTurn($lastRefresh, $gameStor->turnterm, GameConst::$minTurnDieOnPrestart);
if ($targetTime > TimeUtil::now()) {
$targetTimeShort = substr($targetTime, 0, 19);
return "아직 삭제할 수 없습니다. {$targetTimeShort} 부터 가능합니다.";
}
$generalObj = General::createObjFromDB($general['no']);
if ($generalObj instanceof DummyGeneral) {
trigger_error("올바르지 않은 삭제 프로세스 $userID", E_USER_WARNING);
}
$generalName = $generalObj->getName();
$josaYi = JosaUtil::pick($generalName, '이');
$generalObj->kill($db, true, "<Y>{$generalName}</>{$josaYi} 홀연히 모습을 <R>감추었습니다</>");
+9 -2
View File
@@ -99,7 +99,7 @@ class Join extends \sammo\BaseAPI
}
$gameID = UniqueConst::$serverID;
$alreadyJoined = $db->queryFirstField('SELECT count(*) FROM `storage` WHERE `namespace` = %s AND `key` LIKE %s', 'inheritance_result', "{$gameID}_{$userID}_%");
$alreadyJoined = $db->queryFirstField('SELECT COUNT(*) FROM `inheritance_result` WHERE `server_id` = %s AND `owner` = %i', $gameID, $userID);
if ($alreadyJoined) {
//이미 받았음
return 0;
@@ -157,7 +157,7 @@ class Join extends \sammo\BaseAPI
$penalty = [];
foreach($penaltyInfo as $penaltyKey => $penaltyValue){
if($penaltyValue['expire'] ?? 0 > TimeUtil::now()){
if(($penaltyValue['expire'] ?? 0) > TimeUtil::now()){
$penalty[$penaltyKey] = $penaltyValue['value'];
}
}
@@ -435,6 +435,13 @@ class Join extends \sammo\BaseAPI
'specage2' => $specage2,
'special2' => $special2,
'penalty' => Json::encode($penalty),
'aux' => Json::encode([
'prestart_delete_after' => addTurn(
$now,
$admin['turnterm'],
GameConst::$minTurnDieOnPrestart
),
]),
]);
$generalID = $db->insertId();
$db->insert('general_access_log', [
@@ -4,6 +4,7 @@ namespace sammo\API\InheritAction;
use sammo\Session;
use DateTimeInterface;
use sammo\CentennialAllStarGrowthService;
use sammo\DB;
use sammo\Enums\APIRecoveryType;
use sammo\Enums\RankColumn;
@@ -98,6 +99,10 @@ class ResetStat extends \sammo\BaseAPI
return 'NPC는 능력치 초기화를 할 수 없습니다.';
}
if (!CentennialAllStarGrowthService::isStatResetAllowed()) {
return '100기 올스타 장수는 능력치 초기화를 사용할 수 없습니다.';
}
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
+7 -3
View File
@@ -8,6 +8,11 @@ use sammo\Util;
abstract class AbsFromUserPool extends AbsGeneralPool{
protected static function getCandidateWeight(array $info, int $owner): int|float
{
return array_sum($info['dex'] ?? []);
}
public function occupyGeneralName(): bool
{
$generalID = $this->getGeneralBuilder()->getGeneralID();
@@ -36,8 +41,7 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
$pool = [];
foreach($db->query('SELECT id, unique_name, info FROM select_pool WHERE reserved_until IS NULL AND general_id IS NULL', $pickCnt) as $cand){
$cand['info'] = Json::decode($cand['info']);
$dexTotal = array_sum($cand['info']['dex']);
$pool[] = [$cand, $dexTotal];
$pool[] = [$cand, static::getCandidateWeight($cand['info'], $owner)];
}
if(count($pool) < $pickCnt){
@@ -69,4 +73,4 @@ abstract class AbsFromUserPool extends AbsGeneralPool{
return array_values($result);
}
}
}
@@ -33,7 +33,7 @@ class event_전투특기_공성 extends \sammo\BaseItem{
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_SIEGE;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -28,7 +28,7 @@ class event_전투특기_궁병 extends \sammo\BaseItem{
if($statName === 'warAvoidRatio'){
return $value + 0.2;
}
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_ARCHER;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -28,7 +28,7 @@ class event_전투특기_귀병 extends \sammo\BaseItem{
if($statName === 'warMagicSuccessProb'){
return $value + 0.2;
}
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_WIZARD;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -32,7 +32,7 @@ class event_전투특기_기병 extends \sammo\BaseItem{
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_CAVALRY;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -33,7 +33,7 @@ class event_전투특기_보병 extends \sammo\BaseItem{
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_FOOTMAN;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -0,0 +1,68 @@
<?php
namespace sammo\ActionScenarioEffect;
use \sammo\iAction;
use sammo\Util;
use sammo\WarUnit;
use sammo\WarUnitCity;
use sammo\WarUnitTrigger\che_전멸시페이즈증가;
use sammo\WarUnitTriggerCaller;
class event_MoreEffect implements iAction
{
use \sammo\DefaultAction;
public function getWarPowerMultiplier(WarUnit $unit): array
{
if ($unit->isAttacker()) {
return [1.4, 0.7143];
}
return [1, 1];
}
public function onCalcDomestic(string $turnType, string $varType, float $value, $aux = null): float
{
$scoreMap = [
'상업' => 2,
'농업' => 2,
'치안' => 2,
'기술' => 2,
'성벽' => 2,
'수비' => 2,
'인구' => 2,
'민심' => 2,
];
if ($turnType == 'changeDefenceTrain') {
return 0;
}
if ($varType === 'score' && key_exists($turnType, $scoreMap)) {
$multiplier = $scoreMap[$turnType];
$value *= $multiplier;
return $value;
}
return $value;
}
public function onCalcNationalIncome(string $type, $amount){
if($type == 'gold'){
return $amount * 2;
}
if($type == 'rice'){
return $amount * 2;
}
if($type == 'pop' && $amount > 0){
return $amount * 2;
}
return $amount;
}
public function getBattlePhaseSkillTriggerList(\sammo\WarUnit $unit): ?WarUnitTriggerCaller
{
return new WarUnitTriggerCaller(
new che_전멸시페이즈증가($unit),
);
}
}
@@ -35,7 +35,7 @@ class che_event_공성 extends \sammo\BaseSpecial{
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_SIEGE;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -31,7 +31,7 @@ class che_event_궁병 extends \sammo\BaseSpecial{
if($statName === 'warAvoidRatio'){
return $value + 0.2;
}
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_ARCHER;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -30,7 +30,7 @@ class che_event_귀병 extends \sammo\BaseSpecial{
if($statName === 'warMagicSuccessProb'){
return $value + 0.2;
}
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_WIZARD;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -35,7 +35,7 @@ class che_event_기병 extends \sammo\BaseSpecial{
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_CAVALRY;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -35,7 +35,7 @@ class che_event_보병 extends \sammo\BaseSpecial{
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_FOOTMAN;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
+1 -1
View File
@@ -35,7 +35,7 @@ class che_공성 extends \sammo\BaseSpecial{
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_SIEGE;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
+1 -1
View File
@@ -31,7 +31,7 @@ class che_궁병 extends \sammo\BaseSpecial{
if($statName === 'warAvoidRatio'){
return $value + 0.2;
}
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_ARCHER;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
+1 -1
View File
@@ -30,7 +30,7 @@ class che_귀병 extends \sammo\BaseSpecial{
if($statName === 'warMagicSuccessProb'){
return $value + 0.2;
}
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_WIZARD;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
+1 -1
View File
@@ -35,7 +35,7 @@ class che_기병 extends \sammo\BaseSpecial{
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_CAVALRY;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
+1 -1
View File
@@ -35,7 +35,7 @@ class che_보병 extends \sammo\BaseSpecial{
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
if(\sammo\Util::starts_with($statName, 'dex')){
if(str_starts_with($statName, 'dex')){
$myArmType = 'dex'.GameUnitConst::T_FOOTMAN;
$opposeArmType = 'dex'.$aux['opposeType']->armType;;
if($aux['isAttacker'] && $opposeArmType === $statName){
@@ -0,0 +1,686 @@
<?php
namespace sammo;
use sammo\Scenario\GeneralBuilder;
final class CentennialAllStarGrowthService
{
public const POOL_CLASS = 'SPoolUnderU100';
public const AUX_KEY = 'event100_allstar';
public const TRAIT_UNLOCK_PROGRESS = 0.4;
public const NPC_PROGRESS_MULTIPLIER = 0.9;
public const DEX_EXTENSION_START_YEAR_OFFSET = 20;
public const DEX_EXTENSION_YEARS = 5;
private const STAT_KEYS = ['leadership', 'strength', 'intel'];
private const DEX_KEYS = ['dex1', 'dex2', 'dex3', 'dex4', 'dex5'];
public static function isActive(): bool
{
return GameConst::$targetGeneralPool === self::POOL_CLASS;
}
public static function isStatResetAllowed(): bool
{
return !self::isActive();
}
public static function initialAux(array $targetInfo, ?array $userInitialStats = null): array
{
$granted = array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
if ($userInitialStats !== null) {
foreach (self::STAT_KEYS as $key) {
$initial = (int) ($userInitialStats[$key] ?? GameConst::$defaultStatMin);
$granted[$key] = max(0, $initial - min($initial, GameConst::$defaultStatMin));
}
}
return [
'targetId' => (string) ($targetInfo['uniqueName'] ?? ''),
'granted' => $granted,
'dexConsumed' => array_fill_keys(self::DEX_KEYS, 0),
'dexFloor' => array_fill_keys(self::DEX_KEYS, 0),
'progressMonth' => -1,
'milestone' => 0,
'dexExtensionMilestone' => 0,
'naturalSpecialDomestic' => null,
'eventSpecialDomestic' => null,
'userInitialStats' => $userInitialStats,
'dexTargetRatio' => 1.0,
];
}
public static function attachInitialTarget(GeneralBuilder $builder, array $targetInfo): void
{
$builder->setAuxVar(self::AUX_KEY, self::initialAux($targetInfo));
}
/**
* Builds an ordinary-user stat total while preserving the selected
* candidate's relative strengths as closely as integer stats allow.
*
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateUserInitialStats(array $targetInfo): array
{
$targets = [];
$bases = [];
foreach (self::STAT_KEYS as $key) {
$target = min(
GameConst::$defaultStatMax,
max(0, (int) ($targetInfo[$key] ?? 0))
);
$targets[$key] = $target;
$bases[$key] = min($target, GameConst::$defaultStatMin);
}
$targetTotal = array_sum($targets);
$desiredTotal = min(GameConst::$defaultStatTotal, $targetTotal);
$baseTotal = array_sum($bases);
$capacityTotal = $targetTotal - $baseTotal;
if ($capacityTotal <= 0 || $desiredTotal <= $baseTotal) {
return $bases;
}
$ratio = ($desiredTotal - $baseTotal) / $capacityTotal;
$result = [];
$fractions = [];
foreach (self::STAT_KEYS as $idx => $key) {
$raw = $bases[$key] + ($targets[$key] - $bases[$key]) * $ratio;
$result[$key] = (int) floor($raw);
$fractions[] = [
'key' => $key,
'fraction' => $raw - $result[$key],
'order' => $idx,
];
}
usort($fractions, static function (array $lhs, array $rhs): int {
$fractionOrder = $rhs['fraction'] <=> $lhs['fraction'];
return $fractionOrder !== 0 ? $fractionOrder : $lhs['order'] <=> $rhs['order'];
});
$remainder = $desiredTotal - array_sum($result);
foreach ($fractions as $fraction) {
if ($remainder <= 0) {
break;
}
$key = $fraction['key'];
if ($result[$key] >= $targets[$key]) {
continue;
}
$result[$key]++;
$remainder--;
}
return $result;
}
/**
* Returns the event stat baseline that a newly selected target receives at
* the supplied game date. Organic growth can still leave the actual stat
* above this baseline.
*
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateUserCurrentTargetStats(
array $targetInfo,
array $env
): array {
$initialStats = self::calculateUserInitialStats($targetInfo);
$progress = self::calculateProgress(
(int) $env['startyear'],
(int) $env['year'],
(int) $env['month']
);
$result = [];
foreach (self::STAT_KEYS as $key) {
$target = min(
GameConst::$maxLevel,
max(0, (int) ($targetInfo[$key] ?? 0))
);
$result[$key] = max(
$initialStats[$key],
CentennialAllStarGrowth::statFloor(
$target,
GameConst::$defaultStatMin,
$progress
)
);
}
return $result;
}
public static function prepareInitialUser(
GeneralBuilder $builder,
array $targetInfo
): void {
$initialStats = self::calculateUserInitialStats($targetInfo);
$builder->setStat(
$initialStats['leadership'],
$initialStats['strength'],
$initialStats['intel']
);
$builder->setAuxVar(
self::AUX_KEY,
self::initialAux($targetInfo, $initialStats)
);
}
/**
* Old 100th-season characters did not distinguish their form-entered
* initial stats from organic growth. Before their first reselection, treat
* the ordinary creation range as the replaceable initial allocation.
*/
public static function prepareLegacyUserReselection(General $general): void
{
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux) || is_array($aux['userInitialStats'] ?? null)) {
return;
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$legacyInitialStats = [];
foreach (self::STAT_KEYS as $key) {
$current = (int) $general->getVar($key);
$granted[$key] = self::calculateLegacyUserGrant(
$current,
(int) ($granted[$key] ?? 0)
);
$beforeEventGrant = max(
0,
$current - max(0, (int) ($aux['granted'][$key] ?? 0))
);
$legacyInitialStats[$key] = min(
$beforeEventGrant,
GameConst::$defaultStatMax
);
}
$aux['granted'] = $granted;
$aux['userInitialStats'] = $legacyInitialStats;
$general->setAuxVar(self::AUX_KEY, $aux);
}
public static function calculateLegacyUserGrant(int $current, int $eventGrant): int
{
$eventGrant = max(0, $eventGrant);
$beforeEventGrant = max(0, $current - $eventGrant);
$replaceableInitialGrant = max(
0,
min($beforeEventGrant, GameConst::$defaultStatMax)
- min($beforeEventGrant, GameConst::$defaultStatMin)
);
return $eventGrant + $replaceableInitialGrant;
}
public static function calculateProgress(
int $startYear,
int $year,
int $month,
float $progressMultiplier = 1.0
): float {
if ($progressMultiplier < 0 || $progressMultiplier > 1) {
throw new \InvalidArgumentException('progress multiplier must be between 0 and 1');
}
return min(
1,
CentennialAllStarGrowth::progress($startYear, $year, $month)
* $progressMultiplier
);
}
public static function calculateDexTargetFloor(
int $target,
array $env,
float $targetRatio = 1.0
): int {
if ($targetRatio < 0 || $targetRatio > 1) {
throw new \InvalidArgumentException('dex target ratio must be between 0 and 1');
}
$target = self::calculateDexExtendedTarget($target, $env);
$scaledTarget = (int) floor($target * $targetRatio);
$progress = self::calculateProgress(
(int) $env['startyear'],
(int) $env['year'],
(int) $env['month']
);
return CentennialAllStarGrowth::dexFloor($scaledTarget, $progress);
}
public static function calculateDexExtensionProgress(array $env): float
{
return CentennialAllStarGrowth::progress(
(int) $env['startyear'] + self::DEX_EXTENSION_START_YEAR_OFFSET,
(int) $env['year'],
(int) $env['month'],
self::DEX_EXTENSION_YEARS
);
}
public static function calculateDexExtendedTarget(int $target, array $env): int
{
$target = max(0, $target);
$baseTarget = min(GameConst::$dexLimit, $target);
$extensionTarget = max(0, $target - $baseTarget);
if ($extensionTarget === 0) {
return $baseTarget;
}
return $baseTarget + (int) floor(
$extensionTarget * self::calculateDexExtensionProgress($env)
);
}
/**
* Mutates the General object but leaves persistence to the caller.
*
* @return array{progress:float,milestone:int,previousMilestone:int,dexExtensionMilestone:int,previousDexExtensionMilestone:int,hasDexExtensionTarget:bool,targetChanged:bool,changed:bool}
*/
public static function applyTarget(
General $general,
array $targetInfo,
array $env,
float $progressMultiplier = 1.0,
float $dexTargetRatio = 1.0
): array
{
$startYear = (int) $env['startyear'];
$year = (int) $env['year'];
$month = (int) $env['month'];
$progress = self::calculateProgress(
$startYear,
$year,
$month,
$progressMultiplier
);
$progressMonth = (int) floor(
max(0, ($year - $startYear) * 12 + $month - 1)
* $progressMultiplier
);
$targetId = (string) ($targetInfo['uniqueName'] ?? '');
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux)) {
$aux = self::initialAux($targetInfo);
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$targetChanged = ($aux['targetId'] ?? '') !== $targetId;
$dexConsumed = is_array($aux['dexConsumed'] ?? null)
? $aux['dexConsumed']
: array_fill_keys(self::DEX_KEYS, 0);
if ($targetChanged) {
$dexConsumed = array_fill_keys(self::DEX_KEYS, 0);
}
$dexFloor = is_array($aux['dexFloor'] ?? null)
? $aux['dexFloor']
: array_fill_keys(self::DEX_KEYS, 0);
$isUserTarget = is_array($aux['userInitialStats'] ?? null);
$nextUserInitialStats = $targetChanged && $isUserTarget
? self::calculateUserInitialStats($targetInfo)
: ($aux['userInitialStats'] ?? null);
$previousDexTargetRatio = is_numeric($aux['dexTargetRatio'] ?? null)
? (float) $aux['dexTargetRatio']
: 1.0;
$dexTargetRatioChanged = $previousDexTargetRatio !== $dexTargetRatio;
$userCurrentTargetStats = $isUserTarget
? self::calculateUserCurrentTargetStats($targetInfo, $env)
: null;
$changed = $dexTargetRatioChanged;
foreach (self::STAT_KEYS as $key) {
if (!array_key_exists($key, $targetInfo)) {
continue;
}
$target = min(GameConst::$maxLevel, max(0, (int) $targetInfo[$key]));
$floor = CentennialAllStarGrowth::statFloor(
$target,
GameConst::$defaultStatMin,
$progress
);
if ($userCurrentTargetStats !== null) {
$floor = $userCurrentTargetStats[$key];
}
$current = (int) $general->getVar($key);
if ($targetChanged) {
$result = CentennialAllStarGrowth::replaceTarget(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
} else {
$result = CentennialAllStarGrowth::advance(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
}
if ($result['value'] !== $current) {
$general->updateVar($key, $result['value']);
$changed = true;
}
$granted[$key] = $result['granted'];
}
$targetDex = $targetInfo['dex'] ?? [];
$hasDexExtensionTarget = false;
foreach (self::DEX_KEYS as $idx => $key) {
if (!array_key_exists($idx, $targetDex)) {
continue;
}
if ((int) $targetDex[$idx] > GameConst::$dexLimit) {
$hasDexExtensionTarget = true;
}
$floor = max(
0,
self::calculateDexTargetFloor(
(int) $targetDex[$idx],
$env,
$dexTargetRatio
) - max(0, (int) ($dexConsumed[$key] ?? 0))
);
$dexFloor[$key] = $floor;
$current = (int) $general->getVar($key);
if ($targetChanged || $dexTargetRatioChanged) {
$result = CentennialAllStarGrowth::replaceTarget(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
} else {
$result = CentennialAllStarGrowth::advance(
$current,
(int) ($granted[$key] ?? 0),
$floor
);
}
if ($result['value'] !== $current) {
$general->updateVar($key, $result['value']);
$changed = true;
}
$granted[$key] = $result['granted'];
}
$oldEventSpecial = $aux['eventSpecialDomestic'] ?? null;
if ($targetChanged && $oldEventSpecial !== null
&& $general->getVar('special') === $oldEventSpecial
) {
$general->updateVar(
'special',
$aux['naturalSpecialDomestic'] ?? GameConst::$defaultSpecialDomestic
);
$changed = true;
$aux['eventSpecialDomestic'] = null;
}
$targetSpecial = $targetInfo['specialDomestic'] ?? null;
if ($progress >= self::TRAIT_UNLOCK_PROGRESS && is_string($targetSpecial) && $targetSpecial !== '') {
if (($aux['naturalSpecialDomestic'] ?? null) === null) {
$aux['naturalSpecialDomestic'] = $general->getVar('special');
}
if ($general->getVar('special') !== $targetSpecial) {
$general->updateVar('special', $targetSpecial);
$changed = true;
}
$aux['eventSpecialDomestic'] = $targetSpecial;
}
$previousMilestone = (int) ($aux['milestone'] ?? 0);
$milestone = min(5, (int) floor($progress * 5 + 0.0000001));
$previousDexExtensionMilestone = (int) ($aux['dexExtensionMilestone'] ?? 0);
$dexExtensionProgress = self::calculateDexExtensionProgress($env);
$dexExtensionMilestone = $hasDexExtensionTarget
? min(5, (int) floor($dexExtensionProgress * 5 + 0.0000001))
: 0;
$aux['targetId'] = $targetId;
$aux['granted'] = $granted;
$aux['dexConsumed'] = $dexConsumed;
$aux['dexFloor'] = $dexFloor;
$aux['progressMonth'] = max((int) ($aux['progressMonth'] ?? -1), $progressMonth);
$aux['milestone'] = max($previousMilestone, $milestone);
$aux['dexExtensionMilestone'] = max(
$previousDexExtensionMilestone,
$dexExtensionMilestone
);
$aux['userInitialStats'] = $nextUserInitialStats;
$aux['dexTargetRatio'] = $dexTargetRatio;
$general->setAuxVar(self::AUX_KEY, $aux);
return [
'progress' => $progress,
'milestone' => $milestone,
'previousMilestone' => $previousMilestone,
'dexExtensionMilestone' => $dexExtensionMilestone,
'previousDexExtensionMilestone' => $previousDexExtensionMilestone,
'hasDexExtensionTarget' => $hasDexExtensionTarget,
'targetChanged' => $targetChanged,
'changed' => $changed
|| $targetChanged
|| $milestone > $previousMilestone
|| $dexExtensionMilestone > $previousDexExtensionMilestone,
];
}
public static function progressMultiplierFor(General $general): float
{
return self::progressMultiplierForNPCType($general->getNPCType());
}
public static function progressMultiplierForNPCType(int $npcType): float
{
return in_array($npcType, [3, 4], true)
? self::NPC_PROGRESS_MULTIPLIER
: 1.0;
}
public static function dexTargetRatioForNPCType(int $npcType): float
{
return in_array($npcType, [3, 4], true)
? GameConst::$centennialNpcDexTargetRatio
: 1.0;
}
public static function applyCurrentTargetToBuiltNPC(
\MeekroDB $db,
GeneralBuilder $builder,
array $targetInfo,
array $env
): ?array {
if (!self::isActive()) {
return null;
}
$general = General::createObjFromDB($builder->getGeneralID());
if (!in_array($general->getNPCType(), [3, 4], true)) {
return null;
}
self::initializeGeneratedNPC($general, $targetInfo);
$result = self::applyTarget(
$general,
$targetInfo,
$env,
self::NPC_PROGRESS_MULTIPLIER,
GameConst::$centennialNpcDexTargetRatio
);
$general->applyDB($db);
return $result;
}
/**
* Keeps the ordinary M/G-general stat total while aligning its strong,
* middle, and weak stats with the selected all-star target. The target's
* creation-date growth floor is applied immediately afterwards.
*/
public static function initializeGeneratedNPC(
General $general,
array $targetInfo
): void {
$generatedStats = [];
foreach (self::STAT_KEYS as $key) {
$generatedStats[$key] = (int) $general->getVar($key);
}
$initialStats = self::calculateGeneratedNPCInitialStats(
$targetInfo,
$generatedStats
);
foreach (self::STAT_KEYS as $key) {
$general->updateVar($key, $initialStats[$key]);
}
foreach (self::DEX_KEYS as $key) {
$general->updateVar($key, 0);
}
$general->setAuxVar(self::AUX_KEY, self::initialAux($targetInfo));
}
/**
* @param array<string, mixed> $targetInfo
* @param array{leadership:int,strength:int,intel:int} $generatedStats
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateGeneratedNPCInitialStats(
array $targetInfo,
array $generatedStats
): array {
$targetOrder = self::STAT_KEYS;
$keyOrder = array_flip(self::STAT_KEYS);
usort(
$targetOrder,
static function (string $lhs, string $rhs) use ($targetInfo, $keyOrder): int {
$targetCompare = (int) ($targetInfo[$rhs] ?? 0)
<=> (int) ($targetInfo[$lhs] ?? 0);
return $targetCompare !== 0
? $targetCompare
: $keyOrder[$lhs] <=> $keyOrder[$rhs];
}
);
$generatedValues = array_map(
static fn (string $key): int => (int) ($generatedStats[$key] ?? 0),
self::STAT_KEYS
);
rsort($generatedValues, SORT_NUMERIC);
$result = array_fill_keys(self::STAT_KEYS, 0);
foreach ($targetOrder as $idx => $key) {
$result[$key] = $generatedValues[$idx];
}
return $result;
}
/**
* Moves the event-backed part of a dex conversion with the converted
* value and consumes any guaranteed floor crossed by the source value.
* This keeps the monthly floor from refilling points already converted.
*/
public static function reconcileDexConversion(
General $general,
string $sourceKey,
string $destinationKey,
int $sourceBefore,
int $sourceAfter,
int $destinationBefore,
int $destinationAfter,
float $convertCoeff
): void {
if (!in_array($sourceKey, self::DEX_KEYS, true)
|| !in_array($destinationKey, self::DEX_KEYS, true)
|| $sourceKey === $destinationKey
) {
throw new \InvalidArgumentException('invalid dex conversion keys');
}
if ($convertCoeff < 0 || $convertCoeff > 1) {
throw new \InvalidArgumentException('dex conversion coefficient must be between 0 and 1');
}
$sourceDecrease = max(0, $sourceBefore - $sourceAfter);
$destinationIncrease = max(0, $destinationAfter - $destinationBefore);
if ($sourceDecrease === 0 && $destinationIncrease === 0) {
return;
}
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux)) {
return;
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$dexConsumed = is_array($aux['dexConsumed'] ?? null)
? $aux['dexConsumed']
: array_fill_keys(self::DEX_KEYS, 0);
$dexFloor = is_array($aux['dexFloor'] ?? null)
? $aux['dexFloor']
: [];
$sourceGrantedBefore = min(
max(0, $sourceBefore),
max(0, (int) ($granted[$sourceKey] ?? 0))
);
/*
* A dex value can contain both organic and event-backed points. Split
* the actual command deltas by their share of the source total instead
* of consuming either bucket first. This keeps the combined ownership
* stable when conversion and target reselection are interleaved.
*/
$eventGrantRemoved = $sourceBefore > 0
? intdiv($sourceDecrease * $sourceGrantedBefore, $sourceBefore)
: 0;
$sourceGrantedAfter = max(0, $sourceGrantedBefore - $eventGrantRemoved);
$destinationGrantedBefore = min(
max(0, $destinationBefore),
max(0, (int) ($granted[$destinationKey] ?? 0))
);
$eventGrantTransferred = $sourceBefore > 0
? intdiv($destinationIncrease * $sourceGrantedBefore, $sourceBefore)
: 0;
$eventGrantTransferred = min(
$destinationIncrease,
$eventGrantRemoved,
$eventGrantTransferred
);
$granted[$sourceKey] = $sourceGrantedAfter;
$granted[$destinationKey] = min(
max(0, $destinationAfter),
$destinationGrantedBefore + $eventGrantTransferred
);
$sourceFloor = max(
0,
(int) ($dexFloor[$sourceKey] ?? $sourceBefore)
);
$gapBefore = max(0, $sourceFloor - $sourceBefore);
$gapAfter = max(0, $sourceFloor - $sourceAfter);
$dexConsumed[$sourceKey] = max(
0,
(int) ($dexConsumed[$sourceKey] ?? 0)
+ max(0, $gapAfter - $gapBefore)
);
$aux['granted'] = $granted;
$aux['dexConsumed'] = $dexConsumed;
$general->setAuxVar(self::AUX_KEY, $aux);
}
public static function recordableValue(General $general, string $key): int
{
$aux = $general->getAuxVar(self::AUX_KEY);
$granted = is_array($aux) && is_array($aux['granted'] ?? null)
? (int) ($aux['granted'][$key] ?? 0)
: 0;
return CentennialAllStarGrowth::recordableValue((int) $general->getVar($key), $granted);
}
public static function recordableRawValue(array $general, string $key): int
{
$aux = Json::decode($general['aux'] ?? '{}');
$eventAux = is_array($aux[self::AUX_KEY] ?? null) ? $aux[self::AUX_KEY] : [];
$granted = is_array($eventAux['granted'] ?? null)
? (int) ($eventAux['granted'][$key] ?? 0)
: 0;
return CentennialAllStarGrowth::recordableValue((int) ($general[$key] ?? 0), $granted);
}
}
@@ -181,7 +181,7 @@ class che_랜덤임관 extends Command\GeneralCommand
if ($notIn) {
$rawGeneralsCnt = $db->query(
"SELECT g.`nation`, n.`gennum`, n.name, SUM((ra.value + 1000)/(rb.value + 1000)*(CASE WHEN g.`npc` < 2 THEN 1.2 ELSE 1 END)*(CASE WHEN g.`leadership` >= 40 THEN g.`leadership` ELSE 0 END)) AS warpower, SUM(SQRT(g.intel * g.strength) * 2 + g.leadership / 2)/5 AS develpower
"SELECT g.`nation`, n.`gennum`, n.name, SUM((ra.value + 50000)/(rb.value + 50000)*(CASE WHEN g.`npc` < 2 THEN 1.15 ELSE 1 END)*(CASE WHEN g.`leadership` >= 40 THEN g.`leadership` ELSE 0 END)) AS warpower, SUM(SQRT(g.intel * g.strength) * 2 + g.leadership / 2)/5 AS develpower
FROM general AS g
LEFT JOIN `rank_data` AS ra ON g.`no` = ra.general_id AND ra.`type` = 'killcrew_person'
LEFT JOIN `rank_data` AS rb ON g.`no` = rb.general_id AND rb.`type` = 'deathcrew_person'
@@ -193,7 +193,7 @@ class che_랜덤임관 extends Command\GeneralCommand
);
} else {
$rawGeneralsCnt = $db->query(
"SELECT g.`nation`, n.`gennum`, n.name, SUM((ra.value + 100)/(rb.value + 100)*(CASE WHEN g.`npc` < 2 THEN 1.2 ELSE 1 END)*(CASE WHEN g.`leadership` >= 40 THEN g.`leadership` ELSE 0 END)) AS warpower, SUM(SQRT(g.intel * g.strength) * 2 + g.leadership / 2)/5 AS develpower
"SELECT g.`nation`, n.`gennum`, n.name, SUM((ra.value + 50000)/(rb.value + 50000)*(CASE WHEN g.`npc` < 2 THEN 1.15 ELSE 1 END)*(CASE WHEN g.`leadership` >= 40 THEN g.`leadership` ELSE 0 END)) AS warpower, SUM(SQRT(g.intel * g.strength) * 2 + g.leadership / 2)/5 AS develpower
FROM general AS g
LEFT JOIN `rank_data` AS ra ON g.`no` = ra.general_id AND ra.`type` = 'killcrew_person'
LEFT JOIN `rank_data` AS rb ON g.`no` = rb.general_id AND rb.`type` = 'deathcrew_person'
@@ -208,7 +208,7 @@ class che_랜덤임관 extends Command\GeneralCommand
foreach ($rawGeneralsCnt as $nation) {
$calcCnt = $nation['warpower'] + $nation['develpower'];
if ($general->getNPCType() < 2 && Util::starts_with($nation['name'], 'ⓤ')) {
if ($general->getNPCType() < 2 && str_starts_with($nation['name'], 'ⓤ')) {
$calcCnt *= 100;
}
@@ -14,6 +14,7 @@ use function \sammo\tryUniqueItemLottery;
use \sammo\Constraint\ConstraintHelper;
use sammo\StaticEventHandler;
use sammo\CentennialAllStarGrowthService;
class che_숙련전환 extends Command\GeneralCommand
{
@@ -157,6 +158,7 @@ class che_숙련전환 extends Command\GeneralCommand
$logger = $general->getLogger();
$srcDex = $general->getVar('dex' . $this->srcArmType);
$destDex = $general->getVar('dex' . $this->destArmType);
$cutDex = Util::toInt($srcDex * static::$decreaseCoeff);
$cutDexText = number_format($cutDex);
$addDex = Util::toInt($cutDex * static::$convertCoeff);
@@ -164,6 +166,19 @@ class che_숙련전환 extends Command\GeneralCommand
$general->increaseVar('dex' . $this->srcArmType, -$cutDex);
$general->increaseVar('dex' . $this->destArmType, $addDex);
// 100기 이벤트 지급분을 목적 숙련으로 옮기고 소비한 성장 하한은 다시 채우지 않는다.
if (CentennialAllStarGrowthService::isActive()) {
CentennialAllStarGrowthService::reconcileDexConversion(
$general,
'dex' . $this->srcArmType,
'dex' . $this->destArmType,
$srcDex,
$general->getVar('dex' . $this->srcArmType),
$destDex,
$general->getVar('dex' . $this->destArmType),
static::$convertCoeff
);
}
$josaUl = JosaUtil::pick($cutDex, '을');
$josaRo = JosaUtil::pick($addDex, '로');
@@ -7,6 +7,7 @@ use \sammo\Util;
use \sammo\JosaUtil;
use \sammo\General;
use \sammo\ActionLogger;
use \sammo\CentennialAllStarGrowthService;
use \sammo\GameConst;
use \sammo\LastTurn;
use \sammo\GameUnitConst;
@@ -190,6 +191,12 @@ class che_인재탐색 extends Command\GeneralCommand
$newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env);
$newNPC->build($this->env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$newNPC,
$pickedNPC->getInfo(),
$this->env
);
$pickedNPC->occupyGeneralName();
$npcName = $newNPC->getGeneralName();
$josaRa = JosaUtil::pick($npcName, '라');
+1 -1
View File
@@ -164,7 +164,7 @@ class che_출병 extends Command\GeneralCommand
$currDist = 999;
$minDist = Util::array_first_key($distanceList);
$minDist = array_key_first($distanceList);
do {
//1: 최단 거리 도시 중 공격 대상이 있는가 확인
//2: 최단 거리 + 1 도시 중 공격 대상이 있는가 확인
@@ -7,6 +7,7 @@ use \sammo\Util;
use \sammo\JosaUtil;
use \sammo\General;
use \sammo\ActionLogger;
use \sammo\CentennialAllStarGrowthService;
use \sammo\GameConst;
use \sammo\LastTurn;
use \sammo\GameUnitConst;
@@ -160,6 +161,12 @@ class che_의병모집 extends Command\NationCommand
$newNPC->fillRemainSpecAsRandom($pickTypeList, $avgGen, $env);
$newNPC->build($this->env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$newNPC,
$pickedNPC->getInfo(),
$this->env
);
$pickedNPC->occupyGeneralName();
}
+1 -1
View File
@@ -162,7 +162,7 @@ class che_초토화 extends Command\NationCommand{
$aux = $this->nation['aux'];
if($destCity['level'] >= 8){
$aux[NationAuxKey::did_특성초토화->value] = $aux[NationAuxKey::did_특성초토화->value] ?? 0 + 1;
$aux[NationAuxKey::did_특성초토화->value] = ($aux[NationAuxKey::did_특성초토화->value] ?? 0) + 1;
}
$db->update('general', [
+1 -1
View File
@@ -50,7 +50,7 @@ class AllowJoinDestNation extends Constraint{
return false;
}
if(($this->general['npc']??2 < 2) && \str_starts_with($this->destNation['name'], 'ⓤ')){
if((($this->general['npc']??2) < 2) && \str_starts_with($this->destNation['name'], 'ⓤ')){
$this->reason = "유저장은 태수국에 임관할 수 없습니다.";
return false;
}
@@ -0,0 +1,67 @@
<?php
namespace sammo\Event\Action;
use sammo\ActionLogger;
use sammo\CentennialAllStarGrowthService;
use sammo\DB;
use sammo\General;
use sammo\Json;
class AdvanceCentennialAllStar extends \sammo\Event\Action
{
public function run(array $env)
{
if (!CentennialAllStarGrowthService::isActive()) {
return [__CLASS__, 0];
}
$db = DB::db();
$updated = 0;
foreach ($db->query(
'SELECT general.no, select_pool.info
FROM general
JOIN select_pool ON select_pool.general_id = general.no'
) as $row) {
$general = General::createObjFromDB((int) $row['no']);
$targetInfo = Json::decode($row['info']);
$result = CentennialAllStarGrowthService::applyTarget(
$general,
$targetInfo,
$env,
CentennialAllStarGrowthService::progressMultiplierFor($general),
CentennialAllStarGrowthService::dexTargetRatioForNPCType(
$general->getNPCType()
)
);
if ($result['milestone'] > $result['previousMilestone']) {
$percent = $result['milestone'] * 20;
$general->getLogger()->pushGeneralActionLog(
"<L>올스타 동조율</>이 <C>{$percent}%</>에 도달했습니다!",
ActionLogger::PLAIN
);
$general->getLogger()->pushGeneralHistoryLog(
"<L>올스타 동조율 {$percent}% 달성</>"
);
}
if ($result['hasDexExtensionTarget']
&& $result['dexExtensionMilestone'] > $result['previousDexExtensionMilestone']
) {
$percent = $result['dexExtensionMilestone'] * 20;
$general->getLogger()->pushGeneralActionLog(
"<L>추가 숙련 동조율</>이 <C>{$percent}%</>에 도달했습니다!",
ActionLogger::PLAIN
);
$general->getLogger()->pushGeneralHistoryLog(
"<L>추가 숙련 동조율 {$percent}% 달성</>"
);
}
if ($general->applyDB($db)) {
$updated++;
}
}
return [__CLASS__, $updated];
}
}
+17
View File
@@ -14,6 +14,13 @@ class ChangeCity extends \sammo\Event\Action{
'def'=>true,
'wall'=>true,
'trade'=>true,
'pop_max'=>true,
'agri_max'=>true,
'comm_max'=>true,
'secu_max'=>true,
'def_max'=>true,
'wall_max'=>true,
];
const REGEXP_PERCENT = '/^(\d+(\.\d+)?)%$/';// 123.5% [1]=float
const REGEXP_MATH = '/^([\+\-\/\*])(\d+(\.\d+)?)$/'; //+30 [1]=기호, [2]=float
@@ -120,6 +127,16 @@ class ChangeCity extends \sammo\Event\Action{
return DB::db()->sqleval('ROUND(%b * %d, 0)', $keyMax, $value/100);
}
if(str_ends_with($key, '_max') && preg_match(self::REGEXP_MATH, $value, $matches)){
//key가 아예 최대치에 대한 값이면 연산은 결과값이 0보다만 높게.
$op = $matches[1];
$value = $matches[2];
if($op == '/' && $value == 0){
throw new \InvalidArgumentException('0으로 나눌 수 없습니다.');
}
return DB::db()->sqleval('greatest(0, ROUND(%b '.$op.' %i, 0))', $key, $value);
}
if(preg_match(self::REGEXP_MATH, $value, $matches)){
$op = $matches[1];
$value = $matches[2];
+9 -1
View File
@@ -5,6 +5,7 @@ namespace sammo\Event\Action;
use \sammo\GameConst;
use \sammo\Util;
use \sammo\DB;
use sammo\CentennialAllStarGrowthService;
use sammo\LiteHashDRBG;
use sammo\RandUtil;
use sammo\UniqueConst;
@@ -35,7 +36,8 @@ class CreateManyNPC extends \sammo\Event\Action
)));
$result = [];
foreach (pickGeneralFromPool(DB::db(), $rng, 0, $cnt) as $pickedNPC) {
$db = DB::db();
foreach (pickGeneralFromPool($db, $rng, 0, $cnt) as $pickedNPC) {
$age = $rng->nextRangeInt(20, 25);
$birthYear = $env['year'] - $age;
$deathYear = $env['year'] + $rng->nextRangeInt(10, 50);
@@ -50,6 +52,12 @@ class CreateManyNPC extends \sammo\Event\Action
}
$newNPC->fillRemainSpecAsZero($env);
$newNPC->build($env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$newNPC,
$pickedNPC->getInfo(),
$env
);
$pickedNPC->occupyGeneralName();
$result[] = [
$newNPC->getGeneralName(), $newNPC->getGeneralID()
+14
View File
@@ -94,6 +94,11 @@ class GameConstBase
/** @var int 최대 레벨 */
public static $maxLevel = 255;
/** @var int 기술등급 허용 증가 단위 년 */
public static $techLevelIncYear = 5;
/** @var int 초기 기술등급 */
public static $initialAllowedTechLevel = 1;
/** @var int 최소 인구 증가량 */
public static $basePopIncreaseAmount = 5000;
/** @var int 증축시 인구 증가량 */
@@ -109,6 +114,9 @@ class GameConstBase
/** @var int 징병 허용 최소 인구 */
public static $minAvailableRecruitPop = 30000;
/** @var int 점령 후 일반 도시 성벽 */
public static $defaultCityWall = 1000;
/** @var int 초기 제한시 장수 제한 */
public static $initialNationGenLimit = 10;
@@ -176,6 +184,10 @@ class GameConstBase
/** @var array 선택할 수 없으나 게임 내에 유효한 장수 내정 특기 */
public static $optionalSpecialDomestic = [
'None',
'che_event_귀병', 'che_event_신산', 'che_event_환술', 'che_event_집중', 'che_event_신중',
'che_event_반계', 'che_event_보병', 'che_event_궁병', 'che_event_기병', 'che_event_공성',
'che_event_돌격', 'che_event_무쌍', 'che_event_견고', 'che_event_위압', 'che_event_저격',
'che_event_필살', 'che_event_징병', 'che_event_의술', 'che_event_격노', 'che_event_척사',
];
/** @var string 기본 전투 특기 */
@@ -408,6 +420,8 @@ class GameConstBase
public static $retirementYear = 80;
public static $targetGeneralPool = 'RandomNameGeneral';
/** @var float 100기 올스타 NPC의 원본 목표 대비 최종 숙련 비율 */
public static $centennialNpcDexTargetRatio = 0.4;
public static $generalPoolAllowOption = ['stat', 'ego', 'picture'];
public static $randGenFirstName = [
+9 -3
View File
@@ -2084,7 +2084,7 @@ class GeneralAI
}
}
$finalCityID = Util::array_first_key($cityScoreList);
$finalCityID = array_key_first($cityScoreList);
$dist = $distanceList[$capital][$finalCityID];
$targetCityID = $finalCityID;
if ($dist > 1) {
@@ -3129,8 +3129,14 @@ class GeneralAI
$db = DB::db();
$dupLord = $db->queryFirstField('SELECT COUNT(*) FROM general WHERE officer_level = 12 AND city = %i', $this->general->getCityID());
if($dupLord <= 1 && !in_array($this->general->getRawCity()['level'], [5, 6])) {
return null;
if($dupLord <= 1) {
if ($this->general->getRawCity() === null) {
$rawCity = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $this->general->getCityID());
$this->general->setRawCity($rawCity);
}
if (in_array($this->general->getRawCity()['level'], [5, 6])) {
return null;
}
}
$general = $this->general;
File diff suppressed because it is too large Load Diff
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace sammo\GeneralPool;
use sammo\AbsFromUserPool;
use sammo\CentennialAllStarGrowthService;
use sammo\Json;
use sammo\RandUtil;
class SPoolUnderU100 extends AbsFromUserPool
{
private const MIN_DEX_WEIGHT = 100000;
private const STAT_BONUS_MIN_TOTAL = 160;
private const STAT_BONUS_MAX_TOTAL = 190;
private const STAT_BONUS_MAX_MULTIPLIER = 1.5;
public function __construct(\MeekroDB $db, RandUtil $rng, array $info, string $validUntil)
{
$targetInfo = $info;
$initialInfo = $info;
foreach ([
'leadership',
'strength',
'intel',
'experience',
'dedication',
'dex',
'specialDomestic',
'specialWar',
] as $targetKey) {
unset($initialInfo[$targetKey]);
}
parent::__construct($db, $rng, $initialInfo, $validUntil);
$this->info = $targetInfo;
CentennialAllStarGrowthService::attachInitialTarget($this->builder, $targetInfo);
}
public static function getPoolName(): string
{
return '100기 올스타 클래식';
}
protected static function getCandidateWeight(array $info, int $owner): int|float
{
$dexWeight = max(
self::MIN_DEX_WEIGHT,
array_sum($info['dex'] ?? [])
);
if ($owner <= 0) {
return $dexWeight;
}
$statTotal = array_sum([
(int) ($info['leadership'] ?? 0),
(int) ($info['strength'] ?? 0),
(int) ($info['intel'] ?? 0),
]);
$normalizedStat = min(1, max(
0,
($statTotal - self::STAT_BONUS_MIN_TOTAL)
/ (self::STAT_BONUS_MAX_TOTAL - self::STAT_BONUS_MIN_TOTAL)
));
$statMultiplier = 1
+ (self::STAT_BONUS_MAX_MULTIPLIER - 1) * $normalizedStat;
return $dexWeight * $statMultiplier;
}
public static function initPool(\MeekroDB $db)
{
$jsonData = Json::decode(file_get_contents(__DIR__ . '/Pool/UnderS100.json'));
$columns = $jsonData['columns'];
$sqlValues = [];
foreach ($jsonData['data'] as $idx => $rawItem) {
if (count($rawItem) !== count($columns)) {
throw new \RuntimeException(($rawItem[0] ?? (string) $idx) . ' Error');
}
$item = array_combine($columns, $rawItem);
$uniqueName = sprintf('A100%04d', $idx + 1);
$item['uniqueName'] = $uniqueName;
$item['event100Growth'] = true;
$sqlValues[] = [
'unique_name' => $uniqueName,
'info' => Json::encode($item),
];
}
$db->insert('select_pool', $sqlValues);
}
}
+10 -2
View File
@@ -334,11 +334,19 @@ class InheritancePointManager
$inheritStor->setValue($key, [$point, $aux]);
}
$oldInheritStor = KVStorage::getStorage(DB::db(), "inheritance_result");
$db = DB::db();
$serverID = UniqueConst::$serverID;
$year = $gameStor->year;
$month = $gameStor->month;
$oldInheritStor->setValue("{$serverID}_{$ownerID}_{$general->getID()}_{$year}_{$month}", $inheritStor->getAll(true));
$db->insert('inheritance_result', [
'server_id'=>$serverID,
'owner'=>$ownerID,
'general_id'=>$general->getID(),
'year'=>$year,
'month'=>$month,
'value'=>Json::encode($inheritStor->getAll(true))
]);
}
+1 -1
View File
@@ -62,7 +62,7 @@ abstract class TriggerCaller{
return $this;
}
$lastKey = Util::array_last_key($this->triggerListByPriority);
$lastKey = array_key_last($this->triggerListByPriority);
if($lastKey < $priority){
$this->triggerListByPriority[$priority] = [$uniqueID=>$trigger];
return $this;
+1 -8
View File
@@ -422,7 +422,7 @@ class TurnExecutionHelper
$date = TimeUtil::now(true);
// 최종 처리 월턴의 다음 월턴시간 구함
$lastExecuted = $gameStor->turntime;
//$lastExecuted = $gameStor->turntime;
$prevTurn = cutTurn($gameStor->turntime, $gameStor->turnterm);
$nextTurn = addTurn($prevTurn, $gameStor->turnterm);
@@ -455,9 +455,6 @@ class TurnExecutionHelper
$gameStor->turntime = $currentTurn;
}
unlock();
if($lastExecuted !== $currentTurn){
$executed = true;
}
return $gameStor->turntime;
}
@@ -516,10 +513,6 @@ class TurnExecutionHelper
$gameStor->resetCache();
unlock();
if($lastExecuted !== $currentTurn){
$executed = true;
}
return $turntime;
}
}
+974
View File
@@ -0,0 +1,974 @@
{
"title":"【역사모드2-2】 반동탁연합 결성(정사)",
"startYear":187,
"life":1,
"fiction":0,
"const":{
"defaultMaxGeneral":600
},
"nation":[
["동탁", "#A9A9A9", 12000, 80000, "천자를 등에 업은 동탁", 2000, "명가", 5, ["낙양", "호로", "사수", "함곡", "장안", "홍농", "사곡", "안정"]],
["조조", "#0000FF", 10000, 40000, "동탁의거의 조조", 1000, "법가", 1, ["진류"]],
["유대", "#800000", 10000, 10000, "연주자사 유대, 유요의 형", 1000, "병가", 3, ["복양"]],
["공손찬", "#FF00FF", 10000, 10000, "백마의 공손찬", 1000, "병가", 1, ["북평"]],
["장양", "#7B68EE", 10000, 10000, "병주자사 장양", 1000, "병가", 3, ["진양"]],
["유언", "#483D8B", 10000, 30000, "익주목 유언", 1000, "유가", 4, ["성도", "자동", "면죽"]],
["원술", "#FFC0CB", 15000, 40000, "후장군 원술", 1000, "병가", 2, ["완"]],
["도겸", "#00FF00", 10000, 10000, "서주목 도겸", 1000, "유가", 4, ["서주", "하비.광릉"]],
["진온", "#808000", 10000, 10000, "양주자사 진온", 1000, "유가", 3, ["수춘", "합비"]],
["마등", "#808000", 10000, 10000, "서량태수 마등", 1000, "병가", 1, ["서량"]],
["공손도", "#A0522D", 10000, 10000, "요동의 왕 공손도", 1000, "덕가", 1, ["안평"]],
["한복", "#A0522D", 10000, 10000, "기주자사 한복", 1000, "병가", 3, ["업"]],
["유표", "#E0FFFF", 10000, 10000, "형주자사 유표", 1000, "유가", 3, ["양양", "장판", "강하"]],
["장로", "#20B2AA", 10000, 10000, "사실 이때 없는 애지만 장로", 1000, "오두미도", 1, ["한중"]],
["왕광", "#483D8B", 10000, 10000, "하내태수 왕광", 1000, "유가", 1, ["하내"]],
["유우", "#6495ED", 10000, 10000, "유주자사 유우", 1000, "유가", 5, ["계", "역경"]],
["원소", "#FFFF00", 10000, 10000, "발해태수 원소", 1000, "병가", 1, ["남피"]],
["사섭", "#800000", 10000, 10000, "교주자사 사섭", 1000, "유가", 3, ["교지", "남해"]],
["공융", "#800080", 10000, 10000, "북해태수 공융", 1000, "유가", 1, ["북해"]],
["한수", "#FFFFFF", 10000, 10000, "서량의 모반자 한수", 1000, "병가", 1, ["천수"]],
["공주", "#800080", 10000, 10000, "예주 자사 공주", 1000, "유가", 3, ["초"]],
["유총", "#7B68EE", 10000, 10000, "진민왕 유총", 1000, "덕가", 6, ["여남"]]
],
"diplomacy":[
[1,2,1,36],
[1,3,1,36],
[1,5,1,36],
[1,6,1,36],
[1,7,1,36],
[1,8,1,36],
[1,9,1,36],
[1,10,1,36],
[1,11,1,36],
[1,12,1,36],
[1,13,1,36],
[1,14,1,36],
[1,15,1,36],
[1,16,1,36],
[1,17,1,36],
[1,18,1,36],
[1,19,1,36],
[1,20,1,36],
[1,21,1,36],
[1,22,1,36],
[2,3,7,24],
[2,5,7,24],
[2,6,7,24],
[2,7,7,24],
[2,8,7,24],
[2,9,7,24],
[2,10,7,24],
[2,11,7,24],
[2,12,7,24],
[2,13,7,24],
[2,14,7,24],
[2,15,7,24],
[2,16,7,24],
[2,17,7,24],
[2,18,7,24],
[2,19,7,24],
[2,20,7,24],
[2,21,7,24],
[2,22,7,24],
[3,5,7,24],
[3,6,7,24],
[3,7,7,24],
[3,8,7,24],
[3,9,7,24],
[3,10,7,24],
[3,11,7,24],
[3,12,7,24],
[3,13,7,24],
[3,14,7,24],
[3,15,7,24],
[3,16,7,24],
[3,17,7,24],
[3,18,7,24],
[3,19,7,24],
[3,20,7,24],
[3,21,7,24],
[3,22,7,24],
[5,6,7,24],
[5,7,7,24],
[5,8,7,24],
[5,9,7,24],
[5,10,7,24],
[5,11,7,24],
[5,12,7,24],
[5,13,7,24],
[5,14,7,24],
[5,15,7,24],
[5,16,7,24],
[5,17,7,24],
[5,18,7,24],
[5,19,7,24],
[5,20,7,24],
[5,21,7,24],
[5,22,7,24],
[6,7,7,24],
[6,8,7,24],
[6,9,7,24],
[6,10,7,24],
[6,11,7,24],
[6,12,7,24],
[6,13,7,24],
[6,14,7,24],
[6,15,7,24],
[6,16,7,24],
[6,17,7,24],
[6,18,7,24],
[6,19,7,24],
[6,20,7,24],
[6,21,7,24],
[6,22,7,24],
[7,8,7,24],
[7,9,7,24],
[7,10,7,24],
[7,11,7,24],
[7,12,7,24],
[7,13,7,24],
[7,14,7,24],
[7,15,7,24],
[7,16,7,24],
[7,17,7,24],
[7,18,7,24],
[7,19,7,24],
[7,20,7,24],
[7,21,7,24],
[7,22,7,24],
[8,9,7,24],
[8,10,7,24],
[8,11,7,24],
[8,12,7,24],
[8,13,7,24],
[8,14,7,24],
[8,15,7,24],
[8,16,7,24],
[8,17,7,24],
[8,18,7,24],
[8,19,7,24],
[8,20,7,24],
[8,21,7,24],
[8,22,7,24],
[9,10,7,24],
[9,11,7,24],
[9,12,7,24],
[9,13,7,24],
[9,14,7,24],
[9,15,7,24],
[9,16,7,24],
[9,17,7,24],
[9,18,7,24],
[9,19,7,24],
[9,20,7,24],
[9,21,7,24],
[9,22,7,24],
[10,11,7,24],
[10,12,7,24],
[10,13,7,24],
[10,14,7,24],
[10,15,7,24],
[10,16,7,24],
[10,17,7,24],
[10,18,7,24],
[10,19,7,24],
[10,20,7,24],
[10,21,7,24],
[10,22,7,24],
[11,12,7,24],
[11,13,7,24],
[11,14,7,24],
[11,15,7,24],
[11,16,7,24],
[11,17,7,24],
[11,18,7,24],
[11,19,7,24],
[11,20,7,24],
[11,21,7,24],
[11,22,7,24],
[12,13,7,24],
[12,14,7,24],
[12,15,7,24],
[12,16,7,24],
[12,17,7,24],
[12,18,7,24],
[12,19,7,24],
[12,20,7,24],
[12,21,7,24],
[12,22,7,24],
[13,14,7,24],
[13,15,7,24],
[13,16,7,24],
[13,17,7,24],
[13,18,7,24],
[13,19,7,24],
[13,20,7,24],
[13,21,7,24],
[13,22,7,24],
[14,15,7,24],
[14,16,7,24],
[14,17,7,24],
[14,18,7,24],
[14,19,7,24],
[14,20,7,24],
[14,21,7,24],
[14,22,7,24],
[15,16,7,24],
[15,17,7,24],
[15,18,7,24],
[15,19,7,24],
[15,20,7,24],
[15,21,7,24],
[15,22,7,24],
[16,17,7,24],
[16,18,7,24],
[16,19,7,24],
[16,20,7,24],
[16,21,7,24],
[16,22,7,24],
[17,18,7,24],
[17,19,7,24],
[17,20,7,24],
[17,21,7,24],
[17,22,7,24],
[18,19,7,24],
[18,20,7,24],
[18,21,7,24],
[18,22,7,24],
[19,20,7,24],
[19,21,7,24],
[19,22,7,24],
[20,21,7,24],
[20,22,7,24],
[21,22,7,24]
],
"general":[
[0, "아회남", null, 0, null, 65, 74, 26, 0, 190, 225, null, null],
[0, "이적", null, 0, null, 29, 24, 84, 0, 162, 273, null, null],
[0, "윤상", null, 0, null, 51, 44, 62, 0, 194, 260, null, null],
[0, "윤묵", null, 0, null, 26, 15, 66, 0, 183, 239, null, null],
[0, "우금1", null, 0, null, 83, 78, 74, 0, 159, 221, null, null],
[0, "위관", null, 0, null, 69, 46, 79, 0, 220, 291, null, null],
[0, "원윤", null, 7, "완", 15, 14, 39, 1, 150, 199, null, null],
[0, "원희", null, 17, "남피", 66, 51, 63, 1, 176, 207, null, null],
[0, "염행", null, 20, "천수", 76, 84, 66, 1, 159, 215, null, null],
[0, "염유", null, 0, "계", 71, 57, 71, 0, 168, 227, null, null],
[0, "원술", null, 7, "완", 44, 65, 65, 12, 155, 199, null, null, "흥 마궁수 주제에 어딜 앞에 나서느냐! 썩 꺼져라!"],
[0, "원상", null, 0, null, 63, 75, 42, 0, 179, 207, null, null],
[0, "원소", null, 17, "남피", 81, 69, 70, 12, 154, 202, null, null, "안량과 문추만 이곳에 있엇어도…"],
[0, "염상", null, 0, null, 30, 27, 71, 0, 158, 199, null, null],
[0, "원담", null, 17, "남피", 60, 69, 30, 1, 173, 205, null, null],
[0, "염포", null, 14, "한중", 29, 25, 82, 11, 167, 231, null, null],
[0, "왕위", null, 0, null, 60, 70, 62, 0, 163, 222, null, null],
[0, "왕윤", null, 1, "장안", 26, 6, 68, 1, 137, 192, null, null],
[0, "왕항", null, 0, null, 76, 60, 61, 0, 184, 254, null, null],
[0, "왕찬", null, 0, null, 15, 3, 79, 0, 177, 217, null, null],
[0, "왕수", null, 19, "북해", 54, 27, 77, 11, 168, 218, null, null],
[0, "왕숙", null, 0, null, 36, 22, 76, 0, 195, 256, null, null],
[0, "왕준", null, 0, null, 81, 73, 77, 0, 206, 286, null, null],
[0, "왕쌍", null, 0, null, 67, 88, 19, 0, 195, 228, null, null],
[0, "왕창", null, 0, null, 74, 64, 79, 0, 188, 259, null, null],
[0, "왕평", null, 0, null, 83, 79, 76, 0, 192, 248, null, null],
[0, "왕보", null, 0, null, 62, 41, 79, 0, 171, 219, null, null],
[0, "왕문", null, 0, null, 65, 64, 31, 0, 162, 205, null, null],
[0, "왕릉", null, 0, null, 74, 64, 71, 0, 172, 251, null, null],
[0, "왕루", null, 0, null, 28, 30, 79, 0, 173, 211, null, null],
[0, "왕랑", null, 8, "서주", 47, 35, 79, 1, 167, 228, null, null],
[0, "온회", null, 0, null, 62, 36, 74, 0, 178, 228, null, null],
[0, "하안", null, 0, null, 15, 30, 72, 0, 190, 249, null, null],
[0, "괴월", null, 13, "양양", 47, 27, 82, 11, 157, 214, null, null],
[0, "괴량", null, 13, "양양", 68, 33, 88, 1, 155, 204, null, null],
[0, "가화", null, 0, null, 49, 65, 41, 0, 176, 222, null, null],
[0, "가규", null, 0, null, 78, 61, 84, 0, 177, 231, null, null],
[0, "하의", null, 0, null, 56, 69, 36, 0, 161, 195, null, null],
[0, "화흠", null, 1, "낙양", 33, 33, 82, 1, 157, 231, null, null],
[0, "가후", null, 1, "안정", 88, 48, 97, 1, 147, 223, null, null],
[0, "곽가", null, 0, null, 88, 15, 96, 0, 170, 207, null, null],
[0, "악환", null, 0, null, 65, 82, 42, 0, 196, 251, null, null],
[0, "곽사", null, 1, "안정", 64, 76, 13, 1, 146, 197, null, null],
[0, "악취", null, 0, null, 53, 66, 61, 0, 157, 195, null, null],
[0, "곽준", null, 0, null, 73, 66, 73, 0, 178, 217, null, null],
[0, "학소", null, 0, null, 86, 79, 78, 0, 185, 229, null, null],
[0, "악진", null, 2, "진류", 80, 84, 56, 1, 159, 218, null, null],
[0, "곽도", null, 17, "남피", 52, 50, 82, 1, 157, 205, null, null],
[0, "학맹", null, 0, null, 62, 67, 42, 0, 156, 300, null, null],
[0, "곽유지", null, 0, null, 28, 9, 69, 0, 190, 259, null, null],
[0, "곽익", null, 0, null, 73, 69, 69, 0, 207, 270, null, null],
[0, "곽회", null, 0, null, 87, 78, 81, 0, 187, 255, null, null],
[0, "하후위", null, 0, null, 71, 73, 49, 0, 204, 253, null, null],
[0, "하후연", null, 2, "진류", 90, 91, 68, 1, 162, 219, null, null],
[0, "하후은", null, 0, null, 63, 72, 53, 0, 180, 208, null, null],
[0, "하후화", null, 0, null, 48, 51, 71, 0, 207, 265, null, null],
[0, "하후혜", null, 0, null, 50, 44, 76, 0, 206, 242, null, null],
[0, "하후현", null, 0, null, 54, 39, 76, 0, 208, 254, null, null],
[0, "하후상", null, 0, null, 79, 72, 75, 0, 181, 225, null, null],
[0, "하후덕", null, 0, null, 69, 73, 32, 0, 178, 218, null, null],
[0, "하후돈", null, 2, "진류", 92, 90, 63, 1, 156, 220, null, null],
[0, "하후패", null, 0, null, 79, 84, 77, 0, 202, 262, null, null],
[0, "하후무", null, 0, null, 20, 7, 4, 0, 201, 259, null, null],
[0, "가충", null, 0, null, 54, 31, 89, 0, 217, 282, null, null],
[0, "하진", null, 0, null, 39, 40, 6, 0, 135, 189, null, null],
[0, "화웅", null, 1, "호로", 88, 92, 61, 1, 155, 190, null, null],
[0, "한윤", null, 7, "완", 26, 29, 65, 1, 155, 197, null, null],
[0, "관우", null, 0, "진류", 97, 97, 79, 0, 162, 219, null, null, "술이 식기 전에 돌아오겠소"],
[0, "환계", null, 7, "완", 25, 26, 68, 1, 156, 221, null, null],
[0, "관해", null, 0, null, 70, 82, 10, 0, 160, 193, null, null],
[0, "관구검", null, 0, null, 78, 74, 55, 0, 202, 255, null, null],
[0, "한현", null, 0, null, 22, 33, 62, 0, 163, 220, null, null],
[0, "한호", null, 15, "하내", 69, 72, 84, 11, 164, 224, null, null],
[0, "관흥", null, 0, null, 76, 86, 67, 0, 199, 234, null, null],
[0, "관색", null, 0, null, 74, 88, 56, 0, 200, 239, null, null],
[0, "한수", null, 20, "천수", 81, 75, 82, 12, 142, 215, null, null],
[0, "한숭", null, 0, null, 25, 15, 71, 0, 154, 210, null, null],
[0, "관정", null, 4, "북평", 36, 52, 73, 11, 158, 199, null, null],
[0, "한섬", null, 0, null, 69, 66, 36, 0, 159, 196, null, null],
[0, "감택", null, 0, null, 43, 49, 83, 0, 182, 243, null, null],
[0, "한충", null, 0, null, 64, 67, 18, 0, 151, 184, null, null],
[0, "한당", null, 7, "완", 76, 85, 61, 1, 156, 227, null, null],
[0, "한덕", null, 0, null, 63, 76, 25, 0, 171, 228, null, null],
[0, "감녕", null, 0, null, 93, 94, 76, 0, 174, 219, null, null],
[0, "한복", null, 12, "업", 28, 3, 27, 12, 149, 193, null, null],
[0, "관평", null, 0, null, 80, 82, 72, 0, 186, 219, null, null],
[0, "간옹", null, 0, "진류", 20, 32, 75, 0, 164, 225, null, null],
[0, "안량", null, 17, "남피", 88, 94, 21, 1, 160, 200, null, null],
[0, "위연", null, 0, null, 85, 92, 72, 0, 175, 234, null, null],
[0, "국의", null, 12, "업", 82, 78, 51, 1, 146, 197, null, null],
[0, "희지재", null, 0, null, 63, 7, 88, 0, 157, 196, null, null],
[0, "위속", null, 0, null, 67, 78, 31, 0, 156, 200, null, null],
[0, "위유", null, 16, "계", 46, 29, 74, 11, 151, 193, null, null],
[0, "우금2", null, 0, null, 72, 79, 38, 0, 173, 226, null, null],
[0, "우보", null, 1, "천수", 38, 60, 21, 1, 159, 192, null, null],
[0, "강유", null, 0, null, 92, 89, 90, 0, 202, 264, null, null],
[0, "공지", null, 0, null, 43, 53, 62, 0, 178, 197, null, null],
[0, "교유", null, 0, null, 64, 69, 38, 0, 143, 195, null, null],
[0, "공도", null, 0, null, 59, 70, 23, 0, 164, 200, null, null],
[0, "허공", null, 0, null, 44, 66, 64, 0, 155, 200, null, null],
[0, "허정", null, 21, "초", 23, 5, 68, 11, 152, 222, null, null],
[0, "허저", null, 0, null, 74, 96, 36, 0, 169, 226, null, null],
[0, "허유", null, 17, "남피", 36, 21, 80, 1, 155, 204, null, null],
[0, "기령", null, 7, "완", 78, 83, 51, 1, 155, 199, null, null],
[0, "김의", null, 0, null, 32, 38, 65, 0, 177, 218, null, null],
[0, "김배삼결", null, 0, null, 64, 76, 19, 0, 192, 225, null, null],
[0, "김선", null, 1, "장안", 52, 68, 13, 1, 155, 220, null, null],
[0, "우번", null, 0, null, 43, 46, 86, 0, 164, 233, null, null],
[0, "형도영", null, 0, null, 69, 80, 41, 0, 174, 208, null, null],
[0, "엄안", null, 6, "자동", 82, 83, 72, 1, 151, 222, null, null],
[0, "곽마", null, 0, null, 68, 71, 40, 0, 239, 281, null, null],
[0, "엄준", null, 0, null, 28, 2, 71, 0, 169, 246, null, null],
[0, "엄정", null, 0, null, 50, 55, 39, 0, 151, 189, null, null],
[0, "엄백호", null, 0, null, 67, 70, 23, 0, 142, 198, null, null, "이 몸은 동오의 덕왕 엄백호라고 하오!"],
[0, "엄여", null, 0, null, 61, 78, 44, 0, 145, 196, null, null],
[0, "오의", null, 0, null, 79, 73, 70, 0, 165, 237, null, null],
[0, "황개", null, 7, "완", 80, 83, 70, 1, 154, 218, null, null],
[0, "고간", null, 0, null, 74, 56, 48, 0, 168, 206, null, null],
[0, "황권", null, 0, null, 75, 59, 82, 0, 167, 240, null, null],
[0, "고유", null, 0, null, 57, 43, 72, 0, 174, 263, null, null],
[0, "고순", null, 0, null, 85, 86, 60, 0, 162, 198, null, null],
[0, "고상", null, 0, null, 68, 68, 53, 0, 194, 252, null, null],
[0, "고승", null, 0, null, 67, 73, 41, 0, 145, 184, null, null],
[0, "후성", null, 0, null, 74, 75, 68, 0, 158, 199, null, null],
[0, "후선", null, 20, "천수", 63, 66, 34, 1, 175, 228, null, null],
[0, "황조", null, 13, "강하", 74, 66, 55, 1, 148, 208, null, null],
[0, "공손월", null, 4, "북평", 74, 72, 47, 1, 160, 192, null, null],
[0, "공손연", null, 0, null, 65, 70, 60, 0, 205, 238, null, null],
[0, "공손공", null, 11, "안평", 39, 17, 69, 1, 174, 240, null, null],
[0, "공손강", null, 11, "안평", 72, 66, 65, 1, 172, 211, null, null],
[0, "공손찬", null, 4, "북평", 84, 83, 75, 12, 152, 199, null, null],
[0, "공손속", null, 4, "북평", 63, 68, 57, 1, 176, 199, null, null],
[0, "공손도", null, 11, "안평", 67, 71, 66, 12, 154, 204, null, null],
[0, "공손범", null, 4, "북평", 73, 69, 66, 1, 158, 199, null, null],
[0, "황충", null, 0, null, 90, 93, 65, 0, 154, 220, null, null],
[0, "고정", null, 0, null, 55, 63, 43, 0, 190, 251, null, null],
[0, "고패", null, 0, null, 66, 61, 69, 0, 170, 212, null, null],
[0, "황보숭", null, 1, "홍농", 92, 61, 73, 1, 132, 195, null, null],
[0, "공융", null, 19, "북해", 30, 5, 73, 12, 153, 208, null, null],
[0, "고람", null, 17, "남피", 76, 82, 68, 1, 159, 201, null, null],
[0, "오경", null, 0, null, 69, 66, 52, 0, 151, 211, null, null],
[0, "호거아", null, 1, "사곡", 70, 82, 41, 1, 164, 206, null, null],
[0, "호진", null, 1, "호로", 65, 74, 12, 1, 148, 190, null, null],
[0, "올돌골", null, 0, null, 79, 86, 1, 0, 186, 225, null, null],
[0, "호반", null, 0, null, 55, 55, 59, 0, 179, 233, null, null],
[0, "오반", null, 0, null, 74, 71, 59, 0, 171, 234, null, null],
[0, "고옹", null, 0, null, 43, 18, 80, 0, 168, 243, null, null],
[0, "오란", null, 6, "성도", 68, 80, 35, 1, 170, 218, null, null],
[0, "채화", null, 13, "장판", 39, 50, 1, 1, 166, 208, null, null],
[0, "채중", null, 13, "장판", 40, 54, 2, 1, 168, 208, null, null],
[0, "채모", null, 13, "양양", 70, 70, 77, 1, 160, 215, null, null],
[0, "착융", null, 0, null, 60, 69, 38, 0, 161, 195, null, null],
[0, "사마의", null, 0, "하내", 98, 63, 98, 0, 179, 251, null, null],
[0, "사마염", null, 0, null, 69, 59, 77, 0, 238, 290, null, null],
[0, "사마사", null, 0, null, 79, 64, 88, 0, 208, 255, null, null],
[0, "사마소", null, 0, null, 76, 57, 87, 0, 211, 265, null, null],
[0, "사마부", null, 0, "하내", 70, 37, 77, 0, 180, 272, null, null],
[0, "사마랑", null, 1, "낙양", 20, 21, 72, 1, 171, 217, null, null],
[0, "사마가", null, 0, null, 66, 84, 25, 0, 167, 222, null, null],
[0, "주흔", null, 9, "수춘", 65, 62, 73, 11, 159, 196, null, null],
[0, "주앙", null, 17, "남피", 74, 65, 65, 1, 162, 195, null, null],
[0, "주지", null, 0, null, 65, 85, 41, 0, 233, 295, null, null],
[0, "주창", null, 0, "여남", 68, 84, 42, 0, 164, 219, null, null],
[0, "주태유평", null, 0, null, 84, 91, 48, 0, 171, 225, null, null],
[0, "주방", null, 0, null, 52, 41, 80, 0, 200, 240, null, null],
[0, "주유", null, 0, null, 95, 71, 96, 0, 175, 210, null, null],
[0, "주환", null, 0, null, 84, 82, 77, 0, 177, 238, null, null],
[0, "주거", null, 0, null, 77, 57, 72, 0, 190, 246, null, null],
[0, "축융", null, 0, null, 74, 85, 29, 0, 182, 230, null, null],
[0, "주준", null, 1, "낙양", 89, 65, 70, 1, 149, 195, null, null],
[0, "주연", null, 0, null, 79, 69, 71, 0, 182, 249, null, null],
[0, "주치", null, 0, null, 71, 56, 72, 0, 156, 224, null, null],
[0, "주포", null, 0, null, 68, 73, 34, 0, 191, 225, null, null],
[0, "주령", null, 0, null, 71, 73, 67, 0, 170, 236, null, null],
[0, "순욱", null, 0, null, 62, 14, 95, 0, 163, 212, null, null],
[0, "순우경", null, 17, "남피", 72, 66, 29, 1, 146, 200, null, null],
[0, "순심", null, 0, null, 34, 25, 78, 0, 164, 208, null, null],
[0, "순유", null, 0, "낙양", 73, 26, 94, 0, 157, 214, null, null],
[0, "장완", null, 0, null, 78, 34, 84, 0, 188, 246, null, null],
[0, "종회", null, 0, null, 82, 51, 91, 0, 225, 264, null, null],
[0, "장간", null, 0, null, 19, 6, 66, 0, 175, 208, null, null],
[0, "장의거", null, 17, "남피", 71, 58, 59, 1, 159, 205, null, null],
[0, "장흠", null, 0, null, 78, 84, 54, 0, 159, 208, null, null],
[0, "장제3", null, 0, null, 48, 43, 85, 0, 188, 249, null, null],
[0, "초주", null, 0, null, 16, 3, 67, 0, 201, 270, null, null],
[0, "초촉", null, 0, null, 65, 76, 33, 0, 159, 208, null, null],
[0, "상총", null, 0, null, 76, 61, 77, 0, 195, 240, null, null],
[0, "종요", null, 0, null, 70, 24, 77, 0, 151, 230, null, null],
[0, "상랑", null, 0, null, 28, 18, 74, 0, 167, 247, null, null],
[0, "서영", null, 1, "사수", 80, 76, 62, 1, 147, 192, null, null],
[0, "제갈각", null, 0, null, 72, 47, 90, 0, 203, 253, null, null],
[0, "제갈근", null, 0, null, 75, 34, 81, 0, 174, 241, null, null],
[0, "제갈균", null, 0, null, 29, 25, 52, 0, 185, 252, null, null],
[0, "제갈탄", null, 0, null, 72, 72, 76, 0, 206, 258, null, null],
[0, "제갈량", null, 0, null, 98, 38, 100, 0, 184, 234, null, null],
[0, "서황", null, 0, null, 91, 90, 78, 0, 165, 228, null, null],
[0, "서서", null, 0, null, 87, 64, 93, 0, 160, 300, null, null],
[0, "서성", null, 0, null, 86, 81, 78, 0, 178, 232, null, null],
[0, "서막", null, 0, null, 55, 32, 68, 0, 171, 249, null, null],
[0, "신의", null, 0, null, 55, 59, 60, 0, 190, 252, null, null],
[0, "신탐", null, 0, null, 56, 62, 54, 0, 188, 254, null, null],
[0, "심배", null, 12, "업", 80, 60, 83, 1, 156, 204, null, null],
[0, "신비", null, 0, null, 39, 24, 79, 0, 171, 240, null, null],
[0, "신평", null, 17, "남피", 69, 43, 77, 1, 165, 204, null, null],
[0, "진복", null, 0, null, 31, 7, 73, 0, 160, 226, null, null],
[0, "진랑", null, 0, null, 58, 70, 35, 0, 192, 234, null, null],
[0, "휴고", null, 0, null, 63, 71, 38, 0, 151, 199, null, null],
[0, "추단", null, 0, null, 61, 64, 34, 0, 144, 193, null, null],
[0, "성의", null, 20, "천수", 74, 72, 41, 1, 168, 211, null, null],
[0, "성공영", null, 20, "천수", 73, 71, 80, 11, 171, 220, null, null],
[0, "설종", null, 0, null, 34, 16, 73, 0, 187, 243, null, null],
[0, "선경", null, 4, "북평", 71, 68, 43, 1, 156, 199, null, null],
[0, "전종", null, 0, null, 78, 72, 75, 0, 183, 249, null, null],
[0, "조예", null, 0, null, 72, 52, 82, 0, 205, 239, null, null],
[0, "조휴", null, 0, null, 74, 75, 62, 0, 174, 228, null, null],
[0, "송헌", null, 0, null, 68, 77, 38, 0, 157, 200, null, null],
[0, "조앙", null, 2, "진류", 74, 67, 69, 1, 161, 197, null, null],
[0, "조홍 자렴", null, 2, "진류", 79, 81, 44, 1, 169, 232, null, null, "좌절감이 사나이를 키우는 것이다!"],
[0, "조순", null, 2, "진류", 75, 71, 63, 1, 170, 210, null, null],
[0, "조창", null, 0, null, 82, 90, 40, 0, 190, 223, null, null],
[0, "조식", null, 0, null, 33, 22, 81, 0, 192, 232, null, null],
[0, "조진", null, 0, null, 81, 74, 70, 0, 185, 231, null, null],
[0, "조인", null, 2, "진류", 89, 86, 62, 1, 168, 223, null, null],
[0, "조성", null, 0, null, 54, 74, 38, 0, 163, 198, null, null],
[0, "조조", null, 2, "진류", 99, 72, 91, 12, 151, 220, null, null, "내가 천하 사람들을 저버릴지언정, 천하 사람들이 나를 저버리게 하지는 않을 것이다."],
[0, "조충", null, 0, null, 31, 21, 85, 0, 196, 207, null, null],
[0, "장패", null, 0, null, 78, 75, 53, 0, 165, 231, null, null],
[0, "조비", null, 0, null, 70, 71, 83, 0, 187, 226, null, null],
[0, "조표", null, 8, "서주", 66, 69, 32, 1, 151, 196, null, null],
[0, "저수", null, 12, "업", 78, 35, 90, 11, 156, 201, null, null],
[0, "소비", null, 0, null, 69, 63, 66, 0, 172, 221, null, null],
[0, "조무", null, 7, "완", 70, 71, 66, 1, 155, 193, null, null],
[0, "손환", null, 0, null, 82, 73, 76, 0, 166, 228, null, null],
[0, "손견", null, 7, "완", 94, 90, 79, 11, 156, 192, null, null, "내가 옥새를 가지고 있다면 나는 칼과 화살을 맞아 죽을 것이다."],
[0, "손건", null, 0, null, 34, 33, 79, 0, 165, 215, null, null],
[0, "손권", null, 0, null, 79, 67, 80, 0, 182, 252, null, null],
[0, "손교", null, 0, null, 77, 72, 67, 0, 181, 219, null, null],
[0, "손책", null, 0, null, 96, 92, 74, 0, 175, 200, null, null],
[0, "손소", null, 0, null, 80, 79, 76, 0, 188, 241, null, null],
[0, "손정", null, 7, "완", 66, 53, 73, 1, 160, 206, null, null],
[0, "손중", null, 0, null, 62, 71, 36, 0, 154, 184, null, null],
[0, "손등", null, 0, null, 62, 37, 79, 0, 209, 241, null, null],
[0, "손유", null, 0, null, 77, 70, 68, 0, 177, 215, null, null],
[0, "손익", null, 0, null, 67, 78, 27, 0, 184, 204, null, null],
[0, "손례", null, 0, null, 78, 81, 67, 0, 180, 250, null, null],
[0, "태사자", null, 0, "안평", 90, 93, 69, 0, 166, 206, null, null],
[0, "대래동주", null, 0, null, 51, 53, 54, 0, 195, 249, null, null],
[0, "대릉", null, 0, null, 70, 75, 39, 0, 199, 258, null, null],
[0, "타사대왕", null, 0, null, 62, 59, 70, 0, 186, 225, null, null],
[0, "담웅", null, 0, null, 58, 73, 14, 0, 188, 221, null, null],
[0, "장윤", null, 13, "양양", 72, 67, 42, 1, 163, 208, null, null],
[0, "조운", null, 0, "진양", 96, 96, 77, 0, 168, 229, null, null],
[0, "장위", null, 14, "한중", 74, 66, 45, 1, 172, 228, null, null, "쌀 다섯 말만 가지고 오시오~ 모두 고쳐드리리다."],
[0, "장영", null, 0, null, 75, 74, 41, 0, 154, 195, null, null],
[0, "장연", null, 0, "진양", 76, 81, 57, 0, 153, 210, null, null],
[0, "장횡", null, 0, null, 59, 70, 23, 0, 178, 211, null, null],
[0, "장온", null, 0, null, 32, 10, 72, 0, 193, 231, null, null],
[0, "장개", null, 8, "하비", 34, 66, 8, 1, 155, 194, null, null],
[0, "장각", null, 0, null, 87, 25, 86, 0, 140, 184, null, null],
[0, "장기", null, 0, null, 74, 35, 76, 0, 170, 223, null, null],
[0, "장의", null, 0, null, 79, 72, 75, 0, 153, 215, null, null],
[0, "장훈", null, 7, "완", 72, 68, 41, 1, 156, 206, null, null],
[0, "장굉", null, 0, null, 24, 22, 86, 0, 153, 212, null, null],
[0, "장합", null, 12, "업", 89, 89, 73, 1, 167, 231, null, null],
[0, "조홍", null, 0, null, 66, 72, 28, 0, 156, 184, null, null],
[0, "장제1", null, 1, "장안", 70, 66, 55, 1, 144, 196, null, null],
[0, "장수", null, 1, "함곡", 80, 73, 64, 1, 154, 207, null, null],
[0, "장소 자포", null, 0, null, 33, 3, 84, 0, 156, 236, null, null],
[0, "장송", null, 0, null, 25, 6, 88, 0, 170, 212, null, null],
[0, "장임", null, 6, "성도", 84, 84, 78, 1, 169, 214, null, null],
[0, "장제", null, 0, null, 67, 43, 80, 0, 236, 280, null, null],
[0, "장막", null, 2, "진류", 52, 53, 71, 1, 155, 195, null, null],
[0, "조범", null, 0, null, 28, 21, 32, 0, 168, 218, null, null],
[0, "장비", null, 0, "진류", 94, 98, 30, 0, 165, 221, null, null],
[0, "장포", null, 0, null, 78, 87, 48, 0, 198, 219, null, null],
[0, "장보", null, 0, null, 83, 71, 81, 0, 148, 184, null, null],
[0, "장만성", null, 0, null, 75, 81, 45, 0, 143, 184, null, null],
[0, "장양", null, 5, "진양", 72, 70, 66, 12, 151, 195, null, null],
[0, "장익", null, 0, null, 75, 74, 76, 0, 188, 264, null, null],
[0, "장료", null, 1, "사곡", 95, 92, 78, 1, 169, 222, null, null],
[0, "장량", null, 0, null, 78, 80, 74, 0, 153, 184, null, null],
[0, "조루", null, 0, null, 70, 43, 68, 0, 183, 219, null, null],
[0, "장로", null, 14, "한중", 51, 26, 74, 12, 163, 216, null, null],
[0, "진횡", null, 0, null, 64, 63, 26, 0, 161, 195, null, null],
[0, "진응", null, 0, null, 62, 72, 45, 0, 172, 208, null, null],
[0, "진기", null, 0, null, 58, 65, 43, 0, 165, 198, null, null],
[0, "진궁", null, 2, "진류", 84, 55, 89, 11, 154, 198, null, null],
[0, "진교", null, 0, null, 61, 27, 77, 0, 175, 237, null, null],
[0, "진군", null, 0, null, 32, 14, 75, 0, 167, 236, null, null],
[0, "진규", null, 8, "서주", 30, 4, 82, 11, 132, 206, null, null],
[0, "진식", null, 0, null, 70, 71, 25, 0, 191, 230, null, null],
[0, "진진", null, 0, null, 46, 46, 69, 0, 170, 235, null, null],
[0, "진태", null, 0, null, 84, 77, 86, 0, 210, 260, null, null],
[0, "진등", null, 8, "하비", 79, 64, 81, 1, 169, 208, null, null],
[0, "진도", null, 0, null, 76, 71, 67, 0, 171, 237, null, null],
[0, "진무", null, 0, null, 76, 87, 43, 0, 176, 215, null, null],
[0, "진란", null, 0, null, 66, 69, 40, 0, 157, 204, null, null],
[0, "진림", null, 0, null, 25, 9, 75, 0, 160, 217, null, null],
[0, "정욱", null, 0, null, 70, 49, 90, 0, 141, 220, null, null],
[0, "정원지", null, 0, null, 69, 74, 17, 0, 145, 184, null, null],
[0, "정은", null, 0, null, 69, 73, 40, 0, 184, 220, null, null],
[0, "정원", null, 0, null, 70, 76, 35, 0, 137, 189, null, null],
[0, "정보", null, 7, "완", 84, 79, 79, 1, 151, 216, null, null],
[0, "정봉승연", null, 0, null, 81, 80, 75, 0, 190, 271, null, null],
[0, "전위", null, 0, null, 72, 95, 35, 0, 160, 197, null, null],
[0, "전해", null, 4, "북평", 68, 65, 58, 1, 154, 199, null, null],
[0, "전주", null, 0, null, 66, 66, 73, 0, 169, 214, null, null],
[0, "전풍", null, 12, "업", 72, 29, 93, 1, 162, 200, null, null],
[0, "전예", null, 0, null, 80, 72, 80, 0, 171, 252, null, null],
[0, "동윤", null, 0, null, 47, 28, 79, 0, 192, 246, null, null],
[0, "동화", null, 0, null, 57, 34, 75, 0, 168, 219, null, null],
[0, "등애", null, 0, null, 93, 87, 89, 0, 197, 264, null, null],
[0, "도겸", null, 8, "서주", 51, 27, 64, 12, 132, 194, null, null],
[0, "등현", null, 0, null, 61, 73, 45, 0, 188, 248, null, null],
[0, "등지", null, 0, null, 73, 52, 81, 0, 178, 251, null, null],
[0, "동습", null, 0, null, 72, 85, 50, 0, 169, 215, null, null],
[0, "동승", null, 1, "낙양", 56, 53, 65, 1, 154, 200, null, null],
[0, "동소", null, 0, null, 34, 25, 80, 0, 156, 236, null, null],
[0, "동탁", null, 1, "낙양", 85, 87, 73, 12, 139, 192, null, null, "크하하, 천하는 이제 나의 것이다!"],
[0, "동도나", null, 0, null, 68, 73, 31, 0, 189, 225, null, null],
[0, "동민", null, 1, "장안", 49, 60, 25, 1, 149, 192, null, null],
[0, "등무", null, 0, null, 61, 75, 29, 0, 147, 184, null, null],
[0, "두기", null, 0, null, 66, 32, 75, 0, 163, 224, null, null],
[0, "두예", null, 0, null, 84, 30, 85, 0, 222, 284, null, null],
[0, "배원소", null, 0, null, 49, 65, 25, 0, 169, 200, null, null],
[0, "마완", null, 0, null, 71, 74, 16, 0, 170, 211, null, null],
[0, "마휴", null, 0, null, 68, 73, 48, 0, 178, 212, null, null],
[0, "파재", null, 0, null, 74, 74, 55, 0, 145, 184, null, null],
[0, "마준", null, 0, null, 38, 52, 31, 0, 196, 230, null, null],
[0, "마속", null, 0, null, 61, 67, 87, 0, 190, 228, null, null],
[0, "마대", null, 0, null, 79, 85, 62, 0, 183, 246, null, null],
[0, "마충", null, 0, null, 78, 72, 68, 0, 187, 249, null, null],
[0, "마초", null, 0, null, 93, 97, 44, 0, 176, 222, null, null],
[0, "마철", null, 0, null, 70, 62, 58, 0, 179, 212, null, null],
[0, "마등", null, 10, "서량", 82, 80, 52, 12, 149, 212, null, null],
[0, "마량", null, 0, null, 53, 23, 88, 0, 187, 222, null, null],
[0, "반준", null, 0, null, 61, 18, 83, 0, 174, 239, null, null],
[0, "반장", null, 0, null, 76, 80, 74, 0, 177, 234, null, null],
[0, "번조", null, 1, "안정", 66, 73, 31, 1, 149, 192, null, null],
[0, "번능", null, 0, null, 69, 61, 36, 0, 158, 195, null, null],
[0, "반봉", null, 12, "업", 56, 77, 4, 1, 155, 190, null, null],
[0, "비의", null, 0, null, 77, 30, 83, 0, 193, 253, null, null],
[0, "비연", null, 0, null, 60, 66, 21, 0, 196, 238, null, null],
[0, "비시", null, 0, null, 18, 28, 65, 0, 176, 240, null, null],
[0, "미축", null, 8, "하비", 33, 29, 78, 1, 165, 220, null, null],
[0, "미방", null, 8, "광릉", 54, 61, 32, 1, 169, 226, null, null],
[0, "비요", null, 0, null, 70, 66, 73, 0, 192, 228, null, null],
[0, "무안국", null, 19, "북해", 70, 83, 34, 1, 156, 193, null, null],
[0, "풍습", null, 0, null, 72, 71, 39, 0, 182, 222, null, null],
[0, "부사인", null, 0, null, 46, 63, 37, 0, 182, 225, null, null],
[0, "부첨", null, 0, null, 73, 85, 76, 0, 216, 263, null, null],
[0, "부손", null, 0, null, 29, 13, 70, 0, 162, 230, null, null],
[0, "문앙", null, 0, null, 76, 93, 65, 0, 238, 291, null, null],
[0, "문흠", null, 0, null, 74, 83, 43, 0, 200, 258, null, null],
[0, "문추", null, 17, "남피", 89, 93, 47, 1, 169, 200, null, null],
[0, "문빙", null, 0, null, 80, 82, 69, 0, 178, 237, null, null],
[0, "변희", null, 0, null, 56, 70, 62, 0, 169, 200, null, null],
[0, "망아장", null, 0, null, 45, 79, 3, 0, 191, 225, null, null],
[0, "봉기", null, 17, "남피", 43, 21, 84, 11, 153, 202, null, null],
[0, "방희", null, 0, null, 60, 38, 69, 0, 152, 192, null, null],
[0, "법정", null, 0, null, 82, 47, 94, 0, 176, 220, null, null],
[0, "방통", null, 0, null, 85, 34, 97, 0, 178, 213, null, null],
[0, "방덕", null, 10, "서량", 89, 94, 75, 11, 170, 219, null, null],
[0, "포륭", null, 0, null, 58, 76, 49, 0, 174, 208, null, null],
[0, "목순", null, 0, null, 52, 77, 11, 0, 157, 190, null, null],
[0, "목록대왕", null, 0, null, 65, 71, 39, 0, 184, 225, null, null],
[0, "보즐", null, 0, null, 72, 51, 84, 0, 177, 247, null, null],
[0, "만총", null, 0, null, 84, 64, 82, 0, 170, 242, null, null],
[0, "모개", null, 0, null, 63, 39, 60, 0, 161, 216, null, null],
[0, "맹획", null, 0, null, 87, 87, 42, 0, 186, 245, null, null],
[0, "맹달", null, 0, null, 75, 73, 75, 0, 172, 228, null, null],
[0, "맹우", null, 0, null, 50, 68, 22, 0, 190, 243, null, null],
[0, "유섭", null, 7, "완", 62, 79, 24, 1, 158, 190, null, null],
[0, "양부2", null, 0, null, 62, 68, 68, 0, 167, 212, null, null],
[0, "옹개", null, 0, null, 73, 75, 57, 0, 188, 225, null, null],
[0, "양의", null, 0, null, 68, 40, 82, 0, 190, 235, null, null],
[0, "양호", null, 0, null, 90, 64, 84, 0, 221, 278, null, null],
[0, "양홍", null, 7, "완", 33, 15, 77, 1, 152, 199, null, null],
[0, "양앙", null, 0, null, 68, 72, 38, 0, 172, 215, null, null],
[0, "양추1", null, 0, null, 52, 65, 40, 0, 159, 199, null, null],
[0, "양수", null, 0, null, 25, 4, 83, 0, 175, 219, null, null],
[0, "양추2", null, 0, null, 67, 64, 60, 0, 172, 238, null, null],
[0, "양송", null, 0, null, 1, 5, 28, 0, 167, 215, null, null],
[0, "양임", null, 14, "한중", 70, 78, 56, 1, 170, 215, null, null],
[0, "양조", null, 0, null, 53, 61, 36, 0, 223, 286, null, null],
[0, "양백", null, 14, "한중", 44, 45, 19, 1, 171, 214, null, null],
[0, "양부1", null, 0, null, 67, 50, 83, 0, 178, 239, null, null],
[0, "양봉1", null, 0, null, 66, 65, 31, 0, 153, 197, null, null],
[0, "양봉2", null, 0, null, 54, 62, 53, 0, 191, 252, null, null],
[0, "뇌동", null, 0, null, 69, 78, 51, 0, 172, 218, null, null],
[0, "뇌박", null, 0, null, 62, 70, 36, 0, 157, 206, null, null],
[0, "이이", null, 0, null, 63, 77, 19, 0, 187, 222, null, null],
[0, "이회", null, 0, null, 79, 65, 79, 0, 175, 231, null, null],
[0, "이각", null, 1, "낙양", 69, 72, 24, 1, 148, 198, null, null],
[0, "육항", null, 0, null, 91, 63, 87, 0, 226, 274, null, null],
[0, "육적", null, 0, null, 20, 7, 72, 0, 187, 219, null, null],
[0, "육손", null, 0, null, 94, 69, 95, 0, 183, 245, null, null],
[0, "이엄", null, 0, null, 83, 84, 76, 0, 172, 234, null, null],
[0, "이유", null, 1, "낙양", 61, 26, 93, 11, 150, 192, null, null, "장안으로 천도를 하는것이 어떻겠습니까?"],
[0, "이숙", null, 1, "낙양", 46, 69, 61, 1, 156, 192, null, null],
[0, "이통", null, 0, "여남", 73, 83, 62, 0, 168, 209, null, null],
[0, "이전", null, 0, null, 78, 77, 79, 0, 181, 216, null, null],
[0, "이풍", null, 0, null, 69, 74, 50, 0, 158, 199, null, null],
[0, "유언", null, 6, "성도", 53, 38, 80, 12, 132, 194, null, null],
[0, "유괴", null, 6, "성도", 71, 73, 67, 1, 165, 214, null, null],
[0, "유기", null, 13, "양양", 52, 12, 63, 1, 174, 210, null, null],
[0, "유우", null, 16, "계", 55, 33, 70, 12, 145, 193, null, null],
[0, "유훈", null, 0, null, 47, 63, 35, 0, 163, 216, null, null],
[0, "유현", null, 0, null, 54, 66, 51, 0, 188, 252, null, null],
[0, "유순", null, 0, null, 63, 46, 41, 0, 172, 255, null, null],
[0, "유장", null, 6, "성도", 31, 5, 34, 1, 162, 219, null, null],
[0, "유심", null, 0, null, 60, 62, 70, 0, 238, 264, null, null],
[0, "유선", null, 0, null, 3, 5, 9, 0, 208, 270, null, null],
[0, "유종", null, 0, null, 24, 22, 65, 0, 195, 220, null, null],
[0, "유도", null, 0, null, 25, 23, 26, 0, 168, 214, null, null],
[0, "유파", null, 0, null, 29, 22, 75, 0, 186, 222, null, null],
[0, "유반", null, 0, null, 69, 76, 47, 0, 168, 210, null, null],
[0, "유비", null, 0, "진류", 81, 77, 80, 0, 161, 223, null, null],
[0, "유표1", null, 13, "양양", 48, 31, 72, 12, 142, 208, null, null, "강하 팔준에서 제일가는 이 유표에게로 오시오!"],
[0, "유복", null, 0, null, 64, 49, 74, 0, 164, 208, null, null],
[0, "유벽", null, 0, null, 70, 72, 47, 0, 145, 201, null, null],
[0, "유봉", null, 0, null, 75, 79, 44, 0, 188, 220, null, null],
[0, "유엽", null, 0, null, 56, 32, 92, 0, 176, 235, null, null],
[0, "유요", null, 0, null, 62, 67, 46, 0, 156, 198, null, null],
[0, "여위황", null, 0, null, 58, 63, 29, 0, 159, 200, null, null],
[0, "요화", null, 0, null, 73, 76, 69, 0, 168, 264, null, null],
[0, "양흥", null, 10, "서량", 61, 65, 19, 1, 169, 211, null, null],
[0, "양습", null, 0, null, 73, 40, 74, 0, 168, 230, null, null],
[0, "양서", null, 0, null, 56, 62, 66, 0, 198, 260, null, null],
[0, "능조", null, 0, null, 75, 81, 42, 0, 168, 203, null, null],
[0, "능통", null, 0, null, 77, 89, 60, 0, 189, 217, null, null],
[0, "여개", null, 0, null, 53, 30, 67, 0, 194, 227, null, null],
[0, "여건", null, 0, null, 57, 70, 62, 0, 173, 238, null, null],
[0, "여광", null, 0, null, 56, 70, 13, 0, 162, 207, null, null],
[0, "여상", null, 0, null, 54, 71, 12, 0, 164, 207, null, null],
[0, "여대", null, 0, null, 75, 71, 70, 0, 161, 256, null, null],
[0, "여범", null, 0, null, 73, 63, 75, 0, 169, 228, null, null],
[0, "여포", null, 1, "사수", 97, 100, 26, 1, 156, 198, null, null],
[0, "여몽", null, 0, null, 82, 81, 20, 0, 178, 219, null, null],
[0, "윤직", null, 0, null, 46, 42, 73, 0, 196, 237, null, null],
[0, "냉포", null, 0, null, 71, 82, 61, 0, 176, 214, null, null],
[0, "진취", null, 0, null, 56, 63, 45, 0, 174, 208, null, null],
[0, "노숙", null, 0, null, 84, 56, 92, 0, 172, 217, null, null],
[0, "노식", null, 0, null, 86, 63, 82, 0, 139, 192, null, null],
[0, "범강", null, 0, null, 49, 50, 36, 0, 174, 222, null, null],
[0, "장달", null, 0, null, 43, 56, 34, 0, 175, 222, null, null],
[0, "규람", null, 0, null, 55, 70, 58, 0, 165, 204, null, null],
[0, "대원", null, 0, null, 38, 32, 68, 0, 166, 204, null, null],
[0, "왕기", null, 0, null, 77, 72, 86, 0, 190, 261, null, null],
[0, "왕상", null, 0, null, 23, 19, 68, 0, 180, 268, null, null],
[0, "왕돈", null, 0, null, 60, 66, 46, 0, 198, 256, null, null],
[0, "악침", null, 0, null, 73, 75, 43, 0, 196, 257, null, null],
[0, "하제", null, 0, null, 83, 70, 82, 0, 171, 227, null, null],
[0, "환범", null, 0, null, 40, 11, 78, 0, 199, 249, null, null],
[0, "견초", null, 0, null, 72, 79, 73, 0, 168, 244, null, null],
[0, "고당륭", null, 0, null, 35, 24, 76, 0, 190, 244, null, null],
[0, "오찬", null, 0, null, 68, 41, 78, 0, 181, 245, null, null],
[0, "호질", null, 0, null, 73, 46, 74, 0, 192, 250, null, null],
[0, "오질", null, 0, null, 16, 29, 68, 0, 177, 230, null, null],
[0, "호준", null, 0, null, 79, 76, 62, 0, 200, 256, null, null],
[0, "고담", null, 0, null, 53, 23, 77, 0, 203, 244, null, null],
[0, "최림", null, 0, null, 32, 22, 66, 0, 187, 244, null, null],
[0, "사마망", null, 0, null, 78, 58, 83, 0, 205, 271, null, null],
[0, "주이", null, 0, null, 70, 73, 61, 0, 201, 257, null, null],
[0, "전단", null, 0, null, 67, 64, 39, 0, 204, 261, null, null],
[0, "조우", null, 0, null, 50, 43, 37, 0, 199, 278, null, null],
[0, "조상", null, 0, null, 39, 33, 30, 0, 207, 249, null, null],
[0, "조방", null, 0, null, 20, 14, 27, 0, 232, 274, null, null],
[0, "태사향", null, 0, null, 57, 66, 48, 0, 189, 246, null, null],
[0, "장휴", null, 0, null, 63, 27, 74, 0, 204, 244, null, null],
[0, "장호", null, 0, null, 75, 74, 51, 0, 195, 240, null, null],
[0, "조광", null, 0, null, 67, 71, 53, 0, 210, 263, null, null],
[0, "장집", null, 0, null, 76, 29, 80, 0, 196, 254, null, null],
[0, "장승", null, 0, null, 77, 70, 75, 0, 178, 244, null, null],
[0, "장소", null, 0, null, 23, 56, 31, 0, 202, 264, null, null],
[0, "조통", null, 0, null, 68, 67, 61, 0, 209, 260, null, null],
[0, "진표", null, 0, null, 66, 42, 74, 0, 207, 237, null, null],
[0, "정무", null, 0, null, 56, 35, 75, 0, 201, 265, null, null],
[0, "정봉", null, 0, null, 66, 66, 53, 0, 198, 266, null, null],
[0, "전만", null, 0, null, 51, 73, 39, 0, 181, 235, null, null],
[0, "마균", null, 0, null, 22, 17, 71, 0, 200, 259, null, null],
[0, "방회", null, 0, null, 69, 75, 49, 0, 205, 272, null, null],
[0, "서구", null, 0, null, 69, 42, 70, 0, 149, 208, null, null],
[0, "육개", null, 0, null, 65, 61, 79, 0, 198, 269, null, null],
[0, "유찬", null, 0, null, 78, 81, 64, 0, 172, 255, null, null],
[0, "유소", null, 0, null, 68, 53, 74, 0, 195, 264, null, null],
[0, "요립", null, 0, null, 32, 15, 76, 0, 181, 250, null, null],
[0, "여거", null, 0, null, 78, 69, 71, 0, 196, 256, null, null],
[0, "제갈첨", null, 0, null, 70, 57, 76, 0, 227, 263, null, null],
[0, "손휴", null, 0, null, 57, 42, 78, 0, 235, 264, null, null],
[0, "극정", null, 0, null, 42, 23, 68, 0, 208, 278, null, null],
[0, "호분", null, 0, null, 71, 76, 50, 0, 222, 288, null, null],
[0, "손량", null, 0, null, 32, 26, 70, 0, 243, 260, null, null],
[0, "마막", null, 0, null, 34, 12, 44, 0, 221, 266, null, null],
[0, "손호", null, 0, null, 41, 73, 71, 0, 242, 284, null, null],
[0, "조모", null, 0, null, 54, 61, 59, 0, 241, 260, null, null],
[0, "조환", null, 0, null, 30, 28, 30, 0, 246, 302, null, null],
[0, "사마유", null, 0, null, 71, 68, 75, 0, 248, 283, null, null],
[0, "제갈상", null, 0, null, 64, 79, 62, 0, 246, 263, null, null],
[0, "손화", null, 0, null, 31, 39, 68, 0, 224, 253, null, null],
[0, "염우", null, 0, null, 52, 61, 51, 0, 209, 264, null, null],
[0, "왕경", null, 0, null, 67, 63, 71, 0, 206, 260, null, null],
[0, "왕혼", null, 0, null, 74, 71, 63, 0, 223, 297, null, null],
[0, "곽혁", null, 0, null, 33, 29, 70, 0, 187, 225, null, null],
[0, "가범", null, 0, null, 60, 62, 73, 0, 202, 237, null, null],
[0, "서질", null, 0, null, 68, 83, 24, 0, 207, 253, null, null],
[0, "손준", null, 0, null, 64, 70, 65, 0, 219, 256, null, null],
[0, "손침", null, 0, null, 58, 55, 65, 0, 231, 258, null, null],
[0, "우사", null, 0, null, 61, 34, 74, 0, 217, 273, null, null],
[0, "진건", null, 0, null, 72, 71, 65, 0, 214, 292, null, null],
[0, "동궐", null, 0, null, 65, 59, 76, 0, 204, 271, null, null],
[0, "당자", null, 0, null, 61, 71, 50, 0, 196, 265, null, null],
[0, "등충", null, 0, null, 76, 86, 69, 0, 230, 264, null, null],
[0, "황숭", null, 0, null, 67, 63, 72, 0, 208, 263, null, null],
[0, "화핵", null, 0, null, 20, 24, 71, 0, 217, 278, null, null],
[0, "허의", null, 0, null, 51, 74, 15, 0, 213, 263, null, null],
[0, "송겸", null, 0, null, 66, 78, 42, 0, 175, 227, null, null],
[0, "호제", null, 0, null, 57, 39, 68, 0, 207, 264, null, null],
[0, "나헌", null, 0, null, 85, 70, 77, 0, 218, 270, null, null],
[0, "아들노숙", null, 0, null, 73, 53, 74, 0, 217, 272, null, null],
[0, "구성", null, 0, null, 63, 70, 38, 0, 157, 187, null, null],
[0, "어부라", null, 0, null, 72, 68, 21, 0, 150, 195, null, null],
[0, "아하소과", null, 0, null, 61, 76, 13, 0, 204, 253, null, null],
[0, "가비능", null, 0, null, 77, 69, 52, 0, 172, 235, null, null],
[0, "구력거", null, 0, null, 80, 68, 56, 0, 152, 193, null, null],
[0, "강단", null, 0, null, 65, 83, 48, 0, 168, 234, null, null],
[0, "황란", null, 0, null, 63, 75, 44, 0, 200, 264, null, null],
[0, "호주천", null, 0, null, 70, 71, 40, 0, 169, 230, null, null],
[0, "종리목", null, 0, null, 80, 72, 73, 0, 214, 269, null, null],
[0, "제갈교", null, 0, null, 62, 27, 75, 0, 204, 228, null, null],
[0, "답돈", null, 0, null, 84, 82, 61, 0, 158, 207, null, null],
[0, "반임", null, 0, null, 66, 79, 38, 0, 168, 225, null, null],
[0, "보도근", null, 0, null, 64, 73, 50, 0, 170, 233, null, null],
[0, "미당대왕", null, 0, null, 59, 69, 21, 0, 202, 260, null, null],
[0, "유표(흉노)", null, 0, null, 73, 74, 48, 0, 173, 252, null, null],
[0, "루반", null, 0, null, 65, 76, 39, 0, 178, 207, null, null],
[0, "손관", null, 0, null, 72, 78, 51, 0, 166, 217, null, null],
[0, "곽석", null, 0, null, 59, 71, 48, 0, 158, 187, null, null],
[0, "주조", null, 0, null, 63, 68, 26, 0, 152, 187, null, null],
[0, "장거", null, 0, null, 79, 75, 56, 0, 150, 188, null, null],
[0, "장순", null, 0, null, 73, 81, 60, 0, 152, 188, null, null],
[0, "해니", null, 0, null, 62, 78, 35, 0, 191, 233, null, null],
[0, "토안", null, 0, null, 53, 82, 30, 0, 191, 233, null, null],
[0, "유주", null, 0, null, 67, 78, 42, 0, 172, 233, null, null],
[0, "비잔", null, 0, null, 60, 71, 51, 0, 181, 216, null, null],
[0, "월길", null, 0, null, 81, 88, 26, 0, 190, 227, null, null],
[0, "아단", null, 0, null, 72, 65, 78, 0, 186, 259, null, null],
[0, "철리길", null, 0, null, 77, 70, 52, 0, 179, 259, null, null],
[0, "주삭", null, 0, null, 54, 60, 50, 0, 174, 253, null, null],
[0, "왕이", null, 0, null, 87, 59, 77, 0, 171, 230, null, null],
[0, "왕원희", null, 0, null, 53, 37, 73, 0, 217, 268, null, null],
[0, "곽씨", null, 0, null, 42, 4, 55, 0, 184, 235, null, null],
[0, "하후씨", null, 0, null, 34, 13, 42, 0, 188, 243, null, null],
[0, "하후령녀", null, 0, null, 22, 33, 60, 0, 207, 257, null, null],
[0, "하씨", null, 0, null, 51, 14, 67, 0, 150, 189, null, null],
[0, "관은병", null, 0, null, 87, 84, 75, 0, 204, 271, null, null],
[0, "감씨", null, 0, null, 14, 10, 44, 0, 175, 210, null, null],
[0, "황월영", null, 0, null, 62, 33, 90, 0, 186, 235, null, null],
[0, "채염", null, 0, null, 53, 14, 80, 0, 168, 224, null, null],
[0, "채씨", null, 0, null, 38, 7, 70, 0, 159, 212, null, null],
[0, "소교", null, 0, null, 40, 3, 70, 0, 178, 212, null, null],
[0, "서씨", null, 0, null, 48, 16, 80, 0, 183, 232, null, null],
[0, "신헌영", null, 0, null, 48, 28, 84, 0, 191, 269, null, null],
[0, "견씨", null, 0, null, 42, 18, 74, 0, 182, 221, null, null],
[0, "추씨", null, 1, "낙양", 4, 9, 66, 1, 165, 200, null, null],
[0, "조절", null, 0, null, 32, 33, 79, 0, 190, 260, null, null],
[0, "손씨", null, 0, null, 32, 14, 55, 0, 188, 250, null, null],
[0, "손상향", null, 0, null, 77, 82, 55, 0, 193, 222, null, null],
[0, "손노육", null, 0, null, 52, 10, 66, 0, 204, 253, null, null],
[0, "손노반", null, 0, null, 17, 15, 80, 0, 202, 258, null, null],
[0, "대교", null, 0, null, 33, 30, 66, 0, 177, 215, null, null],
[0, "장씨", null, 0, null, 55, 35, 66, 0, 207, 237, null, null],
[0, "장춘화", null, 0, null, 66, 29, 77, 0, 179, 234, null, null],
[0, "초선", null, 0, null, 42, 18, 81, 0, 176, 230, null, null],
[0, "번씨", null, 0, null, 6, 12, 50, 0, 183, 225, null, null],
[0, "미씨", null, 0, null, 13, 6, 72, 0, 176, 208, null, null],
[0, "변씨", null, 0, null, 30, 23, 55, 0, 160, 230, null, null],
[0, "보련사", null, 0, null, 45, 30, 77, 0, 198, 248, null, null],
[0, "유씨", null, 0, null, 8, 8, 44, 0, 164, 215, null, null],
[0, "화만", null, 0, null, 85, 92, 40, 0, 191, 233, null, null],
[0, "포삼낭", null, 0, null, 87, 83, 36, 0, 191, 240, null, null],
[0, "성렴", null, 1, "사수", 66, 72, 43, 1, 168, 199, null, null],
[0, "위월", null, 0, null, 68, 76, 39, 0, 170, 199, null, null],
[0, "진의록", null, 0, null, 46, 43, 17, 0, 168, 199, null, null],
[0, "채옹", null, 1, "장안", 27, 16, 81, 1, 133, 192, null, null],
[0, "지양군", null, 1, "낙양", 13, 8, 55, 1, 103, 192, null, null],
[0, "위소", null, 0, null, 21, 23, 65, 0, 204, 273, null, null],
[0, "위강", null, 0, null, 58, 35, 56, 0, 168, 213, null, null],
[0, "원요", null, 0, null, 28, 41, 50, 0, 177, 206, null, null],
[0, "왕기2", null, 0, null, 69, 67, 61, 0, 217, 281, null, null],
[0, "원환", null, 0, null, 30, 17, 72, 0, 165, 225, null, null],
[0, "우전", null, 0, null, 64, 76, 40, 0, 204, 258, null, null],
[0, "왕광", null, 15, "하내", 33, 65, 55, 12, 150, 190, null, null],
[0, "왕업", null, 0, null, 33, 6, 66, 0, 220, 280, null, null],
[0, "왕융", null, 0, null, 40, 57, 68, 0, 234, 305, null, null],
[0, "왕충", null, 0, null, 46, 39, 57, 0, 152, 214, null, null],
[0, "위풍", null, 0, null, 52, 23, 72, 0, 182, 219, null, null],
[0, "구건", null, 0, null, 39, 52, 65, 0, 239, 272, null, null],
[0, "구본", null, 0, null, 50, 41, 67, 0, 232, 269, null, null],
[0, "동백", null, 1, "장안", 12, 7, 52, 1, 173, 192, null, null],
[0, "마운", null, 0, null, 78, 88, 53, 0, 183, 225, null, null],
[0, "사섭", null, 18, "교지", 53, 31, 76, 12, 137, 226, null, null],
[0, "사지", null, 18, "교지", 58, 51, 52, 1, 163, 227, null, null],
[0, "사휘", null, 18, "남해", 69, 64, 44, 1, 165, 227, null, null],
[0, "사일", null, 18, "남해", 48, 36, 67, 1, 153, 230, null, null],
[0, "사광", null, 18, "교지", 58, 45, 64, 1, 175, 235, null, null],
[0, "후음", null, 0, null, 76, 72, 45, 0, 184, 219, null, null],
[0, "공주", null, 21, "초", 26, 16, 68, 12, 151, 191, null, null],
[0, "고담륭", null, 0, null, 35, 24, 76, 0, 190, 251, null, null],
[0, "국연", null, 0, null, 50, 19, 77, 0, 160, 219, null, null],
[0, "오언", null, 0, null, 74, 72, 77, 0, 235, 297, null, null],
[0, "오강", null, 0, null, 48, 38, 65, 0, 216, 275, null, null],
[0, "최염", null, 0, null, 18, 55, 77, 0, 162, 216, null, null],
[0, "좌혁", null, 0, null, 62, 66, 51, 0, 232, 280, null, null],
[0, "차주", null, 0, null, 49, 49, 40, 0, 164, 199, null, null],
[0, "주태", null, 0, null, 71, 64, 71, 0, 207, 261, null, null],
[0, "북궁백옥", null, 0, null, 66, 72, 49, 0, 146, 187, null, null],
[0, "변장", null, 0, null, 69, 56, 47, 0, 133, 187, null, null],
[0, "순의", null, 0, null, 16, 13, 72, 0, 207, 274, null, null],
[0, "서진순욱", null, 0, null, 8, 19, 77, 0, 225, 289, null, null],
[0, "사마준", null, 0, null, 85, 82, 80, 0, 232, 286, null, null],
[0, "종육", null, 0, null, 32, 12, 71, 0, 223, 263, null, null],
[0, "초이", null, 0, null, 62, 69, 63, 0, 219, 266, null, null],
[0, "제갈정", null, 0, null, 58, 63, 66, 0, 246, 300, null, null],
[0, "보협", null, 0, null, 62, 49, 53, 0, 216, 264, null, null],
[0, "복양흥", null, 0, null, 32, 70, 65, 0, 224, 264, null, null],
[0, "보천", null, 0, null, 71, 60, 71, 0, 222, 272, null, null],
[0, "유대연주", null, 3, "복양", 53, 32, 55, 12, 148, 192, null, null],
[0, "유대", null, 0, null, 63, 58, 30, 0, 155, 202, null, null],
[0, "유승", null, 0, null, 45, 65, 33, 0, 215, 258, null, null],
[0, "여영기", null, 0, null, 79, 88, 37, 0, 182, 198, null, null],
[0, "독발수기능", null, 0, null, 89, 93, 77, 0, 232, 279, null, null],
[0, "진수", null, 0, null, 12, 22, 68, 0, 233, 297, null, null],
[0, "마륭", null, 0, null, 84, 75, 76, 0, 231, 296, null, null],
[0, "도황", null, 0, null, 76, 68, 72, 0, 232, 290, null, null],
[0, "진온", null, 9, "수춘", 60, 38, 48, 12, 167, 192, null, null],
[0, "교모", null, 3, "복양", 54, 45, 69, 11, 137, 190, null, null],
[0, "교국로", null, 0, null, 38, 17, 55, 0, 150, 220, null, null],
[0, "포신", null, 2, "진류", 77, 67, 81, 1, 152, 192, null, null],
[0, "포훈", null, 0, null, 41, 35, 75, 0, 180, 226, null, null],
[0, "포도", null, 2, "진류", 57, 70, 43, 1, 159, 190, null, null],
[0, "위자", null, 2, "진류", 58, 38, 62, 1, 150, 190, null, null],
[0, "유화", null, 16, "계", 21, 16, 59, 1, 165, 216, null, null],
[0, "이이자", null, 0, null, 32, 18, 48, 0, 157, 199, null, null],
[0, "선우보", null, 16, "계", 74, 69, 65, 1, 161, 229, null, null],
[0, "원유", null, 3, "복양", 57, 39, 73, 1, 150, 193, null, null],
[0, "사마휘", null, 0, null, 21, 8, 92, 0, 173, 208, "은둔", null, "좋지~좋아"],
[0, "화타", null, 0, null, 8, 7, 68, 0, 173, 207, "은둔", "의술"],
[0, "관로", null, 0, null, 13, 9, 72, 0, 200, 233, "은둔", null],
[0, "예형", null, 0, null, 13, 4, 90, 0, 173, 199, null, null],
[0, "원혁", null, 0, null, 18, 18, 18, 0, 180, 246, null, null],
[0, "헌제", null, 1, "낙양", 23, 17, 66, 1, 184, 234, null, null, "한 황실은 이대로 끝나는 것인가…"],
[0, "황완", null, 1, "낙양", 40, 34, 71, 1, 141, 192, null, null],
[0, "마일제", null, 1, "낙양", 43, 25, 70, 1, 130, 194, null, null],
[0, "유총", null, 22, "여남", 82, 45, 60, 12, 151, 197, null, null],
[0, "낙준", null, 0, null, 51, 37, 66, 0, 152, 197, null, null],
[0, "낙통", null, 0, null, 76, 57, 73, 0, 193, 228, null, null],
[0, "이리", null, 1, "홍농", 65, 72, 38, 1, 171, 198, null, null],
[0, "허소", null, 0, null, 46, 13, 78, 0, 150, 195, null, null, "자네는 치세의 능신, 난세의 간옹이 되겠군."],
[0, "허사", null, 0, null, 24, 20, 60, 0, 163, 199, null, null],
[0, "장초", null, 0, null, 61, 32, 60, 0, 168, 196, null, null],
[0, "양표", null, 1, "낙양", 24, 20, 60, 1, 142, 225, null, null],
[0, "왕필", null, 0, null, 53, 44, 59, 0, 170, 228, null, null],
[0, "사환", null, 2, "진류", 70, 71, 45, 1, 173, 209, null, null],
[0, "정비", null, 0, null, 34, 30, 57, 0, 158, 214, null, null],
[0, "두씨", null, 1, "낙양", 26, 41, 52, 1, 172, 236, null, null],
[0, "누규", null, 13, "양양", 58, 40, 87, 1, 143, 212, null, null],
[0, "단외", null, 1, "장안", 55, 48, 55, 1, 148, 209, null, null],
[0, "원사", null, 0, null, 65, 43, 78, 0, 159, 210, null, null],
[0, "사의관", null, 0, null, 22, 12, 67, 0, 168, 199, null, null],
[0, "송건", null, 0, null, 66, 77, 40, 0, 164, 214, null, null],
[0, "곽원", null, 17, "업", 66, 73, 36, 1, 162, 205, null, null],
[0, "소유", null, 0, null, 51, 59, 48, 0, 167, 210, null, null],
[0, "주한", null, 0, null, 49, 51, 26, 0, 162, 204, null, null],
[0, "뇌서", null, 0, null, 68, 65, 34, 0, 170, 212, null, null],
[0, "장홍", null, 0, "복양", 72, 81, 65, 0, 164, 196, null, null],
[0, "유위대", null, 4, "북평", 53, 44, 62, 1, 152, 199, null, null],
[0, "문칙", null, 4, "계", 59, 53, 32, 1, 164, 199, null, null],
[0, "습진", null, 0, null, 64, 68, 51, 0, 189, 221, null, null],
[0, "습정", null, 0, null, 49, 29, 70, 0, 181, 229, null, null]
],
"general_ex":[],
"cities":[
],
"history":[
"<C>●</>190년 1월:<L><b>【역사모드2-2】</b></>반동탁연합 결성(정사)",
"<C>●</>190년 1월:<G><b>【연합】</b></>동탁을 토벌하기 위해 18로 제후가 뭉쳤다."
],
"initialEvents":[
[
true,
["ChangeCity", "free", {
"pop":"70%",
"agri":"70%",
"comm":"70%",
"secu":"70%",
"trust":80
}],
["ChangeCity", "occupied", {
"pop":"70%",
"agri":"70%",
"comm":"70%",
"secu":"70%",
"trust":80,
"def":"70%",
"wall":"70%"
}]
]
],
"events":[
[
"destroy_nation", 1000,
["and",
["Date", ">=", 183, 1],
["RemainNation", "==", 1]
],
["BlockScoutAction"],
["DeleteEvent"]
]
]
}
+1 -1
View File
@@ -31,7 +31,7 @@
["여포", "#A9A9A9", 100000, 100000, "조조의 본거지를 빼앗은 당대 최고의 맹장 여포", 1000, "병가", 2, [
"복양"
]],
["손", "#FF0000", 100000, 100000, "손견의 뒤를 이은 소패왕 손책", 1000, "병가", 2, [
["손", "#FF0000", 100000, 100000, "손견의 뒤를 이은 소패왕 손책", 1000, "병가", 2, [
"여강"
]],
["공손찬", "#FF00FF", 100000, 100000, "북평 태수 공손찬", 1000, "병가", 3, [
+766
View File
@@ -0,0 +1,766 @@
{
"title":"【역사모드3-2】 군웅축록",
"startYear":192,
"life":1,
"fiction":0,
"const":{
"defaultMaxGeneral":600
},
"nation":[
["이각", "#A9A9A9", 100000, 100000, "천자를 등에 업은 이각", 1000, "명가", 5, ["함곡", "장안", "홍농", "사곡", "안정"]],
["조조", "#0000FF", 10000, 40000, "위태로운 조조", 1000, "병가", 3, ["진류", "정도", ""]],
["여포", "#A9A9A9", 10000, 10000, "조조를 공격하는 여포", 1000, "병가", 1, ["복양"]],
["공손찬", "#FF00FF", 10000, 10000, "백마의 공손찬", 1000, "병가", 3, ["북평", "역경", "계"]],
["장양", "#7B68EE", 10000, 10000, "병주자사 장양", 1000, "병가", 3, ["하내"]],
["유장", "#483D8B", 10000, 30000, "익주목 유장", 1000, "유가", 4, ["성도", "자동", "면죽", "강주"]],
["원술", "#FFC0CB", 15000, 40000, "후장군 원술", 1000, "병가", 2, ["수춘"]],
["유비", "#008000", 10000, 10000, "서주목 유비", 1000, "유가", 4, ["서주", "하비", "소패"]],
["유요", "#808000", 10000, 10000, "양주자사 유요", 1000, "유가", 3, ["합비", "건업"]],
["마등", "#808000", 10000, 10000, "서량태수 마등", 1000, "병가", 3, ["서량"]],
["공손도", "#A0522D", 10000, 10000, "요동의 왕 공손도", 1000, "덕가", 1, ["안평"]],
["엄백호", "#800080", 10000, 10000, "동오의 호족, 엄백호", 1000, "병가", 1, ["오"]],
["유표", "#E0FFFF", 10000, 10000, "형주자사 유표", 1000, "유가", 4, ["양양", "장판", "강하"]],
["장로", "#20B2AA", 10000, 10000, "오두미도 장로", 1000, "오두미도", 1, ["한중", "양평"]],
["장연", "#87CEEB", 10000, 10000, "흑산적 장연", 1000, "도적", 2, ["진양"]],
["화흠", "#6495ED", 10000, 10000, "예장태수 화흠", 1000, "유가", 1, ["시상"]],
["원소", "#FFFF00", 10000, 10000, "기주목 원소", 1000, "병가", 4, ["남피", "업", "계교", "평원"]],
["사섭", "#800000", 10000, 10000, "교주자사 사섭", 1000, "유가", 3, ["교지", "남해"]],
["공융", "#800080", 10000, 10000, "북해태수 공융", 1000, "유가", 1, ["북해"]],
["한수", "#FFFFFF", 10000, 10000, "서량의 모반자 한수", 1000, "병가", 1, ["천수"]],
["왕랑", "#87CEEB", 10000, 10000, "회계태수 왕랑", 1000, "유가", 1, ["회계"]],
["유총", "#7B68EE", 10000, 10000, "진민왕 유총", 1000, "덕가", 6, ["초"]],
["손책", "#FF0000", 10000, 10000, "원술에게서 벗어나려는 손책", 1000, "병가", 2, ["여강"]],
["유벽", "#FFD700", 10000, 10000, "여남 황건적", 1000, "도적", 1, ["여남"]]
],
"diplomacy":[
[4, 17, 1, 36],
[23, 9, 1, 36],
[4, 8, 7, 48],
[17, 2, 7, 48],
[7, 23, 7, 48],
[3, 2, 1, 36]
],
"general":[
[0, "아회남", null, 0, null, 65, 74, 26, 0, 190, 225, null, null],
[0, "이적", null, 0, null, 29, 24, 84, 0, 162, 273, null, null],
[0, "윤상", null, 0, null, 51, 44, 62, 0, 194, 260, null, null],
[0, "윤묵", null, 0, null, 26, 15, 66, 0, 183, 239, null, null],
[0, "우금1", null, 2, "진류", 83, 78, 74, 1, 159, 221, null, null],
[0, "위관", null, 0, null, 69, 46, 79, 0, 220, 291, null, null],
[0, "원윤", null, 7, "수춘", 15, 14, 39, 1, 150, 199, null, null],
[0, "원희", null, 17, "업", 66, 51, 63, 1, 176, 207, null, null],
[0, "염행", null, 20, "천수", 76, 84, 66, 1, 159, 215, null, null],
[0, "염유", null, 0, "계", 71, 57, 71, 0, 168, 227, null, null],
[0, "원술", null, 7, "수춘", 44, 65, 65, 12, 155, 199, null, null, "꿀물~! 꿀을 더 가져 오거라"],
[0, "원상", null, 0, null, 63, 75, 42, 0, 179, 207, null, null],
[0, "원소", null, 17, "업", 81, 69, 70, 12, 154, 202, null, null, "사세 삼공의 이 원소가 모조리 통일하겠소"],
[0, "염상", null, 7, "수춘", 30, 27, 71, 11, 158, 199, null, null],
[0, "원담", null, 17, "평원", 60, 69, 30, 1, 173, 205, null, null],
[0, "염포", null, 14, "한중", 29, 25, 82, 11, 167, 231, null, null],
[0, "왕위", null, 0, null, 60, 70, 62, 0, 163, 222, null, null],
[0, "왕윤", null, 0, null, 26, 6, 68, 0, 137, 192, null, null],
[0, "왕항", null, 0, null, 76, 60, 61, 0, 184, 254, null, null],
[0, "왕찬", null, 13, "양양", 15, 3, 79, 1, 177, 217, null, null],
[0, "왕수", null, 19, "북해", 54, 27, 77, 11, 168, 218, null, null],
[0, "왕숙", null, 0, null, 36, 22, 76, 0, 195, 256, null, null],
[0, "왕준", null, 0, null, 81, 73, 77, 0, 206, 286, null, null],
[0, "왕쌍", null, 0, null, 67, 88, 19, 0, 195, 228, null, null],
[0, "왕창", null, 0, null, 74, 64, 79, 0, 188, 259, null, null],
[0, "왕평", null, 0, null, 83, 79, 76, 0, 192, 248, null, null],
[0, "왕보", null, 0, null, 62, 41, 79, 0, 171, 219, null, null],
[0, "왕문", null, 17, "남피", 65, 64, 31, 1, 162, 205, null, null],
[0, "왕릉", null, 0, null, 74, 64, 71, 0, 172, 251, null, null],
[0, "왕루", null, 0, null, 28, 30, 79, 0, 173, 211, null, null],
[0, "왕랑", null, 21, "회계", 47, 35, 79, 12, 167, 228, null, null],
[0, "온회", null, 0, null, 62, 36, 74, 0, 178, 228, null, null],
[0, "하안", null, 0, null, 15, 30, 72, 0, 190, 249, null, null],
[0, "괴월", null, 13, "양양", 47, 27, 82, 1, 157, 214, null, null],
[0, "괴량", null, 13, "양양", 68, 33, 88, 11, 155, 204, null, null],
[0, "가화", null, 0, null, 49, 65, 41, 0, 176, 222, null, null],
[0, "가규", null, 0, null, 78, 61, 84, 0, 177, 231, null, null],
[0, "하의", null, 7, "수춘", 56, 69, 36, 1, 161, 196, null, null],
[0, "화흠", null, 16, "시상", 33, 33, 82, 12, 157, 231, null, null],
[0, "가후", null, 1, "장안", 88, 48, 97, 11, 147, 223, null, null],
[0, "곽가", null, 2, "진류", 88, 15, 96, 11, 170, 207, null, null],
[0, "악환", null, 0, null, 65, 82, 42, 0, 196, 251, null, null],
[0, "곽사", null, 1, "안정", 64, 76, 13, 1, 146, 197, null, null],
[0, "악취", null, 0, null, 53, 66, 61, 0, 157, 195, null, null],
[0, "곽준", null, 0, null, 73, 66, 73, 0, 178, 217, null, null],
[0, "학소", null, 0, null, 86, 79, 78, 0, 185, 229, null, null],
[0, "악진", null, 2, "진류", 80, 84, 56, 1, 159, 218, null, null],
[0, "곽도", null, 17, "업", 52, 50, 82, 1, 157, 205, null, null],
[0, "학맹", null, 0, null, 62, 67, 42, 0, 156, 300, null, null],
[0, "곽유지", null, 0, null, 28, 9, 69, 0, 190, 259, null, null],
[0, "곽익", null, 0, null, 73, 69, 69, 0, 207, 270, null, null],
[0, "곽회", null, 0, null, 87, 78, 81, 0, 187, 255, null, null],
[0, "하후위", null, 0, null, 71, 73, 49, 0, 204, 253, null, null],
[0, "하후연", null, 2, "진류", 90, 91, 68, 1, 162, 219, null, null],
[0, "하후은", null, 0, null, 63, 72, 53, 0, 180, 208, null, null],
[0, "하후화", null, 0, null, 48, 51, 71, 0, 207, 265, null, null],
[0, "하후혜", null, 0, null, 50, 44, 76, 0, 206, 242, null, null],
[0, "하후현", null, 0, null, 54, 39, 76, 0, 208, 254, null, null],
[0, "하후상", null, 0, null, 79, 72, 75, 0, 181, 225, null, null],
[0, "하후덕", null, 0, null, 69, 73, 32, 0, 178, 218, null, null],
[0, "하후돈", null, 2, "진류", 92, 90, 63, 1, 156, 220, null, null],
[0, "하후패", null, 0, null, 79, 84, 77, 0, 202, 262, null, null],
[0, "하후무", null, 0, null, 20, 7, 4, 0, 201, 259, null, null],
[0, "가충", null, 0, null, 54, 31, 89, 0, 217, 282, null, null],
[0, "하진", null, 0, null, 39, 40, 6, 0, 135, 189, null, null],
[0, "화웅", null, 0, null, 88, 92, 61, 0, 155, 190, null, null],
[0, "한윤", null, 7, "수춘", 26, 29, 65, 1, 155, 197, null, null],
[0, "관우", null, 8, "서주", 97, 97, 79, 1, 162, 219, null, null, "술이 식기 전에 돌아오겠소"],
[0, "환계", null, 23, "여강", 25, 26, 68, 1, 156, 221, null, null],
[0, "관해", null, 0, null, 70, 82, 10, 0, 160, 193, null, null],
[0, "관구검", null, 0, null, 78, 74, 55, 0, 202, 255, null, null],
[0, "한현", null, 0, null, 22, 33, 62, 0, 163, 220, null, null],
[0, "한호", null, 2, "진류", 69, 72, 84, 1, 164, 224, null, null],
[0, "관흥", null, 0, null, 76, 86, 67, 0, 199, 234, null, null],
[0, "관색", null, 0, null, 74, 88, 56, 0, 200, 239, null, null],
[0, "한수", null, 20, "천수", 81, 75, 82, 12, 142, 215, null, null],
[0, "한숭", null, 13, "강하", 25, 15, 71, 1, 154, 210, null, null],
[0, "관정", null, 4, "북평", 36, 52, 73, 11, 158, 199, null, null],
[0, "한섬", null, 0, null, 69, 66, 36, 0, 159, 196, null, null],
[0, "감택", null, 0, null, 43, 49, 83, 0, 182, 243, null, null],
[0, "한충", null, 0, null, 64, 67, 18, 0, 151, 184, null, null],
[0, "한당", null, 23, "여강", 76, 85, 61, 1, 156, 227, null, null],
[0, "한덕", null, 0, null, 63, 76, 25, 0, 171, 228, null, null],
[0, "감녕", null, 13, "강하", 93, 94, 76, 1, 174, 219, null, null],
[0, "한복", null, 0, null, 28, 3, 27, 0, 149, 193, null, null],
[0, "관평", null, 0, null, 80, 82, 72, 0, 186, 219, null, null],
[0, "간옹", null, 8, "하비", 20, 32, 75, 1, 164, 225, null, null],
[0, "안량", null, 17, "업", 88, 94, 21, 1, 160, 200, null, null],
[0, "위연", null, 0, null, 85, 92, 72, 0, 175, 234, null, null],
[0, "국의", null, 17, "업", 82, 78, 51, 1, 146, 197, null, null],
[0, "희지재", null, 2, "진류", 63, 7, 88, 1, 157, 196, null, null],
[0, "위속", null, 3, "복양", 67, 78, 31, 1, 156, 200, null, null],
[0, "위유", null, 0, null, 46, 29, 74, 0, 151, 193, null, null],
[0, "우금2", null, 0, null, 72, 79, 38, 0, 173, 226, null, null],
[0, "우보", null, 0, null, 38, 60, 21, 0, 159, 192, null, null],
[0, "강유", null, 0, null, 92, 89, 90, 0, 202, 264, null, null],
[0, "공지", null, 0, null, 43, 53, 62, 0, 178, 197, null, null],
[0, "교유", null, 7, "수춘", 64, 69, 38, 1, 143, 195, null, null],
[0, "공도", null, 24, "여남", 59, 70, 23, 1, 164, 200, null, null],
[0, "허공", null, 0, null, 44, 66, 64, 0, 155, 200, null, null],
[0, "허정", null, 21, "회계", 23, 5, 68, 1, 152, 222, null, null],
[0, "허저", null, 2, "진류", 74, 96, 36, 1, 169, 226, null, null],
[0, "허유", null, 17, "업", 36, 21, 80, 1, 155, 204, null, null],
[0, "기령", null, 7, "수춘", 78, 83, 51, 1, 155, 199, null, null],
[0, "김의", null, 0, null, 32, 38, 65, 0, 177, 218, null, null],
[0, "김배삼결", null, 0, null, 64, 76, 19, 0, 192, 225, null, null],
[0, "김선", null, 0, null, 52, 68, 13, 0, 155, 220, null, null],
[0, "우번", null, 21, "회계", 43, 46, 86, 11, 164, 233, null, null],
[0, "형도영", null, 0, null, 69, 80, 41, 0, 174, 208, null, null],
[0, "엄안", null, 6, "자동", 82, 83, 72, 1, 151, 222, null, null],
[0, "곽마", null, 0, null, 68, 71, 40, 0, 239, 281, null, null],
[0, "엄준", null, 0, null, 28, 2, 71, 0, 169, 246, null, null],
[0, "엄정", null, 0, null, 50, 55, 39, 0, 151, 189, null, null],
[0, "엄백호", null, 12, "오", 67, 70, 23, 12, 142, 198, null, null, "이 몸은 동오의 덕왕 엄백호라고 하오!"],
[0, "엄여", null, 12, "오", 61, 78, 44, 1, 145, 196, null, null],
[0, "오의", null, 0, null, 79, 73, 70, 0, 165, 237, null, null],
[0, "황개", null, 23, "여강", 80, 83, 70, 1, 154, 218, null, null],
[0, "고간", null, 17, "업", 74, 56, 48, 1, 168, 206, null, null],
[0, "황권", null, 6, "성도", 75, 59, 82, 11, 167, 240, null, null],
[0, "고유", null, 17, "업", 57, 43, 72, 1, 174, 263, null, null],
[0, "고순", null, 3, "복양", 85, 86, 60, 1, 162, 198, null, null],
[0, "고상", null, 0, null, 68, 68, 53, 0, 194, 252, null, null],
[0, "고승", null, 0, null, 67, 73, 41, 0, 145, 184, null, null],
[0, "후성", null, 3, "복양", 74, 75, 68, 1, 158, 199, null, null],
[0, "후선", null, 20, "천수", 63, 66, 34, 1, 175, 228, null, null],
[0, "황조", null, 13, "강하", 74, 66, 55, 1, 148, 208, null, null],
[0, "공손월", null, 0, null, 74, 72, 47, 0, 160, 192, null, null],
[0, "공손연", null, 0, null, 65, 70, 60, 0, 205, 238, null, null],
[0, "공손공", null, 11, "안평", 39, 17, 69, 1, 174, 240, null, null],
[0, "공손강", null, 11, "안평", 72, 66, 65, 1, 172, 211, null, null],
[0, "공손찬", null, 4, "북평", 84, 83, 75, 12, 152, 199, null, null],
[0, "공손속", null, 4, "북평", 63, 68, 57, 1, 176, 199, null, null],
[0, "공손도", null, 11, "안평", 67, 71, 66, 12, 154, 204, null, null],
[0, "공손범", null, 4, "북평", 73, 69, 66, 1, 158, 199, null, null],
[0, "황충", null, 0, null, 90, 93, 65, 0, 154, 220, null, null],
[0, "고정", null, 0, null, 55, 63, 43, 0, 190, 251, null, null],
[0, "고패", null, 0, null, 66, 61, 69, 0, 170, 212, null, null],
[0, "황보숭", null, 0, null, 92, 61, 73, 0, 132, 195, null, null],
[0, "공융", null, 19, "북해", 30, 5, 73, 12, 153, 208, null, null],
[0, "고람", null, 17, "평원", 76, 82, 68, 1, 159, 201, null, null],
[0, "오경", null, 0, null, 69, 66, 52, 0, 151, 211, null, null],
[0, "호거아", null, 1, "홍농", 70, 82, 41, 1, 164, 206, null, null],
[0, "호진", null, 0, null, 65, 74, 12, 0, 148, 190, null, null],
[0, "올돌골", null, 0, null, 79, 86, 1, 0, 186, 225, null, null],
[0, "호반", null, 0, null, 55, 55, 59, 0, 179, 233, null, null],
[0, "오반", null, 0, null, 74, 71, 59, 0, 171, 234, null, null],
[0, "고옹", null, 0, null, 43, 18, 80, 0, 168, 243, null, null],
[0, "오란", null, 6, "성도", 68, 80, 35, 1, 170, 218, null, null],
[0, "채화", null, 13, "장판", 39, 50, 1, 1, 166, 208, null, null],
[0, "채중", null, 13, "장판", 40, 54, 2, 1, 168, 208, null, null],
[0, "채모", null, 13, "양양", 70, 70, 77, 1, 160, 215, null, null],
[0, "착융", null, 9, "건업", 60, 69, 38, 1, 161, 196, null, null],
[0, "사마의", null, 0, null, 98, 63, 98, 0, 179, 251, null, null],
[0, "사마염", null, 0, null, 69, 59, 77, 0, 238, 290, null, null],
[0, "사마사", null, 0, null, 79, 64, 88, 0, 208, 255, null, null],
[0, "사마소", null, 0, null, 76, 57, 87, 0, 211, 265, null, null],
[0, "사마부", null, 0, null, 70, 37, 77, 0, 180, 272, null, null],
[0, "사마랑", null, 0, null, 20, 21, 72, 0, 171, 217, null, null],
[0, "사마가", null, 0, null, 66, 84, 25, 0, 167, 222, null, null],
[0, "주흔", null, 21, "회계", 65, 62, 73, 1, 159, 196, null, null],
[0, "주앙", null, 17, "남피", 74, 65, 65, 1, 162, 195, null, null],
[0, "주지", null, 0, null, 65, 85, 41, 0, 233, 295, null, null],
[0, "주창", null, 0, null, 68, 84, 42, 0, 164, 219, null, null],
[0, "주태유평", null, 23, "여강", 84, 91, 48, 1, 171, 225, null, null],
[0, "주방", null, 0, null, 52, 41, 80, 0, 200, 240, null, null],
[0, "주유", null, 23, "여강", 95, 71, 96, 11, 175, 210, null, null],
[0, "주환", null, 0, null, 84, 82, 77, 0, 177, 238, null, null],
[0, "주거", null, 0, null, 77, 57, 72, 0, 190, 246, null, null],
[0, "축융", null, 0, null, 74, 85, 29, 0, 182, 230, null, null],
[0, "주준", null, 1, "장안", 89, 65, 70, 1, 149, 195, null, null],
[0, "주연", null, 0, null, 79, 69, 71, 0, 182, 249, null, null],
[0, "주치", null, 0, null, 71, 56, 72, 0, 156, 224, null, null],
[0, "주포", null, 0, null, 68, 73, 34, 0, 191, 225, null, null],
[0, "주령", null, 2, "진류", 71, 73, 67, 1, 170, 236, null, null],
[0, "순욱", null, 2, "진류", 62, 14, 95, 1, 163, 212, null, null],
[0, "순우경", null, 17, "남피", 72, 66, 29, 1, 146, 200, null, null],
[0, "순심", null, 17, "업", 34, 25, 78, 1, 164, 208, null, null],
[0, "순유", null, 2, "진류", 73, 26, 94, 1, 157, 214, null, null],
[0, "장완", null, 0, null, 78, 34, 84, 0, 188, 246, null, null],
[0, "종회", null, 0, null, 82, 51, 91, 0, 225, 264, null, null],
[0, "장간", null, 0, null, 19, 6, 66, 0, 175, 208, null, null],
[0, "장의거", null, 17, "업", 71, 58, 59, 1, 159, 205, null, null],
[0, "장흠", null, 23, "여강", 78, 84, 54, 1, 159, 208, null, null],
[0, "장제3", null, 0, null, 48, 43, 85, 0, 188, 249, null, null],
[0, "초주", null, 0, null, 16, 3, 67, 0, 201, 270, null, null],
[0, "초촉", null, 17, "업", 65, 76, 33, 1, 159, 208, null, null],
[0, "상총", null, 0, null, 76, 61, 77, 0, 195, 240, null, null],
[0, "종요", null, 1, "장안", 70, 24, 77, 1, 151, 230, null, null],
[0, "상랑", null, 0, null, 28, 18, 74, 0, 167, 247, null, null],
[0, "서영", null, 0, null, 80, 76, 62, 0, 147, 192, null, null],
[0, "제갈각", null, 0, null, 72, 47, 90, 0, 203, 253, null, null],
[0, "제갈근", null, 0, null, 75, 34, 81, 0, 174, 241, null, null],
[0, "제갈균", null, 0, null, 29, 25, 52, 0, 185, 252, null, null],
[0, "제갈탄", null, 0, null, 72, 72, 76, 0, 206, 258, null, null],
[0, "제갈량", null, 0, null, 98, 38, 100, 0, 184, 234, null, null],
[0, "서황", null, 1, "사곡", 91, 90, 78, 1, 165, 228, null, null],
[0, "서서", null, 0, null, 87, 64, 93, 0, 160, 300, null, null],
[0, "서성", null, 0, null, 86, 81, 78, 0, 178, 232, null, null],
[0, "서막", null, 0, null, 55, 32, 68, 0, 171, 249, null, null],
[0, "신의", null, 0, null, 55, 59, 60, 0, 190, 252, null, null],
[0, "신탐", null, 0, null, 56, 62, 54, 0, 188, 254, null, null],
[0, "심배", null, 17, "업", 80, 60, 83, 1, 156, 204, null, null],
[0, "신비", null, 17, "남피", 39, 24, 79, 1, 171, 240, null, null],
[0, "신평", null, 17, "남피", 69, 43, 77, 1, 165, 204, null, null],
[0, "진복", null, 0, null, 31, 7, 73, 0, 160, 226, null, null],
[0, "진랑", null, 0, null, 58, 70, 35, 0, 192, 234, null, null],
[0, "휴고", null, 15, "진양", 63, 71, 38, 1, 151, 199, null, null],
[0, "추단", null, 0, null, 61, 64, 34, 0, 144, 193, null, null],
[0, "성의", null, 20, "천수", 74, 72, 41, 1, 168, 211, null, null],
[0, "성공영", null, 20, "천수", 73, 71, 80, 11, 172, 220, null, null],
[0, "설종", null, 0, null, 34, 16, 73, 0, 187, 243, null, null],
[0, "선경", null, 4, "북평", 71, 68, 43, 1, 156, 199, null, null],
[0, "전종", null, 0, null, 78, 72, 75, 0, 183, 249, null, null],
[0, "조예", null, 0, null, 72, 52, 82, 0, 205, 239, null, null],
[0, "조휴", null, 2, "진류", 74, 75, 62, 1, 174, 228, null, null],
[0, "송헌", null, 3, "복양", 68, 77, 38, 1, 157, 200, null, null],
[0, "조앙", null, 2, "진류", 74, 67, 69, 1, 161, 197, null, null],
[0, "조홍 자렴", null, 2, "진류", 79, 81, 44, 1, 169, 232, null, null, "좌절감이 사나이를 키우는 것이다!"],
[0, "조순", null, 2, "진류", 75, 71, 63, 1, 170, 210, null, null],
[0, "조창", null, 0, null, 82, 90, 40, 0, 190, 223, null, null],
[0, "조식", null, 0, null, 33, 22, 81, 0, 192, 232, null, null],
[0, "조진", null, 0, null, 81, 74, 70, 0, 185, 231, null, null],
[0, "조인", null, 2, "진류", 89, 86, 62, 1, 168, 223, null, null],
[0, "조성", null, 0, null, 54, 74, 38, 0, 163, 198, null, null],
[0, "조조", null, 2, "진류", 99, 72, 91, 12, 151, 220, null, null, "내가 천하 사람들을 저버릴지언정, 천하 사람들이 나를 저버리게 하지는 않을 것이다."],
[0, "조충", null, 0, null, 31, 21, 85, 0, 196, 207, null, null],
[0, "장패", null, 0, null, 78, 75, 53, 0, 165, 231, null, null],
[0, "조비", null, 0, null, 70, 71, 83, 0, 187, 226, null, null],
[0, "조표", null, 8, "서주", 66, 69, 32, 1, 151, 196, null, null],
[0, "저수", null, 17, "업", 78, 35, 90, 1, 156, 201, null, null],
[0, "소비", null, 0, null, 69, 63, 66, 0, 172, 221, null, null],
[0, "조무", null, 0, null, 70, 71, 66, 0, 155, 193, null, null],
[0, "손환", null, 0, null, 82, 73, 76, 0, 166, 228, null, null],
[0, "손견", null, 0, null, 94, 90, 79, 0, 156, 192, null, null, "내가 옥새를 가지고 있다면 나는 칼과 화살을 맞아 죽을 것이다."],
[0, "손건", null, 0, null, 34, 33, 79, 0, 165, 215, null, null],
[0, "손권", null, 0, null, 79, 67, 80, 0, 182, 252, null, null],
[0, "손교", null, 0, null, 77, 72, 67, 0, 181, 219, null, null],
[0, "손책", null, 23, "여강", 96, 92, 74, 12, 175, 200, null, null],
[0, "손소", null, 0, null, 80, 79, 76, 0, 188, 241, null, null],
[0, "손정", null, 23, "여강", 66, 53, 73, 1, 160, 206, null, null],
[0, "손중", null, 0, null, 62, 71, 36, 0, 154, 184, null, null],
[0, "손등", null, 0, null, 62, 37, 79, 0, 209, 241, null, null],
[0, "손유", null, 0, null, 77, 70, 68, 0, 177, 215, null, null],
[0, "손익", null, 0, null, 67, 78, 27, 0, 184, 204, null, null],
[0, "손례", null, 0, null, 78, 81, 67, 0, 180, 250, null, null],
[0, "태사자", null, 9, "건업", 90, 93, 69, 1, 166, 206, null, null],
[0, "대래동주", null, 0, null, 51, 53, 54, 0, 195, 249, null, null],
[0, "대릉", null, 0, null, 70, 75, 39, 0, 199, 258, null, null],
[0, "타사대왕", null, 0, null, 62, 59, 70, 0, 186, 225, null, null],
[0, "담웅", null, 0, null, 58, 73, 14, 0, 188, 221, null, null],
[0, "장윤", null, 13, "양양", 72, 67, 42, 1, 163, 208, null, null],
[0, "조운", null, 4, "북평", 96, 96, 77, 1, 168, 229, null, null],
[0, "장위", null, 14, "한중", 74, 66, 45, 1, 172, 228, null, null, "쌀 다섯 말만 가지고 오시오~ 모두 고쳐드리리다."],
[0, "장영", null, 9, "건업", 75, 74, 41, 1, 154, 196, null, null],
[0, "장연", null, 15, "진양", 76, 81, 57, 12, 153, 210, null, null],
[0, "장횡", null, 9, "건업", 59, 70, 23, 1, 178, 211, null, null],
[0, "장온", null, 0, null, 32, 10, 72, 0, 193, 231, null, null],
[0, "장개", null, 0, null, 34, 66, 8, 0, 155, 194, null, null],
[0, "장각", null, 0, null, 87, 25, 86, 0, 140, 184, null, null],
[0, "장기", null, 0, null, 74, 35, 76, 0, 170, 223, null, null],
[0, "장의", null, 17, "남피", 79, 72, 75, 1, 153, 215, null, null],
[0, "장훈", null, 7, "수춘", 72, 68, 41, 1, 156, 206, null, null],
[0, "장굉", null, 23, "여강", 24, 22, 86, 1, 153, 212, null, null],
[0, "장합", null, 17, "평원", 89, 89, 73, 1, 167, 231, null, null],
[0, "조홍", null, 0, null, 66, 72, 28, 0, 156, 184, null, null],
[0, "장제1", null, 1, "홍농", 70, 66, 55, 1, 144, 196, null, null],
[0, "장수", null, 1, "홍농", 80, 73, 64, 1, 154, 207, null, null],
[0, "장소 자포", null, 23, "여강", 33, 3, 84, 1, 156, 236, null, null],
[0, "장송", null, 0, null, 25, 6, 88, 0, 170, 212, null, null],
[0, "장임", null, 6, "성도", 84, 84, 78, 1, 169, 214, null, null],
[0, "장제", null, 0, null, 67, 43, 80, 0, 236, 280, null, null],
[0, "장막", null, 3, "복양", 52, 53, 71, 1, 155, 195, null, null],
[0, "조범", null, 0, null, 28, 21, 32, 0, 168, 218, null, null],
[0, "장비", null, 8, "하비", 94, 98, 30, 1, 165, 221, null, null],
[0, "장포", null, 0, null, 78, 87, 48, 0, 198, 219, null, null],
[0, "장보", null, 0, null, 83, 71, 81, 0, 148, 184, null, null],
[0, "장만성", null, 0, null, 75, 81, 45, 0, 143, 184, null, null],
[0, "장양", null, 5, "진양", 72, 70, 66, 12, 151, 195, null, null],
[0, "장익", null, 0, null, 75, 74, 76, 0, 188, 264, null, null],
[0, "장료", null, 3, "복양", 95, 92, 78, 1, 169, 222, null, null],
[0, "장량", null, 0, null, 78, 80, 74, 0, 153, 184, null, null],
[0, "조루", null, 0, null, 70, 43, 68, 0, 183, 219, null, null],
[0, "장로", null, 14, "한중", 51, 26, 74, 12, 163, 216, null, null],
[0, "진횡", null, 9, "건업", 64, 63, 26, 1, 161, 196, null, null],
[0, "진응", null, 0, null, 62, 72, 45, 0, 172, 208, null, null],
[0, "진기", null, 7, "수춘", 58, 65, 43, 1, 165, 198, null, null],
[0, "진궁", null, 3, "복양", 84, 55, 89, 11, 154, 198, null, null, "조조…그자는 더 이상 내가 감당하기 어려워졌다"],
[0, "진교", null, 0, null, 61, 27, 77, 0, 175, 237, null, null],
[0, "진군", null, 8, "하비", 32, 14, 75, 11, 167, 236, null, null],
[0, "진규", null, 8, "서주", 30, 4, 82, 1, 132, 206, null, null],
[0, "진식", null, 0, null, 70, 71, 25, 0, 191, 230, null, null],
[0, "진진", null, 0, null, 46, 46, 69, 0, 170, 235, null, null],
[0, "진태", null, 0, null, 84, 77, 86, 0, 210, 260, null, null],
[0, "진등", null, 8, "하비", 79, 64, 81, 1, 169, 208, null, null],
[0, "진도", null, 0, null, 76, 71, 67, 0, 171, 237, null, null],
[0, "진무", null, 0, null, 76, 87, 43, 0, 176, 215, null, null],
[0, "진란", null, 7, "수춘", 66, 69, 40, 1, 157, 204, null, null],
[0, "진림", null, 17, "남피", 25, 9, 75, 1, 160, 217, null, null],
[0, "정욱", null, 2, "진류", 70, 49, 90, 1, 141, 220, null, null],
[0, "정원지", null, 0, null, 69, 74, 17, 0, 145, 184, null, null],
[0, "정은", null, 10, "서량", 69, 73, 40, 1, 184, 220, null, null],
[0, "정원", null, 0, null, 70, 76, 35, 0, 137, 189, null, null],
[0, "정보", null, 23, "여강", 84, 79, 79, 1, 151, 216, null, null],
[0, "정봉승연", null, 0, null, 81, 80, 75, 0, 190, 271, null, null],
[0, "전위", null, 0, null, 72, 95, 35, 0, 160, 197, null, null],
[0, "전해", null, 4, "북평", 68, 65, 58, 1, 154, 199, null, null],
[0, "전주", null, 0, null, 66, 66, 73, 0, 169, 214, null, null],
[0, "전풍", null, 17, "업", 72, 29, 93, 11, 162, 200, null, null],
[0, "전예", null, 4, "계", 80, 72, 80, 1, 171, 252, null, null],
[0, "동윤", null, 0, null, 47, 28, 79, 0, 192, 246, null, null],
[0, "동화", null, 0, null, 57, 34, 75, 0, 168, 219, null, null],
[0, "등애", null, 0, null, 93, 87, 89, 0, 197, 264, null, null],
[0, "도겸", null, 0, null, 51, 27, 64, 0, 132, 194, null, null],
[0, "등현", null, 0, null, 61, 73, 45, 0, 188, 248, null, null],
[0, "등지", null, 0, null, 73, 52, 81, 0, 178, 251, null, null],
[0, "동습", null, 0, null, 72, 85, 50, 0, 169, 215, null, null],
[0, "동승", null, 1, "장안", 56, 53, 65, 1, 154, 200, null, null],
[0, "동소", null, 5, "하내", 34, 25, 80, 11, 156, 236, null, null],
[0, "동탁", null, 0, null, 85, 87, 73, 0, 139, 192, null, null],
[0, "동도나", null, 0, null, 68, 73, 31, 0, 189, 225, null, null],
[0, "동민", null, 0, null, 49, 60, 25, 0, 149, 192, null, null],
[0, "등무", null, 0, null, 61, 75, 29, 0, 147, 184, null, null],
[0, "두기", null, 0, null, 66, 32, 75, 0, 163, 224, null, null],
[0, "두예", null, 0, null, 84, 30, 85, 0, 222, 284, null, null],
[0, "배원소", null, 0, null, 49, 65, 25, 0, 169, 200, null, null],
[0, "마완", null, 0, null, 71, 74, 16, 0, 170, 211, null, null],
[0, "마휴", null, 0, null, 68, 73, 48, 0, 178, 212, null, null],
[0, "파재", null, 0, null, 74, 74, 55, 0, 145, 184, null, null],
[0, "마준", null, 0, null, 38, 52, 31, 0, 196, 230, null, null],
[0, "마속", null, 0, null, 61, 67, 87, 0, 190, 228, null, null],
[0, "마대", null, 0, null, 79, 85, 62, 0, 183, 246, null, null],
[0, "마충", null, 0, null, 78, 72, 68, 0, 187, 249, null, null],
[0, "마초", null, 0, "서량", 93, 97, 44, 0, 176, 222, null, null],
[0, "마철", null, 0, null, 70, 62, 58, 0, 179, 212, null, null],
[0, "마등", null, 10, "서량", 82, 80, 52, 12, 149, 212, null, null],
[0, "마량", null, 0, null, 53, 23, 88, 0, 187, 222, null, null],
[0, "반준", null, 0, null, 61, 18, 83, 0, 174, 239, null, null],
[0, "반장", null, 0, null, 76, 80, 74, 0, 177, 234, null, null],
[0, "번조", null, 0, null, 66, 73, 31, 0, 149, 192, null, null],
[0, "번능", null, 0, null, 69, 61, 36, 0, 158, 195, null, null],
[0, "반봉", null, 0, null, 56, 77, 4, 0, 155, 190, null, null],
[0, "비의", null, 0, null, 77, 30, 83, 0, 193, 253, null, null],
[0, "비연", null, 0, null, 60, 66, 21, 0, 196, 238, null, null],
[0, "비시", null, 0, null, 18, 28, 65, 0, 176, 240, null, null],
[0, "미축", null, 8, "하비", 33, 29, 78, 1, 165, 220, null, null],
[0, "미방", null, 8, "하비", 54, 61, 32, 1, 169, 226, null, null],
[0, "비요", null, 0, null, 70, 66, 73, 0, 192, 228, null, null],
[0, "무안국", null, 19, "북해", 70, 83, 34, 1, 156, 193, null, null],
[0, "풍습", null, 0, null, 72, 71, 39, 0, 182, 222, null, null],
[0, "부사인", null, 0, null, 46, 63, 37, 0, 182, 225, null, null],
[0, "부첨", null, 0, null, 73, 85, 76, 0, 216, 263, null, null],
[0, "부손", null, 0, null, 29, 13, 70, 0, 162, 230, null, null],
[0, "문앙", null, 0, null, 76, 93, 65, 0, 238, 291, null, null],
[0, "문흠", null, 0, null, 74, 83, 43, 0, 200, 258, null, null],
[0, "문추", null, 17, "남피", 89, 93, 47, 1, 169, 200, null, null],
[0, "문빙", null, 0, null, 80, 82, 69, 0, 178, 237, null, null],
[0, "변희", null, 0, null, 56, 70, 62, 0, 169, 200, null, null],
[0, "망아장", null, 0, null, 45, 79, 3, 0, 191, 225, null, null],
[0, "봉기", null, 17, "남피", 43, 21, 84, 1, 153, 202, null, null],
[0, "방희", null, 0, null, 60, 38, 69, 0, 152, 192, null, null],
[0, "법정", null, 0, null, 82, 47, 94, 0, 176, 220, null, null],
[0, "방통", null, 0, null, 85, 34, 97, 0, 178, 213, null, null],
[0, "방덕", null, 10, "서량", 89, 94, 75, 11, 170, 219, null, null],
[0, "포륭", null, 0, null, 58, 76, 49, 0, 174, 208, null, null],
[0, "목순", null, 0, null, 52, 77, 11, 0, 157, 190, null, null],
[0, "목록대왕", null, 0, null, 65, 71, 39, 0, 184, 225, null, null],
[0, "보즐", null, 0, null, 72, 51, 84, 0, 177, 247, null, null],
[0, "만총", null, 0, null, 84, 64, 82, 0, 170, 242, null, null],
[0, "모개", null, 0, null, 63, 39, 60, 0, 161, 216, null, null],
[0, "맹획", null, 0, null, 87, 87, 42, 0, 186, 245, null, null],
[0, "맹달", null, 0, null, 75, 73, 75, 0, 172, 228, null, null],
[0, "맹우", null, 0, null, 50, 68, 22, 0, 190, 243, null, null],
[0, "유섭", null, 7, "완", 62, 79, 24, 1, 158, 190, null, null],
[0, "양부2", null, 0, null, 62, 68, 68, 0, 167, 212, null, null],
[0, "옹개", null, 0, null, 73, 75, 57, 0, 188, 225, null, null],
[0, "양의", null, 0, null, 68, 40, 82, 0, 190, 235, null, null],
[0, "양호", null, 0, null, 90, 64, 84, 0, 221, 278, null, null],
[0, "양홍", null, 7, "완", 33, 15, 77, 1, 152, 199, null, null],
[0, "양앙", null, 0, null, 68, 72, 38, 0, 172, 215, null, null],
[0, "양추1", null, 0, null, 52, 65, 40, 0, 159, 199, null, null],
[0, "양수", null, 0, null, 25, 4, 83, 0, 175, 219, null, null],
[0, "양추2", null, 0, null, 67, 64, 60, 0, 172, 238, null, null],
[0, "양송", null, 0, null, 1, 5, 28, 0, 167, 215, null, null],
[0, "양임", null, 14, "한중", 70, 78, 56, 1, 170, 215, null, null],
[0, "양조", null, 0, null, 53, 61, 36, 0, 223, 286, null, null],
[0, "양백", null, 14, "한중", 44, 45, 19, 1, 171, 214, null, null],
[0, "양부1", null, 0, null, 67, 50, 83, 0, 178, 239, null, null],
[0, "양봉1", null, 0, null, 66, 65, 31, 0, 153, 197, null, null],
[0, "양봉2", null, 0, null, 54, 62, 53, 0, 191, 252, null, null],
[0, "뇌동", null, 0, null, 69, 78, 51, 0, 172, 218, null, null],
[0, "뇌박", null, 7, "수춘", 62, 70, 36, 1, 157, 206, null, null],
[0, "이이", null, 0, null, 63, 77, 19, 0, 187, 222, null, null],
[0, "이회", null, 0, null, 79, 65, 79, 0, 175, 231, null, null],
[0, "이각", null, 1, "장안", 69, 72, 24, 12, 148, 198, null, null],
[0, "육항", null, 0, null, 91, 63, 87, 0, 226, 274, null, null],
[0, "육적", null, 0, null, 20, 7, 72, 0, 187, 219, null, null],
[0, "육손", null, 0, null, 94, 69, 95, 0, 183, 245, null, null],
[0, "이엄", null, 0, null, 83, 84, 76, 0, 172, 234, null, null],
[0, "이유", null, 0, null, 61, 26, 93, 0, 150, 192, null, null, "장안으로 천도를 하는것이 어떻겠습니까?"],
[0, "이숙", null, 0, null, 46, 69, 61, 0, 156, 192, null, null],
[0, "이통", null, 0, "여남", 73, 83, 62, 0, 168, 209, null, null],
[0, "이전", null, 0, null, 78, 77, 79, 0, 181, 216, null, null],
[0, "이풍", null, 7, "수춘", 69, 74, 50, 1, 158, 199, null, null],
[0, "유언", null, 0, null, 53, 38, 80, 0, 132, 194, null, null],
[0, "유괴", null, 6, "성도", 71, 73, 67, 1, 165, 214, null, null],
[0, "유기", null, 13, "양양", 52, 12, 63, 1, 174, 210, null, null],
[0, "유우", null, 0, null, 55, 33, 70, 0, 145, 193, null, null],
[0, "유훈", null, 7, "수춘", 47, 63, 35, 1, 163, 216, null, null],
[0, "유현", null, 0, null, 54, 66, 51, 0, 188, 252, null, null],
[0, "유순", null, 0, null, 63, 46, 41, 0, 172, 255, null, null],
[0, "유장", null, 6, "성도", 31, 5, 34, 12, 162, 219, null, null],
[0, "유심", null, 0, null, 60, 62, 70, 0, 238, 264, null, null],
[0, "유선", null, 0, null, 3, 5, 9, 0, 208, 270, null, null],
[0, "유종", null, 0, null, 24, 22, 65, 0, 195, 220, null, null],
[0, "유도", null, 0, null, 25, 23, 26, 0, 168, 214, null, null],
[0, "유파", null, 0, null, 29, 22, 75, 0, 186, 222, null, null],
[0, "유반", null, 0, null, 69, 76, 47, 0, 168, 210, null, null],
[0, "유비", null, 8, "하비", 81, 77, 80, 12, 161, 223, null, null],
[0, "유표1", null, 13, "양양", 48, 31, 72, 1, 142, 208, null, null, "강하 팔준에서 제일가는 이 유표에게로 오시오!"],
[0, "유복", null, 0, null, 64, 49, 74, 0, 164, 208, null, null],
[0, "유벽", null, 24, "여남", 70, 72, 47, 12, 145, 201, null, null],
[0, "유봉", null, 0, null, 75, 79, 44, 0, 188, 220, null, null],
[0, "유엽", null, 0, null, 56, 32, 92, 0, 176, 235, null, null],
[0, "유요", null, 9, "건업", 62, 67, 46, 12, 156, 198, null, null],
[0, "여위황", null, 17, "남피", 58, 63, 29, 1, 159, 200, null, null],
[0, "요화", null, 0, null, 73, 76, 69, 0, 168, 264, null, null],
[0, "양흥", null, 10, "서량", 61, 65, 19, 1, 169, 211, null, null],
[0, "양습", null, 0, null, 73, 40, 74, 0, 168, 230, null, null],
[0, "양서", null, 0, null, 56, 62, 66, 0, 198, 260, null, null],
[0, "능조", null, 0, null, 75, 81, 42, 0, 168, 203, null, null],
[0, "능통", null, 0, null, 77, 89, 60, 0, 189, 217, null, null],
[0, "여개", null, 0, null, 53, 30, 67, 0, 194, 227, null, null],
[0, "여건", null, 2, "진류", 57, 70, 62, 1, 173, 238, null, null],
[0, "여광", null, 0, null, 56, 70, 13, 0, 162, 207, null, null],
[0, "여상", null, 0, null, 54, 71, 12, 0, 164, 207, null, null],
[0, "여대", null, 0, null, 75, 71, 70, 0, 161, 256, null, null],
[0, "여범", null, 0, null, 73, 63, 75, 0, 169, 228, null, null],
[0, "여포", null, 3, "복양", 97, 100, 26, 12, 156, 198, null, null, "크하하하 다 부숴 버리겠다!"],
[0, "여몽", null, 0, null, 82, 81, 20, 0, 178, 219, null, null],
[0, "윤직", null, 0, null, 46, 42, 73, 0, 196, 237, null, null],
[0, "냉포", null, 0, null, 71, 82, 61, 0, 176, 214, null, null],
[0, "진취", null, 13, "강하", 56, 63, 45, 1, 174, 208, null, null],
[0, "노숙", null, 0, null, 84, 56, 92, 0, 172, 217, null, null],
[0, "노식", null, 0, null, 86, 63, 82, 0, 139, 192, null, null],
[0, "범강", null, 0, null, 49, 50, 36, 0, 174, 222, null, null],
[0, "장달", null, 0, null, 43, 56, 34, 0, 175, 222, null, null],
[0, "규람", null, 0, null, 55, 70, 58, 0, 165, 204, null, null],
[0, "대원", null, 0, null, 38, 32, 68, 0, 166, 204, null, null],
[0, "왕기", null, 0, null, 77, 72, 86, 0, 190, 261, null, null],
[0, "왕상", null, 0, null, 23, 19, 68, 0, 180, 268, null, null],
[0, "왕돈", null, 0, null, 60, 66, 46, 0, 198, 256, null, null],
[0, "악침", null, 0, null, 73, 75, 43, 0, 196, 257, null, null],
[0, "하제", null, 0, null, 83, 70, 82, 0, 171, 227, null, null],
[0, "환범", null, 0, null, 40, 11, 78, 0, 199, 249, null, null],
[0, "견초", null, 17, "남피", 72, 79, 73, 1, 168, 244, null, null],
[0, "강경", null, 0, null, 69, 64, 54, 0, 178, 212, null, null],
[0, "오찬", null, 0, null, 68, 41, 78, 0, 181, 245, null, null],
[0, "호질", null, 0, null, 73, 46, 74, 0, 192, 250, null, null],
[0, "오질", null, 0, null, 16, 29, 68, 0, 177, 230, null, null],
[0, "호준", null, 0, null, 79, 76, 62, 0, 200, 256, null, null],
[0, "고담", null, 0, null, 53, 23, 77, 0, 203, 244, null, null],
[0, "최림", null, 0, null, 32, 22, 66, 0, 187, 244, null, null],
[0, "사마망", null, 0, null, 78, 58, 83, 0, 205, 271, null, null],
[0, "주이", null, 0, null, 70, 73, 61, 0, 201, 257, null, null],
[0, "전단", null, 0, null, 67, 64, 39, 0, 204, 261, null, null],
[0, "조우", null, 0, null, 50, 43, 37, 0, 199, 278, null, null],
[0, "조상", null, 0, null, 39, 33, 30, 0, 207, 249, null, null],
[0, "조방", null, 0, null, 20, 14, 27, 0, 232, 274, null, null],
[0, "태사향", null, 0, null, 57, 66, 48, 0, 189, 246, null, null],
[0, "장휴", null, 0, null, 63, 27, 74, 0, 204, 244, null, null],
[0, "장호", null, 0, null, 75, 74, 51, 0, 195, 240, null, null],
[0, "조광", null, 0, null, 67, 71, 53, 0, 210, 263, null, null],
[0, "장집", null, 0, null, 76, 29, 80, 0, 196, 254, null, null],
[0, "장승", null, 0, null, 77, 70, 75, 0, 178, 244, null, null],
[0, "장소", null, 0, null, 23, 56, 31, 0, 202, 264, null, null],
[0, "조통", null, 0, null, 68, 67, 61, 0, 209, 260, null, null],
[0, "진표", null, 0, null, 66, 42, 74, 0, 207, 237, null, null],
[0, "정무", null, 0, null, 56, 35, 75, 0, 201, 265, null, null],
[0, "정봉", null, 0, null, 66, 66, 53, 0, 198, 266, null, null],
[0, "전만", null, 0, null, 51, 73, 39, 0, 181, 235, null, null],
[0, "마균", null, 0, null, 22, 17, 71, 0, 200, 259, null, null],
[0, "방회", null, 0, null, 69, 75, 49, 0, 205, 272, null, null],
[0, "서구", null, 0, null, 69, 42, 70, 0, 149, 208, null, null],
[0, "육개", null, 0, null, 65, 61, 79, 0, 198, 269, null, null],
[0, "유찬", null, 0, null, 78, 81, 64, 0, 172, 255, null, null],
[0, "유소", null, 0, null, 68, 53, 74, 0, 195, 264, null, null],
[0, "요립", null, 0, null, 32, 15, 76, 0, 181, 250, null, null],
[0, "여거", null, 0, null, 78, 69, 71, 0, 196, 256, null, null],
[0, "제갈첨", null, 0, null, 70, 57, 76, 0, 227, 263, null, null],
[0, "손휴", null, 0, null, 57, 42, 78, 0, 235, 264, null, null],
[0, "극정", null, 0, null, 42, 23, 68, 0, 208, 278, null, null],
[0, "호분", null, 0, null, 71, 76, 50, 0, 222, 288, null, null],
[0, "손량", null, 0, null, 32, 26, 70, 0, 243, 260, null, null],
[0, "마막", null, 0, null, 34, 12, 44, 0, 221, 266, null, null],
[0, "손호", null, 0, null, 41, 73, 71, 0, 242, 284, null, null],
[0, "조모", null, 0, null, 54, 61, 59, 0, 241, 260, null, null],
[0, "조환", null, 0, null, 30, 28, 30, 0, 246, 302, null, null],
[0, "사마유", null, 0, null, 71, 68, 75, 0, 248, 283, null, null],
[0, "제갈상", null, 0, null, 64, 79, 62, 0, 246, 263, null, null],
[0, "손화", null, 0, null, 31, 39, 68, 0, 224, 253, null, null],
[0, "염우", null, 0, null, 52, 61, 51, 0, 209, 264, null, null],
[0, "왕경", null, 0, null, 67, 63, 71, 0, 206, 260, null, null],
[0, "왕혼", null, 0, null, 74, 71, 63, 0, 223, 297, null, null],
[0, "곽혁", null, 0, null, 33, 29, 70, 0, 187, 225, null, null],
[0, "가범", null, 0, null, 60, 62, 73, 0, 202, 237, null, null],
[0, "서질", null, 0, null, 68, 83, 24, 0, 207, 253, null, null],
[0, "손준", null, 0, null, 64, 70, 65, 0, 219, 256, null, null],
[0, "손침", null, 0, null, 58, 55, 65, 0, 231, 258, null, null],
[0, "우사", null, 0, null, 61, 34, 74, 0, 217, 273, null, null],
[0, "진건", null, 0, null, 72, 71, 65, 0, 214, 292, null, null],
[0, "동궐", null, 0, null, 65, 59, 76, 0, 204, 271, null, null],
[0, "당자", null, 0, null, 61, 71, 50, 0, 196, 265, null, null],
[0, "등충", null, 0, null, 76, 86, 69, 0, 230, 264, null, null],
[0, "황숭", null, 0, null, 67, 63, 72, 0, 208, 263, null, null],
[0, "화핵", null, 0, null, 20, 24, 71, 0, 217, 278, null, null],
[0, "허의", null, 0, null, 51, 74, 15, 0, 213, 263, null, null],
[0, "송겸", null, 0, null, 66, 78, 42, 0, 175, 227, null, null],
[0, "호제", null, 0, null, 57, 39, 68, 0, 207, 264, null, null],
[0, "나헌", null, 0, null, 85, 70, 77, 0, 218, 270, null, null],
[0, "아들노숙", null, 0, null, 73, 53, 74, 0, 217, 272, null, null],
[0, "구성", null, 0, null, 63, 70, 38, 0, 157, 187, null, null],
[0, "어부라", null, 0, null, 72, 68, 21, 0, 150, 195, null, null],
[0, "아하소과", null, 0, null, 61, 76, 13, 0, 204, 253, null, null],
[0, "가비능", null, 0, null, 77, 69, 52, 0, 172, 235, null, null],
[0, "구력거", null, 0, null, 80, 68, 56, 0, 152, 193, null, null],
[0, "강단", null, 0, null, 65, 83, 48, 0, 168, 234, null, null],
[0, "황란", null, 0, null, 63, 75, 44, 0, 200, 264, null, null],
[0, "호주천", null, 0, null, 70, 71, 40, 0, 169, 230, null, null],
[0, "종리목", null, 0, null, 80, 72, 73, 0, 214, 269, null, null],
[0, "제갈교", null, 0, null, 62, 27, 75, 0, 204, 228, null, null],
[0, "답돈", null, 0, null, 84, 82, 61, 0, 158, 207, null, null],
[0, "반임", null, 0, null, 66, 79, 38, 0, 168, 225, null, null],
[0, "보도근", null, 0, null, 64, 73, 50, 0, 170, 233, null, null],
[0, "미당대왕", null, 0, null, 59, 69, 21, 0, 202, 260, null, null],
[0, "유표(흉노)", null, 0, null, 73, 74, 48, 0, 173, 252, null, null],
[0, "루반", null, 0, null, 65, 76, 39, 0, 178, 207, null, null],
[0, "손관", null, 0, null, 72, 78, 51, 0, 166, 217, null, null],
[0, "곽석", null, 0, null, 59, 71, 48, 0, 158, 187, null, null],
[0, "주조", null, 0, null, 63, 68, 26, 0, 152, 187, null, null],
[0, "장거", null, 0, null, 79, 75, 56, 0, 150, 188, null, null],
[0, "장순", null, 0, null, 73, 81, 60, 0, 152, 188, null, null],
[0, "해니", null, 0, null, 62, 78, 35, 0, 191, 233, null, null],
[0, "토안", null, 0, null, 53, 82, 30, 0, 191, 233, null, null],
[0, "유주", null, 0, null, 67, 78, 42, 0, 172, 233, null, null],
[0, "비잔", null, 0, null, 60, 71, 51, 0, 181, 216, null, null],
[0, "월길", null, 0, null, 81, 88, 26, 0, 190, 227, null, null],
[0, "아단", null, 0, null, 72, 65, 78, 0, 186, 259, null, null],
[0, "철리길", null, 0, null, 77, 70, 52, 0, 179, 259, null, null],
[0, "주삭", null, 0, null, 54, 60, 50, 0, 174, 253, null, null],
[0, "왕이", null, 0, null, 87, 59, 77, 0, 171, 230, null, null],
[0, "왕원희", null, 0, null, 53, 37, 73, 0, 217, 268, null, null],
[0, "곽씨", null, 0, null, 42, 4, 55, 0, 184, 235, null, null],
[0, "하후씨", null, 0, null, 34, 13, 42, 0, 188, 243, null, null],
[0, "하후령녀", null, 0, null, 22, 33, 60, 0, 207, 257, null, null],
[0, "하씨", null, 0, null, 51, 14, 67, 0, 150, 189, null, null],
[0, "관은병", null, 0, null, 87, 84, 75, 0, 204, 271, null, null],
[0, "감씨", null, 8, "서주", 14, 10, 44, 1, 175, 210, null, null],
[0, "황월영", null, 0, null, 62, 33, 90, 0, 186, 235, null, null],
[0, "채염", null, 0, null, 53, 14, 80, 0, 168, 224, null, null],
[0, "채씨", null, 13, "양양", 38, 7, 70, 1, 159, 212, null, null],
[0, "소교", null, 0, null, 40, 3, 70, 0, 178, 212, null, null],
[0, "서씨", null, 0, null, 48, 16, 80, 0, 183, 232, null, null],
[0, "신헌영", null, 0, null, 48, 28, 84, 0, 191, 269, null, null],
[0, "견씨", null, 0, null, 42, 18, 74, 0, 182, 221, null, null],
[0, "추씨", null, 1, "홍농", 4, 9, 66, 1, 165, 200, null, null],
[0, "조절", null, 0, null, 32, 33, 79, 0, 190, 260, null, null],
[0, "손씨", null, 0, null, 32, 14, 55, 0, 188, 250, null, null],
[0, "손상향", null, 0, null, 77, 82, 55, 0, 193, 222, null, null],
[0, "손노육", null, 0, null, 52, 10, 66, 0, 204, 253, null, null],
[0, "손노반", null, 0, null, 17, 15, 80, 0, 202, 258, null, null],
[0, "대교", null, 0, null, 33, 30, 66, 0, 177, 215, null, null],
[0, "장씨", null, 0, null, 55, 35, 66, 0, 207, 237, null, null],
[0, "장춘화", null, 0, null, 66, 29, 77, 0, 179, 234, null, null],
[0, "초선", null, 0, null, 42, 18, 81, 0, 176, 230, null, null],
[0, "번씨", null, 0, null, 6, 12, 50, 0, 183, 225, null, null],
[0, "미씨", null, 0, null, 13, 6, 72, 0, 176, 208, null, null],
[0, "변씨", null, 0, null, 30, 23, 55, 0, 160, 230, null, null],
[0, "보련사", null, 0, null, 45, 30, 77, 0, 198, 248, null, null],
[0, "유씨", null, 17, "업", 8, 8, 44, 1, 164, 215, null, null],
[0, "화만", null, 0, null, 85, 92, 40, 0, 191, 233, null, null],
[0, "포삼낭", null, 0, null, 87, 83, 36, 0, 191, 240, null, null],
[0, "성렴", null, 3, "복양", 66, 72, 43, 1, 168, 199, null, null],
[0, "위월", null, 3, "복양", 68, 76, 39, 1, 170, 199, null, null],
[0, "진의록", null, 3, "복양", 46, 43, 17, 1, 168, 199, null, null],
[0, "채옹", null, 0, null, 27, 16, 81, 0, 133, 192, null, null],
[0, "지양군", null, 0, null, 13, 8, 55, 0, 103, 192, null, null],
[0, "위소", null, 0, null, 21, 23, 65, 0, 204, 273, null, null],
[0, "위강", null, 0, null, 58, 35, 56, 0, 168, 213, null, null],
[0, "원요", null, 0, null, 28, 41, 50, 0, 177, 206, null, null],
[0, "왕기2", null, 0, null, 69, 67, 61, 0, 217, 281, null, null],
[0, "원환", null, 0, null, 30, 17, 72, 0, 165, 225, null, null],
[0, "우전", null, 0, null, 64, 76, 40, 0, 204, 258, null, null],
[0, "왕광", null, 0, null, 33, 65, 55, 0, 150, 190, null, null],
[0, "왕업", null, 0, null, 33, 6, 66, 0, 220, 280, null, null],
[0, "왕융", null, 0, null, 40, 57, 68, 0, 234, 305, null, null],
[0, "왕충", null, 0, null, 46, 39, 57, 0, 152, 214, null, null],
[0, "위풍", null, 0, null, 52, 23, 72, 0, 182, 219, null, null],
[0, "구건", null, 0, null, 39, 52, 65, 0, 239, 272, null, null],
[0, "구본", null, 0, null, 50, 41, 67, 0, 232, 269, null, null],
[0, "동백", null, 0, null, 12, 7, 52, 0, 173, 192, null, null],
[0, "마운", null, 0, null, 78, 88, 53, 0, 183, 225, null, null],
[0, "사섭", null, 18, "교지", 53, 31, 76, 12, 137, 226, null, null],
[0, "사지", null, 18, "교지", 58, 51, 52, 1, 163, 227, null, null],
[0, "사휘", null, 18, "남해", 69, 64, 44, 1, 165, 227, null, null],
[0, "사일", null, 18, "남해", 48, 36, 67, 1, 153, 230, null, null],
[0, "사광", null, 18, "교지", 58, 45, 64, 1, 175, 235, null, null],
[0, "후음", null, 0, null, 76, 72, 45, 0, 184, 219, null, null],
[0, "공주", null, 0, null, 26, 16, 68, 0, 151, 191, null, null],
[0, "고담륭", null, 0, null, 35, 24, 76, 0, 190, 251, null, null],
[0, "국연", null, 0, null, 50, 19, 77, 0, 160, 219, null, null],
[0, "오언", null, 0, null, 74, 72, 77, 0, 235, 297, null, null],
[0, "오강", null, 0, null, 48, 38, 65, 0, 216, 275, null, null],
[0, "최염", null, 0, null, 18, 55, 77, 0, 162, 216, null, null],
[0, "좌혁", null, 0, null, 62, 66, 51, 0, 232, 280, null, null],
[0, "차주", null, 0, null, 49, 49, 40, 0, 164, 199, null, null],
[0, "주태", null, 0, null, 71, 64, 71, 0, 207, 261, null, null],
[0, "북궁백옥", null, 0, null, 66, 72, 49, 0, 146, 187, null, null],
[0, "변장", null, 0, null, 69, 56, 47, 0, 133, 187, null, null],
[0, "순의", null, 0, null, 16, 13, 72, 0, 207, 274, null, null],
[0, "서진순욱", null, 0, null, 8, 19, 77, 0, 225, 289, null, null],
[0, "사마준", null, 0, null, 85, 82, 80, 0, 232, 286, null, null],
[0, "종육", null, 0, null, 32, 12, 71, 0, 223, 263, null, null],
[0, "초이", null, 0, null, 62, 69, 63, 0, 219, 266, null, null],
[0, "제갈정", null, 0, null, 58, 63, 66, 0, 246, 300, null, null],
[0, "보협", null, 0, null, 62, 49, 53, 0, 216, 264, null, null],
[0, "복양흥", null, 0, null, 32, 70, 65, 0, 224, 264, null, null],
[0, "보천", null, 0, null, 71, 60, 71, 0, 222, 272, null, null],
[0, "유대연주", null, 0, null, 53, 32, 55, 0, 148, 192, null, null],
[0, "유대", null, 2, "진류", 63, 58, 30, 1, 155, 202, null, null],
[0, "유승", null, 0, null, 45, 65, 33, 0, 215, 258, null, null],
[0, "여영기", null, 0, null, 79, 88, 37, 0, 182, 198, null, null],
[0, "독발수기능", null, 0, null, 89, 93, 77, 0, 232, 279, null, null],
[0, "진수", null, 0, null, 12, 22, 68, 0, 233, 297, null, null],
[0, "마륭", null, 0, null, 84, 75, 76, 0, 231, 296, null, null],
[0, "도황", null, 0, null, 76, 68, 72, 0, 232, 290, null, null],
[0, "진온", null, 0, null, 60, 38, 48, 0, 167, 192, null, null],
[0, "교모", null, 0, null, 54, 45, 69, 0, 137, 190, null, null],
[0, "교국로", null, 0, null, 38, 17, 55, 0, 150, 220, null, null],
[0, "포신", null, 0, null, 77, 67, 81, 0, 152, 192, null, null],
[0, "포훈", null, 0, null, 41, 35, 75, 0, 180, 226, null, null],
[0, "포도", null, 0, null, 57, 70, 43, 0, 159, 190, null, null],
[0, "위자", null, 0, null, 58, 38, 62, 0, 150, 190, null, null],
[0, "유화", null, 17, "남피", 21, 16, 59, 1, 165, 216, null, null],
[0, "이이자", null, 4, "북평", 32, 18, 48, 1, 157, 199, null, null],
[0, "선우보", null, 0, null, 74, 69, 65, 0, 161, 229, null, null],
[0, "원유", null, 0, null, 57, 39, 73, 0, 150, 193, null, null],
[0, "사마휘", null, 0, null, 21, 8, 92, 0, 173, 208, "은둔", null, "좋지~좋아"],
[0, "화타", null, 0, null, 8, 7, 68, 0, 173, 207, "은둔", "의술"],
[0, "관로", null, 0, null, 13, 9, 72, 0, 200, 233, "은둔", null],
[0, "예형", null, 0, null, 13, 4, 90, 0, 173, 199, null, null],
[0, "원혁", null, 7, "수춘", 18, 18, 18, 1, 180, 246, null, null],
[0, "헌제", null, 1, "장안", 23, 17, 66, 1, 184, 234, null, null, "한 황실은 이대로 끝나는 것인가…"],
[0, "황완", null, 0, null, 40, 34, 71, 0, 141, 192, null, null],
[0, "마일제", null, 0, "수춘", 43, 25, 70, 0, 130, 194, null, null],
[0, "유총", null, 22, "여남", 82, 45, 60, 12, 151, 197, null, null],
[0, "낙준", null, 22, "여남", 51, 37, 66, 11, 152, 197, null, null],
[0, "낙통", null, 0, null, 76, 57, 73, 0, 193, 228, null, null],
[0, "허소", null, 9, "건업", 46, 11, 82, 11, 150, 196, null, null, "태사자는 배신의 상이요! 쓰면 배신을 할것이오"],
[0, "허사", null, 3, "복양", 24, 20, 60, 1, 163, 199, null, null],
[0, "장초", null, 3, "복양", 61, 32, 60, 1, 168, 196, null, null],
[0, "양표", null, 1, "장안", 24, 20, 60, 1, 142, 225, null, null],
[0, "왕필", null, 2, "진류", 53, 44, 59, 1, 170, 228, null, null],
[0, "사환", null, 2, "진류", 70, 71, 45, 1, 173, 209, null, null],
[0, "정비", null, 2, "진류", 34, 30, 57, 1, 158, 214, null, null],
[0, "두씨", null, 3, "복양", 26, 41, 52, 1, 172, 236, null, null],
[0, "누규", null, 13, "양양", 58, 40, 87, 1, 143, 212, null, null],
[0, "단외", null, 1, "장안", 55, 48, 55, 1, 148, 209, null, null],
[0, "원사", null, 7, "수춘", 65, 43, 78, 1, 159, 210, null, null],
[0, "사의관", null, 7, "수춘", 22, 12, 67, 1, 168, 199, null, null],
[0, "송건", null, 0, null, 66, 77, 40, 0, 164, 214, null, null],
[0, "곽원", null, 17, "업", 66, 73, 36, 1, 162, 205, null, null],
[0, "소유", null, 17, "업", 51, 59, 48, 1, 167, 210, null, null],
[0, "주한", null, 17, "남피", 49, 51, 26, 1, 162, 204, null, null],
[0, "뇌서", null, 0, null, 68, 65, 34, 0, 170, 212, null, null],
[0, "장홍", null, 3, "복양", 72, 81, 65, 1, 164, 196, null, null],
[0, "유위대", null, 4, "북평", 53, 44, 62, 1, 152, 199, null, null],
[0, "문칙", null, 4, "계", 59, 53, 32, 1, 164, 199, null, null],
[0, "습진", null, 0, null, 64, 68, 51, 0, 189, 221, null, null],
[0, "습정", null, 0, null, 49, 29, 70, 0, 181, 229, null, null],
[0, "이리", null, 1, "장안", 65, 72, 37, 1, 171, 198, null, null]
],
"general_ex":[
],
"cities":[
],
"history":[
"<C>●</>195년 1월:<L><b>【역사모드3-2】</b>군욱축록</>",
"<C>●</>195년 1월:<L><b>【이벤트】</b></>천하는 분열하고 군웅할거가 도래하다"
],
"initialEvents":[
[
true,
["ChangeCity", "free", {
"pop":"70%",
"agri":"70%",
"comm":"70%",
"secu":"70%",
"trust":80
}],
["ChangeCity", "occupied", {
"pop":"70%",
"agri":"70%",
"comm":"70%",
"secu":"70%",
"trust":80,
"def":"70%",
"wall":"70%"
}]
]
],
"events":[
[
"destroy_nation", 1000,
["and",
["Date", ">=", 195, 1],
["RemainNation", "==", 1]
],
["BlockScoutAction"],
["DeleteEvent"]
]
]
}
+764
View File
@@ -0,0 +1,764 @@
{
"title":"【역사모드4-2】 황제 원술",
"startYear":194,
"life":1,
"fiction":0,
"const":{
"defaultMaxGeneral":600
},
"nation":[
["이각", "#A9A9A9", 5000, 5000, "바람앞의 등불 이각", 1000, "도적", 5, ["장안", "함곡"]],
["조조", "#0000FF", 10000, 40000, "승천하는 조조", 1000, "병가", 3, ["진류", "정도", "복양", "초", "낙양", "사곡", "허창", "사수", "호로"]],
["여포", "#A9A9A9", 10000, 10000, "서주를 뺏은 여포", 1000, "병가", 1, ["서주", "하비", "광릉"]],
["공손찬", "#FF00FF", 10000, 10000, "히키코모리 공손찬", 1000, "병가", 3, ["북평", "역경"]],
["장수", "#7B68EE", 10000, 10000, "동탁의 잔당 장수", 1000, "병가", 3, ["완"]],
["유장", "#483D8B", 10000, 30000, "익주목 유장", 1000, "유가", 4, ["성도", "자동", "면죽", "강주"]],
["중", "#FFC0CB", 15000, 40000, "중나라 황제 원술", 1000, "병가", 7, ["수춘", "여강", "합비"]],
["유비", "#008000", 10000, 10000, "예주자사 좌장군 유비", 1000, "유가", 3, ["패"]],
["마등", "#808000", 10000, 10000, "서량태수 마등", 1000, "병가", 3, ["서량"]],
["공손도", "#A0522D", 10000, 10000, "요동의 왕 공손도", 1000, "덕가", 1, ["안평"]],
["엄백호", "#800080", 10000, 10000, "엄백호의 잔당세력", 1000, "도적", 1, ["산월"]],
["유표", "#E0FFFF", 10000, 10000, "형주자사 유표", 1000, "유가", 4, ["양양", "장판", "강하"]],
["장로", "#20B2AA", 10000, 10000, "오두미도국 장로", 1000, "오두미도", 2, ["한중", "양평"]],
["장연", "#87CEEB", 10000, 10000, "흑산적 장연", 1000, "유가", 2, ["진양"]],
["화흠", "#6495ED", 10000, 10000, "예장태수 화흠", 1000, "유가", 1, ["시상"]],
["원소", "#FFFF00", 10000, 10000, "대장군 원소", 1000, "병가", 5, ["남피", "업", "계교", "평원", "북해"]],
["사섭", "#800000", 10000, 10000, "교주자사 사섭", 1000, "유가", 3, ["교지", "남해"]],
["한수", "#FFFFFF", 10000, 10000, "서량의 모반자 한수", 1000, "병가", 1, ["천수"]],
["손책", "#FF0000", 10000, 10000, "강동을 제패한 손책", 1000, "병가", 2, ["건업", "오", "회계"]],
["유벽", "#FFD700", 10000, 10000, "여남 황건적", 1000, "도적", 1, ["여남"]]
],
"diplomacy":[
[7, 2, 1, 36],
[7, 8, 1, 36],
[7, 19, 1, 36],
[3, 8, 7, 48],
[2, 8, 7, 48],
[16, 2, 7, 48],
[13, 6, 1, 36]
],
"general":[
[0, "아회남", null, 0, null, 65, 74, 26, 0, 190, 225, null, null],
[0, "이적", null, 0, null, 29, 24, 84, 0, 162, 273, null, null],
[0, "윤상", null, 0, null, 51, 44, 62, 0, 194, 260, null, null],
[0, "윤묵", null, 0, null, 26, 15, 66, 0, 183, 239, null, null],
[0, "우금1", null, 2, "진류", 83, 78, 74, 1, 159, 221, null, null],
[0, "위관", null, 0, null, 69, 46, 79, 0, 220, 291, null, null],
[0, "원윤", null, 7, "수춘", 15, 14, 39, 1, 150, 199, null, null],
[0, "원희", null, 16, "업", 66, 51, 63, 1, 176, 207, null, null],
[0, "염행", null, 18, "천수", 76, 84, 66, 1, 159, 215, null, null],
[0, "염유", null, 0, null, 71, 57, 71, 0, 168, 227, null, null],
[0, "원술", null, 7, "수춘", 44, 65, 65, 12, 155, 199, null, null, "이몸은 이제부터 중의 황제다!"],
[0, "원상", null, 0, null, 63, 75, 42, 0, 179, 207, null, null],
[0, "원소", null, 16, "업", 81, 69, 70, 12, 154, 202, null, null, "사세 삼공의 이 원소가 천하를 지배하겠다!"],
[0, "염상", null, 7, "수춘", 30, 27, 71, 11, 158, 199, null, null],
[0, "원담", null, 16, "북해", 60, 69, 30, 1, 173, 205, null, null],
[0, "염포", null, 13, "한중", 29, 25, 82, 11, 167, 231, null, null],
[0, "왕위", null, 12, "양양", 60, 70, 62, 1, 163, 222, null, null],
[0, "왕윤", null, 0, null, 26, 6, 68, 0, 137, 192, null, null],
[0, "왕항", null, 0, null, 76, 60, 61, 0, 184, 254, null, null],
[0, "왕찬", null, 12, "양양", 15, 3, 79, 1, 177, 217, null, null],
[0, "왕수", null, 16, "북해", 54, 27, 77, 1, 168, 218, null, null],
[0, "왕숙", null, 0, null, 36, 22, 76, 0, 195, 256, null, null],
[0, "왕준", null, 0, null, 81, 73, 77, 0, 206, 286, null, null],
[0, "왕쌍", null, 0, null, 67, 88, 19, 0, 195, 228, null, null],
[0, "왕창", null, 0, null, 74, 64, 79, 0, 188, 259, null, null],
[0, "왕평", null, 0, null, 83, 79, 76, 0, 192, 248, null, null],
[0, "왕보", null, 0, null, 62, 41, 79, 0, 171, 219, null, null],
[0, "왕문", null, 16, "남피", 65, 64, 31, 1, 162, 205, null, null],
[0, "왕릉", null, 0, null, 74, 64, 71, 0, 172, 251, null, null],
[0, "왕루", null, 0, null, 28, 30, 79, 0, 173, 211, null, null],
[0, "왕랑", null, 0, null, 47, 35, 79, 0, 167, 228, null, null],
[0, "온회", null, 0, null, 62, 36, 74, 0, 178, 228, null, null],
[0, "하안", null, 0, null, 15, 30, 72, 0, 190, 249, null, null],
[0, "괴월", null, 12, "양양", 47, 27, 82, 1, 157, 214, null, null],
[0, "괴량", null, 12, "양양", 68, 33, 88, 11, 155, 204, null, null],
[0, "가화", null, 0, null, 49, 65, 41, 0, 176, 222, null, null],
[0, "가규", null, 0, null, 78, 61, 84, 0, 177, 231, null, null],
[0, "하의", null, 0, null, 56, 69, 36, 0, 161, 196, null, null],
[0, "화흠", null, 15, "시상", 33, 33, 82, 12, 157, 231, null, null],
[0, "가후", null, 5, "완", 88, 48, 97, 11, 147, 223, null, null],
[0, "곽가", null, 2, "진류", 88, 15, 96, 11, 170, 207, null, null],
[0, "악환", null, 0, null, 65, 82, 42, 0, 196, 251, null, null],
[0, "곽사", null, 1, "장안", 64, 76, 13, 1, 146, 197, null, null],
[0, "악취", null, 0, null, 53, 66, 61, 0, 157, 195, null, null],
[0, "곽준", null, 0, null, 73, 66, 73, 0, 178, 217, null, null],
[0, "학소", null, 0, null, 86, 79, 78, 0, 185, 229, null, null],
[0, "악진", null, 2, "진류", 80, 84, 56, 1, 159, 218, null, null],
[0, "곽도", null, 16, "업", 52, 50, 82, 1, 157, 205, null, null],
[0, "학맹", null, 0, null, 62, 67, 42, 0, 156, 196, null, null],
[0, "곽유지", null, 0, null, 28, 9, 69, 0, 190, 259, null, null],
[0, "곽익", null, 0, null, 73, 69, 69, 0, 207, 270, null, null],
[0, "곽회", null, 0, null, 87, 78, 81, 0, 187, 255, null, null],
[0, "하후위", null, 0, null, 71, 73, 49, 0, 204, 253, null, null],
[0, "하후연", null, 2, "낙양", 90, 91, 68, 1, 162, 219, null, null],
[0, "하후은", null, 2, "복양", 63, 72, 53, 1, 180, 208, null, null],
[0, "하후화", null, 0, null, 48, 51, 71, 0, 207, 265, null, null],
[0, "하후혜", null, 0, null, 50, 44, 76, 0, 206, 242, null, null],
[0, "하후현", null, 0, null, 54, 39, 76, 0, 208, 254, null, null],
[0, "하후상", null, 0, null, 79, 72, 75, 0, 181, 225, null, null],
[0, "하후덕", null, 0, null, 69, 73, 32, 0, 178, 218, null, null],
[0, "하후돈", null, 2, "진류", 92, 90, 63, 1, 156, 220, null, null],
[0, "하후패", null, 0, null, 79, 84, 77, 0, 202, 262, null, null],
[0, "하후무", null, 0, null, 20, 7, 4, 0, 201, 259, null, null],
[0, "가충", null, 0, null, 54, 31, 89, 0, 217, 282, null, null],
[0, "하진", null, 0, null, 39, 40, 6, 0, 135, 189, null, null],
[0, "화웅", null, 0, null, 88, 92, 61, 0, 155, 190, null, null],
[0, "한윤", null, 7, "수춘", 26, 29, 65, 1, 155, 197, null, null],
[0, "관우", null, 8, "소패", 97, 97, 79, 1, 162, 219, null, null, "술이 식기 전에 돌아오겠소"],
[0, "환계", null, 19, "건업", 25, 26, 68, 1, 156, 221, null, null],
[0, "관해", null, 0, null, 70, 82, 10, 0, 160, 193, null, null],
[0, "관구검", null, 0, null, 78, 74, 55, 0, 202, 255, null, null],
[0, "한현", null, 12, "양양", 22, 33, 62, 1, 163, 220, null, null],
[0, "한호", null, 2, "진류", 69, 72, 84, 1, 164, 224, null, null],
[0, "관흥", null, 0, null, 76, 86, 67, 0, 199, 234, null, null],
[0, "관색", null, 0, null, 74, 88, 56, 0, 200, 239, null, null],
[0, "한수", null, 18, "천수", 81, 75, 82, 12, 142, 215, null, null],
[0, "한숭", null, 12, "강하", 25, 15, 71, 1, 154, 210, null, null],
[0, "관정", null, 4, "북평", 36, 52, 73, 11, 158, 199, null, null],
[0, "한섬", null, 7, "수춘", 69, 66, 36, 1, 159, 197, null, null],
[0, "감택", null, 0, null, 43, 49, 83, 0, 182, 243, null, null],
[0, "한충", null, 0, null, 64, 67, 18, 0, 151, 184, null, null],
[0, "한당", null, 19, "건업", 76, 85, 61, 1, 156, 227, null, null],
[0, "한덕", null, 0, null, 63, 76, 25, 0, 171, 228, null, null],
[0, "감녕", null, 12, "강하", 93, 94, 76, 1, 174, 219, null, null],
[0, "한복", null, 0, null, 28, 3, 27, 0, 149, 193, null, null],
[0, "관평", null, 0, null, 80, 82, 72, 0, 186, 219, null, null],
[0, "간옹", null, 8, "소패", 20, 32, 75, 1, 164, 225, null, null],
[0, "안량", null, 16, "업", 88, 94, 21, 1, 160, 200, null, null],
[0, "위연", null, 0, null, 85, 92, 72, 0, 175, 234, null, null],
[0, "국의", null, 0, null, 82, 78, 51, 0, 146, 197, null, null],
[0, "희지재", null, 0, null, 63, 7, 88, 0, 157, 196, null, null],
[0, "위속", null, 3, "서주", 67, 78, 31, 1, 156, 200, null, null],
[0, "위유", null, 0, null, 46, 29, 74, 0, 151, 193, null, null],
[0, "우금2", null, 2, "낙양", 72, 79, 38, 1, 173, 226, null, null],
[0, "우보", null, 0, null, 38, 60, 21, 0, 159, 192, null, null],
[0, "강유", null, 0, null, 92, 89, 90, 0, 202, 264, null, null],
[0, "공지", null, 0, null, 43, 53, 62, 0, 178, 197, null, null],
[0, "교유", null, 0, null, 64, 69, 38, 0, 143, 195, null, null],
[0, "공도", null, 20, "여남", 59, 70, 23, 1, 164, 200, null, null],
[0, "허공", null, 0, null, 44, 66, 64, 0, 155, 200, null, null],
[0, "허정", null, 17, "남해", 23, 5, 68, 1, 152, 222, null, null],
[0, "허저", null, 2, "낙양", 74, 96, 36, 1, 169, 226, null, null],
[0, "허유", null, 16, "업", 36, 21, 80, 1, 155, 204, null, null],
[0, "기령", null, 7, "수춘", 78, 83, 51, 1, 155, 199, null, null],
[0, "김의", null, 0, null, 32, 38, 65, 0, 177, 218, null, null],
[0, "김배삼결", null, 0, null, 64, 76, 19, 0, 192, 225, null, null],
[0, "김선", null, 12, "양양", 52, 68, 13, 1, 155, 220, null, null],
[0, "우번", null, 19, "회계", 43, 46, 86, 1, 164, 233, null, null],
[0, "형도영", null, 0, null, 69, 80, 41, 0, 174, 208, null, null],
[0, "엄안", null, 6, "자동", 82, 83, 72, 1, 151, 222, null, null],
[0, "곽마", null, 0, null, 68, 71, 40, 0, 239, 281, null, null],
[0, "엄준", null, 0, null, 28, 2, 71, 0, 169, 246, null, null],
[0, "엄정", null, 0, null, 50, 55, 39, 0, 151, 189, null, null],
[0, "엄백호", null, 11, "산월", 67, 70, 23, 12, 142, 198, null, null, "이 몸은 동오의 덕왕 엄백호라고 하오!"],
[0, "엄여", null, 0, null, 61, 78, 44, 0, 145, 196, null, null],
[0, "오의", null, 0, null, 79, 73, 70, 0, 165, 237, null, null],
[0, "황개", null, 19, "건업", 80, 83, 70, 1, 154, 218, null, null],
[0, "고간", null, 16, "업", 74, 56, 48, 1, 168, 206, null, null],
[0, "황권", null, 6, "성도", 75, 59, 82, 11, 167, 240, null, null],
[0, "고유", null, 16, "업", 57, 43, 72, 1, 174, 263, null, null],
[0, "고순", null, 3, "하비", 85, 86, 60, 1, 162, 198, null, null],
[0, "고상", null, 0, null, 68, 68, 53, 0, 194, 252, null, null],
[0, "고승", null, 0, null, 67, 73, 41, 0, 145, 184, null, null],
[0, "후성", null, 3, "하비", 74, 75, 68, 1, 158, 199, null, null],
[0, "후선", null, 18, "천수", 63, 66, 34, 1, 175, 228, null, null],
[0, "황조", null, 12, "강하", 74, 66, 55, 1, 148, 208, null, null],
[0, "공손월", null, 0, null, 74, 72, 47, 0, 160, 192, null, null],
[0, "공손연", null, 0, null, 65, 70, 60, 0, 205, 238, null, null],
[0, "공손공", null, 10, "안평", 39, 17, 69, 1, 174, 240, null, null],
[0, "공손강", null, 10, "안평", 72, 66, 65, 1, 172, 211, null, null],
[0, "공손찬", null, 4, "북평", 84, 83, 75, 12, 152, 199, null, null],
[0, "공손속", null, 4, "북평", 63, 68, 57, 1, 176, 199, null, null],
[0, "공손도", null, 10, "안평", 67, 71, 66, 12, 154, 204, null, null],
[0, "공손범", null, 4, "북평", 73, 69, 66, 1, 158, 199, null, null],
[0, "황충", null, 12, "양양", 90, 93, 65, 1, 154, 220, null, null],
[0, "고정", null, 6, "자동", 55, 63, 43, 1, 190, 251, null, null],
[0, "고패", null, 0, null, 66, 61, 69, 0, 170, 212, null, null],
[0, "황보숭", null, 0, null, 92, 61, 73, 0, 132, 195, null, null],
[0, "공융", null, 2, "허창", 30, 5, 73, 1, 153, 208, null, null],
[0, "고람", null, 16, "평원", 76, 82, 68, 1, 159, 201, null, null],
[0, "오경", null, 0, null, 69, 66, 52, 0, 151, 211, null, null],
[0, "호거아", null, 5, "완", 70, 82, 41, 1, 164, 206, null, null],
[0, "호진", null, 0, null, 65, 74, 12, 0, 148, 190, null, null],
[0, "올돌골", null, 0, null, 79, 86, 1, 0, 186, 225, null, null],
[0, "호반", null, 0, null, 55, 55, 59, 0, 179, 233, null, null],
[0, "오반", null, 6, "성도", 74, 71, 59, 1, 171, 234, null, null],
[0, "고옹", null, 0, null, 43, 18, 80, 0, 168, 243, null, null],
[0, "오란", null, 6, "성도", 68, 80, 35, 1, 170, 218, null, null],
[0, "채화", null, 12, "장판", 39, 50, 1, 1, 166, 208, null, null],
[0, "채중", null, 12, "장판", 40, 54, 2, 1, 168, 208, null, null],
[0, "채모", null, 12, "양양", 70, 70, 77, 1, 160, 215, null, null],
[0, "착융", null, 0, "건업", 60, 69, 38, 0, 161, 196, null, null],
[0, "사마의", null, 0, null, 98, 63, 98, 0, 179, 251, null, null],
[0, "사마염", null, 0, null, 69, 59, 77, 0, 238, 290, null, null],
[0, "사마사", null, 0, null, 79, 64, 88, 0, 208, 255, null, null],
[0, "사마소", null, 0, null, 76, 57, 87, 0, 211, 265, null, null],
[0, "사마부", null, 0, null, 70, 37, 77, 0, 180, 272, null, null],
[0, "사마랑", null, 0, null, 20, 21, 72, 0, 171, 217, null, null],
[0, "사마가", null, 0, null, 66, 84, 25, 0, 167, 222, null, null],
[0, "주흔", null, 0, null, 65, 62, 73, 0, 159, 196, null, null],
[0, "주앙", null, 0, null, 74, 65, 65, 0, 162, 195, null, null],
[0, "주지", null, 0, null, 65, 85, 41, 0, 233, 295, null, null],
[0, "주창", null, 0, null, 68, 84, 42, 0, 164, 219, null, null],
[0, "주태유평", null, 19, "건업", 84, 91, 48, 1, 171, 225, null, null],
[0, "주방", null, 0, null, 52, 41, 80, 0, 200, 240, null, null],
[0, "주유", null, 19, "건업", 95, 71, 96, 11, 175, 210, null, null],
[0, "주환", null, 0, null, 84, 82, 77, 0, 177, 238, null, null],
[0, "주거", null, 0, null, 77, 57, 72, 0, 190, 246, null, null],
[0, "축융", null, 0, null, 74, 85, 29, 0, 182, 230, null, null],
[0, "주준", null, 0, null, 89, 65, 70, 0, 149, 195, null, null],
[0, "주연", null, 0, null, 79, 69, 71, 0, 182, 249, null, null],
[0, "주치", null, 0, null, 71, 56, 72, 0, 156, 224, null, null],
[0, "주포", null, 0, null, 68, 73, 34, 0, 191, 225, null, null],
[0, "주령", null, 2, "진류", 71, 73, 67, 1, 170, 236, null, null],
[0, "순욱", null, 2, "진류", 62, 14, 95, 1, 163, 212, null, null],
[0, "순우경", null, 16, "남피", 72, 66, 29, 1, 146, 200, null, null],
[0, "순심", null, 16, "업", 34, 25, 78, 1, 164, 208, null, null],
[0, "순유", null, 2, "진류", 73, 26, 94, 1, 157, 214, null, null],
[0, "장완", null, 0, null, 78, 34, 84, 0, 188, 246, null, null],
[0, "종회", null, 0, null, 82, 51, 91, 0, 225, 264, null, null],
[0, "장간", null, 0, null, 19, 6, 66, 0, 175, 208, null, null],
[0, "장의거", null, 16, "업", 71, 58, 59, 1, 159, 205, null, null],
[0, "장흠", null, 19, "오", 78, 84, 54, 1, 159, 208, null, null],
[0, "장제3", null, 0, null, 48, 43, 85, 0, 188, 249, null, null],
[0, "초주", null, 0, null, 16, 3, 67, 0, 201, 270, null, null],
[0, "초촉", null, 16, "업", 65, 76, 33, 1, 159, 208, null, null],
[0, "상총", null, 0, null, 76, 61, 77, 0, 195, 240, null, null],
[0, "종요", null, 2, "낙양", 70, 24, 77, 1, 151, 230, null, null],
[0, "상랑", null, 0, null, 28, 18, 74, 0, 167, 247, null, null],
[0, "서영", null, 0, null, 80, 76, 62, 0, 147, 192, null, null],
[0, "제갈각", null, 0, null, 72, 47, 90, 0, 203, 253, null, null],
[0, "제갈근", null, 0, null, 75, 34, 81, 0, 174, 241, null, null],
[0, "제갈균", null, 0, null, 29, 25, 52, 0, 185, 252, null, null],
[0, "제갈탄", null, 0, null, 72, 72, 76, 0, 206, 258, null, null],
[0, "제갈량", null, 0, null, 98, 38, 100, 0, 184, 234, null, null],
[0, "서황", null, 2, "낙양", 91, 90, 78, 1, 165, 228, null, null],
[0, "서서", null, 0, null, 87, 64, 93, 0, 160, 300, null, null],
[0, "서성", null, 0, null, 86, 81, 78, 0, 178, 232, null, null],
[0, "서막", null, 0, null, 55, 32, 68, 0, 171, 249, null, null],
[0, "신의", null, 0, null, 55, 59, 60, 0, 190, 252, null, null],
[0, "신탐", null, 0, null, 56, 62, 54, 0, 188, 254, null, null],
[0, "심배", null, 16, "업", 80, 60, 83, 1, 156, 204, null, null],
[0, "신비", null, 16, "남피", 39, 24, 79, 1, 171, 240, null, null],
[0, "신평", null, 16, "남피", 69, 43, 77, 1, 165, 204, null, null],
[0, "진복", null, 0, null, 31, 7, 73, 0, 160, 226, null, null],
[0, "진랑", null, 0, null, 58, 70, 35, 0, 192, 234, null, null],
[0, "휴고", null, 14, "진양", 63, 71, 38, 1, 151, 199, null, null],
[0, "추단", null, 0, null, 61, 64, 34, 0, 144, 193, null, null],
[0, "성의", null, 18, "천수", 74, 72, 41, 1, 168, 211, null, null],
[0, "성공영", null, 18, "천수", 73, 71, 80, 11, 172, 220, null, null],
[0, "설종", null, 0, null, 34, 16, 73, 0, 187, 243, null, null],
[0, "선경", null, 4, "북평", 71, 68, 43, 1, 156, 199, null, null],
[0, "전종", null, 0, null, 78, 72, 75, 0, 183, 249, null, null],
[0, "조예", null, 0, null, 72, 52, 82, 0, 205, 239, null, null],
[0, "조휴", null, 2, "진류", 74, 75, 62, 1, 174, 228, null, null],
[0, "송헌", null, 3, "하비", 68, 77, 38, 1, 157, 200, null, null],
[0, "조앙", null, 2, "진류", 74, 67, 69, 1, 161, 197, null, null],
[0, "조홍 자렴", null, 2, "진류", 79, 81, 44, 1, 169, 232, null, null, "좌절감이 사나이를 키우는 것이다!"],
[0, "조순", null, 2, "진류", 75, 71, 63, 1, 170, 210, null, null],
[0, "조창", null, 0, null, 82, 90, 40, 0, 190, 223, null, null],
[0, "조식", null, 0, null, 33, 22, 81, 0, 192, 232, null, null],
[0, "조진", null, 0, null, 81, 74, 70, 0, 185, 231, null, null],
[0, "조인", null, 2, "진류", 89, 86, 62, 1, 168, 223, null, null],
[0, "조성", null, 0, null, 54, 74, 38, 0, 163, 198, null, null],
[0, "조조", null, 2, "진류", 99, 72, 91, 12, 151, 220, null, null, "내가 천하 사람들을 저버릴지언정, 천하 사람들이 나를 저버리게 하지는 않을 것이다."],
[0, "조충", null, 0, null, 31, 21, 85, 0, 196, 207, null, null],
[0, "장패", null, 0, null, 78, 75, 53, 0, 165, 231, null, null],
[0, "조비", null, 0, null, 70, 71, 83, 0, 187, 226, null, null],
[0, "조표", null, 0, null, 66, 69, 32, 0, 151, 196, null, null],
[0, "저수", null, 16, "업", 78, 35, 90, 1, 156, 201, null, null],
[0, "소비", null, 0, null, 69, 63, 66, 0, 172, 221, null, null],
[0, "조무", null, 0, null, 70, 71, 66, 0, 155, 193, null, null],
[0, "손환", null, 0, null, 82, 73, 76, 0, 166, 228, null, null],
[0, "손견", null, 0, null, 94, 90, 79, 0, 156, 192, null, null, "내가 옥새를 가지고 있다면 나는 칼과 화살을 맞아 죽을 것이다."],
[0, "손건", null, 0, null, 34, 33, 79, 0, 165, 215, null, null],
[0, "손권", null, 19, "건업", 79, 67, 80, 1, 182, 252, null, null],
[0, "손교", null, 0, null, 77, 72, 67, 0, 181, 219, null, null],
[0, "손책", null, 19, "건업", 96, 92, 74, 12, 175, 200, null, null],
[0, "손소", null, 0, null, 80, 79, 76, 0, 188, 241, null, null],
[0, "손정", null, 19, "오", 66, 53, 73, 1, 160, 206, null, null],
[0, "손중", null, 0, null, 62, 71, 36, 0, 154, 184, null, null],
[0, "손등", null, 0, null, 62, 37, 79, 0, 209, 241, null, null],
[0, "손유", null, 0, null, 77, 70, 68, 0, 177, 215, null, null],
[0, "손익", null, 0, null, 67, 78, 27, 0, 184, 204, null, null],
[0, "손례", null, 0, null, 78, 81, 67, 0, 180, 250, null, null],
[0, "태사자", null, 19, "건업", 90, 93, 69, 1, 166, 206, null, null],
[0, "대래동주", null, 0, null, 51, 53, 54, 0, 195, 249, null, null],
[0, "대릉", null, 0, null, 70, 75, 39, 0, 199, 258, null, null],
[0, "타사대왕", null, 0, null, 62, 59, 70, 0, 186, 225, null, null],
[0, "담웅", null, 0, null, 58, 73, 14, 0, 188, 221, null, null],
[0, "장윤", null, 12, "양양", 72, 67, 42, 1, 163, 208, null, null],
[0, "조운", null, 4, "북평", 96, 96, 77, 1, 168, 229, null, null],
[0, "장위", null, 13, "한중", 74, 66, 45, 1, 172, 228, null, null, "쌀 다섯말만 가지고 오시오~ 모두 고쳐드리리다."],
[0, "장영", null, 0, null, 75, 74, 41, 0, 154, 196, null, null],
[0, "장연", null, 14, "진양", 76, 81, 57, 12, 153, 210, null, null],
[0, "장횡", null, 0, null, 59, 70, 23, 0, 178, 211, null, null],
[0, "장온", null, 0, null, 32, 10, 72, 0, 193, 231, null, null],
[0, "장개", null, 0, null, 34, 66, 8, 0, 155, 194, null, null],
[0, "장각", null, 0, null, 87, 25, 86, 0, 140, 184, null, null],
[0, "장기", null, 0, null, 74, 35, 76, 0, 170, 223, null, null],
[0, "장의", null, 16, "남피", 79, 72, 75, 1, 153, 215, null, null],
[0, "장훈", null, 7, "수춘", 72, 68, 41, 1, 156, 206, null, null],
[0, "장굉", null, 19, "건업", 24, 22, 86, 1, 153, 212, null, null],
[0, "장합", null, 16, "평원", 89, 89, 73, 1, 167, 231, null, null],
[0, "조홍", null, 0, null, 66, 72, 28, 0, 156, 184, null, null],
[0, "장제1", null, 1, "홍농", 70, 66, 55, 1, 144, 196, null, null],
[0, "장수", null, 5, "완", 80, 73, 64, 12, 154, 207, null, null],
[0, "장소 자포", null, 19, "건업", 33, 3, 84, 1, 156, 236, null, null],
[0, "장송", null, 0, null, 25, 6, 88, 0, 170, 212, null, null],
[0, "장임", null, 6, "성도", 84, 84, 78, 1, 169, 214, null, null],
[0, "장제", null, 0, null, 67, 43, 80, 0, 236, 280, null, null],
[0, "장막", null, 0, null, 52, 53, 71, 0, 155, 195, null, null],
[0, "조범", null, 0, null, 28, 21, 32, 0, 168, 218, null, null],
[0, "장비", null, 8, "소패", 94, 98, 30, 1, 165, 221, null, null],
[0, "장포", null, 0, null, 78, 87, 48, 0, 198, 219, null, null],
[0, "장보", null, 0, null, 83, 71, 81, 0, 148, 184, null, null],
[0, "장만성", null, 0, null, 75, 81, 45, 0, 143, 184, null, null],
[0, "장양", null, 0, null, 72, 70, 66, 0, 151, 195, null, null],
[0, "장익", null, 0, null, 75, 74, 76, 0, 188, 264, null, null],
[0, "장료", null, 3, "서주", 95, 92, 78, 1, 169, 222, null, null],
[0, "장량", null, 0, null, 78, 80, 74, 0, 153, 184, null, null],
[0, "조루", null, 0, null, 70, 43, 68, 0, 183, 219, null, null],
[0, "장로", null, 13, "한중", 51, 26, 74, 12, 163, 216, null, null],
[0, "진횡", null, 0, null, 64, 63, 26, 0, 161, 196, null, null],
[0, "진응", null, 0, null, 62, 72, 45, 0, 172, 208, null, null],
[0, "진기", null, 7, "수춘", 58, 65, 43, 1, 165, 198, null, null],
[0, "진궁", null, 3, "하비", 84, 55, 89, 11, 154, 198, null, null],
[0, "진교", null, 0, null, 61, 27, 77, 0, 175, 237, null, null],
[0, "진군", null, 2, "진류", 32, 14, 75, 1, 167, 236, null, null],
[0, "진규", null, 3, "서주", 30, 4, 82, 1, 132, 206, null, null],
[0, "진식", null, 0, null, 70, 71, 25, 0, 191, 230, null, null],
[0, "진진", null, 16, "업", 46, 46, 69, 1, 170, 235, null, null],
[0, "진태", null, 0, null, 84, 77, 86, 0, 210, 260, null, null],
[0, "진등", null, 3, "하비", 79, 64, 81, 1, 169, 208, null, null],
[0, "진도", null, 0, null, 76, 71, 67, 0, 171, 237, null, null],
[0, "진무", null, 0, null, 76, 87, 43, 0, 176, 215, null, null],
[0, "진란", null, 7, "여강", 66, 69, 40, 1, 157, 204, null, null],
[0, "진림", null, 16, "남피", 25, 9, 75, 1, 160, 217, null, null],
[0, "정욱", null, 2, "진류", 70, 49, 90, 1, 141, 220, null, null],
[0, "정원지", null, 0, null, 69, 74, 17, 0, 145, 184, null, null],
[0, "정은", null, 9, "서량", 69, 73, 40, 1, 184, 220, null, null],
[0, "정원", null, 0, null, 70, 76, 35, 0, 137, 189, null, null],
[0, "정보", null, 19, "건업", 84, 79, 79, 1, 151, 216, null, null],
[0, "정봉승연", null, 0, null, 81, 80, 75, 0, 190, 271, null, null],
[0, "전위", null, 0, null, 72, 95, 35, 0, 160, 197, null, null],
[0, "전해", null, 4, "북평", 68, 65, 58, 1, 154, 199, null, null],
[0, "전주", null, 0, null, 66, 66, 73, 0, 169, 214, null, null],
[0, "전풍", null, 16, "남피", 72, 29, 93, 11, 162, 200, null, null],
[0, "전예", null, 4, "역경", 80, 72, 80, 1, 171, 252, null, null],
[0, "동윤", null, 0, null, 47, 28, 79, 0, 192, 246, null, null],
[0, "동화", null, 0, null, 57, 34, 75, 0, 168, 219, null, null],
[0, "등애", null, 0, null, 93, 87, 89, 0, 197, 264, null, null],
[0, "도겸", null, 0, null, 51, 27, 64, 0, 132, 194, null, null],
[0, "등현", null, 0, null, 61, 73, 45, 0, 188, 248, null, null],
[0, "등지", null, 0, null, 73, 52, 81, 0, 178, 251, null, null],
[0, "동습", null, 0, null, 72, 85, 50, 0, 169, 215, null, null],
[0, "동승", null, 2, "허창", 56, 53, 65, 1, 154, 200, null, null],
[0, "동소", null, 0, null, 34, 25, 80, 0, 156, 236, null, null],
[0, "동탁", null, 0, null, 85, 87, 73, 0, 139, 192, null, null, "크하하, 천하는 이제 나의 것이다!"],
[0, "동도나", null, 0, null, 68, 73, 31, 0, 189, 225, null, null],
[0, "동민", null, 0, null, 49, 60, 25, 0, 149, 192, null, null],
[0, "등무", null, 0, null, 61, 75, 29, 0, 147, 184, null, null],
[0, "두기", null, 0, null, 66, 32, 75, 0, 163, 224, null, null],
[0, "두예", null, 0, null, 84, 30, 85, 0, 222, 284, null, null],
[0, "배원소", null, 0, null, 49, 65, 25, 0, 169, 200, null, null],
[0, "마완", null, 0, null, 71, 74, 16, 0, 170, 211, null, null],
[0, "마휴", null, 9, "서량", 68, 73, 48, 1, 178, 212, null, null],
[0, "파재", null, 0, null, 74, 74, 55, 0, 145, 184, null, null],
[0, "마준", null, 0, null, 38, 52, 31, 0, 196, 230, null, null],
[0, "마속", null, 0, null, 61, 67, 87, 0, 190, 228, null, null],
[0, "마대", null, 0, null, 79, 85, 62, 0, 183, 246, null, null],
[0, "마충", null, 0, null, 78, 72, 68, 0, 187, 249, null, null],
[0, "마초", null, 9, "서량", 93, 97, 44, 1, 176, 222, null, null],
[0, "마철", null, 9, "서량", 70, 62, 58, 1, 179, 212, null, null],
[0, "마등", null, 9, "서량", 82, 80, 52, 12, 149, 212, null, null],
[0, "마량", null, 0, null, 53, 23, 88, 0, 187, 222, null, null],
[0, "반준", null, 0, null, 61, 18, 83, 0, 174, 239, null, null],
[0, "반장", null, 0, null, 76, 80, 74, 0, 177, 234, null, null],
[0, "번조", null, 0, null, 66, 73, 31, 0, 149, 192, null, null],
[0, "번능", null, 0, null, 69, 61, 36, 0, 158, 195, null, null],
[0, "반봉", null, 0, null, 56, 77, 4, 0, 155, 190, null, null],
[0, "비의", null, 0, null, 77, 30, 83, 0, 193, 253, null, null],
[0, "비연", null, 0, null, 60, 66, 21, 0, 196, 238, null, null],
[0, "비시", null, 0, null, 18, 28, 65, 0, 176, 240, null, null],
[0, "미축", null, 8, "소패", 33, 29, 78, 1, 165, 220, null, null],
[0, "미방", null, 8, "소패", 54, 61, 32, 1, 169, 226, null, null],
[0, "비요", null, 0, null, 70, 66, 73, 0, 192, 228, null, null],
[0, "무안국", null, 0, null, 70, 83, 34, 0, 156, 193, null, null],
[0, "풍습", null, 0, null, 72, 71, 39, 0, 182, 222, null, null],
[0, "부사인", null, 0, null, 46, 63, 37, 0, 182, 225, null, null],
[0, "부첨", null, 0, null, 73, 85, 76, 0, 216, 263, null, null],
[0, "부손", null, 0, null, 29, 13, 70, 0, 162, 230, null, null],
[0, "문앙", null, 0, null, 76, 93, 65, 0, 238, 291, null, null],
[0, "문흠", null, 0, null, 74, 83, 43, 0, 200, 258, null, null],
[0, "문추", null, 16, "남피", 89, 93, 47, 1, 169, 200, null, null],
[0, "문빙", null, 0, null, 80, 82, 69, 0, 178, 237, null, null],
[0, "변희", null, 0, null, 56, 70, 62, 0, 169, 200, null, null],
[0, "망아장", null, 0, null, 45, 79, 3, 0, 191, 225, null, null],
[0, "봉기", null, 16, "남피", 43, 21, 84, 1, 153, 202, null, null],
[0, "방희", null, 0, null, 60, 38, 69, 0, 152, 192, null, null],
[0, "법정", null, 0, null, 82, 47, 94, 0, 176, 220, null, null],
[0, "방통", null, 0, null, 85, 34, 97, 0, 178, 213, null, null],
[0, "방덕", null, 9, "서량", 89, 94, 75, 11, 170, 219, null, null],
[0, "포륭", null, 0, null, 58, 76, 49, 0, 174, 208, null, null],
[0, "목순", null, 0, null, 52, 77, 11, 0, 157, 190, null, null],
[0, "목록대왕", null, 0, null, 65, 71, 39, 0, 184, 225, null, null],
[0, "보즐", null, 0, null, 72, 51, 84, 0, 177, 247, null, null],
[0, "만총", null, 0, null, 84, 64, 82, 0, 170, 242, null, null],
[0, "모개", null, 0, null, 63, 39, 60, 0, 161, 216, null, null],
[0, "맹획", null, 0, null, 87, 87, 42, 0, 186, 245, null, null],
[0, "맹달", null, 0, null, 75, 73, 75, 0, 172, 228, null, null],
[0, "맹우", null, 0, null, 50, 68, 22, 0, 190, 243, null, null],
[0, "유섭", null, 0, null, 62, 79, 24, 0, 158, 190, null, null],
[0, "양부2", null, 0, null, 62, 68, 68, 0, 167, 212, null, null],
[0, "옹개", null, 0, null, 73, 75, 57, 0, 188, 225, null, null],
[0, "양의", null, 0, null, 68, 40, 82, 0, 190, 235, null, null],
[0, "양호", null, 0, null, 90, 64, 84, 0, 221, 278, null, null],
[0, "양홍", null, 7, "수춘", 33, 15, 77, 1, 152, 199, null, null],
[0, "양앙", null, 13, "한중", 68, 72, 38, 1, 172, 215, null, null],
[0, "양추1", null, 0, null, 52, 65, 40, 0, 159, 199, null, null],
[0, "양수", null, 0, null, 25, 4, 83, 0, 175, 219, null, null],
[0, "양추2", null, 0, null, 67, 64, 60, 0, 172, 238, null, null],
[0, "양송", null, 0, null, 1, 5, 28, 0, 167, 215, null, null],
[0, "양임", null, 13, "한중", 70, 78, 56, 1, 170, 215, null, null],
[0, "양조", null, 0, null, 53, 61, 36, 0, 223, 286, null, null],
[0, "양백", null, 13, "한중", 44, 45, 19, 1, 171, 214, null, null],
[0, "양부1", null, 0, null, 67, 50, 83, 0, 178, 239, null, null],
[0, "양봉1", null, 0, null, 66, 65, 31, 0, 153, 197, null, null],
[0, "양봉2", null, 0, null, 54, 62, 53, 0, 191, 252, null, null],
[0, "뇌동", null, 0, null, 69, 78, 51, 0, 172, 218, null, null],
[0, "뇌박", null, 7, "수춘", 62, 70, 36, 1, 157, 206, null, null],
[0, "이이", null, 0, null, 63, 77, 19, 0, 187, 222, null, null],
[0, "이회", null, 0, null, 79, 65, 79, 0, 175, 231, null, null],
[0, "이각", null, 1, "장안", 69, 72, 24, 12, 148, 198, null, null],
[0, "육항", null, 0, null, 91, 63, 87, 0, 226, 274, null, null],
[0, "육적", null, 0, null, 20, 7, 72, 0, 187, 219, null, null],
[0, "육손", null, 0, null, 94, 69, 95, 0, 183, 245, null, null],
[0, "이엄", null, 0, null, 83, 84, 76, 0, 172, 234, null, null],
[0, "이유", null, 0, null, 61, 26, 93, 0, 150, 192, null, null, "장안으로 천도를 하는것이 어떻겠습니까?"],
[0, "이숙", null, 0, null, 46, 69, 61, 0, 156, 192, null, null],
[0, "이통", null, 2, "초", 73, 83, 62, 1, 168, 209, null, null],
[0, "이전", null, 2, "초", 78, 77, 79, 1, 181, 216, null, null],
[0, "이풍", null, 7, "수춘", 69, 74, 50, 1, 158, 199, null, null],
[0, "유언", null, 0, null, 53, 38, 80, 0, 132, 194, null, null],
[0, "유괴", null, 6, "성도", 71, 73, 67, 1, 165, 214, null, null],
[0, "유기", null, 12, "양양", 52, 12, 63, 1, 174, 210, null, null],
[0, "유우", null, 0, null, 55, 33, 70, 0, 145, 193, null, null],
[0, "유훈", null, 7, "여강", 47, 63, 35, 1, 163, 216, null, null],
[0, "유현", null, 0, null, 54, 66, 51, 0, 188, 252, null, null],
[0, "유순", null, 0, null, 63, 46, 41, 0, 172, 255, null, null],
[0, "유장", null, 6, "성도", 31, 5, 34, 12, 162, 219, null, null],
[0, "유심", null, 0, null, 60, 62, 70, 0, 238, 264, null, null],
[0, "유선", null, 0, null, 3, 5, 9, 0, 208, 270, null, null],
[0, "유종", null, 0, null, 24, 22, 65, 0, 195, 220, null, null],
[0, "유도", null, 0, null, 25, 23, 26, 0, 168, 214, null, null],
[0, "유파", null, 0, null, 29, 22, 75, 0, 186, 222, null, null],
[0, "유반", null, 0, null, 69, 76, 47, 0, 168, 210, null, null],
[0, "유비", null, 8, "하비", 81, 77, 80, 12, 161, 223, null, null],
[0, "유표1", null, 12, "양양", 48, 31, 72, 12, 142, 208, null, null, "강하 팔준에서 제일가는 이 유표에게로 오시오!"],
[0, "유복", null, 0, null, 64, 49, 74, 0, 164, 208, null, null],
[0, "유벽", null, 20, "여남", 70, 72, 47, 12, 145, 201, null, null],
[0, "유봉", null, 0, null, 75, 79, 44, 0, 188, 220, null, null],
[0, "유엽", null, 7, "여강", 56, 32, 92, 1, 176, 235, null, null],
[0, "유요", null, 0, null, 62, 67, 46, 0, 156, 198, null, null],
[0, "여위황", null, 16, "남피", 58, 63, 29, 1, 159, 200, null, null],
[0, "요화", null, 0, null, 73, 76, 69, 0, 168, 264, null, null],
[0, "양흥", null, 9, "서량", 61, 65, 19, 1, 169, 211, null, null],
[0, "양습", null, 0, null, 73, 40, 74, 0, 168, 230, null, null],
[0, "양서", null, 0, null, 56, 62, 66, 0, 198, 260, null, null],
[0, "능조", null, 0, null, 75, 81, 42, 0, 168, 203, null, null],
[0, "능통", null, 0, null, 77, 89, 60, 0, 189, 217, null, null],
[0, "여개", null, 0, null, 53, 30, 67, 0, 194, 227, null, null],
[0, "여건", null, 2, "진류", 57, 70, 62, 1, 173, 238, null, null],
[0, "여광", null, 16, "북해", 56, 70, 13, 1, 162, 207, null, null],
[0, "여상", null, 16, "북해", 54, 71, 12, 1, 164, 207, null, null],
[0, "여대", null, 0, null, 75, 71, 70, 0, 161, 256, null, null],
[0, "여범", null, 19, "건업", 73, 63, 75, 1, 169, 228, null, null],
[0, "여포", null, 3, "하비", 97, 100, 26, 12, 156, 198, null, null, "크하하하 다 부숴 버리겠다!"],
[0, "여몽", null, 0, null, 82, 81, 20, 0, 178, 219, null, null],
[0, "윤직", null, 0, null, 46, 42, 73, 0, 196, 237, null, null],
[0, "냉포", null, 0, null, 71, 82, 61, 0, 176, 214, null, null],
[0, "진취", null, 0, null, 56, 65, 48, 0, 174, 208, null, null],
[0, "노숙", null, 0, null, 84, 56, 92, 0, 172, 217, null, null],
[0, "노식", null, 0, null, 86, 63, 82, 0, 139, 192, null, null],
[0, "범강", null, 0, null, 49, 50, 36, 0, 174, 222, null, null],
[0, "장달", null, 0, null, 43, 56, 34, 0, 175, 222, null, null],
[0, "규람", null, 0, null, 55, 70, 58, 0, 165, 204, null, null],
[0, "대원", null, 0, null, 38, 32, 68, 0, 166, 204, null, null],
[0, "왕기", null, 0, null, 77, 72, 86, 0, 190, 261, null, null],
[0, "왕상", null, 0, null, 23, 19, 68, 0, 180, 268, null, null],
[0, "왕돈", null, 0, null, 60, 66, 46, 0, 198, 256, null, null],
[0, "악침", null, 0, null, 73, 75, 43, 0, 196, 257, null, null],
[0, "하제", null, 19, "회계", 83, 70, 82, 1, 171, 227, null, null],
[0, "환범", null, 0, null, 40, 11, 78, 0, 199, 249, null, null],
[0, "견초", null, 16, "업", 72, 79, 73, 1, 168, 244, null, null],
[0, "고당륭", null, 0, null, 35, 24, 76, 0, 190, 244, null, null],
[0, "오찬", null, 0, null, 68, 41, 78, 0, 181, 245, null, null],
[0, "호질", null, 0, null, 73, 46, 74, 0, 192, 250, null, null],
[0, "오질", null, 0, null, 16, 29, 68, 0, 177, 230, null, null],
[0, "호준", null, 0, null, 79, 76, 62, 0, 200, 256, null, null],
[0, "고담", null, 0, null, 53, 23, 77, 0, 203, 244, null, null],
[0, "최림", null, 0, null, 32, 22, 66, 0, 187, 244, null, null],
[0, "사마망", null, 0, null, 78, 58, 83, 0, 205, 271, null, null],
[0, "주이", null, 0, null, 70, 73, 61, 0, 201, 257, null, null],
[0, "전단", null, 0, null, 67, 64, 39, 0, 204, 261, null, null],
[0, "조우", null, 0, null, 50, 43, 37, 0, 199, 278, null, null],
[0, "조상", null, 0, null, 39, 33, 30, 0, 207, 249, null, null],
[0, "조방", null, 0, null, 20, 14, 27, 0, 232, 274, null, null],
[0, "태사향", null, 0, null, 57, 66, 48, 0, 189, 246, null, null],
[0, "장휴", null, 0, null, 63, 27, 74, 0, 204, 244, null, null],
[0, "장호", null, 0, null, 75, 74, 51, 0, 195, 240, null, null],
[0, "조광", null, 0, null, 67, 71, 53, 0, 210, 263, null, null],
[0, "장집", null, 0, null, 76, 29, 80, 0, 196, 254, null, null],
[0, "장승", null, 0, null, 77, 70, 75, 0, 178, 244, null, null],
[0, "장소", null, 0, null, 23, 56, 31, 0, 202, 264, null, null],
[0, "조통", null, 0, null, 68, 67, 61, 0, 209, 260, null, null],
[0, "진표", null, 0, null, 66, 42, 74, 0, 207, 237, null, null],
[0, "정무", null, 0, null, 56, 35, 75, 0, 201, 265, null, null],
[0, "정봉", null, 0, null, 66, 66, 53, 0, 198, 266, null, null],
[0, "전만", null, 2, "허창", 51, 73, 39, 1, 181, 235, null, null],
[0, "마균", null, 0, null, 22, 17, 71, 0, 200, 259, null, null],
[0, "방회", null, 0, null, 69, 75, 49, 0, 205, 272, null, null],
[0, "서구", null, 0, null, 69, 42, 70, 0, 149, 208, null, null],
[0, "육개", null, 0, null, 65, 61, 79, 0, 198, 269, null, null],
[0, "유찬", null, 0, null, 78, 81, 64, 0, 172, 255, null, null],
[0, "유소", null, 0, null, 68, 53, 74, 0, 195, 264, null, null],
[0, "요립", null, 0, null, 32, 15, 76, 0, 181, 250, null, null],
[0, "여거", null, 0, null, 78, 69, 71, 0, 196, 256, null, null],
[0, "제갈첨", null, 0, null, 70, 57, 76, 0, 227, 263, null, null],
[0, "손휴", null, 0, null, 57, 42, 78, 0, 235, 264, null, null],
[0, "극정", null, 0, null, 42, 23, 68, 0, 208, 278, null, null],
[0, "호분", null, 0, null, 71, 76, 50, 0, 222, 288, null, null],
[0, "손량", null, 0, null, 32, 26, 70, 0, 243, 260, null, null],
[0, "마막", null, 0, null, 34, 12, 44, 0, 221, 266, null, null],
[0, "손호", null, 0, null, 41, 73, 71, 0, 242, 284, null, null],
[0, "조모", null, 0, null, 54, 61, 59, 0, 241, 260, null, null],
[0, "조환", null, 0, null, 30, 28, 30, 0, 246, 302, null, null],
[0, "사마유", null, 0, null, 71, 68, 75, 0, 248, 283, null, null],
[0, "제갈상", null, 0, null, 64, 79, 62, 0, 246, 263, null, null],
[0, "손화", null, 0, null, 31, 39, 68, 0, 224, 253, null, null],
[0, "염우", null, 0, null, 52, 61, 51, 0, 209, 264, null, null],
[0, "왕경", null, 0, null, 67, 63, 71, 0, 206, 260, null, null],
[0, "왕혼", null, 0, null, 74, 71, 63, 0, 223, 297, null, null],
[0, "곽혁", null, 0, null, 33, 29, 70, 0, 187, 225, null, null],
[0, "가범", null, 0, null, 60, 62, 73, 0, 202, 237, null, null],
[0, "서질", null, 0, null, 68, 83, 24, 0, 207, 253, null, null],
[0, "손준", null, 0, null, 64, 70, 65, 0, 219, 256, null, null],
[0, "손침", null, 0, null, 58, 55, 65, 0, 231, 258, null, null],
[0, "우사", null, 0, null, 61, 34, 74, 0, 217, 273, null, null],
[0, "진건", null, 0, null, 72, 71, 65, 0, 214, 292, null, null],
[0, "동궐", null, 0, null, 65, 59, 76, 0, 204, 271, null, null],
[0, "당자", null, 0, null, 61, 71, 50, 0, 196, 265, null, null],
[0, "등충", null, 0, null, 76, 86, 69, 0, 230, 264, null, null],
[0, "황숭", null, 0, null, 67, 63, 72, 0, 208, 263, null, null],
[0, "화핵", null, 0, null, 20, 24, 71, 0, 217, 278, null, null],
[0, "허의", null, 0, null, 51, 74, 15, 0, 213, 263, null, null],
[0, "송겸", null, 0, null, 66, 78, 42, 0, 175, 227, null, null],
[0, "호제", null, 0, null, 57, 39, 68, 0, 207, 264, null, null],
[0, "나헌", null, 0, null, 85, 70, 77, 0, 218, 270, null, null],
[0, "아들노숙", null, 0, null, 73, 53, 74, 0, 217, 272, null, null],
[0, "구성", null, 0, null, 63, 70, 38, 0, 157, 187, null, null],
[0, "어부라", null, 0, null, 72, 68, 21, 0, 150, 195, null, null],
[0, "아하소과", null, 0, null, 61, 76, 13, 0, 204, 253, null, null],
[0, "가비능", null, 0, null, 77, 69, 52, 0, 172, 235, null, null],
[0, "구력거", null, 0, null, 80, 68, 56, 0, 152, 193, null, null],
[0, "강단", null, 0, null, 65, 83, 48, 0, 168, 234, null, null],
[0, "황란", null, 0, null, 63, 75, 44, 0, 200, 264, null, null],
[0, "호주천", null, 0, null, 70, 71, 40, 0, 169, 230, null, null],
[0, "종리목", null, 0, null, 80, 72, 73, 0, 214, 269, null, null],
[0, "제갈교", null, 0, null, 62, 27, 75, 0, 204, 228, null, null],
[0, "답돈", null, 0, null, 84, 82, 61, 0, 158, 207, null, null],
[0, "반임", null, 0, null, 66, 79, 38, 0, 168, 225, null, null],
[0, "보도근", null, 0, null, 64, 73, 50, 0, 170, 233, null, null],
[0, "미당대왕", null, 0, null, 59, 69, 21, 0, 202, 260, null, null],
[0, "유표(흉노)", null, 0, null, 73, 74, 48, 0, 173, 252, null, null],
[0, "루반", null, 0, null, 65, 76, 39, 0, 178, 207, null, null],
[0, "손관", null, 0, null, 72, 78, 51, 0, 166, 217, null, null],
[0, "곽석", null, 0, null, 59, 71, 48, 0, 158, 187, null, null],
[0, "주조", null, 0, null, 63, 68, 26, 0, 152, 187, null, null],
[0, "장거", null, 0, null, 79, 75, 56, 0, 150, 188, null, null],
[0, "장순", null, 0, null, 73, 81, 60, 0, 152, 188, null, null],
[0, "해니", null, 0, null, 62, 78, 35, 0, 191, 233, null, null],
[0, "토안", null, 0, null, 53, 82, 30, 0, 191, 233, null, null],
[0, "유주", null, 0, null, 67, 78, 42, 0, 172, 233, null, null],
[0, "비잔", null, 0, null, 60, 71, 51, 0, 181, 216, null, null],
[0, "월길", null, 0, null, 81, 88, 26, 0, 190, 227, null, null],
[0, "아단", null, 0, null, 72, 65, 78, 0, 186, 259, null, null],
[0, "철리길", null, 0, null, 77, 70, 52, 0, 179, 259, null, null],
[0, "주삭", null, 0, null, 54, 60, 50, 0, 174, 253, null, null],
[0, "왕이", null, 0, null, 87, 59, 77, 0, 171, 230, null, null],
[0, "왕원희", null, 0, null, 53, 37, 73, 0, 217, 268, null, null],
[0, "곽씨", null, 0, null, 42, 4, 55, 0, 184, 235, null, null],
[0, "하후씨", null, 0, null, 34, 13, 42, 0, 188, 243, null, null],
[0, "하후령녀", null, 0, null, 22, 33, 60, 0, 207, 257, null, null],
[0, "하씨", null, 0, null, 51, 14, 67, 0, 150, 189, null, null],
[0, "관은병", null, 0, null, 87, 84, 75, 0, 204, 271, null, null],
[0, "감씨", null, 8, "소패", 14, 10, 44, 1, 175, 210, null, null],
[0, "황월영", null, 0, null, 62, 33, 90, 0, 186, 235, null, null],
[0, "채염", null, 0, null, 53, 14, 80, 0, 168, 224, null, null],
[0, "채씨", null, 12, "양양", 38, 7, 70, 1, 159, 212, null, null],
[0, "소교", null, 0, null, 40, 3, 70, 0, 178, 212, null, null],
[0, "서씨", null, 0, null, 48, 16, 80, 0, 183, 232, null, null],
[0, "신헌영", null, 0, null, 48, 28, 84, 0, 191, 269, null, null],
[0, "견씨", null, 16, "업", 42, 18, 74, 1, 182, 221, null, null],
[0, "추씨", null, 5, "완", 4, 9, 66, 1, 165, 200, null, null],
[0, "조절", null, 0, null, 32, 33, 79, 0, 190, 260, null, null],
[0, "손씨", null, 0, null, 32, 14, 55, 0, 188, 250, null, null],
[0, "손상향", null, 0, null, 77, 82, 55, 0, 193, 222, null, null],
[0, "손노육", null, 0, null, 52, 10, 66, 0, 204, 253, null, null],
[0, "손노반", null, 0, null, 17, 15, 80, 0, 202, 258, null, null],
[0, "대교", null, 0, null, 33, 30, 66, 0, 177, 215, null, null],
[0, "장씨", null, 0, null, 55, 35, 66, 0, 207, 237, null, null],
[0, "장춘화", null, 0, null, 66, 29, 77, 0, 179, 234, null, null],
[0, "초선", null, 3, "하비", 42, 18, 81, 1, 176, 230, null, null],
[0, "번씨", null, 0, null, 6, 12, 50, 0, 183, 225, null, null],
[0, "미씨", null, 8, "소패", 13, 6, 72, 1, 176, 208, null, null],
[0, "변씨", null, 2, "허창", 30, 23, 55, 1, 160, 230, null, null],
[0, "보련사", null, 0, null, 45, 30, 77, 0, 198, 248, null, null],
[0, "유씨", null, 16, "업", 8, 8, 44, 1, 164, 215, null, null],
[0, "화만", null, 0, null, 85, 92, 40, 0, 191, 233, null, null],
[0, "포삼낭", null, 0, null, 87, 83, 36, 0, 191, 240, null, null],
[0, "성렴", null, 3, "서주", 66, 72, 43, 1, 168, 199, null, null],
[0, "위월", null, 3, "서주", 68, 76, 39, 1, 170, 199, null, null],
[0, "진의록", null, 3, "하비", 46, 43, 17, 1, 168, 199, null, null],
[0, "채옹", null, 0, null, 27, 16, 81, 0, 133, 192, null, null],
[0, "지양군", null, 0, null, 13, 8, 55, 0, 103, 192, null, null],
[0, "위소", null, 0, null, 21, 23, 65, 0, 204, 273, null, null],
[0, "위강", null, 0, null, 58, 35, 56, 0, 168, 213, null, null],
[0, "원요", null, 0, null, 28, 41, 50, 0, 177, 206, null, null],
[0, "왕기2", null, 0, null, 69, 67, 61, 0, 217, 281, null, null],
[0, "원환", null, 0, null, 30, 17, 72, 0, 165, 225, null, null],
[0, "우전", null, 0, null, 64, 76, 40, 0, 204, 258, null, null],
[0, "왕광", null, 0, null, 33, 65, 55, 0, 150, 190, null, null],
[0, "왕업", null, 0, null, 33, 6, 66, 0, 220, 280, null, null],
[0, "왕융", null, 0, null, 40, 57, 68, 0, 234, 305, null, null],
[0, "왕충", null, 2, "진류", 46, 39, 57, 1, 152, 214, null, null],
[0, "위풍", null, 0, null, 52, 23, 72, 0, 182, 219, null, null],
[0, "구건", null, 0, null, 39, 52, 65, 0, 239, 272, null, null],
[0, "구본", null, 0, null, 50, 41, 67, 0, 232, 269, null, null],
[0, "동백", null, 0, null, 12, 7, 52, 0, 173, 192, null, null],
[0, "마운", null, 0, null, 78, 88, 53, 0, 183, 225, null, null],
[0, "사섭", null, 17, "교지", 53, 31, 76, 12, 137, 226, null, null],
[0, "사지", null, 17, "교지", 58, 51, 52, 1, 163, 227, null, null],
[0, "사휘", null, 17, "남해", 69, 64, 44, 1, 165, 227, null, null],
[0, "사일", null, 17, "남해", 48, 36, 67, 1, 153, 230, null, null],
[0, "사광", null, 17, "교지", 58, 45, 64, 1, 175, 235, null, null],
[0, "후음", null, 0, null, 76, 72, 45, 0, 184, 219, null, null],
[0, "공주", null, 0, null, 26, 16, 68, 0, 151, 191, null, null],
[0, "고담륭", null, 0, null, 35, 24, 76, 0, 190, 251, null, null],
[0, "국연", null, 0, null, 50, 19, 77, 0, 160, 219, null, null],
[0, "오언", null, 0, null, 74, 72, 77, 0, 235, 297, null, null],
[0, "오강", null, 0, null, 48, 38, 65, 0, 216, 275, null, null],
[0, "최염", null, 16, "업", 18, 55, 77, 1, 162, 216, null, null],
[0, "좌혁", null, 0, null, 62, 66, 51, 0, 232, 280, null, null],
[0, "차주", null, 2, "정도", 49, 49, 40, 1, 164, 199, null, null],
[0, "주태", null, 0, null, 71, 64, 71, 0, 207, 261, null, null],
[0, "북궁백옥", null, 0, null, 66, 72, 49, 0, 146, 187, null, null],
[0, "변장", null, 0, null, 69, 56, 47, 0, 133, 187, null, null],
[0, "순의", null, 0, null, 16, 13, 72, 0, 207, 274, null, null],
[0, "서진순욱", null, 0, null, 8, 19, 77, 0, 225, 289, null, null],
[0, "사마준", null, 0, null, 85, 82, 80, 0, 232, 286, null, null],
[0, "종육", null, 0, null, 32, 12, 71, 0, 223, 263, null, null],
[0, "초이", null, 0, null, 62, 69, 63, 0, 219, 266, null, null],
[0, "제갈정", null, 0, null, 58, 63, 66, 0, 246, 300, null, null],
[0, "보협", null, 0, null, 62, 49, 53, 0, 216, 264, null, null],
[0, "복양흥", null, 0, null, 32, 70, 65, 0, 224, 264, null, null],
[0, "보천", null, 0, null, 71, 60, 71, 0, 222, 272, null, null],
[0, "유대연주", null, 0, null, 53, 32, 55, 0, 148, 192, null, null],
[0, "유대", null, 2, "진류", 63, 58, 30, 1, 155, 202, null, null],
[0, "유승", null, 0, null, 45, 65, 33, 0, 215, 258, null, null],
[0, "여영기", null, 3, "하비", 79, 88, 37, 1, 182, 198, null, null],
[0, "독발수기능", null, 0, null, 89, 93, 77, 0, 232, 279, null, null],
[0, "진수", null, 0, null, 12, 22, 68, 0, 233, 297, null, null],
[0, "마륭", null, 0, null, 84, 75, 76, 0, 231, 296, null, null],
[0, "도황", null, 0, null, 76, 68, 72, 0, 232, 290, null, null],
[0, "진온", null, 0, null, 60, 38, 48, 0, 167, 192, null, null],
[0, "교모", null, 0, null, 54, 45, 69, 0, 137, 190, null, null],
[0, "교국로", null, 19, "건업", 38, 17, 55, 1, 150, 220, null, null],
[0, "포신", null, 0, null, 77, 67, 81, 0, 152, 192, null, null],
[0, "포훈", null, 2, "복양", 41, 35, 75, 1, 180, 226, null, null],
[0, "포도", null, 0, null, 57, 70, 43, 0, 159, 190, null, null],
[0, "위자", null, 0, null, 58, 38, 62, 0, 150, 190, null, null],
[0, "유화", null, 16, "남피", 21, 16, 59, 1, 165, 216, null, null],
[0, "이이자", null, 4, "북평", 32, 18, 48, 1, 157, 199, null, null],
[0, "선우보", null, 0, null, 74, 69, 65, 0, 161, 229, null, null],
[0, "원유", null, 0, null, 57, 39, 73, 0, 150, 193, null, null],
[0, "사마휘", null, 0, null, 21, 8, 92, 0, 173, 208, "은둔", null, "좋지~좋아"],
[0, "화타", null, 0, null, 8, 7, 68, 0, 173, 207, "은둔", "의술"],
[0, "관로", null, 0, null, 13, 9, 72, 0, 200, 233, "은둔", null],
[0, "예형", null, 0, null, 13, 4, 90, 0, 173, 199, null, null],
[0, "원혁", null, 7, "수춘", 52, 18, 18, 1, 180, 246, null, null],
[0, "헌제", null, 2, "낙양", 23, 17, 66, 1, 184, 234, null, null, "….."],
[0, "황완", null, 0, null, 40, 34, 71, 0, 141, 192, null, null],
[0, "마일제", null, 0, null, 43, 25, 70, 0, 130, 194, null, null],
[0, "유총", null, 0, null, 82, 45, 60, 0, 151, 197, null, null],
[0, "낙준", null, 0, null, 51, 37, 66, 0, 152, 197, null, null],
[0, "낙통", null, 0, null, 76, 57, 73, 0, 193, 228, null, null],
[0, "허소", null, 0, null, 46, 11, 82, 0, 150, 196, null, null],
[0, "허사", null, 3, "하비", 24, 20, 60, 1, 163, 199, null, null],
[0, "장초", null, 0, null, 61, 32, 60, 0, 168, 196, null, null],
[0, "양표", null, 2, "낙양", 24, 20, 60, 1, 142, 225, null, null],
[0, "왕필", null, 2, "낙양", 53, 44, 59, 1, 170, 228, null, null],
[0, "사환", null, 2, "진류", 70, 71, 45, 1, 173, 209, null, null],
[0, "정비", null, 2, "진류", 34, 30, 57, 1, 158, 214, null, null],
[0, "두씨", null, 3, "하비", 26, 41, 52, 1, 172, 236, null, null],
[0, "누규", null, 12, "양양", 58, 40, 87, 1, 143, 212, null, null],
[0, "단외", null, 1, "장안", 55, 48, 55, 1, 148, 209, null, null],
[0, "원사", null, 2, "초", 65, 43, 78, 1, 159, 210, null, null],
[0, "사의관", null, 7, "수춘", 22, 12, 67, 1, 168, 199, null, null],
[0, "송건", null, 0, null, 66, 77, 40, 0, 164, 214, null, null],
[0, "곽원", null, 16, "업", 66, 73, 36, 1, 162, 205, null, null],
[0, "소유", null, 16, "업", 51, 59, 48, 1, 167, 210, null, null],
[0, "주한", null, 16, "남피", 49, 51, 26, 1, 162, 204, null, null],
[0, "뇌서", null, 0, null, 68, 65, 34, 0, 170, 212, null, null],
[0, "장홍", null, 3, "복양", 72, 81, 65, 1, 164, 196, null, null],
[0, "유위대", null, 4, "북평", 53, 44, 62, 1, 152, 199, null, null],
[0, "문칙", null, 4, "계", 59, 53, 32, 1, 164, 199, null, null],
[0, "습진", null, 0, null, 64, 68, 51, 0, 189, 221, null, null],
[0, "습정", null, 0, null, 49, 29, 70, 0, 181, 229, null, null],
[0, "이리", null, 1, "장안", 65, 72, 37, 1, 171, 198, null, null]
],
"general_ex":[
],
"cities":[
],
"history":[
"<C>●</>197년 1월:<L><b>【역사모드4-2】</b>황제 원술</>",
"<C>●</>197년 1월:<L><b>【이벤트】</b></>원술이 황제가 되고 반원술 연합이 결성되다"
],
"initialEvents":[
[
true,
["ChangeCity", "free", {
"pop":"70%",
"agri":"70%",
"comm":"70%",
"secu":"70%",
"trust":80
}],
["ChangeCity", "occupied", {
"pop":"70%",
"agri":"70%",
"comm":"70%",
"secu":"70%",
"trust":80,
"def":"70%",
"wall":"70%"
}]
]
],
"events":[
[
"destroy_nation", 1000,
["and",
["Date", ">=", 183, 1],
["RemainNation", "==", 1]
],
["BlockScoutAction"],
["DeleteEvent"]
]
]
}
+3 -3
View File
@@ -1,9 +1,9 @@
{
"title": "【가상모드28】 강서유서 소설 월드",
"title": "【가상모드28】 강서유서 월드",
"startYear": 180,
"history": [
"<C>●</>180년 1월:<L><b>【가상모드28】</b>강서유서 소설 월드</>",
"<C>●</>180년 1월:<L><b>【이벤트】</b></>강서유서의 소설 캐릭터들이 날뛰는곳"
"<C>●</>180년 1월:<L><b>【가상모드28】</b>강서유서 월드</>",
"<C>●</>180년 1월:<L><b>【이벤트】</b></>강서유서의 세계 속 캐릭터들이 날뛰는곳"
],
"stat": {
"total": 210,
+71 -69
View File
@@ -1,9 +1,9 @@
{
"title": "【가상모드28-b】 강서유서 소설 월드(비세력)",
"title": "【가상모드28-b】 강서유서 월드(비세력)",
"startYear": 180,
"history": [
"<C>●</>180년 1월:<L><b>【가상모드28】</b>강서유서 소설 월드</>",
"<C>●</>180년 1월:<L><b>【이벤트】</b></>강서유서의 소설 캐릭터들이 날뛰는곳"
"<C>●</>180년 1월:<L><b>【가상모드28】</b>강서유서 월드</>",
"<C>●</>180년 1월:<L><b>【이벤트】</b></>강서유서의 세계 속 캐릭터들이 날뛰는곳"
],
"stat": {
"total": 210,
@@ -18,10 +18,10 @@
"general":[
[1, "키타노 유키코", null, 0, null, 95, 31, 95, 0, 160, 300, "대의", "저격", "모든것은 우리들의 꿈을 이루기 위해…"],
[1, "키타노 시오리", null, 0, null, 88, 96, 89, 0, 160, 300, "대의", "무쌍", "언니를 지켜보일거야"],
[1, "이치가와 아이리", null, 0, null, 87, 73, 94, 0, 160, 300, "대의", null, "유키코를 지킬거야. 내 속죄를 위해"],
[1, "이즈카에 에리카", null, 0, null, 77, 93, 72, 0, 160, 300, "의협", "저격"],
[1, "이치가와 아이리", null, 0, null, 87, 73, 94, 0, 160, 300, "대의", null, "모두와 함께 세계에 혁명을…."],
[1, "이즈카에 에리카", null, 0, null, 77, 93, 72, 0, 160, 300, "의협", "저격", "모두와 함께 그녀를 지키며 혁명을!"],
[1, "타에구치 유카리", null, 0, null, 71, 66, 80, 0, 160, 300, "할거", null, "아버지와 어머니를 지키기 위해 미래에서 왓어 난"],
[1, "오모타 아미유", null, 0, null, 64, 51, 44, 0, 160, 300, "대의", null],
[1, "오모타 아미유", null, 0, null, 64, 51, 44, 0, 160, 300, "유지", null],
[1, "소카이 요시나", null, 0, null, 79, 62, 80, 0, 160, 300, "의협", null],
[1, "소미타키 코토키", null, 0, null, 86, 79, 55, 0, 160, 300, "재간", null],
[1, "타카이시 유키", null, 0, null, 83, 64, 72, 0, 160, 300, "재간", null],
@@ -29,11 +29,11 @@
[1, "요리시마 사치코", null, 0, null, 51, 81, 70, 0, 160, 300, null, "저격"],
[0, "큐어 스플릿", null, 0, null, 80, 90, 56, 0, 160, 300, null, "무쌍"],
[0, "큐어 솔크", null, 0, null, 93, 97, 67, 0, 160, 300, null, null, "아버지를 이겨보일거야."],
[0, "큐어 샤인", null, 0, null, 89, 75, 99, 0, 160, 300, "패권", "신산", "디지털 월드와 인간세계 모두를 위해"],
[0, "큐어 샤인", null, 0, null, 89, 75, 99, 0, 160, 300, "패권", "신산", "아빠를 막겠어. 디지털 월드와 현실세계의 평화를 위해... 내가 선택했으니까"],
[0, "큐어 러브", null, 0, null, 77, 86, 54, 0, 160, 300, null, null, "사랑을 추구하는 전사 큐어 러브"],
[0, "큐어 프로즌", null, 0, null, 64, 88, 72, 0, 160, 300, null, null],
[0, "큐어 바이올렛", null, 0, null, 49, 89, 77, 0, 160, 300, null, "상재"],
[0, "큐어 드림", null, 0, null, 97, 99, 71, 0, 160, 300, null, "무쌍"],
[0, "큐어 프로즌", null, 0, null, 64, 88, 72, 0, 160, 300, null, null, "얼음처럼 차가운 성격, 큐어 프로즌"],
[0, "큐어 바이올렛", null, 0, null, 49, 89, 77, 0, 160, 300, null, "상재", "바이올렛은 조화의 상징 큐어 바이올렛"],
[0, "큐어 드림", null, 0, null, 97, 99, 71, 0, 160, 300, null, "무쌍", "희망이 가진 위대한 힘, 큐어 드림!"],
[0, "큐어 뮤즈", null, 0, null, 71, 95, 64, 0, 160, 300, null, null],
[0, "큐어 플리오네", null, 0, null, 89, 102, 86, 0, 148, 300, null, null],
[0, "큐어 리큐브", null, 0, null, 67, 88, 82, 0, 152, 300, null, null],
@@ -41,13 +41,13 @@
[0, "큐어 뮤직", null, 0, null, 79, 84, 60, 0, 160, 300, null, null],
[0, "큐어 포르테", null, 0, null, 72, 77, 55, 0, 160, 300, null, null],
[0, "큐어 리스", null, 0, null, 89, 80, 67, 0, 160, 300, null, null],
[0, "분비", null, 0, null, 85, 94, 90, 0, 148, 300, null, null],
[0, "시비렛타", null, 0, null, 82, 73, 90, 0, 127, 300, null, null],
[0, "히토", null, 0, null, 64, 88, 80, 0, 115, 300, null, null],
[2, "모요코모 미에카", null, 0, null, 91, 60, 97, 0, 141, 300, "할거", null, "모든것은 분비님이 부활시키려는 그자를 막기 위해서야! 그자는 우리가 죽을뻔한 선택받은 아이들의 적이니까!"],
[2, "노자미 미노코", null, 0, null, 80, 77, 80, 0, 143, 300, null, null, "도체 요시미츠님(분비님)은 뭔 생각이신거야…"],
[2, "노자미 미에키", null, 0, null, 73, 70, 55, 0, 160, 300, null, null],
[999, "리아", null, 0, null, 99, 34, 100, 0, 132, 300, null, "집중", "이 세계를 부디 안정시켜줄 사람이 필요해…."],
[0, "분비", null, 0, null, 85, 94, 90, 0, 148, 300, null, null, "세상은 말이지, 강한자가 약한자를 지배하는 법이 당연시 되어 있어"],
[0, "시비렛타", null, 0, null, 82, 73, 90, 0, 127, 300, null, null, "싫어….! 이대로 죽고싶진 않단 말이야!"],
[0, "히토", null, 0, null, 64, 88, 80, 0, 115, 300, null, null, "이젠 내가 나설 차례인가?"],
[2, "모요코모 미에카", null, 0, null, 91, 60, 97, 0, 141, 300, "할거", null, "우리가 이럴수 있는 이유는 디지몬의 독재자 그를 막기 위해서…"],
[2, "노자미 미노코", null, 0, null, 80, 77, 80, 0, 143, 300, null, null, "도그분은 무슨 생각을 하는거지?"],
[2, "노자미 미에키", null, 0, null, 73, 70, 55, 0, 160, 300, null, null, "우리들은 디지털 월드와 현실세계를 잇는 아이들"],
[0, "리아", null, 0, null, 99, 34, 100, 0, 132, 300, null, "집중", "어느새, 두세계의 융합이 코앞으로 다가와 버렸어"],
[0, "코구라 아치모토", null, 0, null, 51, 44, 114, 0, 160, 300, null, "발명", "크흐흐…다크 프리큐어여 모습을 드러내라…!"],
[0, "다크 프리큐어즈", null, 0, null, 84, 97, 92, 0, 160, 300, null, null, "모든건… 그분의 부활을 위해."],
[0, "레톨프", null, 0, null, 87, 88, 64, 0, 160, 300, null, null],
@@ -56,7 +56,7 @@
[2, "아야토케 나이미", null, 0, null, 76, 44, 88, 0, 151, 300, null, null],
[2, "히마즈라 유게키", null, 0, null, 54, 80, 12, 0, 149, 300, null, null],
[2, "하루사키 유나", null, 0, null, 77, 44, 91, 0, 150, 300, null, null],
[0, "야가미 타이치", null, 0, null, 89, 88, 67, 0, 153, 300, null, null],
[0, "야가미 타이치", null, 0, null, 89, 88, 67, 0, 153, 300, null, null, "반드시 돌아올게. 감기, 빨리 나아야해."],
[0, "야가미 소라", null, 0, null, 62, 37, 75, 0, 153, 300, null, null],
[0, "야가미 유메", null, 0, null, 80, 55, 78, 0, 160, 300, null, null],
[0, "이시다 야마토", null, 0, null, 72, 77, 60, 0, 153, 300, null, null],
@@ -70,14 +70,15 @@
[2, "야카기 츠에루", null, 0, null, 83, 80, 51, 0, 160, 300, null, null, "할머니의 업보… 제가 신 갚아나갈 거에요"],
[0, "사이온지 카나타", null, 0, null, 81, 79, 85, 0, 160, 300, null, null, "내가 먼 말할게 좋아해 미유,"],
[0, "사이온지 미유", null, 0, null, 78, 64, 82, 0, 160, 300, null, null, "미우… 방금 뭐라고?.....루? 루란 말이야?"],
[0, "사이온지 미우", null, 0, null, 85, 91, 33, 0, 160, 300, null, "견고", "크하하… 모두 박살나 버려라, 이딴 세상!"],
[0, "사이온지 미우", null, 0, null, 85, 91, 33, 0, 160, 300, null, "견고", "엄마도, 아빠도 우리들도 모두, 모든것은.... 이곳에서 시작됬어"],
[0, "오카자키 나기사", null, 0, null, 77, 17, 91, 0, 160, 300, null, null, "경단~ 경단~ 경단~경단 경단 가족~"],
[0, "오카자키 토모야", null, 0, null, 79, 82, 51, 0, 160, 300, null, null],
[0, "오카자키 우시오", null, 0, null, 67, 77, 68, 0, 160, 300, null, null, "미우를 구하기 위해..! 난 싸울거야 친구니까!"],
[0, "오카자키 우시오", null, 0, null, 67, 77, 68, 0, 160, 300, null, null, "이곳은 마을의 마음이니까"],
[0, "최산해", null, 0, null, 82, 88, 33, 0, 160, 300, null, null],
[0, "모토미야 유지", null, 0, null, 86, 31, 88, 0, 160, 300, null, "상재", "허허…. 이나라가 어찌되련지…"],
[0, "유에", null, 0, null, 56, 14, 80, 0, 160, 300, null, null, "백성을 우선시하는게 통치의 근본."],
[0, "에이", null, 0, null, 64, 33, 97, 0, 160, 300, null, null, "유에는 소중한 친구이자 주군,"],
[0, "견휘", null, 0, null, 85, 44, 92, 0, 160, 300, null, "집중"],
[0, "고현", null, 0, null, 52, 37, 81, 0, 160, 300, null, null],
[5, "고순", null, 0, null, 88, 86, 66, 0, 160, 300, null, "기병"],
[5, "장료", null, 0, null, 85, 92, 78, 0, 160, 300, null, "무쌍"],
@@ -96,7 +97,7 @@
[3, "유희", null, 0, null, 64, 75, 62, 0, 160, 300, null, null],
[3, "나지현", null, 0, null, 64, 33, 71, 0, 160, 300, null, null],
[3, "아사쿠라 나오", null, 0, null, 81, 63, 83, 0, 160, 300, null, "저격", "모든건 우리를 싸우게 만들고 서로 죽이게 한 어른들에 한 복수,"],
[3, "아오이 타쿠마", null, 0, null, 77, 89, 55, 0, 160, 300, null, null],
[3, "아오이 타쿠마", null, 0, null, 77, 89, 55, 0, 160, 300, null, null, "웃기지마!"],
[0, "우라베 슈고", null, 0, null, 53, 68, 61, 0, 160, 300, null, null],
[0, "카사이 오사무", null, 0, null, 66, 54, 39, 0, 160, 300, null, null],
[0, "카케이 쿄코", null, 0, null, 77, 55, 81, 0, 160, 300, null, null],
@@ -109,20 +110,20 @@
[0, "야기 아야네", null, 0, null, 78, 83, 55, 0, 160, 300, null, "저격"],
[0, "야자와 아이", null, 0, null, 67, 89, 57, 0, 160, 300, null, "무쌍"],
[0, "유키 카나", null, 0, null, 79, 24, 88, 0, 160, 300, null, "통찰"],
[0, "노다 샤토미", null, 0, null, 61, 55, 69, 0, 160, 300, null, null],
[0, "노다 샤토미", null, 0, null, 61, 55, 69, 0, 160, 300, null, null, "적어도 내일까진 살수 있을거라 생각했어…."],
[0, "사사가와 류헤이", null, 0, null, 66, 58, 66, 0, 160, 300, null, null],
[0, "키리야마 카즈오", null, 0, null, 91, 92, 99, 0, 160, 300, "패권", "위압", "뒷면이 나오면…이 게임을 받아들이기로…!"],
[0, "이치가와 쥬리", null, 0, null, 34, 32, 91, 0, 160, 300, null, "발명"],
[0, "모토무라 아야네", null, 0, null, 77, 67, 55, 0, 163, 300, null, null],
[0, "코토히키 카요코", null, 0, null, 78, 33, 78, 0, 160, 300, null, null],
[0, "스기무라 히로키", null, 0, null, 67, 90, 45, 0, 160, 300, "유지", null],
[0, "코토히키 카요코", null, 0, null, 78, 33, 78, 0, 160, 300, null, null, "나도 널 좋아해 히로키…."],
[0, "스기무라 히로키", null, 0, null, 67, 90, 45, 0, 160, 300, "유지", null, "살고싶은 마음은 모두가 다 똑같아, 그런데 왜 자기 혼자만 살려고 하는거지?!"],
[0, "민유희", null, 0, null, 89, 55, 81, 0, 163, 300, null, null],
[0, "사카키 유코", null, 0, null, 45, 34, 60, 0, 160, 300, null, null],
[0, "사카키 유코", null, 0, null, 45, 34, 60, 0, 160, 300, null, null, "그러니까 안돼…. 나혼자만 살아남아선 안되는거야…."],
[0, "사쿠라이 사키", null, 0, null, 78, 77, 66, 0, 160, 300, null, "저격"],
[0, "마키", null, 0, null, 72, 74, 56, 0, 160, 300, null, null],
[0, "마이", null, 0, null, 70, 70, 60, 0, 160, 300, null, null],
[0, "소마 미츠코", null, 0, null, 93, 66, 90, 0, 160, 300, null, null, "나는 뺏는쪽에 서서 살아가는 거잖아…?"],
[0, "타카구치유이치로", null, 0, null, 77, 54, 79, 0, 160, 300, null, null],
[0, "타카구치유이치로", null, 0, null, 77, 54, 79, 0, 160, 300, null, null, "미츠코는 다들 얘기하는 것만큼 나쁜 애가 아니라고… 설령 진짜로 나쁜 짓을 하고 다닌다 해도 그렇게라도 하지 않곤 못 견딜 이유가 있을거라고 분명 그건 미츠코가 나빠서 그러는게 아니라고 생각해"],
[0, "스즈키 아야로", null, 0, null, 55, 44, 77, 0, 160, 300, null, null],
[0, "아사기 유미", null, 0, null, 84, 56, 66, 0, 160, 300, null, null],
[1, "아오조야 하나코", null, 0, null, 89, 66, 94, 0, 160, 300, null, null],
@@ -131,7 +132,7 @@
[0, "아카타 마야코", null, 0, null, 35, 40, 55, 0, 160, 300, null, null],
[0, "아타키 류야", null, 0, null, 64, 12, 80, 0, 160, 300, null, null],
[0, "우츠미 유키에", null, 0, null, 71, 44, 82, 0, 160, 300, null, null, "바보야…모두 바보야! 모두 살아돌아갈수도 있엇는데…."],
[0, "아제라크", null, 0, null, 81, 89, 68, 0, 160, 300, null, null, "프리큐어…! 모조리 거미밥으로 만들어주마!"],
[0, "아제라크", null, 0, null, 81, 89, 68, 0, 160, 300, null, null],
[0, "악튜러스 멩스크", null, 0, null, 95, 71, 99, 0, 151, 300, "패권", "집중", "누구도 날 막을 수 없어. 네놈이건, 연합이건, 프로토스건, 그 누구도! 코프룰루를 가질 수 없다면 차라리 잿더미로 만들어 버릴테다!"],
[0, "발레리안 멩스크", null, 0, null, 85, 63, 88, 0, 160, 300, null, null, "난 국민의 편이오. 아버지의 죄는 용서받을 수 없다는 걸 알고 있소. 권좌에서… 내려오셔야 한다는 것도."],
[10, "호러스 워필드", null, 0, null, 95, 83, 82, 0, 160, 300, "대의", "인덕", "캐리건... 잘 들어라. 지금 왕복선 3엔 부상병들만 타고 있다. 다들 누군가의... 남편이자, 아버지야. 들은 그냥 보내다오. 알겠나? 독한 것. 넌 인간도 아니야. 넌 우릴 배신했어. 뭣 때문에? 그깟 복수 때문에?\n무고한 사람들을 얼마나 죽인 거야? 또 얼마나 더 죽일 셈이야?\n레이너가 널 보면 뭐라고 할까?"],
@@ -148,11 +149,11 @@
[0, "유리아 베넷", null, 0, null, 76, 41, 55, 0, 160, 300, null, null],
[0, "루미 베레스", null, 0, null, 77, 33, 89, 0, 160, 300, null, null],
[0, "타이커스", null, 0, null, 61, 85, 40, 0, 160, 300, null, null],
[0, "제임스 레이너", null, 0, null, 99, 88, 72, 0, 160, 300, "대의", "돌격", "피닉스! 안돼!!"],
[0, "맷 호너", null, 0, null, 87, 64, 95, 0, 160, 300, null, null],
[0, "제임스 레이너", null, 0, null, 99, 88, 72, 0, 160, 300, "대의", "돌격", "아크튜러스, 이 나쁜 자식! 그러지 마!"],
[0, "맷 호너", null, 0, null, 87, 64, 95, 0, 160, 300, null, null, "멩스크를 끌어내리는 건 시작일 뿐입니다. 우리 목표는 더 밝은 미래라고요. 모르시겠습니까? 우린 멩스크에게 맞선 과학자, 철학자, 자유사상가들을 해방했어요. 그것이 오늘의 가장 큰 승리입니다."],
[0, "로리 스완", null, 0, null, 32, 55, 93, 0, 160, 300, null, "발명"],
[0, "이곤 스텟먼", null, 0, null, 11, 7, 91, 0, 160, 300, null, "발명"],
[0, "가브리엘 토시", null, 0, null, 85, 96, 55, 0, 160, 300, null, null],
[0, "가브리엘 토시", null, 0, null, 85, 96, 55, 0, 160, 300, null, null, "순진하기 짝이 없군. 내일 새로운 멩스크가 나타나고 그 후에 또 비슷한 놈이 나타날 겁니다. 밝게 빛나는 미래 따위는...그저 허상일 뿐이지."],
[0, "로사 모랄레스", null, 0, null, 51, 44, 67, 0, 160, 300, null, null],
[0, "레베카 레이너", null, 0, null, 81, 93, 44, 0, 160, 300, null, null],
[0, "케이트 록웰", null, 0, null, 43, 10, 78, 0, 160, 300, null, null],
@@ -161,7 +162,7 @@
[0, "알렌 세자르", null, 0, null, 85, 61, 82, 0, 160, 300, null, null],
[0, "마이클 리버티", null, 0, null, 64, 33, 71, 0, 160, 300, null, null],
[0, "시온 크루스", null, 0, null, 51, 14, 85, 0, 160, 300, null, null],
[0, "사라 케리건", null, 0, null, 95, 95, 81, 0, 160, 300, null, "무쌍"],
[0, "사라 케리건", null, 0, null, 95, 95, 81, 0, 160, 300, null, "무쌍", "지미, 갑옷을 걸친 기사 행세는 그만둬. 그게 어울릴 때도 있지만, 지금은... 지금은 아냐. 난 내가 뭘 해야 하는지 잘 아니까. 프로토스는 저그뿐만 아니라 행성을 통째로 파괴하고 말 거야. 난 알아. 왜냐하면... 그냥 알 수 있어. 난... 유령이잖아. 프로토스를 처리하고 나서, 저그는 그때 생각할 거야. 아크튜러스도 결국 정신을 차릴 테고. 난 알아."],
[10, "드랄리스카", null, 0, null, 82, 93, 78, 0, 160, 300, null, null, "케리건님이 죽다니…근데 왜 내가 그 중임을 이어받아야 하는거야?"],
[0, "에레니카", null, 0, null, 78, 38, 91, 0, 160, 300, null, null, "곧있으면 위하신 아몬님이 완전히 부활할거야 드랄리스카 언니 후후후…."],
[0, "세페니아", null, 0, null, 75, 81, 51, 0, 160, 300, null, null, "에레니카 언니 뭔가 이상해"],
@@ -170,24 +171,24 @@
[0, "데히카", null, 0, null, 75, 91, 55, 0, 160, 300, null, null],
[0, "스투코프 부제독", null, 0, null, 87, 71, 93, 0, 160, 300, null, null],
[0, "니아드라", null, 0, null, 80, 83, 52, 0, 160, 300, null, null],
[0, "아바투르", null, 0, null, 43, 33, 81, 0, 160, 300, null, "발명"],
[0, "아바투르", null, 0, null, 43, 33, 81, 0, 160, 300, null, "발명", "아바투르. 군단 진화 및 유전자 조작. 처음엔 초월체, 이후 칼날 여왕을 섬겼음. 이제 당신을 섬김.."],
[0, "다고쓰", null, 0, null, 81, 85, 80, 0, 160, 300, null, null, "휴우… 드랄리스카도 환생자라 다행이야, 죽을일은 없겠네… 근데 케리건이 죽을줄이야…"],
[0, "초월체", null, 0, null, 95, 55, 99, 0, 160, 300, null, null],
[0, "초월체", null, 0, null, 95, 55, 99, 0, 160, 300, null, null, "일어나라, 내 아이야, 일어나 네 생득의 영광을 보듬어라.\n나는 초월체, 군단의 영원한 의지이며 넌 날 섬기기 위해 창조되었다.\n군락을 섬겨라. 나는 리듬을 지배한다. 군락을 섬겨라. 나는 리듬을 지배한다. 군락을 섬겨라.난 너의 행동을 지배한다. 내 리듬이 어떠한가, 다고스?"],
[0, "자츠", null, 0, null, 82, 55, 71, 0, 160, 300, null, null],
[0, "곤", null, 0, null, 79, 56, 77, 0, 160, 300, null, null],
[0, "카그", null, 0, null, 80, 55, 69, 0, 160, 300, null, null],
[0, "아우자", null, 0, null, 64, 71, 86, 0, 160, 300, null, null],
[0, "제라드 듀갈", null, 0, null, 87, 51, 80, 0, 160, 300, null, null],
[0, "맘스틴 령", null, 0, null, 80, 51, 66, 0, 160, 300, null, null],
[0, "스투코프 주니어", null, 0, null, 81, 56, 72, 0, 160, 300, null, null, "ued 2차원정로 우린 아몬과의 결전에 참가할거야."],
[0, "레비나 스투코프", null, 0, null, 66, 31, 90, 0, 160, 300, null, null, "근데 우리 병사로 괜찮을까 오빠"],
[0, "제라툴", null, 0, null, 85, 90, 82, 0, 160, 300, null, null, "젤나가 맙소사! 누가 이런 끔찍한 혼종을 만들었단 말인가"],
[0, "테사다르", "`", 0, null, 82, 77, 86, 0, 160, 300, null, null, "난 죽음을 경험한 적이 없네"],
[0, "피닉스", null, 0, null, 79, 80, 65, 0, 160, 300, null, null],
[0, "알다리스", null, 0, null, 80, 51, 77, 0, 160, 300, null, null],
[0, "제라드 듀갈", null, 0, null, 87, 51, 80, 0, 160, 300, null, null, "다시는 나를 볼 수 없을 거요, 헬레나.\n아이들에게 사랑한다고 전해 주시오. 아버지가 너희들의 미래를 지키다가 죽었다고도 말해 주시오.\n그럼, 안녕히...(Au revoir)."],
[0, "맘스틴 령", null, 0, null, 80, 51, 66, 0, 160, 300, null, null],
[0, "스투코프 주니어", null, 0, null, 81, 56, 72, 0, 160, 300, null, null],
[0, "레비나 스투코프", null, 0, null, 66, 31, 90, 0, 160, 300, null, null],
[0, "제라툴", null, 0, null, 85, 90, 82, 0, 160, 300, null, null, "젤나가 맙소사! 누가 이런 혼종을 만들었단 말인가."],
[0, "테사다르", "`", 0, null, 82, 77, 86, 0, 160, 300, null, null, "집행관, 초월체가 약해졌소. 하지만 우리도 심각한 피해를 입었군. 내가 간트리서를 초월체에게 충돌시키겠소. 간트리서의 선체에 암흑 기사단의 힘을 충분히 모은다면, 저 저주받은 흉물에게 죽음을 선사할 수 있을 거요. 우리를 기억해주오, 집행관이여. 오늘 우리가 한 일을 기억하시오. 아둔이 그대를 보살피시길"],
[0, "피닉스", null, 0, null, 79, 80, 65, 0, 160, 300, null, null, "칼라가 나를 기다린다, 케리건. 비록 난 운명을 맞이할 준비가 되기는 했지만, 그렇게 쉬운 먹이가 되진 않겠다!"],
[0, "알다리스", null, 0, null, 80, 51, 77, 0, 160, 300, null, null, "집행관, 태사다르, 소식이 늦어서 미안하군. 대의회는 그대들이 정신체를 쓰러뜨리는 것을 지켜보았소. 그리고, 그 용감한 행위가 헛되지 않았음을 깨달았지. 그대들을 심판하려 했으나, 그릇된 생각을 한 것은 우리였소. 그대들은 우리의 내면에 존재하는 위대함의 상징이니, 우리 모두의 희망을 걸겠소. 엔 타로 아둔! 용감한 아이어의 후예들이여!"],
[0, "탈란다르", null, 0, null, 84, 66, 80, 0, 160, 300, null, null],
[0, "아르타니스", null, 0, null, 80, 71, 84, 0, 160, 300, null, null, "모조리 용기병으로 만들어주마!"],
[0, "라자갈", null, 0, null, 90, 75, 89, 0, 160, 300, null, null],
[0, "아르타니스", null, 0, null, 80, 71, 84, 0, 160, 300, null, null, "모조리 용기병으로 만들어주마"],
[0, "라자갈", null, 0, null, 90, 75, 89, 0, 160, 300, null, null, "고맙다, 제라툴... 네가 나를 놈의 추악한 손아귀에서 벗어나게 해주었다. 너는 늘 명예롭게 나를 섬겨주었지... 그러니 부탁하마... 내 부족을 돌봐 다오... 네 손에 미래를 맡기겠다"],
[0, "우룬 제독", null, 0, null, 84, 80, 55, 0, 160, 300, null, null],
[0, "셀렌디스", null, 0, null, 80, 80, 67, 0, 160, 300, null, null],
[0, "보라준", null, 0, null, 87, 51, 91, 0, 160, 300, null, null],
@@ -195,7 +196,7 @@
[0, "말라쉬", null, 0, null, 80, 96, 65, 0, 160, 300, null, null],
[0, "니온", null, 0, null, 62, 73, 43, 0, 160, 300, null, null],
[0, "지나라", null, 0, null, 75, 90, 55, 0, 160, 300, null, null],
[0, "아리샤", null, 0, null, 51, 81, 50, 0, 160, 300, null, null, "난 XX부르드의 울트라 표 아리샤다!"],
[0, "아리샤", null, 0, null, 51, 81, 50, 0, 160, 300, null, null],
[0, "아몬", null, 0, null, 97, 107, 85, 0, 160, 300, null, null, "나는 너희들의 시작이었으니 또한 끝이리라….!"],
[0, "에밀 나루드", null, 0, null, 85, 92, 96, 0, 160, 300, null, null, "아몬께서 별빛으로 속삭이셨다. 그분은 돌아오신다고 하셨다. 그분은 파괴한다고 하셨다. 멸종. 모든 것이 종말을 맞이할 것이라고"],
[0, "오로스", null, 0, null, 80, 79, 55, 0, 160, 300, null, null],
@@ -206,14 +207,14 @@
[0, "유당찬", null, 0, null, 83, 69, 55, 0, 160, 300, null, null],
[0, "유시하", "`", 0, null, 67, 55, 81, 0, 160, 300, null, null],
[0, "유미하", null, 0, null, 41, 34, 48, 0, 160, 300, null, null],
[0, "유지은", null, 0, null, 55, 78, 64, 0, 160, 300, null, null, "나는 레플리카"],
[0, "유혜선", null, 0, null, 78, 89, 55, 0, 160, 300, null, null, "나는"],
[0, "유지은", null, 0, null, 55, 78, 64, 0, 160, 300, null, null, "나는 레플리카, 적의 기술을 베껴서 사용 가능하지."],
[0, "유혜선", null, 0, null, 78, 89, 55, 0, 160, 300, null, null],
[0, "최정윤", null, 0, null, 85, 71, 66, 0, 160, 300, null, null, "우리는 다미키즈, 다미의 친구들이자 다미에 의해 도움을 받은 아이들 거기서도 나는 캔디팝이야"],
[0, "이선아", null, 0, null, 83, 88, 56, 0, 160, 300, null, null, "나는 마콧치, 마법나라 감사부문의 부부장이야."],
[0, "나서희", null, 0, null, 81, 77, 44, 0, 160, 300, null, null, "다미를 지킬거야"],
[0, "나서희", null, 0, null, 81, 77, 44, 0, 160, 300, null, null, "우리는 다미를 지킬거야"],
[0, "스노우 화이트", null, 0, null, 71, 80, 66, 0, 160, 300, null, null, "이번에야 말로 친구들을 지켜 보이겠어. 소타도, 아카리도"],
[0, "워터 스플렛", null, 0, null, 65, 80, 52, 0, 160, 300, null, null, "이번생에는 그런 셔플린 따위에게 지지 않을거야,"],
[0, "스노우 슬러스", null, 0, null, 69, 75, 53, 0, 160, 300, null, null, "번생이랑 달라! 나도 강해졋다고!"],
[0, "워터 스플렛", null, 0, null, 65, 80, 52, 0, 160, 300, null, null, "…. 이번에야말로…"],
[0, "스노우 슬러스", null, 0, null, 69, 75, 53, 0, 160, 300, null, null, "번생이랑 달라,… 나도 언니처럼 강해질수 있어"],
[0, "리하나", null, 0, null, 79, 70, 65, 0, 160, 300, null, null, "프흐레 언니…"],
[0, "블루베리", null, 0, null, 81, 88, 73, 0, 160, 300, null, null],
[0, "스윔스윔", null, 0, null, 54, 90, 71, 0, 163, 300, null, null, "룰러가 그랫어 마법소녀는 본모습을 남에게 보여주면 안된다고"],
@@ -221,22 +222,22 @@
[0, "정혜림", null, 0, null, 91, 88, 80, 0, 160, 300, null, null, "우리 다미키즈는 다미언니에 의해 많은걸 받은 아이들이야,"],
[0, "하나조노 아무", null, 0, null, 67, 53, 81, 0, 160, 300, null, null, "캐릭터 변신! 애뮬릿 다이아!"],
[0, "하나조노 카렌", null, 0, null, 66, 51, 80, 0, 160, 300, null, null, "나는 하나조노 카렌, 전 이름은 하나무라 카렌이야."],
[0, "하나조노 아리스", null, 0, null, 53, 14, 61, 0, 160, 300, null, null],
[0, "하나조노 아리스", null, 0, null, 53, 14, 61, 0, 160, 300, null, null, "당신에겐 고민이 있나요? 학교생활, 그리고 친구, 가족과의 관계와 장래의 꿈에 대한 고민, 혼자서는 도저히 해결할수 없는 고민들이 있죠? 바로 그럴떄 내 이야기를 들어줄 누군가가 필요하지 않나요? 그게 누구냐고요? 만나고 싶으면 리루 리루 페어리루라 외치세요!"],
[0, "린", null, 0, null, 38, 32, 69, 0, 160, 300, null, null, "난 용담꽃의 페어리루 린이야~"],
[0, "하나조노 마츠아", null, 0, null, 44, 40, 55, 0, 160, 300, null, null],
[0, "롭프", null, 0, null, 55, 47, 50, 0, 160, 300, null, null],
[0, "이정선 황제", null, 0, null, 74, 44, 85, 0, 160, 300, null, null, "감히 우리 제국에 반기를 든자들! 용서하지 않는다! !"],
[0, "이양선 공", null, 0, null, 77, 42, 79, 0, 160, 300, null, null, "형님이 리 화를 내시다니 내심 단하구나 그들은"],
[0, "이양선 공", null, 0, null, 77, 42, 79, 0, 160, 300, null, null, "형님께서 이리 화를 내시다니, 그들도 강하군"],
[0, "이을용 황태자", null, 0, null, 51, 33, 69, 0, 160, 300, null, null],
[0, "카라시프 장군", null, 0, null, 75, 69, 71, 0, 160, 300, null, null],
[0, "카몬 요네미", null, 0, null, 40, 8, 66, 0, 160, 300, null, null],
[0, "키타노 타케시", null, 0, null, 43, 14, 75, 0, 160, 300, null, null, "우리가족의 행복과 안전을 우선시해야지"],
[0, "키타노 유리아", null, 0, null, 32, 8, 69, 0, 160, 300, null, null],
[0, "후타바 안즈", null, 0, null, 67, 44, 90, 0, 160, 300, null, null, "나도 하메르도 행복하길"],
[0, "히나노", null, 0, null, 66, 32, 73, 0, 160, 300, null, null, "이번생에는 행복하고 싶어"],
[0, "히나노", null, 0, null, 66, 32, 73, 0, 160, 300, null, null, "히힛"],
[0, "나츠세 유우", null, 0, null, 66, 30, 70, 0, 160, 300, null, null],
[0, "크로노스 신", null, 0, null, 50, 15, 90, 0, 160, 300, null, null],
[0, "프흐레", null, 0, null, 69, 24, 100, 0, 160, 300, null, null, "마법나라가 바라는 마법소녀란 말이지…"],
[0, "크로노스 신", null, 0, null, 33, 15, 90, 0, 160, 300, null, null],
[0, "프흐레", null, 0, null, 69, 24, 100, 0, 160, 300, null, null, "마법나라'원하는 마법소녀다운 마법소녀란 건 말이지. 여차할 때 주저없이 자기 ​​자신을 희생할 수 있는 성인이야. 제대로 돼먹은 정신머리로는 못 해먹을 짓거리지."],
[0, "나나하라 슈야", null, 0, null, 73, 72, 62, 0, 160, 300, null, null, "전세계의 어른들에게 선전포고 한다!"],
[0, "타에구치유이치로", null, 0, null, 51, 63, 44, 0, 160, 300, null, null],
[0, "카와다 소교", null, 0, null, 49, 70, 85, 0, 160, 300, null, null],
@@ -246,12 +247,12 @@
[0, "큐어 리프렛", null, 0, null, 71, 89, 72, 0, 160, 300, null, null],
[0, "큐어 실프", null, 0, null, 83, 92, 88, 0, 160, 300, null, null],
[0, "큐어 아네모스", null, 0, null, 69, 80, 55, 0, 160, 300, null, null],
[0, "카마디야", null, 0, null, 85, 93, 55, 0, 160, 300, null, null, "크흐흐… 감히 인간 따위가!! 나에게 덤비다니 용서못한다!!"],
[0, "카마디야", null, 0, null, 85, 93, 55, 0, 160, 300, null, null, "이번엔 방심하지 않고 인간들을 처리하겟다!"],
[0, "토토리몬", null, 0, null, 69, 88, 44, 0, 160, 300, null, null, "모든것은 카마디야님의 부활을 위해서…."],
[0, "카와리노", null, 0, null, 73, 91, 80, 0, 160, 300, null, null, "나를 그곳에 꺼내주신 카마디야 님을 위해 난 일한다!"],
[0, "가마오", null, 0, null, 41, 65, 13, 0, 160, 300, null, null, "이씨 너무 배고파…"],
[0, "밀키 로즈", null, 0, null, 64, 82, 52, 0, 160, 300, null, null],
[0, "옐로우 미치", null, 0, null, 61, 80, 80, 0, 160, 300, null, null, "난 마법나라 외교부문의 부문장 옐로우 미치야"],
[0, "옐로우 미치", null, 0, null, 61, 80, 80, 0, 160, 300, null, null, "후후… 모든건 모두의 행복을 위해서"],
[0, "마왕팜", null, 0, null, 50, 90, 70, 0, 160, 300, null, null, "무지개? 어째서…!"],
[0, "그림하트", null, 0, null, 74, 63, 41, 0, 160, 300, null, null, "목을 쳐라!!"],
[0, "루다", null, 0, null, 62, 66, 62, 0, 160, 300, null, null, "널 좋아해 미우."],
@@ -272,7 +273,7 @@
[0, "유해", null, 0, null, 85, 87, 64, 0, 160, 300, null, null],
[0, "유채", null, 0, null, 64, 71, 23, 0, 160, 300, null, null],
[0, "유창", null, 0, null, 59, 68, 33, 0, 160, 300, null, null],
[0, "타카이시 타케루", null, 0, null, 80, 92, 65, 0, 160, 300, null, "무쌍"],
[0, "타카이시 타케루", null, 0, null, 80, 92, 65, 0, 160, 300, null, null],
[0, "타카이시 히카리", null, 0, null, 31, 11, 90, 0, 160, 300, null, "인덕"],
[0, "타카이시 노조미", null, 0, null, 82, 66, 81, 0, 160, 300, null, null],
[0, "타카이시 미츠루", null, 0, null, 85, 51, 71, 0, 160, 300, null, null, "이 나라가 왜이리 망가져 버린걸까"],
@@ -280,7 +281,7 @@
[0, "유상호", null, 0, null, 39, 56, 44, 0, 160, 300, null, null, "쯧. 런애가 내 딸이라니 수치야"],
[0, "서유현", null, 0, null, 42, 44, 60, 0, 160, 300, null, null, "하… 내가 교육을 잘못했구나"],
[0, "오준", null, 0, null, 69, 12, 84, 0, 160, 300, null, null, "이 지역은 이제부터 내것이다!"],
[10, "도니 버밀리언", null, 0, null, 48, 14, 66, 0, 160, 300, null, null],
[10, "도니 버밀리언", null, 0, null, 48, 14, 66, 0, 160, 300, null, null, "레이너, 당신은 양심도 없습니까?"],
[0, "모한다르", null, 0, null, 68, 81, 45, 0, 160, 300, null, null],
[0, "월리엄 와이트", null, 0, null, 55, 64, 88, 0, 160, 300, null, null, "아니 나를 별명으로 부르지 말라고!"],
[0, "도미니크 헥터", null, 0, null, 57, 69, 41, 0, 160, 300, null, null],
@@ -296,7 +297,7 @@
[0, "사야코", null, 0, null, 69, 75, 60, 0, 160, 300, null, null],
[0, "미요가타노 나코", null, 0, null, 77, 55, 79, 0, 160, 300, null, null, "fucking doll!"],
[0, "첼너 마우스", null, 0, null, 51, 85, 2, 0, 160, 300, null, null, "체르나는 그런거 싫단 말이야! 내꺼라고!"],
[0, "페치카", null, 0, null, 68, 8, 80, 0, 160, 300, null, null],
[0, "페치카", null, 0, null, 68, 8, 80, 0, 160, 300, null, null, "마법소녀끼리 정정당당하게 싸우면…… 크랑테일은 당신 같은 사람에게 지지 않아."],
[0, "기타노 유리코", null, 0, null, 14, 9, 64, 0, 160, 300, null, null, "유키코는 참 의젓한 아이야."],
[0, "니얀다 카멘", null, 0, null, 86, 85, 41, 0, 160, 300, null, null, "살려달라고 외치면 그 누구든 구할거야, 설사 적이라 할지라도"],
[0, "니얀다 카렌", null, 0, null, 73, 88, 60, 0, 160, 300, null, null],
@@ -318,9 +319,9 @@
[0, "모노미야 세이나", null, 0, null, 77, 83, 41, 0, 160, 300, null, null, "어떤 유혹에도 빛을 잃지않는 깨끗한 마음 큐어 다이아!"],
[0, "모노야마 유이", null, 0, null, 69, 85, 60, 0, 160, 300, null, null, "사랑하는 사람을 지키기 위한 진실한 마음 큐어 실린!"],
[0, "루시에르", null, 0, null, 84, 72, 80, 0, 160, 300, null, null, "내 딸들은 행복했으면 좋겟어"],
[0, "큐어 루프", null, 0, null, 63, 81, 40, 0, 160, 300, null, null, "감히… 내동생을 어둠의 가면으로 죽였겟다! 복수할거야! 로스쿨"],
[0, "가제크", null, 0, null, 55, 79, 51, 0, 160, 300, null, null, "어…언니 사…살려줘!!"],
[0, "큐어 가젯", null, 0, null, 67, 81, 55, 0, 160, 300, null, null, "어머니… 어머니의 원수 꼭 갚겠어요.. 우리를 버린 로스쿨에게 복수할 겁니다"],
[0, "큐어 루프", null, 0, null, 63, 81, 40, 0, 160, 300, null, null, "우리는 왜 이런 직장에 충성한 걸까"],
[0, "가제크", null, 0, null, 55, 79, 51, 0, 160, 300, null, null],
[0, "큐어 가젯", null, 0, null, 67, 81, 55, 0, 160, 300, null, null, "어머니… 어머니의 원수 꼭 갚겠어요.. 우리를 버린 그들에게 복수할 겁니다."],
[0, "큐어 루비", null, 0, null, 73, 79, 59, 0, 160, 300, null, null, "언니 우리 같이 힘내자"],
[0, "루미에르", null, 0, null, 80, 81, 64, 0, 160, 300, null, null],
[0, "토모야마 리에리", null, 0, null, 92, 55, 91, 0, 160, 300, null, null],
@@ -333,12 +334,12 @@
[0, "에트리카 에슈", null, 0, null, 84, 53, 82, 0, 160, 300, null, null, "모든것은, 나를 이렇게 만든 세상에 복수하기 위해!"],
[0, "유가을", null, 0, null, 87, 95, 60, 0, 160, 300, null, null],
[0, "츠바이 유시", null, 0, null, 43, 63, 77, 0, 160, 300, null, null],
[0, "크람베리", null, 0, null, 82, 93, 51, 0, 160, 300, null, null],
[0, "파브", null, 0, null, 41, 10, 78, 0, 160, 300, null, null],
[0, "파르", null, 0, null, 43, 10, 88, 0, 160, 300, null, null],
[0, "크람베리", null, 0, null, 82, 93, 51, 0, 160, 300, null, null, "전 숲의 음악가 크람베리, 마법소녀를 하고 있습니다."],
[0, "파브", null, 0, null, 41, 10, 78, 0, 160, 300, null, null, "지금부터 이 마을의 마법소녀를 반으로 줄여야 합니다퐁!"],
[0, "파르", null, 0, null, 43, 10, 88, 0, 160, 300, null, null, "역시 스노우화이트는 내가 없으면 안되퐁…"],
[0, "타이요 아카리", null, 0, null, 81, 92, 77, 0, 160, 300, null, null, "나 잊지 않을테니까, 후유나에 대해 절대로 잊지않을 테니까"],
[0, "신자키 후유나", null, 0, null, 74, 79, 55, 0, 160, 300, null, null, "안녕 아카리, 아카리와 만나 행복했어"],
[0, "크랑테일", null, 0, null, 77, 81, 55, 0, 160, 300, null, null],
[0, "크랑테일", null, 0, null, 77, 81, 55, 0, 160, 300, null, null, "라즐리느와 페치카가 살려준 난...... 못 죽어"],
[0, "타카이시 히모리", null, 0, null, 69, 37, 89, 0, 160, 300, null, null, "내 나라, 부모님의 나라 내가 지킬거야"],
[0, "사카나기 아야나", null, 0, null, 73, 89, 53, 0, 160, 300, null, null, "난 스윔스윔, 잘부탁해"],
[0, "사카나기 이즈카", null, 0, null, 77, 82, 60, 0, 160, 300, null, null, "난 사야코야"],
@@ -359,8 +360,8 @@
[0, "히노모토 아카리", null, 0, null, 70, 17, 81, 0, 160, 300, null, null],
[0, "아오누마 키리하", null, 0, null, 84, 67, 85, 0, 160, 300, null, null],
[0, "츠루기 젠지로", null, 0, null, 56, 69, 40, 0, 160, 300, null, null],
[0, "향상성", null, 0, null, 82, 77, 90, 0, 160, 300, null, null],
[0, "이그드라실", null, 0, null, 80, 80, 77, 0, 160, 300, null, null, "저는 디지털 월드의 평화를 바라는자,"],
[0, "향상성", null, 0, null, 82, 77, 90, 0, 160, 300, null, null, "저는 디지털 월드의 평화를 바라는 자,"],
[0, "이그드라실", null, 0, null, 80, 80, 77, 0, 160, 300, null, null, "어머니와 향상성을 이해할수가 없군요"],
[0, "렌", null, 0, null, 89, 100, 42, 0, 160, 300, null, "무쌍"],
[0, "네네네", null, 0, null, 63, 55, 89, 0, 160, 300, null, null],
[0, "이소긴&야도간", null, 0, null, 58, 77, 39, 0, 160, 300, null, null],
@@ -377,7 +378,8 @@
[0, "큐어 리히트", null, 0, null, 89, 93, 74, 0, 160, 300, null, null],
[0, "다크 드림", null, 0, null, 77, 94, 76, 0, 160, 300, null, null],
[0, "큐어 프렌디", null, 0, null, 86, 83, 77, 0, 160, 300, null, null],
[0, "큐어 릴리안", null, 0, null, 87, 82, 80, 0, 160, 300, null, null]
[0, "큐어 릴리안", null, 0, null, 87, 82, 80, 0, 160, 300, null, null],
[0, "마시로 리마", null, 0, null, 86, 79, 82, 0, 160, 300, null, null]
],
"general_ex":[
+211
View File
@@ -0,0 +1,211 @@
{
"title":"【공백지】 무한대흥 ",
"startYear":180,
"map":{
"mapName":"miniche"
},
"history":[
"<C>●</>180년 1월:<L><b>【이벤트】</b></>많은 제한치가 더 높게 풀려있습니다."
],
"stat": {
"total": 200,
"min": 20,
"max": 100,
"chiefMin": 20
},
"const": {
"joinRuinedNPCProp":0,
"npcBanMessageProb":0.005,
"scenarioEffect": "event_MoreEffect",
"availableInstantAction": {
"instantRetreat": true
},
"defaultMaxGeneral": 800,
"basePopIncreaseAmount": 10000,
"maxTechLevel": 15,
"upgradeLimit": 20,
"initialAllowedTechLevel": 2,
"expandCityPopIncreaseAmount": 150000,
"expandCityDevelIncreaseAmount": 3000,
"expandCityWallIncreaseAmount": 3000,
"defaultCityWall": 3000,
"maxResourceActionAmount": 30000,
"resourceActionAmountGuide": [
100, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
1200, 1500, 2000, 2500, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000,
12000, 15000, 20000, 25000, 30000
],
"allItems": {
"horse" : {
"che_명마_01_노기" : 0, "che_명마_02_조랑" : 0, "che_명마_03_노새" : 0,
"che_명마_04_나귀" : 0, "che_명마_05_갈색마" : 0, "che_명마_06_흑색마" : 0,
"che_명마_07_백마" : 4, "che_명마_07_기주마" : 4, "che_명마_07_오환마" : 4, "che_명마_07_백상" : 4,
"che_명마_08_양주마" : 4, "che_명마_08_흉노마" : 4, "che_명마_09_과하마" : 4, "che_명마_09_의남백마" : 4,
"che_명마_10_대완마" : 4, "che_명마_10_옥추마" : 4, "che_명마_11_서량마" : 4, "che_명마_11_화종마" : 4,
"che_명마_12_사륜거" : 4, "che_명마_12_옥란백용구": 4, "che_명마_13_절영" : 4, "che_명마_13_적로" : 4,
"che_명마_14_적란마" : 4, "che_명마_14_조황비전" : 4, "che_명마_15_한혈마" : 4, "che_명마_15_적토마" : 4
},
"weapon" : {
"che_무기_01_단도" : 0, "che_무기_02_단궁" : 0, "che_무기_03_단극" : 0,
"che_무기_04_목검" : 0, "che_무기_05_죽창" : 0, "che_무기_06_소부" : 0,
"che_무기_07_동추" : 4, "che_무기_07_철편" : 4, "che_무기_07_철쇄" : 4, "che_무기_07_맥궁" : 4,
"che_무기_08_유성추" : 4, "che_무기_08_철질여골" : 4, "che_무기_09_쌍철극" : 4, "che_무기_09_동호비궁" : 4,
"che_무기_10_삼첨도" : 4, "che_무기_10_대부" : 4, "che_무기_11_고정도" : 4, "che_무기_11_이광궁" : 4,
"che_무기_12_철척사모" : 4, "che_무기_12_칠성검" : 4, "che_무기_13_사모" : 4, "che_무기_13_양유기궁" : 4,
"che_무기_14_언월도" : 4, "che_무기_14_방천화극" : 4, "che_무기_15_청홍검" : 4, "che_무기_15_의천검" : 4
},
"book" : {
"che_서적_01_효경전" : 0, "che_서적_02_회남자" : 0, "che_서적_03_변도론" : 0,
"che_서적_04_건상역주" : 0, "che_서적_05_여씨춘추" : 0, "che_서적_06_사민월령" : 0,
"che_서적_07_위료자" : 4, "che_서적_07_사마법" : 4, "che_서적_07_한서" : 4, "che_서적_07_논어" : 4,
"che_서적_08_전론" : 4, "che_서적_08_사기" : 4, "che_서적_09_장자" : 4, "che_서적_09_역경" : 4,
"che_서적_10_시경" : 4, "che_서적_10_구국론" : 4, "che_서적_11_상군서" : 4, "che_서적_11_춘추전" : 4,
"che_서적_12_산해경" : 4, "che_서적_12_맹덕신서" : 4, "che_서적_13_관자" : 4, "che_서적_13_병법24편" : 4,
"che_서적_14_한비자" : 4, "che_서적_14_오자병법" : 4, "che_서적_15_노자" : 4, "che_서적_15_손자병법" : 4
},
"item": {
"che_치료_환약": 0,
"che_저격_수극": 0,
"che_사기_탁주": 0,
"che_훈련_청주": 0,
"che_계략_이추": 0,
"che_계략_향낭": 0,
"event_전투특기_격노": 0,
"event_전투특기_견고": 0,
"event_전투특기_공성": 0,
"event_전투특기_궁병": 0,
"event_전투특기_귀병": 0,
"event_전투특기_기병": 0,
"event_전투특기_돌격": 0,
"event_전투특기_무쌍": 0,
"event_전투특기_반계": 0,
"event_전투특기_보병": 0,
"event_전투특기_신산": 0,
"event_전투특기_신중": 0,
"event_전투특기_위압": 0,
"event_전투특기_의술": 0,
"event_전투특기_저격": 0,
"event_전투특기_집중": 0,
"event_전투특기_징병": 0,
"event_전투특기_척사": 0,
"event_전투특기_필살": 0,
"event_전투특기_환술": 0
}
}
},
"initialEvents":[
[
true,
["ChangeCity", ["cities", "낙양", "성도", "건업", "업", "장안", "허창", "양양"], {
"pop_max": "+300000",
"agri_max": "+6000",
"comm_max": "+6000",
"secu_max": "+5000",
"def_max": "+6000",
"wall_max": "+6000"
}],
["ChangeCity", ["cities", "시상", "수춘", "한중", "남피", "위례", "북평", "강릉", "완", "장사"], {
"pop_max": "+250000",
"agri_max": "+5000",
"comm_max": "+5000",
"secu_max": "+4000",
"def_max": "+5000",
"wall_max": "+5000"
}],
["ChangeCity", ["cities", "오", "하비", "복양", "웅진", "강주", "무도", "국내", "진류", "계양", "계림", "계", "무위", "제남", "남해", "덕양", "하내", "상용"], {
"pop_max": "+200000",
"agri_max": "+4000",
"comm_max": "+4000",
"secu_max": "+3000",
"def_max": "+4000",
"wall_max": "+4000"
}],
["ChangeCity", ["cities", "초", "운남", "대", "하동", "무릉", "교지", "단양", "영안", "북해", "건녕", "강하", "진양", "평원", "회계", "천수", "평양", "요동", "거록", "여강", "패", "자동", "광릉", "장가", "영릉", "월수", "건안", "신야", "상동", "안정", "여남", "홍농", "주제"], {
"pop_max": "+150000",
"agri_max": "+3000",
"comm_max": "+3000",
"secu_max": "+2000",
"def_max": "+3000",
"wall_max": "+3000"
}],
["ChangeCity", ["cities", "남만", "산월", "오환", "강", "왜", "흉노"], {
"pop_max": "+100000",
"agri_max": "+2000",
"comm_max": "+2000",
"secu_max": "+1000",
"def_max": "+2000",
"wall_max": "+2000"
}],
["ChangeCity", ["cities", "합비", "이릉", "오장원", "호로", "탐라", "적벽"], {
"pop_max": "+100000",
"agri_max": "+1000",
"comm_max": "+1000",
"secu_max": "+1000",
"def_max": "+1000",
"wall_max": "+1000"
}],
["ChangeCity", "free", {
"pop":"50%"
}]
]
],
"events":[
[
"month", 1000,
["or", ["Date", "==", null, 12], ["Date", "==", null, 6]],
["CreateManyNPC", 15, 10],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", 181, 1],
["RaiseNPCNation"],
["DeleteEvent"]
],
[
"month", 999,
["Date", "==", 181, 1],
["OpenNationBetting", 4, 5000],
["OpenNationBetting", 1, 2000],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", 182, 1],
["ChangeCity", "occupied", {
"trade":100
}],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", 182, 7],
["ChangeCity", "occupied", {
"trade":100
}],
["DeleteEvent"]
],
[
"month", 999,
["and",
["Date", ">=", 183, 1],
["RemainNation", "<=", 8]
],
["OpenNationBetting", 1, 1000],
["DeleteEvent"]
],
[
"destroy_nation", 1000,
["and",
["Date", ">=", 183, 1],
["RemainNation", "==", 1]
],
["BlockScoutAction"],
["DeleteEvent"]
]
]
}
+92
View File
@@ -0,0 +1,92 @@
{
"title":"【공백지】 요양 ",
"startYear":180,
"map":{
"mapName":"miniche"
},
"history":[
"<C>●</>180년 1월:<L><b>【이벤트】</b></>쉬었다 가는 깃수입니다."
],
"const": {
"joinRuinedNPCProp":0,
"npcBanMessageProb":0.005,
"defaultMaxGeneral": 800,
"allItems":{
"horse":{
"che_명마_01_노기":0, "che_명마_02_조랑":0, "che_명마_03_노새":0,
"che_명마_04_나귀":0, "che_명마_05_갈색마":0, "che_명마_06_흑색마":0,
"che_명마_07_백마" : 2, "che_명마_07_기주마" : 2, "che_명마_07_오환마" : 2, "che_명마_07_백상" : 2,
"che_명마_08_양주마" : 2, "che_명마_08_흉노마" : 2, "che_명마_09_과하마" : 2, "che_명마_09_의남백마" : 2,
"che_명마_10_대완마" : 2, "che_명마_10_옥추마" : 2, "che_명마_11_서량마" : 2, "che_명마_11_화종마" : 2,
"che_명마_12_사륜거" : 2, "che_명마_12_옥란백용구": 2, "che_명마_13_절영" : 2, "che_명마_13_적로" : 2,
"che_명마_14_적란마" : 2, "che_명마_14_조황비전" : 2, "che_명마_15_한혈마" : 2, "che_명마_15_적토마" : 2
},
"weapon":{
"che_무기_01_단도":0, "che_무기_02_단궁":0, "che_무기_03_단극":0,
"che_무기_04_목검":0, "che_무기_05_죽창":0, "che_무기_06_소부":0,
"che_무기_07_동추":2, "che_무기_07_철편":2, "che_무기_07_철쇄":2, "che_무기_07_맥궁":2,
"che_무기_08_유성추":2, "che_무기_08_철질여골":2, "che_무기_09_쌍철극":2, "che_무기_09_동호비궁":2,
"che_무기_10_삼첨도":2, "che_무기_10_대부":2, "che_무기_11_고정도":2, "che_무기_11_이광궁":2,
"che_무기_12_철척사모":2, "che_무기_12_칠성검":2, "che_무기_13_사모":2, "che_무기_13_양유기궁":2,
"che_무기_14_언월도":2, "che_무기_14_방천화극":2, "che_무기_15_청홍검":2, "che_무기_15_의천검":2
},
"book":{
"che_서적_01_효경전":0, "che_서적_02_회남자":0, "che_서적_03_변도론":0,
"che_서적_04_건상역주":0, "che_서적_05_여씨춘추":0, "che_서적_06_사민월령":0,
"che_서적_07_위료자":2, "che_서적_07_사마법":2, "che_서적_07_한서":2, "che_서적_07_논어":2,
"che_서적_08_전론":2, "che_서적_08_사기":2, "che_서적_09_장자":2, "che_서적_09_역경":2,
"che_서적_10_시경":2, "che_서적_10_구국론":2, "che_서적_11_상군서":2, "che_서적_11_춘추전":2,
"che_서적_12_산해경":2, "che_서적_12_맹덕신서":2, "che_서적_13_관자":2, "che_서적_13_병법24편":2,
"che_서적_14_한비자":2, "che_서적_14_오자병법":2, "che_서적_15_노자":2, "che_서적_15_손자병법":2
},
"item":{
"che_치료_환약":0, "che_저격_수극":0, "che_사기_탁주":0,
"che_훈련_청주":0, "che_계략_이추":0, "che_계략_향낭":0,
"event_전투특기_격노":0, "event_전투특기_견고":0, "event_전투특기_공성":0, "event_전투특기_궁병":0,
"event_전투특기_귀병":0, "event_전투특기_기병":0, "event_전투특기_돌격":0, "event_전투특기_무쌍":0,
"event_전투특기_반계":0, "event_전투특기_보병":0, "event_전투특기_신산":0, "event_전투특기_신중":0,
"event_전투특기_위압":0, "event_전투특기_의술":0, "event_전투특기_저격":0, "event_전투특기_집중":0,
"event_전투특기_징병":0, "event_전투특기_척사":0, "event_전투특기_필살":0, "event_전투특기_환술":0
}
}
},
"events":[
[
"month", 1000,
["or", ["Date", "==", null, 12], ["Date", "==", null, 6]],
["CreateManyNPC", 15, 10],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", 181, 1],
["RaiseNPCNation"],
["DeleteEvent"]
],
[
"month", 999,
["Date", "==", 181, 1],
["OpenNationBetting", 4, 5000],
["OpenNationBetting", 1, 2000],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", null, 1],
["ChangeCity", "all", {
"trade":100
}]
],
[
"month", 1000,
["Date", "==", null, 7],
["ChangeCity", "all", {
"trade":100
}]
]
]
}
+71
View File
@@ -0,0 +1,71 @@
{
"title": "【공백지】 100기 올스타 클래식",
"startYear": 180,
"map": {
"mapName": "miniche",
"targetGeneralPool": "SPoolUnderU100",
"generalPoolAllowOption": ["stat", "ego", "picture"],
"centennialNpcDexTargetRatio": 0.4
},
"history": [
"<C>●</>180년 1월:<L><b>【100기 이벤트】</b></> 역대 장수들이 평범한 능력으로 다시 모여, 지난 전성기의 힘과 서서히 동조하기 시작했다!"
],
"const": {
"npcBanMessageProb":0.005,
"defaultMaxGeneral": 800,
"uniqueTrialCoef": 2
},
"events": [
[
"month", 8000,
true,
["AdvanceCentennialAllStar"]
],
[
"month", 1000,
["Date", "==", null, 12],
["CreateManyNPC", 100, 0],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", 181, 1],
["RaiseNPCNation"],
["DeleteEvent"]
],
[
"destroy_nation", 1000,
["and",
["Date", ">=", 183, 1],
["RemainNation", "==", 1]
],
["BlockScoutAction"],
["DeleteEvent"]
],
[
"month", 999,
["Date", "==", 181, 1],
["OpenNationBetting", 4, 5000],
["OpenNationBetting", 1, 2000],
["DeleteEvent"]
],
[
"month", 999,
["and",
["Date", ">=", 183, 1],
["RemainNation", "<=", 8]
],
["OpenNationBetting", 1, 1000],
["DeleteEvent"]
],
[
"destroy_nation", 1000,
["and",
["Date", ">=", 183, 1],
["RemainNation", "==", 1]
],
["BlockScoutAction"],
["DeleteEvent"]
]
]
}
+88 -31
View File
@@ -19,8 +19,16 @@ if ($admin['npcmode'] != 2) {
$member = RootDB::db()->queryFirstRow("SELECT no,name,picture,imgsvr,grade from member where no= %i", $userID);
$generalID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID);
$currentGeneral = $db->queryFirstRow(
'SELECT no,picture,imgsvr FROM general WHERE owner = %i',
$userID
);
$generalID = $currentGeneral['no'] ?? null;
$gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2');
$isCentennialAllStar = CentennialAllStarGrowthService::isActive();
$canUseOwnPicture = $admin['show_img_level'] >= 1
&& $member['grade'] >= 1
&& $member['picture'] != "";
$nationList = $db->query('SELECT nation,`name`,color,scout FROM nation');
shuffle($nationList);
@@ -54,6 +62,7 @@ foreach (getCharacterList(false) as $id => [$name, $info]) {
<script>
var hasGeneralID = <?= $generalID === null ? 'false' : 'true' ?>;
var isCentennialAllStar = <?= $isCentennialAllStar ? 'true' : 'false' ?>;
var defaultStatTotal = <?= GameConst::$defaultStatTotal ?>;
var defaultStatMin = <?= GameConst::$defaultStatMin ?>;
var defaultStatMax = <?= GameConst::$defaultStatMax ?>;
@@ -104,6 +113,27 @@ if ($gencount >= $admin['maxgeneral']) {
<small id="valid_until">(<span id="valid_until_text"></span>까지 유효)</small><small id="outdate_token">- 만료 -</small><br>
<form class="card_holder">
</form>
<?php if ($isCentennialAllStar && $generalID !== null) : ?>
<div id="reselect_picture_plate" class="picture_choice">
<strong>변경 전콘</strong>
<label>
<input type="radio" name="reselect_picture_source" value="selected" checked>
새로 선택할 장수 전콘
</label>
<label>
<input type="radio" name="reselect_picture_source" value="current">
<img width="32" height="32" src="<?= GetImageURL($currentGeneral['imgsvr']) ?>/<?= $currentGeneral['picture'] ?>" border="0">
현재 장수 전콘
</label>
<?php if ($canUseOwnPicture) : ?>
<label>
<input type="radio" name="reselect_picture_source" value="own">
<img width="32" height="32" src="<?= GetImageURL($member['imgsvr']) ?>/<?= $member['picture'] ?>" border="0">
원래 전콘
</label>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
@@ -117,7 +147,28 @@ if ($gencount >= $admin['maxgeneral']) {
<form id='custom_form'>
<table class='tb_layout' style='width:100%;text-align:left;'>
<?php
if ($admin['show_img_level'] >= 1 && $member['grade'] >= 1 && $member['picture'] != "") {
if ($isCentennialAllStar) {
echo "
<tr class='event_picture'>
<td align=right class='bg1'>전콘 선택</td>
<td colspan=2>
<label><input type=radio name=picture_source value=selected checked> 선택한 장수 전콘</label>
";
if ($canUseOwnPicture) {
$imageTemp = GetImageURL($member['imgsvr']);
echo "
<label>
<input type=radio name=picture_source value=own>
<img width='64' height='64' src='{$imageTemp}/{$member['picture']}' border='0'>
전콘
</label>
";
}
echo "
</td>
</tr>
";
} elseif ($canUseOwnPicture) {
$imageTemp = GetImageURL($member['imgsvr']);
echo "
<tr class='custom_picture'>
@@ -144,36 +195,42 @@ if ($gencount >= $admin['maxgeneral']) {
</select> <span id="charInfoText"></span>
</td>
</tr>
<tr class='custom_stat'>
<td align=right class='bg1'>통솔</td>
<td colspan=2><input type="number" name="leadership" id="leadership" value="50"></td>
</tr>
<tr class='custom_stat'>
<td align=right class='bg1'>무력</td>
<td colspan=2><input type="number" name="strength" id="strength" value="50"></td>
</tr>
<tr class='custom_stat'>
<td align=right class='bg1'>지력</td>
<td colspan=2><input type="number" name="intel" id="intel" value="50"></td>
</tr>
<tr class='custom_stat'>
<td align=right class='bg1'>능력치 조정</td>
<td colspan=2>
<input type=button value=랜덤형 onclick=abilityRand()>
<input type=button value=통솔무력 onclick=abilityLeadpow()>
<input type=button value=통솔력형 onclick=abilityLeadint()>
<input type=button value=무력지력형 onclick=abilityPowint()>
</td>
</tr>
<tr class='custom_stat'>
<td align=center colspan=3>
<font color=orange>모든 능력치는 ( <?= GameConst::$defaultStatMin ?> <= 능력치 <=<?= GameConst::$defaultStatMax ?> ) 사이로 잡으셔야 합니다.<br>
외의 능력치는 가입되지 않습니다.</font>
</td>
</tr>
<?php if (!$isCentennialAllStar) : ?>
<tr class='custom_stat'>
<td align=right class='bg1'>통솔</td>
<td colspan=2><input type="number" name="leadership" id="leadership" value="50"></td>
</tr>
<tr class='custom_stat'>
<td align=right class='bg1'>무력</td>
<td colspan=2><input type="number" name="strength" id="strength" value="50"></td>
</tr>
<tr class='custom_stat'>
<td align=right class='bg1'>지력</td>
<td colspan=2><input type="number" name="intel" id="intel" value="50"></td>
</tr>
<tr class='custom_stat'>
<td align=right class='bg1'>능력치 조정</td>
<td colspan=2>
<input type=button value=랜덤 onclick=abilityRand()>
<input type=button value=통솔력형 onclick=abilityLeadpow()>
<input type=button value=통솔지력형 onclick=abilityLeadint()>
<input type=button value=무력지력형 onclick=abilityPowint()>
</td>
</tr>
<tr class='custom_stat'>
<td align=center colspan=3>
<font color=orange>모든 능력치는 ( <?= GameConst::$defaultStatMin ?> <= 능력치 <=<?= GameConst::$defaultStatMax ?> ) 사이로 잡으셔야 합니다.<br>
외의 능력치는 가입되지 않습니다.</font>
</td>
</tr>
<?php endif; ?>
<tr>
<td align=center colspan=3>
<span class='custom_stat'>능력치의 총합은 <?= GameConst::$defaultStatTotal ?>입니다. 가입후 0~10의 능력치 보너스를 받게 됩니다.<br></span>
<?php if ($isCentennialAllStar) : ?>
선택한 장수의 최종 능력치 비율을 반영한 약화 능력치로 시작합니다.<br>
<?php else : ?>
<span class='custom_stat'>능력치의 총합은 <?= GameConst::$defaultStatTotal ?>입니다. 가입후 0~10의 능력치 보너스를 받게 됩니다.<br></span>
<?php endif; ?>
임의의 도시에서 재야로 시작하며 건국과 임관은 게임 내에서 실행합니다.
</td>
</tr>
@@ -194,4 +251,4 @@ if ($gencount >= $admin['maxgeneral']) {
</div>
</body>
</html>
</html>
+14
View File
@@ -583,6 +583,20 @@ CREATE TABLE IF NOT EXISTS `nation_env` (
UNIQUE INDEX `key` (`namespace`, `key`),
CONSTRAINT `json` CHECK (json_valid(`value`))
) COLLATE = 'utf8mb4_bin' ENGINE = Aria;
CREATE TABLE IF NOT EXISTS `inheritance_result` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`server_id` CHAR(20) NOT NULL COLLATE 'utf8mb4_general_ci',
`owner` INT(11) NOT NULL,
`general_id` INT(11) NOT NULL,
`year` INT(11) NOT NULL,
`month` INT(11) NOT NULL,
`value` LONGTEXT NOT NULL COLLATE 'utf8mb4_bin',
PRIMARY KEY (`id`),
INDEX `by_owner` (`server_id`, `owner`, `general_id`, `year`, `month`)
)
COLLATE='utf8mb4_general_ci'
ENGINE=Aria
;
##############################
##
+15 -2
View File
@@ -172,7 +172,7 @@
<BButton class="col-6 offset-6" variant="primary" @click="checkOwner"> 소유자 찾기 </BButton>
</div>
</div>
<div class="col col-lg-4 col-sm-6 col-12 py-2">
<div v-if="canResetStat" class="col col-lg-4 col-sm-6 col-12 py-2">
<div class="row px-4">
<div class="a-right col-6 align-self-center">능력치 초기화</div>
<div class="col-6">
@@ -222,6 +222,17 @@
<BButton class="col-6 offset-6" variant="primary" @click="resetStat"> 능력치 초기화</BButton>
</div>
</div>
<div v-else class="col col-lg-4 col-sm-6 col-12 py-2">
<div class="row px-4">
<div class="a-right col-6 align-self-center">능력치 초기화</div>
<div class="col-6 align-self-center">사용 불가</div>
</div>
<div class="a-right">
<small class="form-text text-muted">
100 올스타 장수는 장수 전환 능력치 성장 기록을 보존하기 위해 능력치 초기화를 사용할 없습니다.
</small>
</div>
</div>
</div>
<div class="row">
<div class="col">
@@ -297,6 +308,7 @@ declare const staticValues: {
}
>;
availableTargetGeneral: Record<number, string>;
canResetStat: boolean;
currentStat: {
leadership: number;
strength: number;
@@ -447,6 +459,7 @@ const {
availableSpecialWar,
availableUnique,
availableTargetGeneral,
canResetStat,
currentStat
} = staticValues;
@@ -739,4 +752,4 @@ async function getMoreLog(): Promise<void> {
.tnum {
font-feature-settings: "tnum";
}
</style>
</style>
+1 -1
View File
@@ -510,7 +510,7 @@ async function reloadCommandList() {
const turnTime = parseTime(result.turnTime);
let nextTurnTime = new Date(turnTime);
const autorunLimitYearMonth = result.autorun_limit ?? yearMonth - 1;
const autorunLimitYearMonth = (result.autorun_limit ?? yearMonth) - 1;
const [autorunLimitYear, autorunLimitMonth] = parseYearMonth(autorunLimitYearMonth);
reservedCommandList.value = [];
+3 -3
View File
@@ -1,6 +1,6 @@
import type { AutoLoginFailed, AutoLoginNonceResponse, AutoLoginResponse, LoginFailed, LoginResponse } from "./defs/API/Login";
import { APIPathGen } from "./util/APIPathGen";
import { callSammoAPI, extractHttpMethod, GET, POST, type APICallT, type APITail, type InvalidResponse, type RawArgType, type ValidResponse } from "./util/callSammoAPI";
import { callSammoAPI, extractHttpMethod, POST, type APICallT, type APITail, type InvalidResponse, type RawArgType, type ValidResponse } from "./util/callSammoAPI";
export type { ValidResponse, InvalidResponse };
const apiRealPath = {
@@ -18,7 +18,7 @@ const apiRealPath = {
hashedToken: string,
token_id: number,
}, AutoLoginResponse, AutoLoginFailed>,
ReqNonce: GET as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed>
ReqNonce: POST as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed>
},
} as const;
@@ -30,4 +30,4 @@ export const SammoRootAPI = APIPathGen(apiRealPath, (path: string[], tail: APITa
}
return callSammoAPI(method, path.join('/'), args, pathParam);
};
});
});
+1 -1
View File
@@ -509,7 +509,7 @@ function updateCommandList() {
const turnTime = parseTime(props.turnTime);
let nextTurnTime = new Date(turnTime);
const autorunLimitYearMonth = autorun_limit.value ?? yearMonth - 1;
const autorunLimitYearMonth = (autorun_limit.value ?? yearMonth) - 1;
const [autorunLimitYear, autorunLimitMonth] = parseYearMonth(autorunLimitYearMonth);
for (const obj of props.turn) {
+5 -3
View File
@@ -170,7 +170,7 @@ import { joinYearMonth } from "@/util/joinYearMonth";
import { parseYearMonth } from "@/util/parseYearMonth";
import type { GameConstStore } from "@/GameConstStore";
import { unwrap_err } from "@/util/unwrap_err";
import { getMaxRelativeTechLevel, TECH_LEVEL_YEAR_GAP } from "@/utilGame/techLevel";
import { getMaxRelativeTechLevel } from "@/utilGame/techLevel";
import { deviceType } from "detect-it";
import MapCityBasic from "./MapCityBasic.vue";
import MapCityDetail from "./MapCityDetail.vue";
@@ -289,12 +289,14 @@ const titleTooltip = computed(()=>{
const { startYear, year } = mapData.value;
const maxTechLevel = gameConstStore.value.gameConst.maxTechLevel;
const currentTechLimit = getMaxRelativeTechLevel(startYear, year, maxTechLevel);
const initialAllowedTechLevel = gameConstStore.value.gameConst.initialAllowedTechLevel;
const techLevelIncYear = gameConstStore.value.gameConst.techLevelIncYear;
const currentTechLimit = getMaxRelativeTechLevel(startYear, year, maxTechLevel, initialAllowedTechLevel, techLevelIncYear);
if (currentTechLimit == maxTechLevel) {
result.push(`기술등급 제한 : ${currentTechLimit}등급 (최종)`);
} else {
const nextTechLimitYear = currentTechLimit * TECH_LEVEL_YEAR_GAP + startYear;
const nextTechLimitYear = currentTechLimit * techLevelIncYear + startYear;
result.push(`기술등급 제한 : ${currentTechLimit}등급 (${nextTechLimitYear}년 해제)`);
}
+5 -2
View File
@@ -124,9 +124,12 @@ watch(
(nation) => {
const { startyear, year } = global.value;
console.log(gameConstStore);
maxTechLevel.value = getMaxRelativeTechLevel(startyear, year, gameConstStore.value.gameConst.maxTechLevel);
const initialAllowedTechLevel = gameConstStore.value.gameConst.initialAllowedTechLevel;
const techLevelIncYear = gameConstStore.value.gameConst.techLevelIncYear;
maxTechLevel.value = getMaxRelativeTechLevel(startyear, year, gameConstStore.value.gameConst.maxTechLevel, initialAllowedTechLevel, techLevelIncYear);
currentTechLevel.value = convTechLevel(nation.tech, maxTechLevel.value);
onTechLimit.value = isTechLimited(startyear, year, nation.tech, gameConstStore.value.gameConst.maxTechLevel);
onTechLimit.value = isTechLimited(startyear, year, nation.tech, gameConstStore.value.gameConst.maxTechLevel, initialAllowedTechLevel, techLevelIncYear);
},
{ immediate: true }
);
+3
View File
@@ -57,6 +57,9 @@ export type GameConstType = {
maxTechLevel: number;
maxBetrayCnt: number;
techLevelIncYear: number;
initialAllowedTechLevel: number;
basePopIncreaseAmount: number;
expandCityPopIncreaseAmount: number;
expandCityDevelIncreaseAmount: number;
+70 -55
View File
@@ -37,14 +37,16 @@ function regNextToken(tokenInfo: [number, string]) {
function getToken(): [number, string] | undefined {
const trialToken = localStorage.getItem(LOGIN_TOKEN_KEY);
if (!trialToken) {
console.log('no token');
return;
}
const tokenItems = JSON.parse(trialToken) as [number, [number, string], string];
const tokenItems = JSON.parse(trialToken) as [number, [number, string], number];
if (tokenItems[0] != TOKEN_VERSION) {
console.log(tokenItems);
resetToken();
return;
}
console.debug(localStorage.getItem(LOGIN_TOKEN_KEY));
const [, token,] = tokenItems;
return token;
}
@@ -53,60 +55,73 @@ function resetToken() {
localStorage.removeItem(LOGIN_TOKEN_KEY);
}
async function tryAutoLogin() {
try {
const tokenInfo = getToken();
if (!tokenInfo) {
return;
}
const [tokenID, token] = tokenInfo;
const result = await SammoRootAPI.Login.ReqNonce(undefined, true);
if (!result) {
//api 에러.
return;
}
if (!result.result) {
resetToken();
return;
}
const nonce = result.loginNonce;
const hashedToken = sha512(token + nonce);
const loginResult = await SammoRootAPI.Login.LoginByToken({
'hashedToken': hashedToken,
'token_id': tokenID,
}, true);
if (!loginResult.result) {
if (!loginResult.silent) {
alert(loginResult.reason);
}
console.error(loginResult.reason);
return;
}
if (loginResult.nextToken) {
regNextToken(loginResult.nextToken);
}
window.location.href = "./";
}
catch (e) {
if (isString(e)) {
alert(e);
}
console.error(e);
return;
}
}
async function tryAutoLogin() {
try {
const tokenInfo = getToken();
if (!tokenInfo) {
return;
}
const [tokenID, token] = tokenInfo;
for (let attempt = 0; attempt < 2; attempt++) {
const reqNonceStartAt = Date.now();
const nonceResult = await SammoRootAPI.Login.ReqNonce(undefined, true);
if (!nonceResult) {
//api 에러.
return;
}
if (!nonceResult.result) {
resetToken();
return;
}
const nonce = nonceResult.loginNonce;
console.debug(
'try auto login with token',
tokenID,
`attempt:${attempt + 1}`,
`reqNonceElapsed:${Date.now() - reqNonceStartAt}ms`
);
const hashedToken = sha512(token + nonce);
const loginResult = await SammoRootAPI.Login.LoginByToken({
'hashedToken': hashedToken,
'token_id': tokenID,
}, true);
if (!loginResult.result) {
if (loginResult.reason === '자동 로그인: 절차 오류' && attempt === 0) {
console.warn('auto login failed by procedure error. retrying once.');
await delay(150);
continue;
}
if (!loginResult.silent) {
alert(loginResult.reason);
}
console.error(loginResult.reason);
return;
}
if (loginResult.nextToken) {
regNextToken(loginResult.nextToken);
}
window.location.href = "./";
return;
}
}
catch (e) {
if (isString(e)) {
alert(e);
}
console.error(e);
return;
}
}
function getOAuthToken(mode: string, scope_list?: string[] | string) {
if (mode === undefined) {
mode = 'login';
+59 -31
View File
@@ -10,7 +10,7 @@ import { unwrap } from '@util/unwrap';
import { TemplateEngine } from '@util/TemplateEngine';
import { Tooltip } from 'bootstrap';
import { trim } from 'lodash-es';
type CardItem = {
type CardItem = {
uniqueName: string,
imgsvr: 0|1,
@@ -26,9 +26,18 @@ type CardItem = {
specialWarInfo?: string,
specialWarText?: string,
personal?: string,
personalText?: string,
}
personal?: string,
personalText?: string,
event100Growth?: boolean,
leadership?: number,
strength?: number,
intel?: number,
selectionStatLabel?: string,
selectionLeadership?: number,
selectionStrength?: number,
selectionIntel?: number,
dex?: number[],
}
type GeneralPoolResponse = {
result: true,
@@ -37,17 +46,23 @@ type GeneralPoolResponse = {
}
declare const characterInfo: Record<string, { name: string, info: string }>;
declare const hasGeneralID: number;
declare const hasGeneralID: number;
declare const isCentennialAllStar: boolean;
declare let currentGeneralInfo: CardItem | undefined;
declare const cards: Record<string, CardItem>;
declare const validCustomOption: string[];
const templateGeneralCard = '<div class="general_card">\
<h4 class="bg1 with_border"><%generalName%></h4>\
<h4><img src="<%iconPath%>" height=64 width=64></h4><p>\
<%if(leadership){%>\
<%leadership%> / <%strength%> / <%intel%><br>\
<%}%>\
const templateGeneralCard = '<div class="general_card">\
<h4 class="bg1 with_border"><%generalName%></h4>\
<h4><img src="<%iconPath%>" height=64 width=64></h4><p>\
<%if(event100Growth){%><b> </b><br>· 195 / 205 <br><%}%>\
<%if(leadership){%>\
<%leadership%> / <%strength%> / <%intel%><br>\
<%}%>\
<%if(selectionStatLabel){%>\
<b><%selectionStatLabel%></b><br>\
<%selectionLeadership%> / <%selectionStrength%> / <%selectionIntel%><br>\
<%}%>\
<%if(personalText){%><%personalText%><br><%}%>\
<%if(specialDomesticText||specialWarText){%>\
<%specialDomesticText%> / <%specialWarText%><br>\
@@ -101,9 +116,12 @@ async function pickGeneral(this: HTMLElement, e: JQuery.Event) {
url: 'j_update_picked_general.php',
method: 'post',
responseType: 'json',
data: convertFormData({
pick: unwrap_any<string>($btn.val())
})
data: convertFormData({
pick: unwrap_any<string>($btn.val()),
picture_source: unwrap_any<string>(
$('input[name="reselect_picture_source"]:checked').val() ?? 'selected'
)
})
});
result = response.data;
if (!result.result) {
@@ -132,19 +150,25 @@ async function buildGeneral(e: JQuery.Event) {
let result: InvalidResponse;
try {
const response = await axios({
url: 'j_select_picked_general.php',
method: 'post',
responseType: 'json',
data: convertFormData({
pick: unwrap(currentGeneralInfo).uniqueName,
use_own_picture: $('#use_own_picture').is(':checked'),
leadership: parseInt(unwrap_any<string>($('#leadership').val())),
strength: parseInt(unwrap_any<string>($('#leadership').val())),
intel: parseInt(unwrap_any<string>($('#leadership').val())),
personal: unwrap_any<string>($('#selChar').val())
})
})
const formData: Record<string, string|number|boolean> = {
pick: unwrap(currentGeneralInfo).uniqueName,
use_own_picture: $('#use_own_picture').is(':checked'),
picture_source: unwrap_any<string>(
$('input[name="picture_source"]:checked').val() ?? 'selected'
),
personal: unwrap_any<string>($('#selChar').val())
};
if (!isCentennialAllStar) {
formData.leadership = parseInt(unwrap_any<string>($('#leadership').val()));
formData.strength = parseInt(unwrap_any<string>($('#leadership').val()));
formData.intel = parseInt(unwrap_any<string>($('#leadership').val()));
}
const response = await axios({
url: 'j_select_picked_general.php',
method: 'post',
responseType: 'json',
data: convertFormData(formData)
})
result = response.data;
if (!result.result) {
throw result.reason;
@@ -190,9 +214,13 @@ function printGenerals(value: GeneralPoolResponse) {
const emptyCard = {
'leadership': null,
'strength': null,
'intel': null,
'personalText': null,
'strength': null,
'intel': null,
'selectionStatLabel': null,
'selectionLeadership': null,
'selectionStrength': null,
'selectionIntel': null,
'personalText': null,
'specialDomesticText': null,
'specialWarText': null,
'dex': null
@@ -287,4 +315,4 @@ $(async function ($) {
$('.custom_stat').show();
}
}
});
});
+4 -5
View File
@@ -1,11 +1,10 @@
import { clamp } from "lodash-es";
export const TECH_LEVEL_YEAR_GAP = 5;
export const TECH_LEVEL_STEP = 1000;
export function isTechLimited(startYear: number, year: number, tech: number, maxTechLevel: number): boolean {
const relMaxTech = getMaxRelativeTechLevel(startYear, year, maxTechLevel);
export function isTechLimited(startYear: number, year: number, tech: number, maxTechLevel: number, initialAllowedTechLevel: number, techLevelIncYear: number): boolean {
const relMaxTech = getMaxRelativeTechLevel(startYear, year, maxTechLevel, initialAllowedTechLevel, techLevelIncYear);
const techLevel = convTechLevel(tech, maxTechLevel);
return techLevel >= relMaxTech;
@@ -16,7 +15,7 @@ export function convTechLevel(tech: number, maxTechLevel: number): number{
}
export function getMaxRelativeTechLevel(startYear: number, year: number, maxTechLevel: number): number {
export function getMaxRelativeTechLevel(startYear: number, year: number, maxTechLevel: number, initialAllowedTechLevel: number, techLevelIncYear: number): number {
const relYear = year - startYear;
return clamp(Math.floor(relYear / TECH_LEVEL_YEAR_GAP) + 1, 1, maxTechLevel);
return clamp(Math.floor(relYear / techLevelIncYear) + initialAllowedTechLevel, 1, maxTechLevel);
}
+2 -1
View File
@@ -105,6 +105,7 @@ $lastInheritPointLogs = $db->query('SELECT id, server_id, year, month, date, tex
'availableUnique' => $availableUnique,
'lastInheritPointLogs' => $lastInheritPointLogs,
'availableTargetGeneral' => $availableTargetGeneral,
'canResetStat' => CentennialAllStarGrowthService::isStatResetAllowed(),
'currentStat' => [
'leadership' => Util::clamp($me->getVar('leadership'), GameConst::$defaultStatMin, GameConst::$defaultStatMax),
'strength' => Util::clamp($me->getVar('strength'), GameConst::$defaultStatMin, GameConst::$defaultStatMax),
@@ -120,4 +121,4 @@ $lastInheritPointLogs = $db->query('SELECT id, server_id, year, month, date, tex
<div id="app"></div>
</body>
</html>
</html>
+1 -1
View File
@@ -42,7 +42,7 @@ if($userInfo['delete_after']){
$penaltyList = Json::decode($userInfo['penalty']??'{}');
foreach($penaltyList as $penaltyKey=>$penaltyValue){
if($penaltyValue['expire'] ?? 0 > TimeUtil::now()){
if(($penaltyValue['expire'] ?? 0) > TimeUtil::now()){
Json::die([
'result'=>false,
'reason'=>"징계가 남아있어 탈퇴할 수 없습니다."
+2 -2
View File
@@ -174,7 +174,7 @@ function tryNpmInstall()
tryComposerInstall();
if (tryNpmInstall()) {
genJS(Util::array_last_key(ServConfig::getServerList()));
genJS(array_key_last(ServConfig::getServerList()));
}
$session = Session::requireLogin(null)->setReadOnly();
@@ -227,7 +227,7 @@ if (!$allowFullUpdate || !$target) {
$target = $request['target'];
}
$baseServerName = Util::array_last_key(ServConfig::getServerList());
$baseServerName = array_key_last(ServConfig::getServerList());
if (!$target && $server != $baseServerName) {
Json::die([
+4 -4
View File
@@ -109,16 +109,16 @@ if($expires < $nowDate){
$access_token = $result['access_token'];
$expires = TimeUtil::nowAddSeconds($result['expires_in']);
if(isset($result['refresh_token'])){
$refresh_token = Util::array_get($result['refresh_token']);
$refresh_token = $result['refresh_token'] ?? null;
$refresh_token_expires = TimeUtil::nowAddSeconds($result['refresh_token_expires_in']);
}
}
$signupResult = $restAPI->signup();
$kakaoID = Util::array_get($signupResult['id']);
$kakaoID = $signupResult['id'] ?? null;
if(!$kakaoID && Util::array_get($signupResult['msg'])!='already registered'){
if(!$kakaoID && ($signupResult['msg'] ?? null) !='already registered'){
Json::die([
'result'=>false,
'reason'=>'카카오 로그인 과정 중 앱 연결 절차를 실패했습니다'.Json::encode($signupResult)
@@ -126,7 +126,7 @@ if(!$kakaoID && Util::array_get($signupResult['msg'])!='already registered'){
}
$me = $restAPI->meWithEmail();
$me['code'] = Util::array_get($me['code'], 0);
$me['code'] = $me['code'] ?? 0;
$kakao_account = $me['kakao_account']??[];
if ($me['code']< 0) {
$restAPI->unlink();
+1 -1
View File
@@ -133,7 +133,7 @@ $userInfo = $RootDB->queryFirstRow(
if (!$userInfo) {
if(!$oauthID){
$me = $restAPI->meWithEmail();
if ($me['code']??0 < 0) {
if (($me['code']??0) < 0) {
Json::die([
'result' => false,
'reqOTP' => false,
+1 -1
View File
@@ -5,7 +5,7 @@
"main": "js/index.js",
"scripts": {
"test": "npm-run-all test-php-gateway test-ts",
"test-php-gateway": "vendor/bin/phpunit --bootstrap vendor/autoload.php tests",
"test-php-gateway": "vendor/bin/phpunit --do-not-cache-result --bootstrap vendor/autoload.php tests",
"test-ts": "mocha",
"build": "webpack",
"buildDev": "webpack --mode=development",
+256
View File
@@ -0,0 +1,256 @@
<?php
declare(strict_types=1);
/**
* Build the static 100th-season all-star pool from a tab-separated query result.
*
* Usage:
* mariadb --batch --raw --skip-column-names DATABASE \
* < src/centennial_allstar_candidates.sql \
* | php src/build_centennial_allstar_pool.php OUTPUT.json
*/
const OUTPUT_COLUMNS = [
'generalName',
'leadership',
'strength',
'intel',
'specialDomestic',
'dex',
'imgsvr',
'picture',
'sourcePhase',
'sourceServerId',
'sourceGeneralNo',
'selectionReasons',
];
const EXCLUDED_EVENT_PHASES = [
5, 10, 15, 20, 25, 30, 35, 40, 45, 50,
55, 60, 65, 70, 75, 80, 85, 90, 95,
];
const LEGACY_SPECIAL_WAR_MAP = [
40 => 'che_event_귀병',
41 => 'che_event_신산',
42 => 'che_event_환술',
43 => 'che_event_집중',
44 => 'che_event_신중',
45 => 'che_event_반계',
50 => 'che_event_보병',
51 => 'che_event_궁병',
52 => 'che_event_기병',
53 => 'che_event_공성',
60 => 'che_event_돌격',
61 => 'che_event_무쌍',
62 => 'che_event_견고',
63 => 'che_event_위압',
70 => 'che_event_저격',
71 => 'che_event_필살',
72 => 'che_event_징병',
73 => 'che_event_의술',
74 => 'che_event_격노',
75 => 'che_event_척사',
];
function fail(string $message): never
{
fwrite(STDERR, "error: {$message}\n");
exit(1);
}
function firstInt(array $data, array $keys): int
{
foreach ($keys as $key) {
if (array_key_exists($key, $data) && is_numeric($data[$key])) {
return (int) $data[$key];
}
}
fail('missing numeric field: ' . implode(' or ', $keys));
}
function normalizeEventSpecial(mixed $rawSpecial): ?string
{
if (is_numeric($rawSpecial)) {
return LEGACY_SPECIAL_WAR_MAP[(int) $rawSpecial] ?? null;
}
if (
!is_string($rawSpecial)
|| $rawSpecial === ''
|| $rawSpecial === '0'
|| strcasecmp($rawSpecial, 'none') === 0
) {
return null;
}
if (str_starts_with($rawSpecial, 'che_event_')) {
return $rawSpecial;
}
if (str_starts_with($rawSpecial, 'che_')) {
return 'che_event_' . substr($rawSpecial, strlen('che_'));
}
fail("unknown historical war special: {$rawSpecial}");
}
function decodeSourceRow(string $line, int $lineNo): array
{
$fields = explode("\t", rtrim($line, "\r\n"), 6);
if (count($fields) !== 6) {
fail("line {$lineNo}: expected 6 tab-separated fields, got " . count($fields));
}
[$phase, $serverId, $generalNo, $name, $rawData, $rawReasons] = $fields;
$data = json_decode($rawData, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($data)) {
fail("line {$lineNo}: general data is not an object");
}
$phaseNo = (int) $phase;
$sourceGeneralNo = (int) $generalNo;
if ($phaseNo < 1 || $phaseNo > 99 || $sourceGeneralNo <= 2) {
fail("line {$lineNo}: invalid phase/general number");
}
$sourceName = trim($name);
if ($sourceName === '') {
fail("line {$lineNo}: empty general name");
}
$generalName = sprintf('%d·%s', $phaseNo, $sourceName);
if (mb_strlen($generalName) > 32) {
fail("line {$lineNo}: generated name exceeds 32 characters: {$generalName}");
}
$dex = [
firstInt($data, ['dex1', 'dex0']),
firstInt($data, ['dex2', 'dex10']),
firstInt($data, ['dex3', 'dex20']),
firstInt($data, ['dex4', 'dex30']),
firstInt($data, ['dex5', 'dex40']),
];
foreach ($dex as $value) {
if ($value < 0) {
fail("line {$lineNo}: negative dex value");
}
}
$reasons = $rawReasons === '' ? [] : explode(',', $rawReasons);
sort($reasons, SORT_STRING);
return [
$generalName,
firstInt($data, ['leadership', 'leader']),
firstInt($data, ['strength', 'power']),
firstInt($data, ['intel']),
normalizeEventSpecial($data['special2'] ?? null),
$dex,
firstInt($data, ['imgsvr']),
is_string($data['picture'] ?? null) && $data['picture'] !== ''
? $data['picture']
: 'default.jpg',
$phaseNo,
$serverId,
$sourceGeneralNo,
$reasons,
];
}
if ($argc !== 2) {
fail('usage: php build_centennial_allstar_pool.php OUTPUT.json');
}
$outputPath = $argv[1];
$rows = [];
$seenSources = [];
$nameIndexes = [];
$phaseCounts = [];
$chiefCounts = [];
$reasonCounts = [];
$lineNo = 0;
while (($line = fgets(STDIN)) !== false) {
$lineNo++;
if (trim($line) === '') {
continue;
}
try {
$row = decodeSourceRow($line, $lineNo);
} catch (JsonException $e) {
fail("line {$lineNo}: invalid JSON: {$e->getMessage()}");
}
$sourceKey = "{$row[8]}:{$row[10]}";
if (isset($seenSources[$sourceKey])) {
fail("line {$lineNo}: duplicate source general {$sourceKey}");
}
$seenSources[$sourceKey] = true;
$nameIndexes[$row[0]][] = count($rows);
$phaseCounts[$row[8]] = ($phaseCounts[$row[8]] ?? 0) + 1;
foreach ($row[11] as $reason) {
$reasonGroup = str_starts_with($reason, 'chief:') ? 'chief' : 'hall';
$reasonCounts[$reasonGroup] = ($reasonCounts[$reasonGroup] ?? 0) + 1;
if ($reasonGroup === 'chief') {
$chiefCounts[$row[8]] = ($chiefCounts[$row[8]] ?? 0) + 1;
}
}
$rows[] = $row;
}
if ($rows === []) {
fail('no input rows');
}
foreach ($nameIndexes as $generalName => $indexes) {
if (count($indexes) === 1) {
continue;
}
foreach ($indexes as $index) {
$rows[$index][0] .= "#{$rows[$index][10]}";
if (mb_strlen($rows[$index][0]) > 32) {
fail("disambiguated name exceeds 32 characters: {$rows[$index][0]}");
}
}
}
ksort($phaseCounts, SORT_NUMERIC);
$expectedPhases = array_values(array_diff(range(1, 99), EXCLUDED_EVENT_PHASES));
if (array_keys($phaseCounts) !== $expectedPhases) {
fail('input does not cover every non-event phase from 1 through 99');
}
foreach ($expectedPhases as $phase) {
if (($chiefCounts[$phase] ?? 0) !== 8) {
fail("phase {$phase}: expected 8 unification chiefs including the ruler");
}
}
$payload = [
'excludedEventPhases' => EXCLUDED_EVENT_PHASES,
'columns' => OUTPUT_COLUMNS,
'data' => $rows,
];
$encoded = json_encode(
$payload,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
) . "\n";
$outputDir = dirname($outputPath);
if (!is_dir($outputDir)) {
fail("output directory does not exist: {$outputDir}");
}
$tempPath = tempnam($outputDir, basename($outputPath) . '.tmp.');
if ($tempPath === false) {
fail("could not create temporary output in {$outputDir}");
}
if (file_put_contents($tempPath, $encoded) === false || !rename($tempPath, $outputPath)) {
@unlink($tempPath);
fail("could not write output: {$outputPath}");
}
fwrite(
STDERR,
sprintf(
"wrote %d candidates across phases %d-%d (%d hall reasons, %d chief reasons)\n",
count($rows),
min(array_keys($phaseCounts)),
max(array_keys($phaseCounts)),
$reasonCounts['hall'] ?? 0,
$reasonCounts['chief'] ?? 0,
)
);
+131
View File
@@ -0,0 +1,131 @@
WITH
phases AS (
SELECT
e.no AS phase_no,
e.server_id,
g.winner_nation,
e.l12name,
e.l12pic,
e.l11name,
e.l11pic,
e.l10name,
e.l10pic,
e.l9name,
e.l9pic,
e.l8name,
e.l8pic,
e.l7name,
e.l7pic,
e.l6name,
e.l6pic,
e.l5name,
e.l5pic
FROM emperior e
LEFT JOIN ng_games g ON g.server_id = e.server_id
WHERE e.no BETWEEN 1 AND 99
AND MOD(e.no, 5) <> 0
),
hall_eligible AS (
SELECT
p.phase_no,
h.server_id,
h.general_no,
h.type,
h.value,
h.id
FROM phases p
JOIN hall h ON h.server_id = p.server_id
JOIN ng_old_generals og
ON og.server_id = h.server_id
AND og.general_no = h.general_no
WHERE h.general_no > 2
AND CAST(COALESCE(JSON_VALUE(og.data, '$.npc'), 0) AS SIGNED) = 0
),
hall_ranked AS (
SELECT
phase_no,
server_id,
general_no,
type,
ROW_NUMBER() OVER (
PARTITION BY server_id, type
ORDER BY value DESC, id ASC
) AS hall_rank
FROM hall_eligible
),
selection_reasons AS (
SELECT
phase_no,
server_id,
general_no,
CONCAT('hall:', type) AS reason
FROM hall_ranked
WHERE hall_rank <= 10
),
chief_slots AS (
SELECT phase_no, server_id, winner_nation, 12 AS officer_level, l12name AS name, l12pic AS picture FROM phases
UNION ALL
SELECT phase_no, server_id, winner_nation, 11, l11name, l11pic FROM phases
UNION ALL
SELECT phase_no, server_id, winner_nation, 10, l10name, l10pic FROM phases
UNION ALL
SELECT phase_no, server_id, winner_nation, 9, l9name, l9pic FROM phases
UNION ALL
SELECT phase_no, server_id, winner_nation, 8, l8name, l8pic FROM phases
UNION ALL
SELECT phase_no, server_id, winner_nation, 7, l7name, l7pic FROM phases
UNION ALL
SELECT phase_no, server_id, winner_nation, 6, l6name, l6pic FROM phases
UNION ALL
SELECT phase_no, server_id, winner_nation, 5, l5name, l5pic FROM phases
),
chief_reasons AS (
SELECT
c.phase_no,
c.server_id,
og.general_no,
CONCAT('chief:', c.officer_level) AS reason
FROM chief_slots c
JOIN ng_old_generals og
ON og.server_id = c.server_id
AND og.name = c.name
AND SUBSTRING_INDEX(
COALESCE(JSON_VALUE(og.data, '$.picture'), ''),
'?=',
1
) = SUBSTRING_INDEX(COALESCE(c.picture, ''), '?=', 1)
AND (
CAST(COALESCE(JSON_VALUE(og.data, '$.officer_level'), -1) AS SIGNED) = c.officer_level
OR (
JSON_VALUE(og.data, '$.officer_level') IS NULL
AND (
c.winner_nation IS NULL
OR CAST(JSON_VALUE(og.data, '$.nation') AS SIGNED) = c.winner_nation
)
)
)
WHERE og.general_no > 2
),
all_reasons AS (
SELECT phase_no, server_id, general_no, reason FROM selection_reasons
UNION ALL
SELECT phase_no, server_id, general_no, reason FROM chief_reasons
)
SELECT
r.phase_no,
r.server_id,
r.general_no,
og.name,
og.data,
GROUP_CONCAT(DISTINCT r.reason ORDER BY r.reason SEPARATOR ',') AS reasons
FROM all_reasons r
JOIN ng_old_generals og
ON og.server_id = r.server_id
AND og.general_no = r.general_no
GROUP BY
r.phase_no,
r.server_id,
r.general_no,
og.name,
og.data
ORDER BY r.phase_no, r.general_no;
+440
View File
@@ -0,0 +1,440 @@
#!/usr/bin/env node
/* eslint-disable no-console */
import fs from "node:fs/promises";
import path from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
//const __dirname = path.dirname(__filename);
// ============ 환경설정 ============
const REQUIRE_PUBLIC = (process.env.REQUIRE_PUBLIC ?? "true").toLowerCase() === "true";
const FETCH_TIMEOUT_MS = nenv("FETCH_TIMEOUT_MS", 60_000);
const IF_LOCKED_WAIT_MS = nenv("IF_LOCKED_WAIT_MS", 10_000);
const IF_UPDATED_WAIT_MS = nenv("IF_UPDATED_WAIT_MS", 1_000);
const DEFAULT_WAIT_MS = nenv("DEFAULT_WAIT_MS", 8_000);
const BASE_SCAN_DEPTH_ONE = (process.env.BASE_SCAN_DEPTH_ONE ?? "true").toLowerCase() === "true";
const RESCAN_INTERVAL_MS = nenv("RESCAN_INTERVAL_MS", 300_000); // 5분
const SHUTDOWN_TIMEOUT_MS = nenv("SHUTDOWN_TIMEOUT_MS", 15_000);
const AUTO_RESET_JITTER_MS = nenv("AUTO_RESET_JITTER_MS", 0); // 0초 직후 지연(밀리초)
function nenv(key: string, def: number): number {
const v = process.env[key];
if (!v) return def;
const num = Number(v);
return Number.isFinite(num) ? num : def;
}
// ============ 유틸 ============
type Json = Record<string, unknown>;
type ProcResult = {
result: boolean;
updated: boolean;
locked: boolean;
lastExecuted?: string;
};
class CookieJar {
#cookies = new Map<string, string>();
getCookieHeader(): string | undefined {
if (this.#cookies.size === 0) {
return undefined;
}
return [...this.#cookies.entries()].map(([key, value]) => `${key}=${value}`).join("; ");
}
ingestSetCookie(setCookieHeaders: string[]): void {
for (const setCookie of setCookieHeaders) {
const cookiePart = setCookie.split(";", 1)[0]?.trim();
if (!cookiePart) {
continue;
}
const eqPos = cookiePart.indexOf("=");
if (eqPos <= 0) {
continue;
}
const key = cookiePart.slice(0, eqPos).trim();
const value = cookiePart.slice(eqPos + 1).trim();
if (!key) {
continue;
}
if (value === "") {
this.#cookies.delete(key);
continue;
}
this.#cookies.set(key, value);
}
}
}
function getSetCookieHeaders(headers: Headers): string[] {
const headerObj = headers as unknown as { getSetCookie?: () => string[] };
if (typeof headerObj.getSetCookie === "function") {
return headerObj.getSetCookie();
}
const fallback = headers.get("set-cookie");
if (!fallback) {
return [];
}
return [fallback];
}
function nowStr() {
const d = new Date();
const pad = (n: number) => n.toString().padStart(2, "0");
const ms = d.getMilliseconds().toString().padStart(3, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${ms}`;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function log(...args: any[]) { console.log(nowStr(), ...args); }
async function fileExists(fp: string): Promise<boolean> {
try { await fs.access(fp, fs.constants.F_OK); return true; } catch { return false; }
}
async function readFileSafe(fp: string): Promise<string | null> {
try { return await fs.readFile(fp, "utf8"); } catch { return null; }
}
async function listDirsOnceAsync(base: string): Promise<string[]> {
const out: string[] = [];
const names = await fs.readdir(base);
for (const name of names) {
const p = path.join(base, name);
try { if ((await fs.stat(p)).isDirectory()) out.push(p); } catch { /* empty */ }
}
return out;
}
function joinUrl(base: string, suffix: string): string {
if (base.endsWith("/") && suffix.startsWith("/")) return base + suffix.slice(1);
if (!base.endsWith("/") && !suffix.startsWith("/")) return base + "/" + suffix;
return base + suffix;
}
// 분 0초 정렬 대기 시간 계산: 지금 시각에서 다음 분의 0초(+지터)까지 남은 ms
function msUntilNextMinuteZero(jitterMs = 0): number {
const now = Date.now();
const nextMinuteStart = Math.ceil(now / 60_000) * 60_000;
const target = nextMinuteStart + Math.max(0, jitterMs);
return Math.max(0, target - now);
}
// ============ 경로/루트 URL ============
const basepath = path.dirname(path.dirname(path.resolve(__filename)));
async function parseWebBaseFromCommonPath(base: string): Promise<string> {
const jsPath = path.join(base, "d_shared", "common_path.js");
const text = await readFileSafe(jsPath);
if (!text) { log("[WARN] common_path.js 읽기 실패 → fallback=http://127.0.0.1"); return "http://127.0.0.1"; }
for (const line of text.split(/\r?\n/)) {
if (line.includes("root:")) {
let v = line.slice(line.indexOf("root:") + 5).trim();
v = v.replace(/^[\s,'"`]+/, "").replace(/[\s,'"`,]+$/, "");
if (v) return v;
}
}
log("[WARN] root: 항목 없음 → fallback=http://127.0.0.1");
return "http://127.0.0.1";
}
const webBase = process.env.PUBLIC_CHECK_URL || await parseWebBaseFromCommonPath(basepath);
log("BASEPATH =", basepath);
log("WEBBASE =", webBase);
// ============ 서버 모델 ============
type ServerEntry = {
name: string; // servRelPath
abs: string; // 절대 경로
isHidden: () => Promise<boolean>; // .htaccess 존재 여부
procUrl: string;
autoresetUrl: string;
};
async function scanOnce(): Promise<ServerEntry[]> {
const dirs = await listDirsOnceAsync(basepath); // 깊이는 1단계만
const out: ServerEntry[] = [];
for (const dir of dirs) {
if (BASE_SCAN_DEPTH_ONE === false) {
// 필요하면 확장 로직 배치
}
const dbphp = path.join(dir, "d_setting", "DB.php");
if (!await fileExists(dbphp)) continue;
const name = path.relative(basepath, dir);
const isHidden = () => fileExists(path.join(dir, ".htaccess"));
out.push({
name,
abs: dir,
isHidden,
procUrl: joinUrl(webBase, `/${name}/proc.php`),
autoresetUrl:joinUrl(webBase, `/${name}/j_autoreset.php`),
});
}
return out;
}
// ============ 공개 접근 체크 ============
async function isPublicReachable(signal?: AbortSignal, cookieJar?: CookieJar): Promise<boolean> {
if (!REQUIRE_PUBLIC) return true;
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), Math.min(5_000, FETCH_TIMEOUT_MS));
const composite = anySignal([ctrl.signal, signal].filter(Boolean) as AbortSignal[]);
try {
const cookieHeader = cookieJar?.getCookieHeader();
const res = await fetch(webBase, {
method: "GET",
signal: composite,
headers: cookieHeader ? { cookie: cookieHeader } : undefined,
});
cookieJar?.ingestSetCookie(getSetCookieHeaders(res.headers));
if (!res.ok) { log(`[PUBLIC] 응답 ${res.status} → 비공개로 간주`); return false; }
return true;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
if (e?.name !== "AbortError") log(`[PUBLIC] 체크 실패: ${e?.name || e}`);
return false;
} finally {
clearTimeout(timer);
}
}
// ============ HTTP ============
async function httpGetJson<T extends Json>(url: string, outerSignal?: AbortSignal, cookieJar?: CookieJar): Promise<T | null> {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
const signal = anySignal([ctrl.signal, outerSignal].filter(Boolean) as AbortSignal[]);
const t0 = performance.now();
try {
const cookieHeader = cookieJar?.getCookieHeader();
const res = await fetch(url, {
signal,
cache: "no-store",
headers: cookieHeader ? { cookie: cookieHeader } : undefined,
});
cookieJar?.ingestSetCookie(getSetCookieHeaders(res.headers));
const dt = Math.round(performance.now() - t0);
if (!res.ok) { log("HTTPError:", res.status, url); return null; }
try {
const data = (await res.json()) as T;
log(url, `${dt}ms`);
return data;
} catch {
await res.arrayBuffer().catch(() => { /* empty */ });
log("ParseError: JSON 변환 실패", url);
return null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
if (e?.name !== "AbortError") log(e?.name || "Exception", e?.message || String(e), url);
return null;
} finally {
clearTimeout(timer);
}
}
function anySignal(signals: AbortSignal[]): AbortSignal {
if (signals.length === 1) return signals[0];
const ctrl = new AbortController();
for (const s of signals) {
if (s.aborted) { ctrl.abort(); break; }
s.addEventListener("abort", () => ctrl.abort(), { once: true });
}
return ctrl.signal;
}
// ============ 서버 러너 ============
class ServerRunner {
#entry: ServerEntry;
#task?: Promise<void>;
#stopCtrl = new AbortController();
#stopped = false;
#lastAutoResetAt = 0;
#cookieJar = new CookieJar();
constructor(entry: ServerEntry) {
this.#entry = entry;
}
name() { return this.#entry.name; }
start() {
if (!this.#task) {
log(`[${this.name()}] 루프 시작`);
this.#task = this.#run();
}
return this.#task;
}
async stop(): Promise<void> {
if (this.#stopped) return;
this.#stopped = true;
this.#stopCtrl.abort();
try { await this.#task; } catch { /* empty */ }
log(`[${this.name()}] 루프 종료`);
}
async #run() {
while (!this.#stopCtrl.signal.aborted) {
try {
// 공개 접근 체크
const pub = await isPublicReachable(this.#stopCtrl.signal, publicCookieJar);
if (!pub) { await delay(15_000, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ }); continue; }
const hidden = await this.#entry.isHidden();
if (hidden) {
// ★ 매 분 0초에 맞춰 호출
const waitMs = msUntilNextMinuteZero(AUTO_RESET_JITTER_MS);
if (waitMs > 0) {
// 신호 안전하게 대기
await delay(waitMs, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
if (this.#stopCtrl.signal.aborted) break;
}
log(`[${this.name()}] 닫힘 - (정렬) autoreset 호출 @ mm:00${AUTO_RESET_JITTER_MS ? `+${AUTO_RESET_JITTER_MS}ms` : ""}`);
await httpGetJson<Json>(this.#entry.autoresetUrl, this.#stopCtrl.signal, this.#cookieJar);
// 같은 분에 중복 호출 방지: 0초 직후 잠깐 쉬고 다음 루프는 자연스럽게 다음 분으로 정렬됨
await delay(200, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
continue;
}
// 열림: proc
const data = await httpGetJson<ProcResult>(this.#entry.procUrl, this.#stopCtrl.signal, this.#cookieJar);
if (!data) {
await delay(IF_LOCKED_WAIT_MS, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
continue;
}
if (data.result === false || data.locked === true) {
log(`[${this.name()}] result=${data.result} locked=${data.locked}${IF_LOCKED_WAIT_MS}ms 대기`);
await delay(IF_LOCKED_WAIT_MS, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
continue;
}
if (data.updated === true) {
log(`[${this.name()}] updated=true → ${IF_UPDATED_WAIT_MS}ms 후 재호출 (last=${data.lastExecuted ?? "-"})`);
await delay(IF_UPDATED_WAIT_MS, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
continue;
}
await delay(DEFAULT_WAIT_MS, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
if (this.#stopCtrl.signal.aborted) break;
log(`[${this.name()}] 루프 예외:`, e?.message || String(e));
await delay(5_000, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
}
}
}
}
// ============ 매니저: 재스캔 & 종료 ============
class DaemonManager {
#runners = new Map<string, ServerRunner>();
#rescanCtrl = new AbortController();
async start() {
log("턴 데몬 시작");
await this.#rescanOnce(); // 초기 스캔
this.#scheduleRescan(); // 주기적 재스캔
}
async shutdown(timeoutMs = SHUTDOWN_TIMEOUT_MS) {
log("[SHUTDOWN] 신호 수신 → 종료 절차 시작");
this.#rescanCtrl.abort();
const stops: Promise<void>[] = [];
for (const r of this.#runners.values()) stops.push(r.stop());
// 타임아웃 대기
const done = Promise.allSettled(stops);
const timeout = delay(timeoutMs).then(() => { throw new Error("Shutdown timeout"); });
try { await Promise.race([done, timeout]); }
catch { log("[SHUTDOWN] 타임아웃 도달 → 강제 종료"); }
log("[SHUTDOWN] 종료 완료");
}
#scheduleRescan() {
// 반복 타이머(AbortSignal과 함께)
const loop = async () => {
while (!this.#rescanCtrl.signal.aborted) {
await delay(RESCAN_INTERVAL_MS, undefined, { signal: this.#rescanCtrl.signal }).catch(() => { /* empty */ });
if (this.#rescanCtrl.signal.aborted) break;
try { await this.#rescanOnce(); }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
catch (e: any) { log("[RESCAN] 예외:", e?.message || String(e)); }
}
};
loop().catch(() => { /* empty */ });
}
async #rescanOnce() {
const entries = await scanOnce();
log(`[RESCAN] 서버 ${entries.length}개 발견`);
// 추가/갱신/삭제 판정
const seen = new Set<string>();
for (const e of entries) {
seen.add(e.name);
if (!this.#runners.has(e.name)) {
// 신규 서버
const runner = new ServerRunner(e);
this.#runners.set(e.name, runner);
runner.start().catch(() => { /* empty */ });
log(`[RESCAN] 추가: ${e.name}`);
} else {
// 이미 러너 있음 → 경로/URL 변경 가능성 거의 없지만, 필요시 갱신 로직 여기에
}
}
// 사라진 서버 중지
for (const name of Array.from(this.#runners.keys())) {
if (!seen.has(name)) {
log(`[RESCAN] 제거: ${name}`);
const r = this.#runners.get(name);
if (!r) continue;
this.#runners.delete(name);
r.stop().catch(() => { /* empty */ });
}
}
}
}
// ============ 메인 ============
const manager = new DaemonManager();
const publicCookieJar = new CookieJar();
async function main() {
// 신호 처리: SIGINT/SIGTERM → graceful shutdown
const onSignal = async (sig: NodeJS.Signals) => {
log(`[SIGNAL] ${sig} 수신`);
// 중복 호출 방지
// eslint-disable-next-line @typescript-eslint/no-explicit-any
process.off("SIGINT", onSignal as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
process.off("SIGTERM", onSignal as any);
await manager.shutdown().catch(() => { /* empty */ });
process.exit(0);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
process.on("SIGINT", onSignal as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
process.on("SIGTERM", onSignal as any);
await manager.start();
}
main().catch((e) => {
log("치명적 예외:", e);
process.exitCode = 1;
});
+11 -2
View File
@@ -21,8 +21,17 @@ def run(webPath):
print(now.strftime("%Y-%m-%d %H:%M:%S"), webPath, flush=True)
startTime = getCurrentMillisecTime()
obj = urllib.request.urlopen(webPath)
obj.read()
try:
obj = urllib.request.urlopen(webPath, timeout=60)
obj.read()
except urllib.error.HTTPError as e:
print('HTTPError:', e.code, webPath)
except urllib.error.URLError as e:
print('URLError:', e.reason, webPath)
except Exception as e:
print('Exception:', str(e), webPath)
else:
pass
timeGap = getCurrentMillisecTime() - startTime
if timeGap < RETRY_SEC * 1000:
+2
View File
@@ -13,6 +13,8 @@ use sammo\TimeUtil;
use sammo\Util;
use sammo\Validator;
use function sammo\logError;
class LoginByToken extends LoginByID
{
static array $sensitiveArgs = ['hashedToken'];
+1 -1
View File
@@ -73,7 +73,7 @@ class APIHelper
$logPath = "{$rootPath}/d_log/{$realYearMonth}_api_log.db";
}
$logDB = FileDB::db($logPath, __DIR__ . '/../../f_install/sql/api_log.sql');
$ip = $_SERVER['REMOTE_ADDR'] ?? 'local';
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'local';
$date = date('Y-m-d H:i:s');
//NOTE: array_merge([], {})의 상황이 가능함.
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace sammo;
final class CentennialAllStarGrowth
{
public const DEFAULT_GROWTH_YEARS = 15;
public static function progress(
int $startYear,
int $year,
int $month,
int $growthYears = self::DEFAULT_GROWTH_YEARS
): float {
if ($growthYears <= 0) {
throw new \InvalidArgumentException('growthYears must be positive');
}
if ($month < 1 || $month > 12) {
throw new \InvalidArgumentException('month must be between 1 and 12');
}
$elapsedMonths = max(0, ($year - $startYear) * 12 + $month - 1);
return min(1.0, $elapsedMonths / ($growthYears * 12));
}
public static function statFloor(int $target, int $minimum, float $progress): int
{
$progress = self::clampProgress($progress);
if ($target <= $minimum) {
return $target;
}
return min($target, (int) floor($minimum + ($target - $minimum) * $progress));
}
public static function dexFloor(int $target, float $progress): int
{
$progress = self::clampProgress($progress);
return min($target, (int) floor($target * $progress * $progress));
}
/**
* @return array{value:int, granted:int, delta:int}
*/
public static function advance(int $current, int $granted, int $floor): array
{
$delta = max(0, $floor - $current);
return [
'value' => $current + $delta,
'granted' => max(0, $granted) + $delta,
'delta' => $delta,
];
}
/**
* @return array{value:int, granted:int, organic:int}
*/
public static function replaceTarget(int $current, int $oldGranted, int $newFloor): array
{
$organic = max(0, $current - max(0, $oldGranted));
$value = max($organic, $newFloor);
return [
'value' => $value,
'granted' => $value - $organic,
'organic' => $organic,
];
}
public static function recordableValue(int $current, int $granted): int
{
return max(0, $current - max(0, $granted));
}
private static function clampProgress(float $progress): float
{
return max(0.0, min(1.0, $progress));
}
}
+4 -4
View File
@@ -88,9 +88,9 @@ class Setting
$tail = new FileTail($this->versionFile);
$version = 'noVersionJson';
foreach ($tail->smart(5, 100, true) as $line) {
if (Util::starts_with($line, '//{')) {
if (str_starts_with($line, '//{')) {
$version = Json::decode(substr($line, 2));
$version = Util::array_get($version['version'], 'noVersionValue');
$version = $version['version'] ?? 'noVersionValue';
break;
}
}
@@ -106,8 +106,8 @@ class Setting
$templates = new \League\Plates\Engine(__DIR__.'/templates');
//TODO: .htaccess가 서버 오픈에도 사용될 수 있으니 별도의 방법이 필요함
$allow_ip = Util::get_client_ip(false);
if (Util::starts_with($allow_ip, '192.168.') ||
Util::starts_with($allow_ip, '10.')) {
if (str_starts_with($allow_ip, '192.168.') ||
str_starts_with($allow_ip, '10.')) {
//172.16~172.31은 코딩하기 귀찮으니까 안할거다
$allow_ip = Util::get_client_ip(true);
}
+89 -1
View File
@@ -3,7 +3,7 @@ namespace sammo;
use Iterator;
class Util extends \utilphp\util
class Util
{
/**
@@ -889,4 +889,92 @@ class Util extends \utilphp\util
}
return join('|', $result);
}
/**
* http://github.com/brandonwamboldt/utilphp/ 구버전 코드에서 이식함
* 일부 코드는 수정
* 해당 라이선스: MIT
*/
/**
* Returns the first element in an array.
* PHP 8.5부터 array_first 사용 가능
*
* @param array $array
* @return mixed
*/
public static function array_first(array $array)
{
return $array[array_key_first($array)];
}
/**
* Returns the last element in an array.
* PHP 8.5부터 array_last 사용 가능
*
* @param array $array
* @return mixed
*/
public static function array_last(array $array)
{
return $array[array_key_last($array)];
}
/**
* Flatten a multi-dimensional array into a one dimensional array.
*
* Contributed by Theodore R. Smith of PHP Experts, Inc. <http://www.phpexperts.pro/>
*
* @param array $array The array to flatten
* @param boolean $preserve_keys Whether or not to preserve array keys.
* Keys from deeply nested arrays will
* overwrite keys from shallowy nested arrays
* @return array
*/
public static function array_flatten(array $array, $preserve_keys = true)
{
$flattened = array();
array_walk_recursive($array, function($value, $key) use (&$flattened, $preserve_keys) {
if ($preserve_keys && !is_int($key)) {
$flattened[$key] = $value;
} else {
$flattened[] = $value;
}
});
return $flattened;
}
public static function get_client_ip($trust_proxy_headers = false)
{
if (!$trust_proxy_headers) {
return $_SERVER['REMOTE_ADDR'];
}
return $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'local';
}
/**
* Access an array index, retrieving the value stored there if it
* exists or a default if it does not. This function allows you to
* concisely access an index which may or may not exist without
* raising a warning.
*
* @deprecated PHP 7.0 이상에서는 null 병합 연산자 사용을 권장
*
* @param array $var Array value to access
* @param mixed $default Default value to return if the key is not
* present in the array
* @return mixed
*/
public static function array_get(&$var, $default = null)
{
if (isset($var)) {
return $var;
}
return $default;
}
};
+1209
View File
@@ -0,0 +1,1209 @@
<?php
use PHPUnit\Framework\TestCase;
use sammo\CentennialAllStarGrowth;
use sammo\CentennialAllStarGrowthService;
use sammo\General;
use sammo\GameConst;
$loader = require __DIR__ . '/../vendor/autoload.php';
$loader->addPsr4('sammo\\', __DIR__ . '/../hwe/sammo', true);
require_once __DIR__ . '/../hwe/sammo/ActionLogger.php';
require_once __DIR__ . '/../hwe/sammo/GameConstBase.php';
require_once __DIR__ . '/../hwe/d_setting/GameConst.php';
require_once __DIR__ . '/../hwe/sammo/CentennialAllStarGrowthService.php';
final class CentennialAllStarGrowthTest extends TestCase
{
public function testProgressMilestones(): void
{
self::assertSame(0.0, CentennialAllStarGrowth::progress(180, 180, 1));
self::assertEqualsWithDelta(0.2, CentennialAllStarGrowth::progress(180, 183, 1), 0.000001);
self::assertEqualsWithDelta(0.4, CentennialAllStarGrowth::progress(180, 186, 1), 0.000001);
self::assertSame(1.0, CentennialAllStarGrowth::progress(180, 195, 1));
self::assertSame(1.0, CentennialAllStarGrowth::progress(180, 210, 1));
}
public function testStatResetIsDisabledOnlyForCentennialAllStarPool(): void
{
$previousPool = GameConst::$targetGeneralPool;
try {
GameConst::$targetGeneralPool = CentennialAllStarGrowthService::POOL_CLASS;
self::assertFalse(CentennialAllStarGrowthService::isStatResetAllowed());
GameConst::$targetGeneralPool = 'RandomNameGeneral';
self::assertTrue(CentennialAllStarGrowthService::isStatResetAllowed());
} finally {
GameConst::$targetGeneralPool = $previousPool;
}
}
public function testMAndGGeneralsAdvanceAtNinetyPercentProgress(): void
{
self::assertSame(
CentennialAllStarGrowthService::NPC_PROGRESS_MULTIPLIER,
CentennialAllStarGrowthService::progressMultiplierForNPCType(3)
);
self::assertSame(
CentennialAllStarGrowthService::NPC_PROGRESS_MULTIPLIER,
CentennialAllStarGrowthService::progressMultiplierForNPCType(4)
);
self::assertSame(1.0, CentennialAllStarGrowthService::progressMultiplierForNPCType(2));
self::assertEqualsWithDelta(
0.36,
CentennialAllStarGrowthService::calculateProgress(180, 186, 1, 0.9),
0.000001
);
self::assertEqualsWithDelta(
0.9,
CentennialAllStarGrowthService::calculateProgress(180, 195, 1, 0.9),
0.000001
);
self::assertEqualsWithDelta(
0.9,
CentennialAllStarGrowthService::calculateProgress(180, 210, 1, 0.9),
0.000001
);
self::assertSame(
91,
CentennialAllStarGrowth::statFloor(100, 15, 0.9)
);
self::assertSame(
0.4,
CentennialAllStarGrowthService::dexTargetRatioForNPCType(3)
);
self::assertSame(
0.4,
CentennialAllStarGrowthService::dexTargetRatioForNPCType(4)
);
self::assertSame(
1.0,
CentennialAllStarGrowthService::dexTargetRatioForNPCType(2)
);
}
public function testGeneratedNpcKeepsOrdinaryTotalAndUsesCreationDateTargetFloor(): void
{
$vars = [
'leadership' => 72,
'strength' => 66,
'intel' => 12,
'dex1' => 120000,
'dex2' => 240000,
'dex3' => 360000,
'dex4' => 480000,
'dex5' => 600000,
'special' => 'None',
];
$target = [
'uniqueName' => 'A1000001',
'leadership' => 100,
'strength' => 80,
'intel' => 10,
'dex' => [900000, 800000, 700000, 600000, 500000],
];
$aux = CentennialAllStarGrowthService::initialAux($target);
$general = $this->createStateGeneralMock($vars, $aux);
CentennialAllStarGrowthService::initializeGeneratedNPC($general, $target);
CentennialAllStarGrowthService::applyTarget(
$general,
$target,
['startyear' => 180, 'year' => 195, 'month' => 1],
CentennialAllStarGrowthService::NPC_PROGRESS_MULTIPLIER,
GameConst::$centennialNpcDexTargetRatio
);
self::assertSame(91, $vars['leadership']);
self::assertSame(73, $vars['strength']);
self::assertSame(12, $vars['intel']);
self::assertSame(176, $vars['leadership'] + $vars['strength'] + $vars['intel']);
self::assertSame(
[360000, 320000, 280000, 240000, 200000],
$this->dexValues($vars)
);
self::assertSame(19, $aux['granted']['leadership']);
self::assertSame(7, $aux['granted']['strength']);
self::assertSame(0, $aux['granted']['intel']);
self::assertSame(360000, $aux['granted']['dex1']);
}
public function testFernandoAndUmaMusumeStartAtOrdinaryNpcTotal(): void
{
$pool = json_decode(
file_get_contents(__DIR__ . '/../hwe/sammo/GeneralPool/Pool/UnderS100.json'),
true,
512,
JSON_THROW_ON_ERROR
);
$columns = array_flip($pool['columns']);
$targets = [];
foreach ($pool['data'] as $row) {
$name = $row[$columns['generalName']];
if (!in_array($name, ['43·페르난도', '47·우마무스메'], true)) {
continue;
}
$targets[$name] = [
'leadership' => $row[$columns['leadership']],
'strength' => $row[$columns['strength']],
'intel' => $row[$columns['intel']],
];
}
self::assertCount(2, $targets);
$generated = [
'leadership' => 73,
'strength' => 10,
'intel' => 67,
];
$fernando = CentennialAllStarGrowthService::calculateGeneratedNPCInitialStats(
$targets['43·페르난도'],
$generated
);
$umaMusume = CentennialAllStarGrowthService::calculateGeneratedNPCInitialStats(
$targets['47·우마무스메'],
$generated
);
self::assertSame([
'leadership' => 67,
'strength' => 73,
'intel' => 10,
], $fernando);
self::assertSame([
'leadership' => 10,
'strength' => 67,
'intel' => 73,
], $umaMusume);
self::assertSame(GameConst::$defaultStatNPCTotal, array_sum($fernando));
self::assertSame(GameConst::$defaultStatNPCTotal, array_sum($umaMusume));
}
public function testEveryCandidateKeepsTheGeneratedNpcStatTotal(): void
{
$pool = json_decode(
file_get_contents(__DIR__ . '/../hwe/sammo/GeneralPool/Pool/UnderS100.json'),
true,
512,
JSON_THROW_ON_ERROR
);
$columns = array_flip($pool['columns']);
$generated = [
'leadership' => 73,
'strength' => 67,
'intel' => 10,
];
foreach ($pool['data'] as $row) {
$target = [
'leadership' => $row[$columns['leadership']],
'strength' => $row[$columns['strength']],
'intel' => $row[$columns['intel']],
];
$initial = CentennialAllStarGrowthService::calculateGeneratedNPCInitialStats(
$target,
$generated
);
self::assertSame(
GameConst::$defaultStatNPCTotal,
array_sum($initial),
$row[$columns['generalName']]
);
sort($initial, SORT_NUMERIC);
self::assertSame([10, 67, 73], array_values($initial));
}
}
public function testProgressMultiplierMustStayWithinUnitInterval(): void
{
$this->expectException(InvalidArgumentException::class);
CentennialAllStarGrowthService::calculateProgress(180, 195, 1, 1.01);
}
public function testLowStatGrowsWhileHigherStatStays(): void
{
$floor = CentennialAllStarGrowth::statFloor(90, 15, 0.6);
self::assertSame(60, $floor);
self::assertSame(
['value' => 70, 'granted' => 0, 'delta' => 0],
CentennialAllStarGrowth::advance(70, 0, $floor)
);
self::assertSame(
['value' => 60, 'granted' => 10, 'delta' => 10],
CentennialAllStarGrowth::advance(50, 0, $floor)
);
}
public function testUserInitialStatsPreserveCandidateShapeAtOrdinaryTotal(): void
{
$initial = CentennialAllStarGrowthService::calculateUserInitialStats([
'leadership' => 80,
'strength' => 70,
'intel' => 50,
]);
self::assertSame([
'leadership' => 65,
'strength' => 58,
'intel' => 42,
], $initial);
self::assertSame(165, array_sum($initial));
self::assertLessThanOrEqual(80, $initial['leadership']);
self::assertLessThanOrEqual(70, $initial['strength']);
self::assertLessThanOrEqual(50, $initial['intel']);
}
public function testUserInitialStatsDoNotRaiseCandidateBelowOrdinaryTotal(): void
{
self::assertSame([
'leadership' => 60,
'strength' => 45,
'intel' => 30,
], CentennialAllStarGrowthService::calculateUserInitialStats([
'leadership' => 60,
'strength' => 45,
'intel' => 30,
]));
}
public function testCurrentReselectionBaselineUsesCurrentYearProgress(): void
{
$target = [
'leadership' => 100,
'strength' => 15,
'intel' => 100,
];
self::assertSame([
'leadership' => 75,
'strength' => 15,
'intel' => 75,
], CentennialAllStarGrowthService::calculateUserCurrentTargetStats(
$target,
['startyear' => 180, 'year' => 180, 'month' => 1]
));
self::assertSame([
'leadership' => 83,
'strength' => 15,
'intel' => 83,
], CentennialAllStarGrowthService::calculateUserCurrentTargetStats(
$target,
['startyear' => 180, 'year' => 192, 'month' => 1]
));
self::assertSame([
'leadership' => 100,
'strength' => 15,
'intel' => 100,
], CentennialAllStarGrowthService::calculateUserCurrentTargetStats(
$target,
['startyear' => 180, 'year' => 195, 'month' => 1]
));
}
public function testInitialUserGrantMakesInitialAllocationReplaceable(): void
{
$initial = CentennialAllStarGrowthService::calculateUserInitialStats([
'uniqueName' => 'A1000001',
'leadership' => 80,
'strength' => 70,
'intel' => 50,
]);
$aux = CentennialAllStarGrowthService::initialAux([
'uniqueName' => 'A1000001',
], $initial);
self::assertSame('A1000001', $aux['targetId']);
self::assertSame($initial, $aux['userInitialStats']);
self::assertSame(1.0, $aux['dexTargetRatio']);
self::assertSame(50, $aux['granted']['leadership']);
self::assertSame(43, $aux['granted']['strength']);
self::assertSame(27, $aux['granted']['intel']);
}
public function testLegacyInitialGrantKeepsOnlyGrowthBeyondCreationRange(): void
{
self::assertSame(
35,
CentennialAllStarGrowthService::calculateLegacyUserGrant(50, 0)
);
self::assertSame(
75,
CentennialAllStarGrowthService::calculateLegacyUserGrant(90, 40)
);
self::assertSame(
105,
CentennialAllStarGrowthService::calculateLegacyUserGrant(140, 40)
);
self::assertSame(
35,
140 - CentennialAllStarGrowthService::calculateLegacyUserGrant(140, 40)
);
}
public function testReselectDropsOldGrantAndKeepsOrganicGrowth(): void
{
self::assertSame(
['value' => 82, 'granted' => 7, 'organic' => 75],
CentennialAllStarGrowth::replaceTarget(86, 11, 82)
);
self::assertSame(
['value' => 90, 'granted' => 0, 'organic' => 90],
CentennialAllStarGrowth::replaceTarget(101, 11, 82)
);
}
public function testRepeatedAdvanceIsIdempotent(): void
{
$first = CentennialAllStarGrowth::advance(50, 0, 60);
$second = CentennialAllStarGrowth::advance($first['value'], $first['granted'], 60);
self::assertSame(60, $second['value']);
self::assertSame(10, $second['granted']);
self::assertSame(0, $second['delta']);
self::assertSame(50, CentennialAllStarGrowth::recordableValue(60, 10));
}
public function testDexUsesSlowEarlyCurve(): void
{
self::assertSame(0, CentennialAllStarGrowth::dexFloor(900000, 0));
self::assertSame(36000, CentennialAllStarGrowth::dexFloor(900000, 0.2));
self::assertSame(144000, CentennialAllStarGrowth::dexFloor(900000, 0.4));
self::assertSame(900000, CentennialAllStarGrowth::dexFloor(900000, 1));
}
public function testDexExtensionRunsFromYear200ThroughYear205(): void
{
$env = static fn(int $year, int $month): array => [
'startyear' => 180,
'year' => $year,
'month' => $month,
];
self::assertSame(0.0, CentennialAllStarGrowthService::calculateDexExtensionProgress($env(199, 12)));
self::assertSame(0.0, CentennialAllStarGrowthService::calculateDexExtensionProgress($env(200, 1)));
self::assertEqualsWithDelta(
0.2,
CentennialAllStarGrowthService::calculateDexExtensionProgress($env(201, 1)),
0.000001
);
self::assertEqualsWithDelta(
0.5,
CentennialAllStarGrowthService::calculateDexExtensionProgress($env(202, 7)),
0.000001
);
self::assertSame(1.0, CentennialAllStarGrowthService::calculateDexExtensionProgress($env(205, 1)));
self::assertSame(1.0, CentennialAllStarGrowthService::calculateDexExtensionProgress($env(210, 1)));
}
public function testDexExtensionOnlyAddsHistoricalTargetAboveOneMillion(): void
{
$env = static fn(int $year, int $month): array => [
'startyear' => 180,
'year' => $year,
'month' => $month,
];
self::assertSame(
900000,
CentennialAllStarGrowthService::calculateDexTargetFloor(900000, $env(205, 1))
);
self::assertSame(
1000000,
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(199, 12))
);
self::assertSame(
1000000,
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(200, 1))
);
self::assertSame(
1200000,
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(201, 1))
);
self::assertSame(
1500000,
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(202, 7))
);
self::assertSame(
2000000,
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(205, 1))
);
}
public function testEveryCandidateDexTargetExtendsMonotonicallyToItsHistoricalValue(): void
{
$pool = json_decode(
file_get_contents(__DIR__ . '/../hwe/sammo/GeneralPool/Pool/UnderS100.json'),
true,
512,
JSON_THROW_ON_ERROR
);
$columns = array_flip($pool['columns']);
$dates = [
['startyear' => 180, 'year' => 199, 'month' => 7],
['startyear' => 180, 'year' => 200, 'month' => 1],
['startyear' => 180, 'year' => 201, 'month' => 1],
['startyear' => 180, 'year' => 202, 'month' => 7],
['startyear' => 180, 'year' => 205, 'month' => 1],
];
$extendedCandidateCount = 0;
$maximumTarget = 0;
foreach ($pool['data'] as $row) {
$hasExtendedTarget = false;
foreach ($row[$columns['dex']] as $target) {
$target = (int) $target;
$maximumTarget = max($maximumTarget, $target);
$hasExtendedTarget = $hasExtendedTarget || $target > GameConst::$dexLimit;
$floors = array_map(
static fn(array $env): int => CentennialAllStarGrowthService::calculateDexTargetFloor(
$target,
$env
),
$dates
);
self::assertSame(
min(GameConst::$dexLimit, $target),
$floors[0],
$row[$columns['generalName']]
);
self::assertSame($floors[0], $floors[1], $row[$columns['generalName']]);
$sortedFloors = $floors;
sort($sortedFloors, SORT_NUMERIC);
self::assertSame($sortedFloors, $floors, $row[$columns['generalName']]);
self::assertSame($target, $floors[4], $row[$columns['generalName']]);
}
if ($hasExtendedTarget) {
$extendedCandidateCount++;
}
}
self::assertSame(208, $extendedCandidateCount);
self::assertSame(2990401, $maximumTarget);
}
public function testNpcReceivesFortyPercentOfExtendedHistoricalDexTarget(): void
{
$ratio = GameConst::$centennialNpcDexTargetRatio;
self::assertSame(
400000,
CentennialAllStarGrowthService::calculateDexTargetFloor(
2000000,
['startyear' => 180, 'year' => 200, 'month' => 1],
$ratio
)
);
self::assertSame(
480000,
CentennialAllStarGrowthService::calculateDexTargetFloor(
2000000,
['startyear' => 180, 'year' => 201, 'month' => 1],
$ratio
)
);
self::assertSame(
800000,
CentennialAllStarGrowthService::calculateDexTargetFloor(
2000000,
['startyear' => 180, 'year' => 205, 'month' => 1],
$ratio
)
);
}
public function testDexExtensionNeverLowersNaturalGrowthAndKeepsItRecordable(): void
{
$target = $this->singleDexTarget('extended', 0, 2000000);
$vars = $this->emptyGeneralVars();
$aux = CentennialAllStarGrowthService::initialAux($target, []);
$general = $this->createStateGeneralMock($vars, $aux);
CentennialAllStarGrowthService::applyTarget(
$general,
$target,
['startyear' => 180, 'year' => 199, 'month' => 7]
);
self::assertSame(1000000, $vars['dex1']);
self::assertSame(1000000, $aux['granted']['dex1']);
$vars['dex1'] += 300000;
$result = CentennialAllStarGrowthService::applyTarget(
$general,
$target,
['startyear' => 180, 'year' => 201, 'month' => 1]
);
self::assertSame(1300000, $vars['dex1']);
self::assertSame(1000000, $aux['granted']['dex1']);
self::assertSame(300000, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
self::assertSame(1, $result['dexExtensionMilestone']);
self::assertSame(0, $result['previousDexExtensionMilestone']);
$result = CentennialAllStarGrowthService::applyTarget(
$general,
$target,
['startyear' => 180, 'year' => 205, 'month' => 1]
);
self::assertSame(2000000, $vars['dex1']);
self::assertSame(1700000, $aux['granted']['dex1']);
self::assertSame(300000, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
self::assertSame(5, $result['dexExtensionMilestone']);
self::assertSame(1, $result['previousDexExtensionMilestone']);
}
public function testDexExtensionDoesNotRefillConsumedConversionBudget(): void
{
$target = $this->singleDexTarget('extended-consumed', 0, 2000000);
$vars = $this->emptyGeneralVars();
$vars['dex1'] = 800000;
$aux = CentennialAllStarGrowthService::initialAux($target, []);
$aux['granted']['dex1'] = 800000;
$aux['dexConsumed']['dex1'] = 200000;
$aux['dexFloor']['dex1'] = 800000;
$general = $this->createStateGeneralMock($vars, $aux);
CentennialAllStarGrowthService::applyTarget(
$general,
$target,
['startyear' => 180, 'year' => 205, 'month' => 1]
);
self::assertSame(1800000, $vars['dex1']);
self::assertSame(1800000, $aux['granted']['dex1']);
self::assertSame(200000, $aux['dexConsumed']['dex1']);
self::assertSame(1800000, $aux['dexFloor']['dex1']);
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
}
public function testNpcDexStopsAtFortyPercentOfHistoricalTarget(): void
{
$target = 900000;
$ratio = GameConst::$centennialNpcDexTargetRatio;
self::assertSame(0.4, $ratio);
self::assertSame(
57600,
CentennialAllStarGrowthService::calculateDexTargetFloor(
$target,
['startyear' => 180, 'year' => 186, 'month' => 1],
$ratio
)
);
self::assertSame(
360000,
CentennialAllStarGrowthService::calculateDexTargetFloor(
$target,
['startyear' => 180, 'year' => 195, 'month' => 1],
$ratio
)
);
self::assertSame(
360000,
CentennialAllStarGrowthService::calculateDexTargetFloor(
$target,
['startyear' => 180, 'year' => 210, 'month' => 1],
$ratio
)
);
}
public function testScenarioConfigKeepsNpcDexTargetRatioAtFortyPercent(): void
{
$scenario = json_decode(
file_get_contents(__DIR__ . '/../hwe/scenario/scenario_915.json'),
true,
512,
JSON_THROW_ON_ERROR
);
self::assertSame(
GameConst::$centennialNpcDexTargetRatio,
$scenario['map']['centennialNpcDexTargetRatio']
);
}
public function testNpcDexRatioChangeRemovesOnlyOldEventGrant(): void
{
self::assertSame(
['value' => 360000, 'granted' => 360000, 'organic' => 0],
CentennialAllStarGrowth::replaceTarget(810000, 810000, 360000)
);
self::assertSame(
['value' => 360000, 'granted' => 310000, 'organic' => 50000],
CentennialAllStarGrowth::replaceTarget(860000, 810000, 360000)
);
self::assertSame(
['value' => 400000, 'granted' => 0, 'organic' => 400000],
CentennialAllStarGrowth::replaceTarget(1210000, 810000, 360000)
);
}
public function testExistingNpcDexGrantIsRebasedWithoutChangingFinalStats(): void
{
$vars = [
'leadership' => 91,
'strength' => 91,
'intel' => 91,
'dex1' => 810000,
'dex2' => 810000,
'dex3' => 810000,
'dex4' => 810000,
'dex5' => 810000,
'special' => 'None',
];
$aux = [
'targetId' => 'A1000001',
'granted' => [
'leadership' => 76,
'strength' => 76,
'intel' => 76,
'dex1' => 810000,
'dex2' => 810000,
'dex3' => 810000,
'dex4' => 810000,
'dex5' => 810000,
],
'progressMonth' => 162,
'milestone' => 4,
'naturalSpecialDomestic' => null,
'eventSpecialDomestic' => null,
'userInitialStats' => null,
];
$general = $this->getMockBuilder(General::class)
->disableOriginalConstructor()
->onlyMethods(['getAuxVar', 'setAuxVar', 'getVar', 'updateVar'])
->getMock();
$general->method('getAuxVar')->willReturnCallback(
static fn(string $key) => $key === CentennialAllStarGrowthService::AUX_KEY
? $aux
: null
);
$general->method('getVar')->willReturnCallback(
static fn(string $key) => $vars[$key] ?? null
);
$general->method('updateVar')->willReturnCallback(
static function (string $key, $value) use (&$vars): void {
$vars[$key] = $value;
}
);
$general->method('setAuxVar')->willReturnCallback(
static function (string $key, $value) use (&$aux): void {
if ($key === CentennialAllStarGrowthService::AUX_KEY) {
$aux = $value;
}
}
);
$result = CentennialAllStarGrowthService::applyTarget(
$general,
[
'uniqueName' => 'A1000001',
'leadership' => 100,
'strength' => 100,
'intel' => 100,
'dex' => [900000, 900000, 900000, 900000, 900000],
],
['startyear' => 180, 'year' => 195, 'month' => 1],
CentennialAllStarGrowthService::NPC_PROGRESS_MULTIPLIER,
GameConst::$centennialNpcDexTargetRatio
);
self::assertTrue($result['changed']);
self::assertSame(91, $vars['leadership']);
self::assertSame(91, $vars['strength']);
self::assertSame(91, $vars['intel']);
foreach (['dex1', 'dex2', 'dex3', 'dex4', 'dex5'] as $key) {
self::assertSame(360000, $vars[$key]);
self::assertSame(360000, $aux['granted'][$key]);
}
self::assertSame(0.4, $aux['dexTargetRatio']);
}
public function testDexConversionConsumesEventFloorWithoutMonthlyRefill(): void
{
$vars = [
'leadership' => 100,
'strength' => 100,
'intel' => 100,
'dex1' => 216000,
'dex2' => 489600,
'dex3' => 360000,
'dex4' => 360000,
'dex5' => 360000,
'special' => 'None',
];
$aux = [
'targetId' => 'A1000001',
'granted' => [
'leadership' => 85,
'strength' => 85,
'intel' => 85,
'dex1' => 360000,
'dex2' => 360000,
'dex3' => 360000,
'dex4' => 360000,
'dex5' => 360000,
],
'dexConsumed' => [
'dex1' => 0,
'dex2' => 0,
'dex3' => 0,
'dex4' => 0,
'dex5' => 0,
],
'progressMonth' => 180,
'milestone' => 5,
'naturalSpecialDomestic' => null,
'eventSpecialDomestic' => null,
'userInitialStats' => [],
'dexTargetRatio' => 1.0,
];
$general = $this->createStateGeneralMock($vars, $aux);
CentennialAllStarGrowthService::reconcileDexConversion(
$general,
'dex1',
'dex2',
360000,
216000,
360000,
489600,
0.9
);
self::assertSame(216000, $aux['granted']['dex1']);
self::assertSame(489600, $aux['granted']['dex2']);
self::assertSame(144000, $aux['dexConsumed']['dex1']);
CentennialAllStarGrowthService::applyTarget(
$general,
[
'uniqueName' => 'A1000001',
'leadership' => 100,
'strength' => 100,
'intel' => 100,
'dex' => [360000, 360000, 360000, 360000, 360000],
],
['startyear' => 180, 'year' => 195, 'month' => 1]
);
self::assertSame(216000, $vars['dex1']);
self::assertSame(489600, $vars['dex2']);
self::assertSame(216000, $aux['dexFloor']['dex1']);
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex2'));
$vars['dex1'] = 129600;
$vars['dex2'] = 567360;
CentennialAllStarGrowthService::reconcileDexConversion(
$general,
'dex1',
'dex2',
216000,
129600,
489600,
567360,
0.9
);
CentennialAllStarGrowthService::applyTarget(
$general,
[
'uniqueName' => 'A1000001',
'leadership' => 100,
'strength' => 100,
'intel' => 100,
'dex' => [360000, 360000, 360000, 360000, 360000],
],
['startyear' => 180, 'year' => 195, 'month' => 1]
);
self::assertSame(129600, $vars['dex1']);
self::assertSame(567360, $vars['dex2']);
self::assertSame(230400, $aux['dexConsumed']['dex1']);
self::assertSame(129600, $aux['dexFloor']['dex1']);
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex2'));
}
public function testNaturalDexConversionDoesNotBecomeEventGrant(): void
{
$vars = [
'dex1' => 216000,
'dex2' => 129600,
];
$aux = [
'targetId' => 'A1000001',
'granted' => [
'dex1' => 0,
'dex2' => 0,
],
'dexConsumed' => [
'dex1' => 0,
'dex2' => 0,
],
'dexFloor' => [
'dex1' => 360000,
'dex2' => 0,
],
];
$general = $this->createStateGeneralMock($vars, $aux);
CentennialAllStarGrowthService::reconcileDexConversion(
$general,
'dex1',
'dex2',
360000,
216000,
0,
129600,
0.9
);
self::assertSame(0, $aux['granted']['dex1']);
self::assertSame(0, $aux['granted']['dex2']);
self::assertSame(144000, $aux['dexConsumed']['dex1']);
self::assertSame(216000, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
self::assertSame(129600, CentennialAllStarGrowthService::recordableValue($general, 'dex2'));
}
public function testMixedDexConversionSplitsEventAndNaturalPointsByTotalShare(): void
{
$vars = [
'dex1' => 360000,
'dex2' => 600000,
];
$aux = [
'targetId' => 'archer',
'granted' => [
'dex1' => 0,
'dex2' => 900000,
],
'dexConsumed' => [
'dex1' => 0,
'dex2' => 0,
],
'dexFloor' => [
'dex1' => 0,
'dex2' => 900000,
],
];
$general = $this->createStateGeneralMock($vars, $aux);
CentennialAllStarGrowthService::reconcileDexConversion(
$general,
'dex2',
'dex1',
1000000,
600000,
0,
360000,
0.9
);
self::assertSame(540000, $aux['granted']['dex2']);
self::assertSame(324000, $aux['granted']['dex1']);
self::assertSame(60_000, CentennialAllStarGrowthService::recordableValue($general, 'dex2'));
self::assertSame(36_000, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
self::assertSame(864000, $aux['granted']['dex1'] + $aux['granted']['dex2']);
self::assertSame(
96000,
CentennialAllStarGrowthService::recordableValue($general, 'dex1')
+ CentennialAllStarGrowthService::recordableValue($general, 'dex2')
);
}
public function testArcherCavalryGhostConversionChainKeepsOnlyCurrentTargetBudget(): void
{
$vars = $this->emptyGeneralVars();
$aux = CentennialAllStarGrowthService::initialAux(
$this->singleDexTarget('archer', 1),
[]
);
$general = $this->createStateGeneralMock($vars, $aux);
$env = ['startyear' => 180, 'year' => 195, 'month' => 1];
CentennialAllStarGrowthService::applyTarget(
$general,
$this->singleDexTarget('archer', 1),
$env
);
$this->convertDex($general, $vars, 'dex2', 'dex1');
self::assertSame([324000, 540000, 0, 0, 0], $this->dexValues($vars));
CentennialAllStarGrowthService::applyTarget(
$general,
$this->singleDexTarget('cavalry', 2),
$env
);
self::assertSame([0, 0, 900000, 0, 0], $this->dexValues($vars));
$this->convertDex($general, $vars, 'dex3', 'dex1');
self::assertSame([324000, 0, 540000, 0, 0], $this->dexValues($vars));
CentennialAllStarGrowthService::applyTarget(
$general,
$this->singleDexTarget('ghost', 3),
$env
);
self::assertSame([0, 0, 0, 900000, 0], $this->dexValues($vars));
$this->convertDex($general, $vars, 'dex4', 'dex1');
self::assertSame([324000, 0, 0, 540000, 0], $this->dexValues($vars));
self::assertSame(864000, array_sum($this->dexValues($vars)));
foreach (['dex1', 'dex2', 'dex3', 'dex4', 'dex5'] as $key) {
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, $key));
}
}
public function testSelectingInfantryAfterConversionReplacesOldEventBudget(): void
{
$vars = $this->emptyGeneralVars();
$aux = CentennialAllStarGrowthService::initialAux(
$this->singleDexTarget('archer', 1),
[]
);
$general = $this->createStateGeneralMock($vars, $aux);
$env = ['startyear' => 180, 'year' => 195, 'month' => 1];
CentennialAllStarGrowthService::applyTarget(
$general,
$this->singleDexTarget('archer', 1),
$env
);
$this->convertDex($general, $vars, 'dex2', 'dex1');
CentennialAllStarGrowthService::applyTarget(
$general,
$this->singleDexTarget('infantry', 0),
$env
);
self::assertSame([900000, 0, 0, 0, 0], $this->dexValues($vars));
self::assertSame(900000, array_sum($aux['granted']));
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
}
public function testMixedConversionThenReselectionPreservesOnlyPostLossNaturalTotal(): void
{
$vars = $this->emptyGeneralVars();
$aux = CentennialAllStarGrowthService::initialAux(
$this->singleDexTarget('archer', 1),
[]
);
$general = $this->createStateGeneralMock($vars, $aux);
$env = ['startyear' => 180, 'year' => 195, 'month' => 1];
CentennialAllStarGrowthService::applyTarget(
$general,
$this->singleDexTarget('archer', 1),
$env
);
$vars['dex2'] += 100000;
$this->convertDex($general, $vars, 'dex2', 'dex1');
CentennialAllStarGrowthService::applyTarget(
$general,
$this->singleDexTarget('infantry', 0),
$env
);
self::assertSame([900000, 60000, 0, 0, 0], $this->dexValues($vars));
self::assertSame(864000, $aux['granted']['dex1']);
self::assertSame(0, $aux['granted']['dex2']);
self::assertSame(96000, array_sum($this->recordableDexValues($general)));
self::assertSame(960000, array_sum($this->dexValues($vars)));
}
public function testReselectionResetsConsumedDexFloorAndTransferredGrant(): void
{
$vars = [
'leadership' => 100,
'strength' => 100,
'intel' => 100,
'dex1' => 216000,
'dex2' => 489600,
'dex3' => 360000,
'dex4' => 360000,
'dex5' => 360000,
'special' => 'None',
];
$aux = [
'targetId' => 'A1000001',
'granted' => [
'leadership' => 85,
'strength' => 85,
'intel' => 85,
'dex1' => 216000,
'dex2' => 489600,
'dex3' => 360000,
'dex4' => 360000,
'dex5' => 360000,
],
'dexConsumed' => [
'dex1' => 144000,
'dex2' => 0,
'dex3' => 0,
'dex4' => 0,
'dex5' => 0,
],
'dexFloor' => [
'dex1' => 216000,
'dex2' => 360000,
'dex3' => 360000,
'dex4' => 360000,
'dex5' => 360000,
],
'progressMonth' => 180,
'milestone' => 5,
'naturalSpecialDomestic' => null,
'eventSpecialDomestic' => null,
'userInitialStats' => [],
'dexTargetRatio' => 1.0,
];
$general = $this->createStateGeneralMock($vars, $aux);
CentennialAllStarGrowthService::applyTarget(
$general,
[
'uniqueName' => 'A1000002',
'leadership' => 100,
'strength' => 100,
'intel' => 100,
'dex' => [400000, 400000, 400000, 400000, 400000],
],
['startyear' => 180, 'year' => 195, 'month' => 1]
);
self::assertSame(400000, $vars['dex1']);
self::assertSame(400000, $vars['dex2']);
self::assertSame(0, $aux['dexConsumed']['dex1']);
self::assertSame(400000, $aux['dexFloor']['dex1']);
self::assertSame(400000, $aux['granted']['dex1']);
self::assertSame(400000, $aux['granted']['dex2']);
}
public function testUserDexStillReachesFullHistoricalTarget(): void
{
self::assertSame(
900000,
CentennialAllStarGrowthService::calculateDexTargetFloor(
900000,
['startyear' => 180, 'year' => 195, 'month' => 1]
)
);
}
public function testUntrackedGeneralKeepsFullHallValue(): void
{
self::assertSame(
123456,
CentennialAllStarGrowthService::recordableRawValue(
['dex1' => 123456, 'aux' => '{}'],
'dex1'
)
);
}
private function createStateGeneralMock(array &$vars, array &$aux): General
{
$general = $this->getMockBuilder(General::class)
->disableOriginalConstructor()
->onlyMethods(['getAuxVar', 'setAuxVar', 'getVar', 'updateVar'])
->getMock();
$general->method('getAuxVar')->willReturnCallback(
static function (string $key) use (&$aux) {
return $key === CentennialAllStarGrowthService::AUX_KEY
? $aux
: null;
}
);
$general->method('getVar')->willReturnCallback(
static function (string $key) use (&$vars) {
return $vars[$key] ?? null;
}
);
$general->method('updateVar')->willReturnCallback(
static function (string $key, $value) use (&$vars): void {
$vars[$key] = $value;
}
);
$general->method('setAuxVar')->willReturnCallback(
static function (string $key, $value) use (&$aux): void {
if ($key === CentennialAllStarGrowthService::AUX_KEY) {
$aux = $value;
}
}
);
return $general;
}
private function emptyGeneralVars(): array
{
return [
'leadership' => 100,
'strength' => 100,
'intel' => 100,
'dex1' => 0,
'dex2' => 0,
'dex3' => 0,
'dex4' => 0,
'dex5' => 0,
'special' => 'None',
];
}
private function singleDexTarget(string $id, int $dexIndex, int $amount = 900000): array
{
$dex = [0, 0, 0, 0, 0];
$dex[$dexIndex] = $amount;
return [
'uniqueName' => $id,
'leadership' => 100,
'strength' => 100,
'intel' => 100,
'dex' => $dex,
];
}
private function convertDex(
General $general,
array &$vars,
string $sourceKey,
string $destinationKey
): void {
$sourceBefore = $vars[$sourceKey];
$destinationBefore = $vars[$destinationKey];
$cut = (int) ($sourceBefore * 0.4);
$add = (int) ($cut * 0.9);
$vars[$sourceKey] -= $cut;
$vars[$destinationKey] += $add;
CentennialAllStarGrowthService::reconcileDexConversion(
$general,
$sourceKey,
$destinationKey,
$sourceBefore,
$vars[$sourceKey],
$destinationBefore,
$vars[$destinationKey],
0.9
);
}
private function dexValues(array $vars): array
{
return [
$vars['dex1'],
$vars['dex2'],
$vars['dex3'],
$vars['dex4'],
$vars['dex5'],
];
}
private function recordableDexValues(General $general): array
{
return array_map(
static fn(string $key): int => CentennialAllStarGrowthService::recordableValue(
$general,
$key
),
['dex1', 'dex2', 'dex3', 'dex4', 'dex5']
);
}
}
+175
View File
@@ -0,0 +1,175 @@
<?php
use PHPUnit\Framework\TestCase;
use sammo\GameConst;
use sammo\GeneralPool\SPoolUnderU100;
require_once __DIR__ . '/../hwe/sammo/AbsGeneralPool.php';
require_once __DIR__ . '/../hwe/sammo/AbsFromUserPool.php';
require_once __DIR__ . '/../hwe/sammo/GeneralPool/SPoolUnderU100.php';
require_once __DIR__ . '/../hwe/sammo/ActionLogger.php';
require_once __DIR__ . '/../hwe/sammo/GameConstBase.php';
require_once __DIR__ . '/../hwe/d_setting/GameConst.php';
final class CentennialAllStarPoolTest extends TestCase
{
private const POOL_PATH = __DIR__ . '/../hwe/sammo/GeneralPool/Pool/UnderS100.json';
public function testPoolCoversEveryCompletedNonEventPhase(): void
{
$pool = json_decode(
file_get_contents(self::POOL_PATH),
true,
512,
JSON_THROW_ON_ERROR
);
self::assertSame(
[
'generalName',
'leadership',
'strength',
'intel',
'specialDomestic',
'dex',
'imgsvr',
'picture',
'sourcePhase',
'sourceServerId',
'sourceGeneralNo',
'selectionReasons',
],
$pool['columns']
);
self::assertCount(4682, $pool['data']);
self::assertSame(
[
5, 10, 15, 20, 25, 30, 35, 40, 45, 50,
55, 60, 65, 70, 75, 80, 85, 90, 95,
],
$pool['excludedEventPhases']
);
$column = array_flip($pool['columns']);
$phases = [];
$chiefCounts = [];
$sourceKeys = [];
$generalNames = [];
foreach ($pool['data'] as $row) {
self::assertCount(count($pool['columns']), $row);
$phase = $row[$column['sourcePhase']];
$sourceKey = "{$phase}:{$row[$column['sourceGeneralNo']]}";
self::assertArrayNotHasKey($sourceKey, $sourceKeys);
$sourceKeys[$sourceKey] = true;
$phases[$phase] = true;
$generalName = $row[$column['generalName']];
self::assertArrayNotHasKey($generalName, $generalNames);
$generalNames[$generalName] = true;
self::assertMatchesRegularExpression('/^(\d{1,2})·.+$/u', $generalName);
self::assertSame(
1,
preg_match('/^(\d{1,2})·/u', $generalName, $nameMatch)
);
self::assertSame($phase, (int) $nameMatch[1]);
self::assertLessThanOrEqual(32, mb_strlen($generalName));
self::assertCount(5, $row[$column['dex']]);
self::assertNotEmpty($row[$column['selectionReasons']]);
foreach ($row[$column['selectionReasons']] as $reason) {
self::assertMatchesRegularExpression(
'/^(hall:[a-z0-9_]+|chief:(?:5|6|7|8|9|10|11|12))$/',
$reason
);
if (str_starts_with($reason, 'chief:')) {
$chiefCounts[$phase] = ($chiefCounts[$phase] ?? 0) + 1;
}
}
}
ksort($phases, SORT_NUMERIC);
$expectedPhases = array_values(array_diff(
range(1, 99),
$pool['excludedEventPhases']
));
self::assertSame($expectedPhases, array_keys($phases));
foreach ($expectedPhases as $phase) {
self::assertSame(
8,
$chiefCounts[$phase] ?? 0,
"phase {$phase} must include the ruler and seven chiefs"
);
}
}
public function testCentennialSelectionWeightFavorsUserStatsAndKeepsZeroDexEligible(): void
{
$method = new ReflectionMethod(SPoolUnderU100::class, 'getCandidateWeight');
$method->setAccessible(true);
$low = [
'leadership' => 80,
'strength' => 70,
'intel' => 10,
'dex' => [0, 0, 0, 0, 0],
];
$high = [
'leadership' => 95,
'strength' => 85,
'intel' => 10,
'dex' => [0, 0, 0, 0, 0],
];
self::assertSame(100000, $method->invoke(null, $low, 0));
self::assertSame(100000.0, $method->invoke(null, $low, 1));
self::assertSame(150000.0, $method->invoke(null, $high, 1));
self::assertSame(100000, $method->invoke(null, $high, 0));
}
public function testEveryHistoricalEventSpecialHasAnImplementation(): void
{
$pool = json_decode(
file_get_contents(self::POOL_PATH),
true,
512,
JSON_THROW_ON_ERROR
);
$column = array_flip($pool['columns']);
foreach ($pool['data'] as $row) {
$special = $row[$column['specialDomestic']];
if ($special === null) {
continue;
}
self::assertFileExists(
__DIR__ . "/../hwe/sammo/ActionSpecialDomestic/{$special}.php"
);
}
}
public function testEveryHistoricalEventSpecialIsExposedToTheFrontend(): void
{
$pool = json_decode(
file_get_contents(self::POOL_PATH),
true,
512,
JSON_THROW_ON_ERROR
);
$column = array_flip($pool['columns']);
$registeredSpecials = array_flip(array_merge(
GameConst::$availableSpecialDomestic,
GameConst::$optionalSpecialDomestic
));
foreach ($pool['data'] as $row) {
$special = $row[$column['specialDomestic']];
if ($special === null || $special === '') {
continue;
}
self::assertArrayHasKey(
$special,
$registeredSpecials,
"{$special} must be included in GetConst iActionInfo.specialDomestic"
);
}
}
}
@@ -0,0 +1,155 @@
import fs from 'node:fs';
import {createHash} from 'node:crypto';
import {chromium} from 'playwright';
const baseURL = process.env.REF_BROWSER_URL;
const screenshotPath = process.env.REF_SCREENSHOT_PATH;
const resultPath = process.env.REF_RESULT_PATH;
const username = process.env.REF_USER_ID ?? 's100user01';
const password = fs.readFileSync('/run/secrets/test_user_password', 'utf8').trim();
const expectedReason = '100기 올스타 장수는 능력치 초기화를 사용할 수 없습니다.';
const expectedWarning = '100기 올스타 장수는 장수 전환 시 능력치 성장 기록을 보존하기 위해 능력치 초기화를 사용할 수 없습니다.';
if (!baseURL || !screenshotPath || !resultPath) {
throw new Error('REF_BROWSER_URL, REF_SCREENSHOT_PATH, and REF_RESULT_PATH are required');
}
const browser = await chromium.launch({headless: true});
const context = await browser.newContext({
viewport: {width: 1280, height: 960},
deviceScaleFactor: 1,
});
const browserMessages = [];
const attachPageListeners = targetPage => {
targetPage.on('console', message => {
browserMessages.push({type: message.type(), text: message.text()});
});
targetPage.on('pageerror', error => {
browserMessages.push({type: 'pageerror', text: error.message});
});
};
let page = await context.newPage();
attachPageListeners(page);
await page.goto(baseURL, {waitUntil: 'domcontentloaded', timeout: 60_000});
const salt = await page.locator('#global_salt').inputValue();
const passwordHash = createHash('sha512')
.update(salt + password + salt)
.digest('hex');
const loginResponse = await page.request.post(
new URL('api.php?path=Login/LoginByID', baseURL).href,
{data: {username, password: passwordHash}},
);
const loginResult = await loginResponse.json();
if (!loginResponse.ok() || loginResult.result !== true) {
throw new Error(`login failed: ${String(loginResult.reason ?? loginResponse.status())}`);
}
const gameLoginResponse = await page.request.get(
new URL('hwe/api.php?path=Global/GetConst', baseURL).href,
);
const gameLoginResult = await gameLoginResponse.json();
if (!gameLoginResponse.ok() || gameLoginResult.result !== true) {
throw new Error(`game login failed: ${JSON.stringify(gameLoginResult)}`);
}
await page.close();
browserMessages.length = 0;
page = await context.newPage();
attachPageListeners(page);
await page.goto(
new URL('hwe/v_inheritPoint.php', baseURL).href,
{waitUntil: 'networkidle', timeout: 60_000},
);
const bodyText = await page.locator('body').innerText();
if (bodyText.includes('#0 /var/www/html/')) {
throw new Error('PHP stack trace was rendered in the inheritance page');
}
if (!bodyText.includes(expectedWarning)) {
await page.screenshot({path: screenshotPath, fullPage: true});
fs.writeFileSync(
resultPath,
`${JSON.stringify({
username,
url: page.url(),
bodyText: bodyText.slice(0, 4000),
browserMessages,
}, null, 2)}\n`,
{mode: 0o600},
);
throw new Error('S100 stat-reset restriction was not rendered');
}
if (await page.getByRole('button', {name: '능력치 초기화'}).count() !== 0) {
throw new Error('S100 stat-reset button must not be rendered');
}
const warning = page.getByText(expectedWarning, {exact: true});
await warning.waitFor({state: 'visible', timeout: 60_000});
const warningGeometry = await warning.evaluate(element => {
const rect = element.getBoundingClientRect();
const style = getComputedStyle(element);
return {
rect: {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
},
style: {
color: style.color,
fontFamily: style.fontFamily,
fontSize: style.fontSize,
lineHeight: style.lineHeight,
},
};
});
const beforeState = await page.evaluate(() => ({
currentStat: staticValues.currentStat,
previousPoint: staticValues.items.previous,
}));
const resetResponse = await page.request.put(
new URL('hwe/api.php?path=InheritAction/ResetStat', baseURL).href,
{
data: {
leadership: 55,
strength: 55,
intel: 55,
},
},
);
const resetResult = await resetResponse.json();
if (!resetResponse.ok()
|| resetResult.result !== false
|| resetResult.reason !== expectedReason
) {
throw new Error(`unexpected ResetStat response: ${JSON.stringify(resetResult)}`);
}
await page.reload({waitUntil: 'networkidle', timeout: 60_000});
const afterState = await page.evaluate(() => ({
currentStat: staticValues.currentStat,
previousPoint: staticValues.items.previous,
}));
if (JSON.stringify(afterState) !== JSON.stringify(beforeState)) {
throw new Error(`ResetStat rejection changed state: ${JSON.stringify({beforeState, afterState})}`);
}
await page.screenshot({path: screenshotPath, fullPage: true});
fs.writeFileSync(
resultPath,
`${JSON.stringify({
username,
url: page.url(),
viewport: {width: 1280, height: 960, deviceScaleFactor: 1},
warningGeometry,
resetResult,
beforeState,
afterState,
browserMessages,
}, null, 2)}\n`,
{mode: 0o600},
);
await browser.close();
console.log(`S100 inheritance stat-reset guard verified: ${screenshotPath}`);
@@ -0,0 +1,76 @@
import fs from 'node:fs';
import {createHash} from 'node:crypto';
import {chromium} from 'playwright';
const baseURL = process.env.REF_BROWSER_URL;
const screenshotPath = process.env.REF_SCREENSHOT_PATH;
const resultPath = process.env.REF_RESULT_PATH;
const username = process.env.REF_USER_ID ?? 's100user01';
const password = fs.readFileSync('/run/secrets/test_user_password', 'utf8').trim();
if (!baseURL || !screenshotPath || !resultPath) {
throw new Error('REF_BROWSER_URL, REF_SCREENSHOT_PATH, and REF_RESULT_PATH are required');
}
const browser = await chromium.launch({headless: true});
const context = await browser.newContext({
viewport: {width: 1280, height: 960},
deviceScaleFactor: 1,
});
const page = await context.newPage();
await page.goto(baseURL, {waitUntil: 'domcontentloaded', timeout: 60_000});
const salt = await page.locator('#global_salt').inputValue();
const passwordHash = createHash('sha512')
.update(salt + password + salt)
.digest('hex');
const loginResponse = await page.request.post(
new URL('api.php?path=Login/LoginByID', baseURL).href,
{data: {username, password: passwordHash}},
);
const loginResult = await loginResponse.json();
if (!loginResponse.ok() || loginResult.result !== true) {
throw new Error(`login failed: ${String(loginResult.reason ?? loginResponse.status())}`);
}
const myPageURL = new URL('hwe/b_myPage.php', baseURL).href;
const readCooldownText = async () => {
await page.goto(myPageURL, {waitUntil: 'networkidle', timeout: 60_000});
const bodyText = await page.locator('body').innerText();
const lines = bodyText.split('\n').map(line => line.trim());
const reselect = lines.find(line => line.startsWith('다른 장수 선택 ('));
const deletion = lines.find(line => line.startsWith('가오픈 기간 내 장수 삭제 ('));
if (!reselect || !deletion) {
throw new Error(`cooldown text missing: ${bodyText.slice(0, 4000)}`);
}
return {reselect, deletion};
};
const first = await readCooldownText();
await page.waitForTimeout(1100);
const second = await readCooldownText();
await page.waitForTimeout(1100);
const third = await readCooldownText();
if (JSON.stringify(first) !== JSON.stringify(second)
|| JSON.stringify(second) !== JSON.stringify(third)
) {
throw new Error(`cooldown moved after refresh: ${JSON.stringify({first, second, third})}`);
}
await page.screenshot({path: screenshotPath, fullPage: true});
fs.writeFileSync(
resultPath,
`${JSON.stringify({
username,
url: page.url(),
viewport: {width: 1280, height: 960, deviceScaleFactor: 1},
first,
second,
third,
}, null, 2)}\n`,
{mode: 0o600},
);
await browser.close();
console.log(`S100 my-page cooldown refresh guard verified: ${screenshotPath}`);
@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
use sammo\CentennialAllStarGrowthService;
use sammo\DB;
use sammo\GameConst;
use sammo\General;
use sammo\GeneralPool\SPoolUnderU100;
use sammo\Json;
use sammo\LiteHashDRBG;
use sammo\RandUtil;
use sammo\Util;
const APP_ROOT = '/var/www/html';
const TARGET_NAMES = ['43·페르난도', '47·우마무스메'];
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$_SERVER['REQUEST_URI'] = '/s100-generated-npc-stat-floor-check';
require APP_ROOT . '/hwe/lib.php';
require APP_ROOT . '/hwe/func.php';
$env = [
'startyear' => 180,
'year' => 180,
'month' => 12,
'fiction' => [1],
'show_img_level' => 3,
];
$pool = Json::decode((string) file_get_contents(
APP_ROOT . '/hwe/sammo/GeneralPool/Pool/UnderS100.json'
));
$columns = array_flip($pool['columns']);
$targets = [];
foreach ($pool['data'] as $idx => $row) {
$name = $row[$columns['generalName']];
if (!in_array($name, TARGET_NAMES, true)) {
continue;
}
$info = array_combine($pool['columns'], $row);
$info['uniqueName'] = sprintf('A100%04d', $idx + 1);
$info['event100Growth'] = true;
$targets[$name] = $info;
}
if (count($targets) !== count(TARGET_NAMES)) {
throw new RuntimeException('Required S100 candidates are missing');
}
$db = DB::db();
$results = [];
foreach (TARGET_NAMES as $name) {
$target = $targets[$name];
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
's100-generated-npc-stat-floor-check',
$target['uniqueName']
)));
$poolGeneral = new SPoolUnderU100(
$db,
$rng,
$target,
'2999-01-01 00:00:00'
);
$builder = $poolGeneral->getGeneralBuilder();
$builder->setNationID(0)
->setNPCType(3)
->setMoney(1000, 1000)
->setExpDed(0, 0)
->setLifeSpan(160, 230);
$builder->fillRandomStat(['무' => 0.333, '지' => 0.333, '무지' => 0.334]);
$generatedStats = $builder->getStat();
assertSameValue(
GameConst::$defaultStatNPCTotal,
array_sum($generatedStats),
"{$name} generic total"
);
$builder->fillRemainSpecAsZero($env);
$builder->build($env);
CentennialAllStarGrowthService::applyCurrentTargetToBuiltNPC(
$db,
$builder,
$target,
$env
);
$general = General::createObjFromDB($builder->getGeneralID());
$actualStats = [
'leadership' => (int) $general->getVar('leadership'),
'strength' => (int) $general->getVar('strength'),
'intel' => (int) $general->getVar('intel'),
];
if (array_sum($actualStats) < GameConst::$defaultStatNPCTotal) {
throw new RuntimeException(
"{$name} total fell below " . GameConst::$defaultStatNPCTotal
);
}
$targetOrder = ['leadership', 'strength', 'intel'];
usort(
$targetOrder,
static fn (string $lhs, string $rhs): int =>
(int) $target[$rhs] <=> (int) $target[$lhs]
);
if ($actualStats[$targetOrder[0]] < $actualStats[$targetOrder[1]]
|| $actualStats[$targetOrder[1]] < $actualStats[$targetOrder[2]]
) {
throw new RuntimeException("{$name} target stat order was not preserved");
}
$results[] = [
'name' => $name,
'target' => array_intersect_key($target, $actualStats),
'generic' => array_combine(
['leadership', 'strength', 'intel'],
$generatedStats
),
'actual' => $actualStats,
'total' => array_sum($actualStats),
];
}
echo Json::encode($results, Json::PRETTY), PHP_EOL;
function assertSameValue(int $expected, int $actual, string $label): void
{
if ($actual !== $expected) {
throw new RuntimeException(
"{$label}: expected {$expected}, got {$actual}"
);
}
}

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