Compare commits

...
38 Commits
Author SHA1 Message Date
Hide_D 1f6259c157 game: 기병 공성 능력 약화, 수비시 공방 각각 5% 손해 2022-01-05 01:00:13 +09:00
Hide_D 8908e12c59 game: 유산으로 입찰된 유니크는 랜덤 유니크로 얻을 수 없음 2022-01-04 23:53:57 +09:00
Hide_D a7d6bc2c5c fix: 자동 사령턴 미 활성화 시 내정보&설정 에러 2022-01-04 06:32:45 +09:00
Hide_D 97fd807c2c fix: 단궁, 맥궁 증가 사기 표기량 수정 2022-01-03 22:45:27 +09:00
Hide_D b31f7d64ec game: 이중 격노가 발동하지 않도록 수정 2022-01-03 22:38:42 +09:00
Hide_D bdd574161e fix: 마지막 국가는 임관 불가능한 것처럼 나오는 버그 수정 2022-01-02 23:01:46 +09:00
Hide_D e23e36cbfa fix: 임관 제어 코드에서 () 누락 2022-01-02 22:50:42 +09:00
Hide_D fb796e21e4 fix: starts_with에 사소한 오류가 있어보임
- 이유는 잘 모르겠지만 starts_with($this->destNation['name'], 'ⓤ')
2022-01-02 22:46:32 +09:00
Hide_D dd46c02f09 feat: 게시판 500px 모드 2022-01-02 00:58:34 +09:00
Hide_D d1835c71b7 game: U국의 유저장간 거리를 4->3로 변경 2022-01-01 23:30:33 +09:00
Hide_D 55332864ad game: 토너먼트 자동 채우기 난이도 수정 2022-01-01 01:22:23 +09:00
Hide_D 96a8ef3bcc fix: 전금 증가 횟수 누락 2021-12-31 23:24:15 +09:00
Hide_D 79cfbfe83b fix: 전쟁 금지 설정 누락 부분 수정 2021-12-31 23:22:20 +09:00
Hide_D e7fa95ce8f fix: front에서 둔전 수입 계산 수정 2021-12-31 23:07:42 +09:00
Hide_D a819dde71c fix: 세수 표기를 잘못 반영하는 버그 수정 2021-12-31 23:05:22 +09:00
Hide_D 281cfd366b build: d_setting json ignore 2021-12-31 17:58:36 +09:00
Hide_D f1ba7e76f3 fix-build: install.php 초기 경로 관련 문제 수정 2021-12-31 17:55:29 +09:00
Hide_D 2da72d9b1a build: postcss 설정
- discard-dedupe
- autoprefixer
2021-12-31 17:33:15 +09:00
Hide_D 4cdc18fe7e misc: 사소하게 수정 2021-12-31 04:41:08 +09:00
Hide_D 4400116bdd feat: 내무부 변경 2021-12-31 04:12:37 +09:00
Hide_D 086d6d1ab5 misc: toast 내부 정리 2021-12-31 02:25:43 +09:00
Hide_D 02e86fd2e9 feat: toast내부 div 객체가 무한히 쌓이지 않도록 2021-12-31 02:22:42 +09:00
Hide_D 7310aa6971 feat(WIP): 내무부 국방/임관권유문 에디터 완료. 2021-12-30 22:50:33 +09:00
Hide_D 4927a26c92 misc: makeAPIPath 흔적 2021-12-29 05:50:21 +09:00
Hide_D ee6bec2e0d feat: APIPathGen 추상화 2021-12-29 05:46:35 +09:00
Hide_D def230bf30 feat: ts APICall을 강제화.
- 오버 엔지니어링의 정석
2021-12-29 03:55:40 +09:00
Hide_D 0a4d568b06 feat(WIP): 내무부 2021-12-28 04:29:13 +09:00
Hide_D 69f8e1f1bd feat: APIPathGen 도우미 2021-12-28 04:28:26 +09:00
Hide_D 6f2cb4e05e misc: 미사용 파일 제거 2021-12-27 21:54:23 +09:00
Hide_D db1fbb700c feat: 턴 선택기에서 '턴' 부분 선택 시 커맨드로 선택 2021-12-26 23:57:20 +09:00
Hide_D 842a37eb7b fix: 턴 선택기 홀짝 색 선택 오류 수정 2021-12-26 23:43:01 +09:00
Hide_D 76ff38ed10 feat: 사령부 턴 선택기 추가, 턴 해제 옵션 추가 2021-12-26 23:37:53 +09:00
Hide_D db6b2be101 feat: 턴 선택기에서 '드래그' 선택 지원 2021-12-26 23:22:43 +09:00
Hide_D a998500839 feat: DragSelect 추가
- Vue2 버전을 Vue3로 이식
2021-12-26 22:32:04 +09:00
Hide_D dd5e686d03 game: 유니크 명마, 명검, 서적 갯수 2배
- 말 유니크로 백상, 오환마, 흉노마, 의남백마, 옥추마, 화종마, 옥란백용구 추가
  - 백상, 옥란백용구는 특수효과 부여
2021-12-26 20:44:32 +09:00
Hide_D c9f28e27d2 feat: 소모 군량 보정을 위한 trigger 추가 2021-12-26 20:06:39 +09:00
Hide_D 1bbeb4f50d fix: 급습에서 대상국에 메시지가 뜨지 않음 2021-12-26 18:46:50 +09:00
Hide_D 410f6b261e fix: processing $destNation 구분 버그 2021-12-26 04:19:26 +09:00
93 changed files with 2798 additions and 1846 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ module.exports = {
"browser": true,
"es2021": true
},
ignorePatterns: ['*.test.ts', '.eslintrc.cjs', 'webpack.config.cjs', '*.js'],
ignorePatterns: ['*.test.ts', '.eslintrc.cjs', 'postcss.config.cjs', 'webpack.config.cjs', '*.js'],
overrides: [{
files: ['*.ts', '*.tsx', "*.vue"],
}],
+1
View File
@@ -43,6 +43,7 @@ d_pic/uploaded_image
d_setting/*.php
*/d_setting/*.php
*/d_setting/*.json
*/d_setting/templates/*.php
!*.orig.php
+1 -6
View File
@@ -52,7 +52,7 @@ return [
'hwe/b_betting.php',
'hwe/v_chiefCenter.php',
'hwe/b_currentCity.php',
'hwe/b_dipcenter.php',
'hwe/v_nationStratFinan.php',
'hwe/b_diplomacy.php',
'hwe/b_genList.php',
'hwe/b_myBossInfo.php',
@@ -64,7 +64,6 @@ return [
'hwe/b_tournament.php',
'hwe/b_troop.php',
'hwe/c_auction.php',
'hwe/c_dipcenter.php',
'hwe/c_tournament.php',
'hwe/c_vote.php',
'hwe/func_auction.php',
@@ -91,7 +90,6 @@ return [
'hwe/j_board_article_add.php',
'hwe/j_board_comment_add.php',
'hwe/j_board_get_articles.php',
'hwe/j_chief_turn.php',
'hwe/j_die_immediately.php',
'hwe/j_diplomacy_destroy_letter.php',
'hwe/j_diplomacy_get_letter.php',
@@ -102,7 +100,6 @@ return [
'hwe/j_general_log_old.php',
'hwe/j_general_set_permission.php',
'hwe/j_get_basic_general_list.php',
'hwe/j_getChiefTurn.php',
'hwe/j_get_city_list.php',
'hwe/j_get_general_list.php',
'hwe/j_get_nation_general_list.php',
@@ -126,8 +123,6 @@ return [
'hwe/j_select_npc.php',
'hwe/j_select_picked_general.php',
'hwe/j_server_basic_info.php',
'hwe/j_set_chief_command.php',
'hwe/j_set_general_command.php',
'hwe/j_set_my_setting.php',
'hwe/j_set_npc_control.php',
'hwe/j_simulate_battle.php',
-269
View File
@@ -1,269 +0,0 @@
<?php
namespace sammo;
include "lib.php";
include "func.php";
//로그인 검사
$session = Session::requireGameLogin()->setReadOnly();
$userID = Session::getUserID();
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
increaseRefresh("내무부", 1);
$me = $db->queryFirstRow('SELECT no, nation, officer_level, con, turntime, belong, permission, penalty FROM general WHERE owner=%i', $userID);
$nationID = $me['nation'];
$nation = $db->queryFirstRow('SELECT nation,level,name,color,type,gold,rice,bill,rate,scout,war,secretlimit,capital FROM nation WHERE nation = %i', $nationID);
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
$con = checkLimit($me['con']);
if ($con >= 2) {
printLimitMsg($me['turntime']);
exit();
}
$permission = checkSecretPermission($me);
if ($permission < 0) {
echo '국가에 소속되어있지 않습니다.';
die();
} else if ($permission < 1) {
echo "권한이 부족합니다. 수뇌부가 아니거나 사관년도가 부족합니다.";
die();
}
if ($me['officer_level'] >= 5) {
$btn = "submit";
$read = "";
} else {
$btn = "hidden";
$read = "readonly";
}
$nationStor->cacheValues(['notice', 'scout_msg', 'available_war_setting_cnt']);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=1024" />
<title><?= UniqueConst::$serverName ?>: 내무부</title>
<?= WebUtil::printStaticValues([
'editable' => ($me['officer_level'] >= 5 || $permission == 4),
'nationMsg' => $nationStor->notice ?? '',
'scoutMsg' => $nationStor->scout_msg ?? '',
]) ?>
<?= WebUtil::printJS('../d_shared/common_path.js') ?>
<?= WebUtil::printCSS('../d_shared/common.css') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css" />
<?= WebUtil::printDist('vue', 'v_dipcenter', true) ?>
</head>
<body>
<table align=center width=1000 class='tb_layout bg0'>
<tr>
<td>내 무 부<br><?= backButton() ?></td>
</tr>
</table>
<table align=center width=1000 class='tb_layout bg0'>
<tr>
<td colspan=9 align=center bgcolor=blue>외 교 관 계</td>
</tr>
<tr>
<td width=130 align=center class=bg1>국 가 명</td>
<td width=50 align=center class=bg1>국력</td>
<td width=40 align=center class=bg1>장수</td>
<td width=40 align=center class=bg1>속령</td>
<td width=80 align=center class=bg1>상태</td>
<td width=60 align=center class=bg1>기간</td>
<td width=100 align=center class=bg1>종 료 시 점</td>
</tr>
<?php
$admin = $gameStor->getValues(['year', 'month']);
$cityCntList = Util::convertPairArrayToDict($db->queryAllLists('SELECT nation, count(city) FROM city GROUP BY nation'));
$dipStateList = Util::convertArrayToDict($db->query('SELECT you,state,term FROM diplomacy WHERE me = %i', $nationID), 'you');
$nationsList = getAllNationStaticInfo();
uasort($nationsList, function (array $lhs, array $rhs) {
return - ($lhs['power'] <=> $rhs['power']);
});
foreach ($nationsList as $staticNation) :
//속령수
$staticNationID = $staticNation['nation'];
$cityCnt = $cityCntList[$staticNation['nation']] ?? 0;
$dipStateText = '-';
$dipTermText = '-';
$dipEndDateText = '-';
if ($staticNationID !== $nationID) {
$diplomacyState = $dipStateList[$staticNationID];
$dipStateText = [
0 => "<font color=red>교 전</font>",
1 => "<font color=magenta>선포중</font>",
2 => "통 상",
7 => "<font color=green>불가침</font>",
][$diplomacyState['state']];
if ($diplomacyState['term']) {
$dipEndMonth = $admin['month'] + $diplomacyState['term'] - 1;
$dipEndYear = $admin['year'] + intdiv($dipEndMonth, 12);
$dipEndMonth = $dipEndMonth % 12 + 1;
$dipTermText = $diplomacyState['term'] . '개월';
$dipEndDateText = "{$dipEndYear}年 {$dipEndMonth}月";
}
}
?>
<tr>
<td class='center' style='color:<?= newColor($staticNation['color']) ?>;background-color:<?= $staticNation['color'] ?>'><?= $staticNation['name'] ?></td>
<td class='center'><?= $staticNation['power'] ?></td>
<td class='center'><?= $staticNation['gennum'] ?></td>
<td class='center'><?= $cityCnt ?></td>
<td class='center'><?= $dipStateText ?></td>
<td class='center'><?= $dipTermText ?></td>
<td class='center'><?= $dipEndDateText ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php
// 수입 연산
$cityList = $db->query('SELECT * FROM city WHERE nation=%i', $nationID);
$dedicationList = $db->query('SELECT dedication FROM general WHERE nation=%i AND npc!=5', $nationID);
$goldIncome = getGoldIncome($nation['nation'], $nation['level'], $nation['rate'], $nation['capital'], $nation['type'], $cityList);
$warIncome = getWarGoldIncome($nation['type'], $cityList);
$totalGoldIncome = $goldIncome + $warIncome;
$riceIncome = getRiceIncome($nation['nation'], $nation['level'], $nation['rate'], $nation['capital'], $nation['type'], $cityList);
$wallIncome = getWallIncome($nation['nation'], $nation['level'], $nation['rate'], $nation['capital'], $nation['type'], $cityList);
$totalRiceIncome = $riceIncome + $wallIncome;
$outcome = getOutcome($nation['bill'], $dedicationList);
$budgetgold = $nation['gold'] + $totalGoldIncome - $outcome;
$budgetrice = $nation['rice'] + $totalRiceIncome - $outcome;
$budgetgolddiff = $totalGoldIncome - $outcome;
$budgetricediff = $totalRiceIncome - $outcome;
if ($budgetgolddiff > 0) {
$budgetgolddiff = '+' . number_format($budgetgolddiff);
} else {
$budgetgolddiff = number_format($budgetgolddiff);
}
if ($budgetricediff > 0) {
$budgetricediff = '+' . number_format($budgetricediff);
} else {
$budgetricediff = number_format($budgetricediff);
}
?>
<div class='tb_layout bg0' style='margin:auto;width: 1000px;' id="editorForm">
</div>
<table class="tb_layout bg0" style="margin:auto;">
<form name=form1 method=post action=c_dipcenter.php>
<tr>
<td colspan=2 align=center bgcolor=green>예 산 &amp; 정 책</td>
</tr>
<tr>
<td colspan=2>
<table width=998 class='tb_layout bg0'>
<tr>
<td colspan=2 align=center class=bg1>자금 예산</td>
<td colspan=2 align=center class=bg1>병량 예산</td>
</tr>
<tr>
<td width=248 align=right class=bg1>현 재&nbsp;&nbsp;&nbsp;</td>
<td width=248 align=center><?= number_format($nation['gold']) ?></td>
<td width=248 align=right class=bg1>현 재&nbsp;&nbsp;&nbsp;</td>
<td width=248 align=center><?= number_format($nation['rice']) ?></td>
</tr>
<tr>
<td align=right class=bg1>단기수입&nbsp;&nbsp;&nbsp;</td>
<td align=center>+<?= number_format($warIncome) ?></td>
<td align=right class=bg1>둔전수입&nbsp;&nbsp;&nbsp;</td>
<td align=center>+<?= number_format($wallIncome) ?></td>
</tr>
<tr>
<td align=right class=bg1>세 금&nbsp;&nbsp;&nbsp;</td>
<td align=center>+<?= number_format($goldIncome) ?></td>
<td align=right class=bg1>세 곡&nbsp;&nbsp;&nbsp;</td>
<td align=center>+<?= number_format($riceIncome) ?></td>
</tr>
<tr>
<td align=right class=bg1>수입 / 지출&nbsp;&nbsp;&nbsp;</td>
<td align=center>+<?= number_format($totalGoldIncome) ?> / -<?= number_format($outcome) ?></td>
<td align=right class=bg1>수입 / 지출&nbsp;&nbsp;&nbsp;</td>
<td align=center>+<?= number_format($totalRiceIncome) ?> / -<?= number_format($outcome) ?></td>
</tr>
<tr>
<td align=right class=bg1>국고 예산&nbsp;&nbsp;&nbsp;</td>
<td align=center><?= number_format($budgetgold) ?> (<?= $budgetgolddiff ?>)</td>
<td align=right class=bg1>병량 예산&nbsp;&nbsp;&nbsp;</td>
<td align=center><?= number_format($budgetrice) ?> (<?= $budgetricediff ?>)</td>
</tr>
<tr>
<td align=right class=bg1>세율 (5 ~ 30%)&nbsp;&nbsp;&nbsp;</td>
<td align=center><input type=text <?= $read ?> name=rate style=text-align:right;color:white;background-color:black; size=3 maxlength=3 value=<?= $nation['rate'] ?>>% <input type=<?= $btn ?> name=btn value=세율></td>
<td align=right class=bg1>봉급 지급률 (20 ~ 200%)&nbsp;&nbsp;&nbsp;</td>
<td align=center><input type=text <?= $read ?> name=bill style=text-align:right;color:white;background-color:black; size=3 maxlength=3 value=<?= $nation['bill'] ?>>% <input type=<?= $btn ?> name=btn value=지급률></td>
</tr>
<tr>
<td align=right class=bg1>기밀 권한 (1 ~ 99년)&nbsp;&nbsp;&nbsp;</td>
<td align=center><input type=text <?= $read ?> name=secretlimit style=text-align:right;color:white;background-color:black; size=3 maxlength=3 value=<?= $nation['secretlimit'] ?>>년 <input type=<?= $btn ?> name=btn value=기밀권한></td>
<td align=right class=bg1>전쟁 허용/금지 변경 가능</td>
<td align=center><?= $nationStor->getValue('available_war_setting_cnt') ?>회(월 +<?= GameConst::$incAvailableWarSettingCnt ?>회, 최대<?= GameConst::$maxAvailableWarSettingCnt ?>회)</td>
</tr>
<tr>
<td colspan=4 align=center>
<?php
if ($nation['scout'] == 0) {
echo "
<input type=$btn name=btn value='임관 금지'>";
} else {
echo "
<input type=$btn name=btn value='임관 허가'>";
}
if ($nation['war'] == 0) {
echo "
<input type=$btn name=btn value='전쟁 금지'>";
} else {
echo "
<input type=$btn name=btn value='전쟁 허가'>";
}
?>
</td>
</tr>
</table>
<tr>
<td colspan=2>기밀 권한이란, 암행부를 열람할 수 있는 일반 장수의 최소 사관 년수를 의미합니다.</td>
</tr>
<tr>
<td colspan=2 height=10></td>
</tr>
</form>
</table>
<table align=center width=1000 class='tb_layout bg0'>
<tr>
<td><?= backButton() ?></td>
</tr>
<tr>
<td><?= banner() ?></td>
</tr>
</table>
</body>
</html>
+2
View File
@@ -94,6 +94,8 @@ $use_auto_nation_turn = $me->getAuxVar('use_auto_nation_turn') ?? 1;
<option value=0 <?= (!$use_auto_nation_turn) ? "selected" : ""; ?>>허용 안함</option>
</select>】<br>
∞<span style='color:orange'>수뇌가 되었을 때 휴식 턴이어도 적당한 턴을 알아서 넣는 것을 허용합니다.</span><br><br>
<?php else: ?>
<input type="hidden" id='use_auto_nation_turn' name='use_auto_nation_turn' value="1"/>
<?php endif; ?>
수비 【<select id='defence_train' name='defence_train'>
<?php foreach ([90, 80, 60, 40] as $targetDefenceTrain) : ?>
-85
View File
@@ -1,85 +0,0 @@
<?php
namespace sammo;
include "lib.php";
include "func.php";
// $btn, $msg, $scoutmsg, $rate, $bill, $secretlimit
$btn = Util::getPost('btn');
//$msg = Util::getPost('msg');
//$scoutmsg = Util::getPost('scoutMsg');
$rate = Util::getPost('rate', 'int');
$bill = Util::getPost('bill', 'int');
$secretlimit = Util::getPost('secretlimit', 'int');
//로그인 검사
$session = Session::requireGameLogin()->setReadOnly();
$userID = Session::getUserID();
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,nation,`officer_level`,permission,penalty FROM general WHERE `owner`=%i', $userID);
//내가 수뇌부이어야함
$permission = checkSecretPermission($me);
if ($permission < 0) {
header('location:b_myBossInfo.php', true, 303);
exit();
} else if ($me['officer_level'] < 5 && $permission != 4) {
header('location:b_myBossInfo.php', true, 303);
exit();
}
$nationID = $me['nation'];
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
/*if ($btn == "국가방침 수정") {
$msg = mb_substr($msg, 0, 16384);
//$msg = StringUtil::
$nationStor->notice = WebUtil::htmlPurify($msg);
} elseif ($btn == "임관 권유문 수정") {
$scoutmsg = mb_substr($scoutmsg, 0, 1000);
$nationStor->scout_msg = WebUtil::htmlPurify($scoutmsg);
} else*/if ($btn == "세율") {
$rate = Util::valueFit($rate, 5, 30);
$db->update('nation', [
'rate' => $rate,
], 'nation=%i', $nationID);
} elseif ($btn == "지급률") {
$bill = Util::valueFit($bill, 20, 200);
$db->update('nation', [
'bill' => $bill
], 'nation=%i', $nationID);
} elseif ($btn == "기밀권한") {
$secretlimit = Util::valueFit($secretlimit, 1, 99);
$db->update('nation', [
'secretlimit' => $secretlimit
], 'nation=%i', $nationID);
} elseif ($btn == "임관 금지") {
$db->update('nation', [
'scout' => 1
], 'nation=%i', $nationID);
} elseif ($btn == "임관 허가") {
$db->update('nation', [
'scout' => 0
], 'nation=%i', $nationID);
} elseif ($btn == "전쟁 금지") {
$avilableCnt = $nationStor->getValue('available_war_setting_cnt') ?? 0;
if ($avilableCnt > 0) {
$db->update('nation', [
'war' => 1
], 'nation=%i', $nationID);
$nationStor->setValue('available_war_setting_cnt', $avilableCnt - 1);
}
} elseif ($btn == "전쟁 허가") {
$avilableCnt = $nationStor->getValue('available_war_setting_cnt') ?? 0;
if ($avilableCnt > 0) {
$db->update('nation', [
'war' => 0
], 'nation=%i', $nationID);
$nationStor->setValue('available_war_setting_cnt', $avilableCnt - 1);
}
}
header('location:b_dipcenter.php');
+12
View File
@@ -1581,6 +1581,18 @@ function giveRandomUniqueItem(General $general, string $acquireType): bool
}
}
foreach($db->queryAllLists('SELECT namespace, count(*) as cnt FROM `storage` WHERE namespace LIKE "ut_%" GROUP BY namespace') as [$uniqueNS, $cnt]){
$itemCode = substr($uniqueNS, 3);
$itemClass = buildItemClass($itemCode);
if (!$itemClass) {
continue;
}
if ($itemClass->isBuyable()) {
continue;
}
$occupiedUnique[$itemCode] = ($occupiedUnique[$itemCode]??0) + $cnt;
}
foreach (GameConst::$allItems as $itemType => $itemCategories) {
if (key_exists($itemType, $invalidItemType)) {
continue;
+1 -1
View File
@@ -513,7 +513,7 @@ function fillLowGenAll($tnmt_type)
'SELECT no,npc,name,leadership,strength,intel,explevel,horse,weapon,book from general where tnmt=1 and tournament=0',
) as $gen){
$score = $scoringCandFunction($gen);
$freeJoinerCandidate[$gen['no']] = [$gen, $score**2];
$freeJoinerCandidate[$gen['no']] = [$gen, $score**1.5];
}
-41
View File
@@ -1,41 +0,0 @@
<?php
namespace sammo;
include "lib.php";
include "func.php";
WebUtil::requireAJAX();
//로그인 검사
$session = Session::requireGameLogin()->setReadOnly();
$userID = Session::getUserID();
$turnAmount = Util::getPost('amount', 'int');
$db = DB::db();
$me = $db->queryFirstRow('SELECT no,nation,officer_level FROM general WHERE owner=%i', $userID);
if($me['nation'] == 0){
Json::die([
'result'=>false,
'reason'=>'국가에 소속되어 있지 않습니다.',
]);
}
if($me['officer_level'] < 5){
Json::die([
'result'=>false,
'reason'=>'수뇌가 아닙니다.',
]);
}
if($turnAmount > 0){
pushNationCommand($me['nation'], $me['officer_level']);
}
else{
pullNationCommand($me['nation'], $me['officer_level']);
}
Json::die([
'result'=>true,
'reason'=>'success',
]);
-103
View File
@@ -1,103 +0,0 @@
<?php
namespace sammo;
include "lib.php";
include "func.php";
//로그인 검사
$session = Session::requireGameLogin()->setReadOnly();
$userID = Session::getUserID();
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
increaseRefresh("사령부", 1);
$me = $db->queryFirstRow('SELECT no,nation,officer_level,con,turntime,belong,penalty,permission FROM general WHERE owner=%i', $userID);
$nationLevel = $db->queryFirstField('SELECT level FROM nation WHERE nation = %i', $me['nation']);
$nationID = $me['nation'];
$con = checkLimit($me['con']);
if($con >= 2) {
Json::die([
'result'=>false,
'reason'=>"접속 제한중입니다. 1턴 이내에 너무 많은 갱신을 하셨습니다. (다음 접속 가능 시각 : {$me['turntime']})"
]);
}
$permission = checkSecretPermission($me);
if($permission < 0){
Json::die([
'result'=>false,
'reason'=>'국가에 소속되어있지 않습니다.'
]);
echo '국가에 소속되어있지 않습니다.';
die();
}
else if ($permission < 1) {
Json::die([
'result'=>false,
'reason'=>'수뇌부가 아니거나 사관년도가 부족합니다.'
]);
die();
}
$date = TimeUtil::now();
// 명령 목록
[$year, $month, $turnterm] = $gameStor->getValuesAsArray(['year', 'month', 'turnterm']);
$lv = getNationChiefLevel($nationLevel);
$turn = [];
$generals = [];
foreach($db->query('SELECT no,name,turntime,npc,city,nation,officer_level FROM general WHERE nation = %i AND officer_level >= 5',$nationID) as $rawGeneral){
$generals[$rawGeneral['officer_level']] = new General($rawGeneral, null, null, null, $year, $month, false);
}
$nationTurnList = [];
foreach(
$db->queryAllLists(
'SELECT officer_level, turn_idx, action, arg, brief FROM nation_turn WHERE nation_id = %i ORDER BY officer_level DESC, turn_idx ASC',
$me['nation']
) as [$officer_level, $turn_idx, $action, $arg, $brief]
){
if(!key_exists($officer_level, $nationTurnList)){
$nationTurnList[$officer_level] = [];
}
$nationTurnList[$officer_level][$turn_idx] = $brief;
}
$nationTurnBrief = [];
foreach($nationTurnList as $officer_level=>$turnBrief){
if(!key_exists($officer_level, $generals)){
$nationTurnBrief[$officer_level] = [
'name'=>null,
'turnTime'=>null,
'officerLevelText'=>getOfficerLevelText($officer_level, $nationLevel),
'npcType'=>null,
'turn'=>$turnBrief
];
continue;
}
$general = $generals[$officer_level];
$nationTurnBrief[$officer_level] = [
'name'=>$general->getName(),
'turnTime'=>$general->getTurnTime($general::TURNTIME_FULL),
'officerLevel'=>$general->getVar('officer_level'),
'officerLevelText'=>getOfficerLevelText($general->getVar('officer_level'), $nationLevel),
'npcType'=>$general->getNPCType(),
'turn'=>$turnBrief,
];
}
$date = substr(TimeUtil::now(), 14);
Json::die([
'result'=>true,
'reason'=>'success',
'date'=>$date,
'nationTurnBrief'=>$nationTurnBrief,
'isChief'=>($me['officer_level'] > 4),
'officerLevel'=>$me['officer_level'],
'turnTerm'=>$turnterm
]);
-66
View File
@@ -1,66 +0,0 @@
<?php
namespace sammo;
include "lib.php";
include "func.php";
WebUtil::requireAJAX();
$session = Session::requireGameLogin([])->setReadOnly();
increaseRefresh("사령턴 입력", 0);
$generalID = $session->generalID;
$action = Util::getPost('action', 'string');
$arg = Json::decode(Util::getPost('arg', 'string'));
$turnList = Util::getPost('turnList', 'array_int');
if(!is_array($turnList) || !$turnList){
Json::die([
'result'=>false,
'reason'=>'턴이 입력되지 않았습니다.',
'test'=>'post',
]);
}
if(!$action){
Json::die([
'result'=>false,
'reason'=>'action이 입력되지 않았습니다.',
'test'=>'post'
]);
}
if(!in_array($action, Util::array_flatten(GameConst::$availableChiefCommand))){
Json::die([
'result'=>false,
'reason'=>'사용할 수 없는 커맨드입니다.',
'test'=>'post'
]);
}
if($arg === null){
$arg = [];
}
if(!is_array($arg)){
Json::die([
'result'=>false,
'reason'=>'올바른 arg 형태가 아닙니다.',
'test'=>'post'
]);
}
$result = setNationCommand($generalID, $turnList, $action, $arg);
if(!key_exists('result', $result)){
$result['result'] = false;
}
if(!key_exists('arg_test', $result)){
$result['arg_test'] = false;
}
if(!key_exists('reason', $result)){
throw new MustNotBeReachedException('reason이 왜 없어?');
}
Json::die($result);
-55
View File
@@ -1,55 +0,0 @@
<?php
namespace sammo;
include "lib.php";
include "func.php";
WebUtil::requireAJAX();
$session = Session::requireGameLogin([])->setReadOnly();
increaseRefresh("턴 입력", 0);
$generalID = $session->generalID;
$action = Util::getPost('action', 'string');
$arg = Json::decode(Util::getPost('arg', 'string'));
$turnList = Util::getPost('turnList', 'array_int');
if(!is_array($turnList) || !$turnList){
Json::die([
'result'=>false,
'reason'=>'턴이 입력되지 않았습니다.',
'test'=>'post',
]);
}
if(!$action){
Json::die([
'result'=>false,
'reason'=>'action이 입력되지 않았습니다.',
'test'=>'post'
]);
}
if(!in_array($action, Util::array_flatten(GameConst::$availableGeneralCommand))){
Json::die([
'result'=>false,
'reason'=>'사용할 수 없는 커맨드입니다.',
'test'=>'post'
]);
}
if($arg === null){
$arg = [];
}
if(!is_array($arg)){
Json::die([
'result'=>false,
'reason'=>'올바른 arg 형태가 아닙니다.',
'test'=>'post'
]);
}
Json::die(setGeneralCommand($generalID, $turnList, $action, $arg));
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace sammo\API\Nation;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\KVStorage;
use sammo\Validator;
use sammo\WebUtil;
use function sammo\checkSecretPermission;
class SetBill extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', [
'amount',
])
->rule('integer', 'amount')
->rule('min', 'amount', 20)
->rule('max', 'amount', 200);
if (!$v->validate()) {
return $v->errorStr();
}
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$amount = $this->args['amount'];
$userID = $session->userID;
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,nation,`officer_level`,permission,penalty FROM general WHERE `owner`=%i', $userID);
$permission = checkSecretPermission($me, false);
if($permission < 0){
return "권한이 부족합니다.";
}
if ($me['officer_level'] < 5 && $permission != 4){
return "권한이 부족합니다.";
}
$nationID = $me['nation'];
$db->update('nation', [
'bill' => $amount
], 'nation=%i', $nationID);
return [
'result' => true
];
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace sammo\API\Nation;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\KVStorage;
use sammo\Validator;
use sammo\WebUtil;
use function sammo\checkSecretPermission;
class SetBlockScout extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', [
'value',
])
->rule('boolean', 'value');
if (!$v->validate()) {
return $v->errorStr();
}
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$value = $this->args['value'];
$userID = $session->userID;
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,nation,`officer_level`,permission,penalty FROM general WHERE `owner`=%i', $userID);
$permission = checkSecretPermission($me, false);
if($permission < 0){
return "권한이 부족합니다.";
}
if ($me['officer_level'] < 5 && $permission != 4){
return "권한이 부족합니다.";
}
$nationID = $me['nation'];
$db->update('nation', [
'scout' => $value?1:0,
], 'nation=%i', $nationID);
return [
'result' => true
];
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace sammo\API\Nation;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\KVStorage;
use sammo\Validator;
use sammo\WebUtil;
use function sammo\checkSecretPermission;
class SetBlockWar extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', [
'value',
])
->rule('boolean', 'value');
if (!$v->validate()) {
return $v->errorStr();
}
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$value = $this->args['value'];
$userID = $session->userID;
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,nation,`officer_level`,permission,penalty FROM general WHERE `owner`=%i', $userID);
$permission = checkSecretPermission($me, false);
if($permission < 0){
return "권한이 부족합니다.";
}
if ($me['officer_level'] < 5 && $permission != 4){
return "권한이 부족합니다.";
}
$nationID = $me['nation'];
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
$avilableCnt = $nationStor->getValue('available_war_setting_cnt') ?? 0;
if ($avilableCnt <= 0){
return "잔여 횟수가 부족합니다.";
}
$db->update('nation', [
'war' => $value?1:0,
], 'nation=%i', $nationID);
$nationStor->setValue('available_war_setting_cnt', $avilableCnt - 1);
return [
'result' => true,
'availableCnt' => $avilableCnt - 1,
];
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace sammo\API\Nation;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\KVStorage;
use sammo\Validator;
use sammo\WebUtil;
use function sammo\checkSecretPermission;
class SetRate extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', [
'amount',
])
->rule('integer', 'amount')
->rule('min', 'amount', 5)
->rule('max', 'amount', 30);
if (!$v->validate()) {
return $v->errorStr();
}
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$amount = $this->args['amount'];
$userID = $session->userID;
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,nation,`officer_level`,permission,penalty FROM general WHERE `owner`=%i', $userID);
$permission = checkSecretPermission($me, false);
if($permission < 0){
return "권한이 부족합니다.";
}
if ($me['officer_level'] < 5 && $permission != 4){
return "권한이 부족합니다.";
}
$nationID = $me['nation'];
$db->update('nation', [
'rate' => $amount
], 'nation=%i', $nationID);
return [
'result' => true
];
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace sammo\API\Nation;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\KVStorage;
use sammo\Validator;
use sammo\WebUtil;
use function sammo\checkSecretPermission;
class SetSecretLimit extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', [
'amount',
])
->rule('integer', 'amount')
->rule('min', 'amount', 1)
->rule('max', 'amount', 99);
if (!$v->validate()) {
return $v->errorStr();
}
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$amount = $this->args['amount'];
$userID = $session->userID;
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,nation,`officer_level`,permission,penalty FROM general WHERE `owner`=%i', $userID);
$permission = checkSecretPermission($me, false);
if($permission < 0){
return "권한이 부족합니다.";
}
if ($me['officer_level'] < 5 && $permission != 4){
return "권한이 부족합니다.";
}
$nationID = $me['nation'];
$db->update('nation', [
'bill' => $amount
], 'nation=%i', $nationID);
return [
'result' => true
];
}
}
@@ -0,0 +1,32 @@
<?php
namespace sammo\ActionItem;
use \sammo\iAction;
use \sammo\General;
use sammo\WarUnit;
class che_명마_07_백상 extends \sammo\BaseStatItem{
protected $cost = 200;
protected $buyable = false;
public function __construct()
{
parent::__construct();
$this->info .= "<br>[전투] 공격력 +20%, 소모 군량 +10%, 공격 시 페이즈 -1";
}
public function onCalcStat(General $general, string $statName, $value, $aux=null){
$value = parent::onCalcStat($general, $statName, $value, $aux);
if($statName == 'killRice'){
return $value * 1.1;
}
if($statName === 'initWarPhase'){
return $value - 1;
}
return $value;
}
public function getWarPowerMultiplier(WarUnit $unit): array
{
return [1.2, 1];
}
}
@@ -0,0 +1,7 @@
<?php
namespace sammo\ActionItem;
class che_명마_07_오환마 extends \sammo\BaseStatItem{
protected $cost = 200;
protected $buyable = false;
}
@@ -0,0 +1,7 @@
<?php
namespace sammo\ActionItem;
class che_명마_08_흉노마 extends \sammo\BaseStatItem{
protected $cost = 200;
protected $buyable = false;
}
@@ -0,0 +1,9 @@
<?php
namespace sammo\ActionItem;
use \sammo\iAction;
use \sammo\General;
class che_명마_09_의남백마 extends \sammo\BaseStatItem{
protected $cost = 200;
protected $buyable = false;
}
@@ -0,0 +1,9 @@
<?php
namespace sammo\ActionItem;
use \sammo\iAction;
use \sammo\General;
class che_명마_10_옥추마 extends \sammo\BaseStatItem{
protected $cost = 200;
protected $buyable = false;
}
@@ -0,0 +1,9 @@
<?php
namespace sammo\ActionItem;
use \sammo\iAction;
use \sammo\General;
class che_명마_11_화종마 extends \sammo\BaseStatItem{
protected $cost = 200;
protected $buyable = false;
}
@@ -0,0 +1,27 @@
<?php
namespace sammo\ActionItem;
use \sammo\iAction;
use \sammo\General;
class che_명마_12_옥란백용구 extends \sammo\BaseStatItem{
protected $cost = 200;
protected $buyable = false;
public function __construct()
{
parent::__construct();
$this->info .= "<br>[전투] 남은 병력에 따라 회피 확률 증가. 최대 +30%p";
}
public function onCalcStat(General $general, string $statName, $value, $aux = null)
{
$value = parent::onCalcStat($general, $statName, $value, $aux);
if($statName == 'warAvoidRatio'){
$leadership = $general->getLeadership(true, true, true, false);
$crewL = $general->getVar('crew') / 100;
return $value + \sammo\Util::valueFit((1 - $crewL / $leadership) * 0.3, 0, 0.3);
}
return $value;
}
}
@@ -15,7 +15,7 @@ class che_무기_02_단궁 extends \sammo\BaseStatItem{
public function __construct()
{
parent::__construct();
$this->info .= "<br>[전투] 새로운 상대와 전투 시 1% 확률로 저격 발동, 성공 시 사기+10";
$this->info .= "<br>[전투] 새로운 상대와 전투 시 1% 확률로 저격 발동, 성공 시 사기+20";
}
public function getBattlePhaseSkillTriggerList(WarUnit $unit):?WarUnitTriggerCaller{
@@ -14,7 +14,7 @@ class che_무기_07_맥궁 extends \sammo\BaseStatItem{
public function __construct()
{
parent::__construct();
$this->info .= "<br>[전투] 새로운 상대와 전투 시 20% 확률로 저격 발동, 성공 시 사기+10";
$this->info .= "<br>[전투] 새로운 상대와 전투 시 20% 확률로 저격 발동, 성공 시 사기+20";
}
public function getBattlePhaseSkillTriggerList(WarUnit $unit):?WarUnitTriggerCaller{
@@ -28,7 +28,7 @@ class che_치트_HideD의_사인검 extends \sammo\BaseItem
통솔 +100,무력 +100, 지력 +100<br>
[군사] 매 턴마다 자신(100%)과 소속 도시 장수(적 포함 50%) 부상 회복<br>
[전투] 페이즈마다 20% 확률로 치료 발동(아군 피해 1/3 감소)<br>
[전투] 새로운 상대와 전투 시 1/2 확률로 저격 발동, 성공 시 사기+10<br>
[전투] 새로운 상대와 전투 시 1/2 확률로 저격 발동, 성공 시 사기+20<br>
[전투] 훈련 보정 +30, 사기 보정 +30<br>
[전투] 상대방 필살 및 회피 시도시 일정 확률로 격노(필살) 발동, 공격 시 일정 확률로 진노(1페이즈 추가)<br>
[전투] 상대 필살, 격노, 위압, 저격 불가, 상대 계략 시도시 성공 확률 -10%p, 부상 없음, 아군 피해 -5%<br>
@@ -27,7 +27,7 @@ class event_전투특기_격노 extends \sammo\BaseItem{
public function getBattlePhaseSkillTriggerList(WarUnit $unit):?WarUnitTriggerCaller{
return new WarUnitTriggerCaller(
new che_격노시도($unit, che_격노시도::TYPE_ITEM),
new che_격노발동($unit, che_격노발동::TYPE_ITEM)
new che_격노발동($unit)
);
}
}
+3 -3
View File
@@ -200,8 +200,8 @@ class che_임관 extends Command\GeneralCommand
$rawNationList = Util::convertArrayToDict($db->query('SELECT nation,`name`,color,gennum,`power` FROM nation'), 'nation');
$scoutMsgs = KVStorage::getValuesFromInterNamespace($db, 'nation_env', 'scout_msg');
foreach ($scoutMsgs as $nationID => $scoutMsg) {
$rawNationList[$nationID]['scoutmsg'] = $scoutMsg;
foreach ($scoutMsgs as $destNationID => $scoutMsg) {
$rawNationList[$destNationID]['scoutmsg'] = $scoutMsg;
}
foreach ($rawNationList as $destNation) {
$testCommand = new static($generalObj, $this->env, ['destNationID' => $destNation['nation']]);
@@ -216,7 +216,7 @@ class che_임관 extends Command\GeneralCommand
if (!$testCommand->hasFullConditionMet()) {
$nationTarget['notAvailable'] = true;
}
if ($destNation['id'] == $nationID) {
if ($destNation['nation'] == $nationID) {
$nationTarget['notAvailable'] = true;
}
+1
View File
@@ -150,6 +150,7 @@ class che_징병 extends Command\GeneralCommand
$reqGold = $this->generalObj->onCalcDomestic('징병', 'cost', $reqGold, ['armType' => $this->reqCrewType->armType]);
$reqGold *= static::$costOffset;
$reqRice = $this->maxCrew / 100;
$reqRice = $this->generalObj->onCalcDomestic('징병', 'rice', $reqRice, ['armType' => $this->reqCrewType->armType]);
$reqGold = Util::round($reqGold);
$reqRice = Util::round($reqRice);
+2 -2
View File
@@ -171,7 +171,7 @@ class che_급습 extends Command\NationCommand
$broadcastMessage = "아국에 <M>{$commandName}</>{$josaYiCommand} 발동되었습니다.";
$destNationGeneralList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND no != %i', $nationID, $generalID);
$destNationGeneralList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i', $destNationID);
foreach ($destNationGeneralList as $destNationGeneralID) {
$destNationGeneralLogger = new ActionLogger($destNationGeneralID, $destNationID, $year, $month);
$destNationGeneralLogger->pushGeneralActionLog($broadcastMessage, ActionLogger::PLAIN);
@@ -214,7 +214,7 @@ class che_급습 extends Command\NationCommand
if (!$testCommand->hasFullConditionMet()) {
$nationTarget['notAvailable'] = true;
}
if ($destNation['id'] == $nationID) {
if ($destNation['nation'] == $nationID) {
$nationTarget['notAvailable'] = true;
}
@@ -248,7 +248,7 @@ class che_불가침제의 extends Command\NationCommand
if (!$testCommand->hasFullConditionMet()) {
$nationTarget['notAvailable'] = true;
}
if ($destNation['id'] == $nationID) {
if ($destNation['nation'] == $nationID) {
$nationTarget['notAvailable'] = true;
}
@@ -193,7 +193,7 @@ class che_불가침파기제의 extends Command\NationCommand{
if($diplomacyStatus[$destNation['nation']]['state'] != 7){
$nationTarget['notAvailable'] = true;
}
if ($destNation['id'] == $nationID) {
if ($destNation['nation'] == $nationID) {
$nationTarget['notAvailable'] = true;
}
@@ -212,7 +212,7 @@ class che_선전포고 extends Command\NationCommand
if (!$testCommand->hasFullConditionMet()) {
$nationTarget['notAvailable'] = true;
}
if ($destNation['id'] == $nationID) {
if ($destNation['nation'] == $nationID) {
$nationTarget['notAvailable'] = true;
}
@@ -168,7 +168,7 @@ class che_이호경식 extends Command\NationCommand
$broadcastMessage = "아국에 <M>{$commandName}</>{$josaYiCommand} 발동되었습니다.";
$destNationGeneralList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND no != %i', $destNationID, $generalID);
$destNationGeneralList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i', $destNationID);
foreach ($destNationGeneralList as $destNationGeneralID) {
$destNationGeneralLogger = new ActionLogger($destNationGeneralID, $destNationID, $year, $month);
$destNationGeneralLogger->pushGeneralActionLog($broadcastMessage, ActionLogger::PLAIN);
@@ -217,7 +217,7 @@ class che_이호경식 extends Command\NationCommand
if (!$testCommand->hasFullConditionMet()) {
$nationTarget['notAvailable'] = true;
}
if ($destNation['id'] == $nationID) {
if ($destNation['nation'] == $nationID) {
$nationTarget['notAvailable'] = true;
}
@@ -186,7 +186,7 @@ class che_종전제의 extends Command\NationCommand{
if (!$testCommand->hasFullConditionMet()) {
$nationTarget['notAvailable'] = true;
}
if ($destNation['id'] == $nationID) {
if ($destNation['nation'] == $nationID) {
$nationTarget['notAvailable'] = true;
}
@@ -212,7 +212,7 @@ class che_피장파장 extends Command\NationCommand
$broadcastMessage = "아국에 <G><b>{$cmd->getName()}</b></> 전략의 <M>{$commandName}</>{$josaYiCommand} 발동되었습니다.";
$destNationGeneralList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND no != %i', $destNationID, $generalID);
$destNationGeneralList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i', $destNationID);
foreach ($destNationGeneralList as $destNationGeneralID) {
$destNationGeneralLogger = new ActionLogger($destNationGeneralID, $destNationID, $year, $month);
$destNationGeneralLogger->pushGeneralActionLog($broadcastMessage, ActionLogger::PLAIN);
@@ -255,7 +255,7 @@ class che_피장파장 extends Command\NationCommand
if (!$testCommand->hasFullConditionMet()) {
$nationTarget['notAvailable'] = true;
}
if ($destNation['id'] == $nationID) {
if ($destNation['nation'] == $nationID) {
$nationTarget['notAvailable'] = true;
}
+1 -1
View File
@@ -50,7 +50,7 @@ class AllowJoinDestNation extends Constraint{
return false;
}
if($this->general['npc']??2 < 2 && \sammo\Util::starts_with($this->destNation['name'], 'ⓤ')){
if(($this->general['npc']??2 < 2) && \str_starts_with($this->destNation['name'], 'ⓤ')){
$this->reason = "유저장은 태수국에 임관할 수 없습니다.";
return false;
}
+1 -1
View File
@@ -25,7 +25,7 @@ class RaiseNPCNation extends \sammo\Event\Action
}
const CITY_KEYS = ['pop', 'agri', 'comm', 'secu', 'def', 'wall'];
const MIN_DIST_USERNATION = 4;
const MIN_DIST_USERNATION = 3;
const MIN_DIST_NPCNATION = 2;
private function calcAvgNationCity(array $cities)
+15 -13
View File
@@ -221,29 +221,31 @@ class GameConstBase
'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_명마_08_양주마' => 2, 'che_명마_09_과하마' => 2,
'che_명마_10_대완마' => 2, 'che_명마_11_서량마' => 2,
'che_명마_12_사륜거' => 2, 'che_명마_13_절영' => 1, 'che_명마_13_적로' => 1,
'che_명마_14_적란마' => 1, 'che_명마_14_조황비전' => 1, 'che_명마_15_한혈마' => 1, 'che_명마_15_적토마' => 1,
'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_동추' => 1, 'che_무기_07_철편' => 1, 'che_무기_07_철쇄' => 1, 'che_무기_07_맥궁' => 1,
'che_무기_08_유성추' => 1, 'che_무기_08_철질여골' => 1, 'che_무기_09_쌍철극' => 1, 'che_무기_09_동호비궁' => 1, 'che_무기_10_삼첨도' => 1, 'che_무기_10_대부' => 1, 'che_무기_11_고정도' => 1, 'che_무기_11_이광궁' => 1, 'che_무기_12_철척사모' => 1, 'che_무기_12_칠성검' => 1, 'che_무기_13_사모' => 1, 'che_무기_13_양유기궁' => 1,
'che_무기_14_언월도' => 1, 'che_무기_14_방천화극' => 1, 'che_무기_15_청홍검' => 1, 'che_무기_15_의천검' => 1
'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_위료자' => 1, 'che_서적_07_사마법' => 1, 'che_서적_07_한서' => 1, 'che_서적_07_논어' => 1,
'che_서적_08_전론' => 1, 'che_서적_08_사기' => 1, 'che_서적_09_장자' => 1, 'che_서적_09_역경' => 1,
'che_서적_10_시경' => 1, 'che_서적_10_구국론' => 1, 'che_서적_11_상군서' => 1, 'che_서적_11_춘추전' => 1,
'che_서적_12_산해경' => 1, 'che_서적_12_맹덕신서' => 1, 'che_서적_13_관자' => 1, 'che_서적_13_병법24편' => 1,
'che_서적_14_한비자' => 1, 'che_서적_14_오자병법' => 1, 'che_서적_15_노자' => 1, 'che_서적_15_손자병법' => 1,
'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,
+8 -8
View File
@@ -161,7 +161,7 @@ class GameUnitConstBase{
[self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2],
[self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8],
['표준적인 기병입니다.','기병은 공격특화입니다.'],
null, null
null, ['che_기병병종전투']
],
[
1301, self::T_CAVALRY, '백마병',
@@ -170,7 +170,7 @@ class GameUnitConstBase{
[self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2],
[self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8],
['백마의 위용을 보여줍니다.'],
null, null
null, ['che_기병병종전투']
],
[
1302, self::T_CAVALRY, '중장기병',
@@ -179,7 +179,7 @@ class GameUnitConstBase{
[self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2],
[self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8],
['갑주를 두른 기병입니다.'],
null, null
null, ['che_기병병종전투']
],
[
1303, self::T_CAVALRY, '돌격기병',
@@ -188,7 +188,7 @@ class GameUnitConstBase{
[self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2],
[self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8],
['저돌적으로 공격합니다.'],
null, null
null, ['che_기병병종전투']
],
[
1304, self::T_CAVALRY, '철기병',
@@ -197,7 +197,7 @@ class GameUnitConstBase{
[self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2],
[self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8],
['철갑을 두른 기병입니다.'],
null, null
null, ['che_기병병종전투']
],
[
1305, self::T_CAVALRY, '수렵기병',
@@ -206,7 +206,7 @@ class GameUnitConstBase{
[self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2],
[self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8],
['날쎄고 빠른 기병입니다.'],
null, null
null, ['che_기병병종전투']
],
[
1306, self::T_CAVALRY, '맹수병',
@@ -215,7 +215,7 @@ class GameUnitConstBase{
[self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2],
[self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8],
['어느 누구보다 강력합니다.'],
null, null
null, ['che_기병병종전투']
],
[
1307, self::T_CAVALRY, '호표기병',
@@ -224,7 +224,7 @@ class GameUnitConstBase{
[self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2],
[self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8],
['정예 기병입니다.'],
null, null
null, ['che_기병병종전투']
],
[
+1
View File
@@ -286,6 +286,7 @@ class WarUnitGeneral extends WarUnit
$rice *= $this->crewType->rice;
$rice *= getTechCost($this->getNationVar('tech'));
$rice = $this->general->onCalcStat($this->general, 'killRice', $rice);
$general->increaseVarWithLimit('rice', -$rice, 0);
@@ -0,0 +1,25 @@
<?php
namespace sammo\WarUnitTrigger;
use sammo\BaseWarUnitTrigger;
use sammo\WarUnitGeneral;
use sammo\WarUnitCity;
use sammo\WarUnit;
use sammo\GameUnitDetail;
use sammo\Util;
use sammo\ObjectTrigger;
class che_기병병종전투 extends BaseWarUnitTrigger{
protected $priority = ObjectTrigger::PRIORITY_FINAL; //최후미
protected function actionWar(WarUnit $self, WarUnit $oppose, array &$selfEnv, array &$opposeEnv):bool{
if(!$self->isAttacker()){
$oppose->multiplyWarPowerMultiply(1/0.95);
$self->multiplyWarPowerMultiply(0.95);
}
if($oppose instanceof WarUnitCity){
$self->multiplyWarPowerMultiply(0.9);
}
return true;
}
}
+63 -56
View File
@@ -26,19 +26,26 @@
"che_명마_05_갈색마": 0,
"che_명마_06_흑색마": 0,
"che_명마_07_백마": 2,
"che_명마_07_기주마": 2,
"che_명마_08_양주마": 2,
"che_명마_09_과하마": 2,
"che_명마_10_대완마": 2,
"che_명마_11_서량마": 2,
"che_명마_12_사륜거": 2,
"che_명마_13_절영": 1,
"che_명마_13_적로": 1,
"che_명마_14_적란마": 1,
"che_명마_14_조황비전": 1,
"che_명마_15_한혈마": 1,
"che_명마_15_적토마": 1
"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,
@@ -48,26 +55,26 @@
"che_무기_05_죽창": 0,
"che_무기_06_소부": 0,
"che_무기_07_동추": 1,
"che_무기_07_철편": 1,
"che_무기_07_철쇄": 1,
"che_무기_07_맥궁": 1,
"che_무기_08_유성추": 1,
"che_무기_08_철질여골": 1,
"che_무기_09_쌍철극": 1,
"che_무기_09_동호비궁": 1,
"che_무기_10_삼첨도": 1,
"che_무기_10_대부": 1,
"che_무기_11_고정도": 1,
"che_무기_11_이광궁": 1,
"che_무기_12_철척사모": 1,
"che_무기_12_칠성검": 1,
"che_무기_13_사모": 1,
"che_무기_13_양유기궁": 1,
"che_무기_14_언월도": 1,
"che_무기_14_방천화극": 1,
"che_무기_15_청홍검": 1,
"che_무기_15_의천검": 1
"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,
@@ -77,26 +84,26 @@
"che_서적_05_여씨춘추": 0,
"che_서적_06_사민월령": 0,
"che_서적_07_위료자": 1,
"che_서적_07_사마법": 1,
"che_서적_07_한서": 1,
"che_서적_07_논어": 1,
"che_서적_08_전론": 1,
"che_서적_08_사기": 1,
"che_서적_09_장자": 1,
"che_서적_09_역경": 1,
"che_서적_10_시경": 1,
"che_서적_10_구국론": 1,
"che_서적_11_상군서": 1,
"che_서적_11_춘추전": 1,
"che_서적_12_산해경": 1,
"che_서적_12_맹덕신서": 1,
"che_서적_13_관자": 1,
"che_서적_13_병법24편": 1,
"che_서적_14_한비자": 1,
"che_서적_14_오자병법": 1,
"che_서적_15_노자": 1,
"che_서적_15_손자병법": 1
"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,
@@ -129,7 +136,7 @@
}
},
"iconPath": ".",
"fiction": 1,
"fiction": 2,
"nation": [],
"diplomacy": [],
"general": [
@@ -281,7 +288,7 @@
[0, "오반", null, 0, null, 74, 71, 50, 0, 160, 300, null, null],
[0, "고옹", null, 0, null, 52, 18, 81, 0, 160, 300, null, null],
[0, "오란", null, 0, null, 68, 80, 35, 0, 160, 300, null, null],
[0, "채화", null, 0, null, 39, 50, 1, 0, 160, 300, null, null],
[0, "채화", null, 0, null, 39, 50, 2, 0, 160, 300, null, null],
[0, "채중", null, 0, null, 40, 54, 2, 0, 160, 300, null, null],
[0, "채모", null, 0, null, 70, 55, 77, 0, 160, 300, null, null],
[0, "착융", null, 0, null, 60, 69, 38, 0, 160, 300, null, null],
@@ -694,7 +701,7 @@
[0, "성만", null, 0, null, 61, 68, 46, 0, 160, 300, null, null],
[0, "오연", null, 0, null, 66, 73, 42, 0, 160, 300, null, null],
[0, "맹충", null, 0, null, 47, 28, 72, 0, 160, 300, null, null],
[0, "잠혼", null, 0, null, 1, 1, 22, 0, 160, 300, null, null],
[0, "잠혼", null, 0, null, 2, 2, 22, 0, 160, 300, null, null],
[0, "사정", null, 0, null, 64, 76, 34, 0, 160, 300, null, null],
[0, "심영", null, 0, null, 70, 77, 55, 0, 160, 300, null, null],
[0, "도준", null, 0, null, 65, 66, 46, 0, 160, 300, null, null],
+15 -15
View File
@@ -19,31 +19,31 @@
"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_명마_08_양주마":2, "che_명마_09_과하마":2,
"che_명마_10_대완마":2, "che_명마_11_서량마":2,
"che_명마_12_사륜거":2, "che_명마_13_절영":1, "che_명마_13_적로":1,
"che_명마_14_적란마":1, "che_명마_14_조황비전":1, "che_명마_15_한혈마":1, "che_명마_15_적토마":1
"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_동추":1, "che_무기_07_철편":1, "che_무기_07_철쇄":1, "che_무기_07_맥궁":1,
"che_무기_08_유성추":1, "che_무기_08_철질여골":1, "che_무기_09_쌍철극":1, "che_무기_09_동호비궁":1,
"che_무기_10_삼첨도":1, "che_무기_10_대부":1, "che_무기_11_고정도":1, "che_무기_11_이광궁":1,
"che_무기_12_철척사모":1, "che_무기_12_칠성검":1, "che_무기_13_사모":1, "che_무기_13_양유기궁":1,
"che_무기_14_언월도":1, "che_무기_14_방천화극":1, "che_무기_15_청홍검":1, "che_무기_15_의천검":1
"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_위료자":1, "che_서적_07_사마법":1, "che_서적_07_한서":1, "che_서적_07_논어":1,
"che_서적_08_전론":1, "che_서적_08_사기":1, "che_서적_09_장자":1, "che_서적_09_역경":1,
"che_서적_10_시경":1, "che_서적_10_구국론":1, "che_서적_11_상군서":1, "che_서적_11_춘추전":1,
"che_서적_12_산해경":1, "che_서적_12_맹덕신서":1, "che_서적_13_관자":1, "che_서적_13_병법24편":1,
"che_서적_14_한비자":1, "che_서적_14_오자병법":1, "che_서적_15_노자":1, "che_서적_15_손자병법":1
"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,
+295 -295
View File
@@ -16,420 +16,420 @@ class GameUnitConst extends GameUnitConstBase
protected static $_buildData = [
[
1000, self::T_CASTLE, '성벽',
100, 100, 7, 0, 0, 99, 9,
999999, null, null, 999999,
100, 100, 7, 0, 0, 99, 9,
999999, null, null, 999999,
[],//성벽은 공격할 수 없다.
[self::T_FOOTMAN=>1.2],
['성벽입니다.','생성할 수 없습니다.'],
null, null
],
[
217100, self::T_FOOTMAN, '보병',
100, 150, 7, 10, 0, 9, 9,
0, null, null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217100, self::T_FOOTMAN, '보병',
100, 150, 7, 10, 0, 9, 9,
0, null, null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["표준적인 보병입니다. 보병은 방어특화입니다."],
null, null
null, ['che_방어력증가5p']
],
[
217101, self::T_FOOTMAN, '마물병',
110, 160, 7, 10, 0, 9, 10,
0, null, ["호넷 마인령","케이브리스 마인령"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217101, self::T_FOOTMAN, '마물병',
110, 160, 7, 10, 0, 9, 10,
0, null, ["호넷 마인령","케이브리스 마인령"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["마군 지역 기본병종입니다. 조금더 강하지만 쌀을 많이 소비합니다."],
null, null
null, ['che_방어력증가5p']
],
[
217102, self::T_FOOTMAN, '중장보병',
100, 250, 7, 5, 0, 13, 10,
1000, null, ["헬만 동부"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217102, self::T_FOOTMAN, '중장보병',
100, 250, 7, 5, 0, 13, 10,
1000, null, ["헬만 동부"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["헬만 특유의 견고한 보병입니다."],
null, null
null, ['che_방어력증가5p']
],
[
217103, self::T_FOOTMAN, '흑의 군 보병',
150, 150, 7, 10, 0, 12, 11,
1000, null, ["리자스 남부","리자스 북부"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217103, self::T_FOOTMAN, '흑의 군 보병',
150, 150, 7, 10, 0, 12, 11,
1000, null, ["리자스 남부","리자스 북부"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["흑의 군 전통의 체계적인 훈련으로 공격력을 보완했습니다."],
null, null
null, ['che_방어력증가5p']
],
[
217104, self::T_FOOTMAN, '용병',
125, 175, 7, 10, 0, 12, 11,
1000, null, ["자유도시 동부"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217104, self::T_FOOTMAN, '용병',
125, 175, 7, 10, 0, 12, 11,
1000, null, ["자유도시 동부"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["일반 보병보다 비싸지만 받은 만큼은 일해줍니다."],
null, null
null, ['che_방어력증가5p']
],
[
217105, self::T_FOOTMAN, '사메라이',
175, 150, 7, 0, 0, 11, 10,
1000, null, ["제스 남부"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217105, self::T_FOOTMAN, '사메라이',
175, 150, 7, 0, 0, 11, 10,
1000, null, ["제스 남부"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["검으로 난무를 가하는 몬스터 보병입니다."],
null, null
null, ['che_방어력증가5p']
],
[
217106, self::T_FOOTMAN, '템플나이트병',
75, 275, 7, 0, 0, 11, 12,
1000, null, ["AL교"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217106, self::T_FOOTMAN, '템플나이트병',
75, 275, 7, 0, 0, 11, 12,
1000, null, ["AL교"], 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["공격을 포기하고 오직 방어에만 집중합니다."],
null, null
null, ['che_방어력증가5p']
],
[
217107, self::T_FOOTMAN, '메이드병',
150, 150, 7, 20, 0, 13, 11,
2000, ["메이드의 묘지"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217107, self::T_FOOTMAN, '메이드병',
150, 150, 7, 20, 0, 13, 11,
2000, ["메이드의 묘지"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["켓셀링크 휘하의 날렵한 전투메이드입니다."],
null, null
null, ['che_방어력증가5p']
],
[
217108, self::T_FOOTMAN, '요괴병',
150, 150, 8, 0, 0, 11, 13,
2000, ["오슈"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217108, self::T_FOOTMAN, '요괴병',
150, 150, 8, 0, 0, 11, 13,
2000, ["오슈"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["단단한 육체를 믿고 적진을 돌파합니다."],
null, null
null, ['che_방어력증가5p']
],
[
217109, self::T_FOOTMAN, '리자스 친위병',
150, 200, 7, 10, 0, 12, 12,
3000, ["리자스성"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217109, self::T_FOOTMAN, '리자스 친위병',
150, 200, 7, 10, 0, 12, 12,
3000, ["리자스성"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["여왕을 수호하는 리자스 최강의 보병 정예보병입니다."],
null, null
null, ['che_방어력증가5p']
],
[
217110, self::T_FOOTMAN, '케이브리스 마물병',
200, 150, 7, 5, 0, 13, 14,
3000, ["케이브리스의 성"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217110, self::T_FOOTMAN, '케이브리스 마물병',
200, 150, 7, 5, 0, 13, 14,
3000, ["케이브리스의 성"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["케이브리스 직속의 마물 정예 전투보병입니다."],
null, null
null, ['che_방어력증가5p']
],
[
217111, self::T_FOOTMAN, '투신',
200, 300, 7, 5, 0, 28, 23,
5000, ["투신도시"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217111, self::T_FOOTMAN, '투신',
200, 300, 7, 5, 0, 28, 23,
5000, ["투신도시"], null, 0,
[self::T_ARCHER=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[self::T_ARCHER=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["성마교단 유적에서 발굴된 사상 최강의 보병입니다."],
null, null
null, ['che_방어력증가5p']
],
[
217200, self::T_ARCHER, '궁병',
100, 100, 7, 20, 0, 10, 10,
0, null, null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
[
217200, self::T_ARCHER, '궁병',
100, 100, 7, 20, 0, 10, 10,
0, null, null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
["표준적인 궁병입니다. 궁병은 회피특화입니다."],
null, null
],
[
217201, self::T_ARCHER, '투척마물병',
150, 100, 7, 10, 0, 11, 12,
1000, null, ["호넷 마인령"], 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
[
217201, self::T_ARCHER, '투척마물병',
150, 100, 7, 10, 0, 11, 12,
1000, null, ["호넷 마인령"], 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
["날렵함을 포기하고 도끼를 투척합니다."],
null, null
],
[
217202, self::T_ARCHER, '백의 군 궁병',
100, 100, 8, 30, 0, 11, 11,
1000, null, ["리자스 남부"], 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
[
217202, self::T_ARCHER, '백의 군 궁병',
100, 100, 8, 30, 0, 11, 11,
1000, null, ["리자스 남부"], 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
["전략을 활용하여 치고 빠지기에 능합니다."],
null, null
],
[
217203, self::T_ARCHER, '벌레술사병',
100, 150, 7, 20, 0, 12, 12,
2000, ["호박성"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
[
217203, self::T_ARCHER, '벌레술사병',
100, 150, 7, 20, 0, 12, 12,
2000, ["호박성"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
["벌레들을 부려 몸을 보호하는 궁병입니다."],
null, null
],
[
217204, self::T_ARCHER, '저격암살병',
225, 75, 5, 10, 0, 14, 11,
2000, ["블라디보스토크"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
[
217204, self::T_ARCHER, '저격암살병',
225, 75, 5, 10, 0, 14, 11,
2000, ["블라디보스토크"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
["독을 바른 탄환으로 적의 목숨을 앗아갑니다."],
null, null
],
[
217205, self::T_ARCHER, '호루스병',
150, 100, 8, 20, 0, 11, 12,
2000, ["거대전함"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
[
217205, self::T_ARCHER, '호루스병',
150, 100, 8, 20, 0, 11, 12,
2000, ["거대전함"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
["민첩하게 파고드는 호루스족 궁병입니다."],
null, null
],
[
217206, self::T_ARCHER, '튤립병',
200, 100, 7, 20, 0, 13, 13,
3000, ["커스텀"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
[
217206, self::T_ARCHER, '튤립병',
200, 100, 7, 20, 0, 13, 13,
3000, ["커스텀"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
["최첨단병기 튤립으로 적을 폭격합니다."],
null, null
],
[
217207, self::T_ARCHER, '아이스플레임 궁병',
150, 150, 7, 20, 0, 13, 13,
3000, ["라그나로크 아크"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
[
217207, self::T_ARCHER, '아이스플레임 궁병',
150, 150, 7, 20, 0, 13, 13,
3000, ["라그나로크 아크"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
["혁명을 성공으로 이끈 정예 게릴라 궁병입니다."],
null, null
],
[
217208, self::T_ARCHER, '카라 궁병',
225, 225, 7, 30, 0, 25, 25,
5000, ["펜실 카우"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
[
217208, self::T_ARCHER, '카라 궁병',
225, 225, 7, 30, 0, 25, 25,
5000, ["펜실 카우"], null, 0,
[ self::T_FOOTMAN=>0.8, self::T_CAVALRY=>1.2, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>1.2, self::T_CAVALRY=>0.8, self::T_SIEGE=>0.8, 217502=>1],
["선조의 힘을 이어받은 최강의 카라 정예 궁병입니다."],
null, null
],
[
217300, self::T_CAVALRY, '기동병',
150, 100, 7, 5, 0, 11, 11,
0, null, null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217300, self::T_CAVALRY, '기동병',
150, 100, 7, 5, 0, 11, 11,
0, null, null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["표준적인 기동병입니다. 기동병은 공격특화입니다."],
null, null
null, ['che_기병병종전투']
],
[
217301, self::T_CAVALRY, '하치온나',
175, 100, 7, 15, 0, 12, 12,
1000, null, ["자유도시 서부"], 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217301, self::T_CAVALRY, '하치온나',
175, 100, 7, 15, 0, 12, 12,
1000, null, ["자유도시 서부"], 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["작은 몸으로 회피하며 따끔한 일격을 먹이는 몬스터 기동병입니다."],
null, null
null, ['che_기병병종전투']
],
[
217302, self::T_CAVALRY, '적의 군 기동병',
200, 100, 7, 5, 0, 12, 13,
1000, null, ["리자스 북부"], 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217302, self::T_CAVALRY, '적의 군 기동병',
200, 100, 7, 5, 0, 12, 13,
1000, null, ["리자스 북부"], 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["리자스군의 자랑인 기동부대입니다."],
null, null
null, ['che_기병병종전투']
],
[
217303, self::T_CAVALRY, '안드로이드 기동병',
175, 150, 7, 10, 0, 13, 12,
2000, ["악의 탑"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217303, self::T_CAVALRY, '안드로이드 기동병',
175, 150, 7, 10, 0, 13, 12,
2000, ["악의 탑"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["파이아르가 개발한 최첨단 안드로이드 기동병입니다."],
null, null
null, ['che_기병병종전투']
],
[
217304, self::T_CAVALRY, '파란쵸 기동병',
200, 100, 8, 5, 0, 13, 11,
2000, ["파란쵸 왕국"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217304, self::T_CAVALRY, '파란쵸 기동병',
200, 100, 8, 5, 0, 13, 11,
2000, ["파란쵸 왕국"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["적진을 일점돌파하는 파란쵸왕국의 돌격 기동병입니다. "],
null, null
null, ['che_기병병종전투']
],
[
217305, self::T_CAVALRY, '비행마물병',
150, 100, 8, 15, 0, 12, 12,
2000, ["죽음의 대지"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217305, self::T_CAVALRY, '비행마물병',
150, 100, 8, 15, 0, 12, 12,
2000, ["죽음의 대지"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["비행마물에 올라탄 기동마물병입니다."],
null, null
null, ['che_기병병종전투']
],
[
217306, self::T_CAVALRY, '마물조련사병',
250, 200, 6, 0, 0, 16, 16,
3000, ["랑그바우"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217306, self::T_CAVALRY, '마물조련사병',
250, 200, 6, 0, 0, 16, 16,
3000, ["랑그바우"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["무시무시한 몬스터에 올라타서 싸우는 극강의 기동병입니다."],
null, null
null, ['che_기병병종전투']
],
[
217307, self::T_CAVALRY, '기마병',
200, 150, 7, 5, 0, 14, 14,
3000, ["오와리"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217307, self::T_CAVALRY, '기마병',
200, 150, 7, 5, 0, 14, 14,
3000, ["오와리"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["JAPAN 특유의 기마에 올라타 적을 짓밟는 기동병입니다."],
null, null
null, ['che_기병병종전투']
],
[
217308, self::T_CAVALRY, '엔젤나이트',
300, 200, 7, 5, 0, 25, 25,
5000, ["떨어진 궁도"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
[
217308, self::T_CAVALRY, '엔젤나이트',
300, 200, 7, 5, 0, 25, 25,
5000, ["떨어진 궁도"], null, 0,
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>0.8, self::T_SIEGE=>1.2, 217502=>1],
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>1.2, self::T_SIEGE=>0.8, 217502=>1],
["신의 명령으로 파멸을 내리기 위해 강림했습니다."],
null, null
null, ['che_기병병종전투']
],
[
217400, self::T_WIZARD, '마법병',
80, 80, 7, 5, 0.6, 9, 9,
0, null, null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217400, self::T_WIZARD, '마법병',
80, 80, 7, 5, 0.6, 9, 9,
0, null, null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["마법을 사용하는 병종입니다."],
null, null
],
[
217401, self::T_WIZARD, '마물 마법병',
90, 90, 7, 0, 0.6, 11, 9,
0, null, ["호넷 마인령","케이브리스 마인령"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217401, self::T_WIZARD, '마물 마법병',
90, 90, 7, 0, 0.6, 11, 9,
0, null, ["호넷 마인령","케이브리스 마인령"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["마군 지역 기본병종입니다. 조금더 강하지만 금을 많이 소비합니다."],
null, null
],
[
217402, self::T_WIZARD, '카라 마법병',
80, 80, 7, 20, 0.6, 10, 10,
1000, null, ["카라의 숲"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217402, self::T_WIZARD, '카라 마법병',
80, 80, 7, 20, 0.6, 10, 10,
1000, null, ["카라의 숲"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["신속히 움직이며 카라의 저주로 적을 공격합니다."],
null, null
],
[
217403, self::T_WIZARD, '제스 마법병',
100, 100, 7, 0, 0.6, 10, 10,
1000, null, ["제스 북부"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217403, self::T_WIZARD, '제스 마법병',
100, 100, 7, 0, 0.6, 10, 10,
1000, null, ["제스 북부"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["마법국가 제스의 전통있는 마법병입니다."],
null, null
],
[
217404, self::T_WIZARD, '무녀',
80, 80, 8, 10, 0.6, 10, 10,
1000, null, ["JAPAN"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217404, self::T_WIZARD, '무녀',
80, 80, 8, 10, 0.6, 10, 10,
1000, null, ["JAPAN"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["신마법과 함께 신통한 춤으로 활력을 불어넣는 마법병입니다."],
null, null
],
[
217405, self::T_WIZARD, '프로즌',
90, 70, 7, 10, 0.8, 10, 10,
1000, null, ["헬만 서부"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217405, self::T_WIZARD, '프로즌',
90, 70, 7, 10, 0.8, 10, 10,
1000, null, ["헬만 서부"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["백병전에 불리한 연약한 몸으로 마법 사용에 집중하는 몬스터입니다."],
null, null
],
[
217406, self::T_WIZARD, '마소한 마법병',
70, 90, 7, 10, 0.8, 11, 10,
1000, null, ["케이브리스 마인령"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217406, self::T_WIZARD, '마소한 마법병',
70, 90, 7, 10, 0.8, 11, 10,
1000, null, ["케이브리스 마인령"], 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["로브를 여러겹 둘러입은 마물마법병입니다. 직접 전투보단 마법에 집중합니다."],
null, null
],
[
217407, self::T_WIZARD, '신관병',
80, 130, 7, 5, 0.6, 9, 11,
2000, ["카이즈"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217407, self::T_WIZARD, '신관병',
80, 130, 7, 5, 0.6, 9, 11,
2000, ["카이즈"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["AL교의 전투신관입니다. 몸을 보호하는 성스러운 마법을 사용합니다."],
null, null
],
[
217408, self::T_WIZARD, '중장마법병',
80, 130, 7, 15, 0.6, 11, 12,
3000, ["로제스그라드"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217408, self::T_WIZARD, '중장마법병',
80, 130, 7, 15, 0.6, 11, 12,
3000, ["로제스그라드"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["두터운 로브를 입고 불길한 주문을 읊습니다."],
null, null
],
[
217409, self::T_WIZARD, '악마병',
130, 80, 7, 15, 0.6, 11, 12,
3000, ["올드제스"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217409, self::T_WIZARD, '악마병',
130, 80, 7, 15, 0.6, 11, 12,
3000, ["올드제스"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["고대 제스 왕가의 계약에 따라 소환된 흑마법병입니다."],
null, null
],
[
217410, self::T_WIZARD, '호넷 마물병',
130, 130, 7, 0, 0.6, 12, 12,
3000, ["구 마왕성"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217410, self::T_WIZARD, '호넷 마물병',
130, 130, 7, 0, 0.6, 12, 12,
3000, ["구 마왕성"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["호넷 직속의 최강 마물 마법병입니다."],
null, null
],
[
217411, self::T_WIZARD, 'Z가디언',
180, 180, 7, 0, 0.7, 28, 20,
5000, ["라그나로크 아크"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
[
217411, self::T_WIZARD, 'Z가디언',
180, 180, 7, 0, 0.7, 28, 20,
5000, ["라그나로크 아크"], null, 0,
[self::T_SIEGE=>1.2],
[self::T_SIEGE=>0.8],
["제스의 기술력이 집약된 최고의 마법병기입니다."],
null, null
],
[
217500, self::T_SIEGE, '정란',
150, 150, 6, 0, 0, 15, 5,
0, null, null, 3,
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>0.8, self::T_CAVALRY=>0.8, self::T_WIZARD=>0.8, self::T_CASTLE=>1.8],
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>1.2, self::T_CAVALRY=>1.2, self::T_WIZARD=>1.2],
[
217500, self::T_SIEGE, '정란',
150, 150, 6, 0, 0, 15, 5,
0, null, null, 3,
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>0.8, self::T_CAVALRY=>0.8, self::T_WIZARD=>0.8, self::T_CASTLE=>1.8],
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>1.2, self::T_CAVALRY=>1.2, self::T_WIZARD=>1.2],
["높은 구조물 위에서 공격합니다."],
['che_성벽부상무효'], null
],
[
217501, self::T_SIEGE, '충차',
150, 100, 6, 0, 0, 20, 5,
1000, null, null, 3,
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>0.8, self::T_CAVALRY=>0.8, self::T_WIZARD=>0.8, self::T_CASTLE=>2.4],
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>1.2, self::T_CAVALRY=>1.2, self::T_WIZARD=>1.2],
[
217501, self::T_SIEGE, '충차',
150, 100, 6, 0, 0, 20, 5,
1000, null, null, 3,
[ self::T_FOOTMAN=>0.8, self::T_ARCHER=>0.8, self::T_CAVALRY=>0.8, self::T_WIZARD=>0.8, self::T_CASTLE=>2.4],
[ self::T_FOOTMAN=>1.2, self::T_ARCHER=>1.2, self::T_CAVALRY=>1.2, self::T_WIZARD=>1.2],
["엄청난 위력으로 성벽을 부수어버립니다."],
['che_성벽부상무효'], null
],
[
217502, self::T_SIEGE, '튤립3호',
275, 300, 6, 0, 0, 35, 15,
5000, ["커스텀"], null, 0,
[ ],
[ ],
[
217502, self::T_SIEGE, '튤립3호',
275, 300, 6, 0, 0, 35, 15,
5000, ["커스텀"], null, 0,
[ ],
[ ],
["파괴적인 위력과 정말 파괴적인 비용을 자랑하는 전차입니다. "],
['che_성벽부상무효'], null
],
+76
View File
@@ -0,0 +1,76 @@
@import "./common/bootstrap5.scss";
@import "@scss/game_bg.scss";
@import "./common_legacy.scss";
@import "../../css/config.css";
@import "./util.scss";
@include media-1000px {
#container {
width: 1000px;
margin: 0 auto;
}
}
@include media-500px {
#container {
width: 500px;
margin: 0 auto;
}
}
.date{
font-size: 0.9em;
}
.authorName,
.authorIcon,
.inputCommentHeader {
flex: 0 0 auto;
width: 120px;
}
#newArticle {
margin-top: 1em;
}
.titleInput {
width: 100%;
color: white;
background-color: transparent;
border: none;
margin: 1px 5px;
}
.contentInput {
width: 100%;
min-height: 3em;
color: white;
background-color: transparent;
border: none;
padding: 1px 5px;
}
.articleFrame {
margin: 20px auto;
}
.commentText {
width: 100%;
}
.text {
text-align: left;
padding: 1px 5px;
}
.submitComment {
width: 100%;
}
.commentText {
color: white;
background-color: transparent;
border: none;
padding: 1px 5px;
}
+13
View File
@@ -5,6 +5,7 @@
@import "@scss/common/variables.scss";
@import "@scss/common/bootswatch_custom_variables.scss";
@import "@scss/game_bg.scss";
@import "@scss/util.scss";
$modcolor2: color.adjust($nbase2color, $lightness: -5%);
@@ -86,6 +87,12 @@ $modcolor2: color.adjust($nbase2color, $lightness: -5%);
background-color: $modcolor2;
}
}
#bottomBar{
width: 1000px;
margin: 0 auto;
position: relative;
}
}
@include media-500px {
@@ -183,4 +190,10 @@ $modcolor2: color.adjust($nbase2color, $lightness: -5%);
text-align: center;
}
}
#bottomBar {
width: 500px;
margin: 0 auto;
position: relative;
}
}
+59 -54
View File
@@ -1,67 +1,72 @@
@function frontColor($color) {
@return color-contrast($color);
@return color-contrast($color);
}
@each $colorType, $color in $nationColors {
.sam-color-#{$colorType} {
.sam-nation-own-bgcolor{
background-color: $color;
color: frontColor($color);
}
.btn-sammo-nation{
@include button-variant($color, $color);
&:hover,
&:active,
&:focus {
background-color: $color;
}
&,
&:not(.disabled):hover,
&:not(.disabled):active,
&:focus {
border-color: shade-color($color, 10%);
}
}
@each $colorType,
$color in $nationColors {
.sam-color-#{$colorType} {
.sam-nation-own-bgcolor {
background-color: $color;
color: frontColor($color);
}
.btn-sammo-nation-#{$colorType}{
@include button-variant($color, $color);
.btn-sammo-nation {
@include button-variant($color, $color);
&:hover,
&:active,
&:focus {
background-color: $color;
}
&:hover,
&:active,
&:focus {
background-color: $color;
}
&,
&:not(.disabled):hover,
&:not(.disabled):active,
&:focus {
border-color: shade-color($color, 10%);
}
&,
&:not(.disabled):hover,
&:not(.disabled):active,
&:focus {
border-color: shade-color($color, 10%);
}
}
}
.sam-nation-bg-#{$colorType} {
background-color: $color;
color: frontColor($color);
}
.btn-sammo-nation-#{$colorType} {
@include button-variant($color, $color);
&:hover,
&:active,
&:focus {
background-color: $color;
}
&,
&:not(.disabled):hover,
&:not(.disabled):active,
&:focus {
border-color: shade-color($color, 10%);
}
}
}
@each $colorType, $color in $sammoColors {
.btn-sammo-#{$colorType}{
@include button-variant($color, $color);
@each $colorType,
$color in $sammoColors {
.btn-sammo-#{$colorType} {
@include button-variant($color, $color);
&:hover,
&:active,
&:focus {
background-color: $color;
}
&,
&:not(.disabled):hover,
&:not(.disabled):active,
&:focus {
border-color: shade-color($color, 10%);
}
&:hover,
&:active,
&:focus {
background-color: $color;
}
&,
&:not(.disabled):hover,
&:not(.disabled):active,
&:focus {
border-color: shade-color($color, 10%);
}
}
}
-23
View File
@@ -1,23 +0,0 @@
@import '@scss/common/bootstrap5.scss';
#scoutMsgForm .viewer{
max-height:200px;
overflow: hidden;
}
.note-editor.card {
background:inherit;
color:inherit;
border:none;
}
.note-editor.note-frame .note-editing-area .note-editable {
background:inherit;
color:inherit;
padding:0;
}
.note-editor.note-frame .note-statusbar{
background-color:#444444;
border-top: 1px solid rgba(0,0,0,0.125);
}
+4
View File
@@ -215,6 +215,10 @@ body {
}
}
#nation-msg{
word-break: break-all;
}
#nation-msg-box {
position: relative;
overflow: hidden;
+97
View File
@@ -0,0 +1,97 @@
@import '@scss/common/bootstrap5.scss';
@import "@scss/game_bg.scss";
@import "@scss/util.scss";
@import "@scss/editor_component.scss";
@import "@scss/common_legacy.scss";
.diplomacyTitle {
background-color: $blue;
text-align: center;
font-size: 1.4em;
}
.noticeTitle {
background-color: $white;
text-align: center;
color: black;
font-size: 1.4em;
}
.financeTitle{
background-color: $green;
text-align: center;
font-size: 1.4em;
}
#app {
width: 100%;
overflow-x: hidden;
}
.tiptap-editor{
width: 100%;
overflow: hidden;
}
#scoutMsgForm .ProseMirror{
max-height: 200px;
overflow-y: hidden;
}
@include media-1000px {
#container {
width: 1000px;
margin: 0 auto;
position: relative;
}
}
@include media-500px {
#container {
position: relative;
width: 500px;
margin: auto;
overflow-x: hidden;
}
#noticeForm .ProseMirror {
transform: scale(0.5);
transform-origin: left top;
width: 1000px;
}
#scoutMsgForm .ProseMirror {
transform: scale(calc(500/ 870));
transform-origin: left top;
width: 870px;
}
}
.diplomacyTable {
.tRow {
display: grid;
grid-template-columns: minmax(130px, 3fr) 1.5fr 1fr 1fr 2fr 1fr 2fr;
text-align: center;
}
}
.note-editor.card {
background: inherit;
color: inherit;
border: none;
}
.note-editor.note-frame .note-editing-area .note-editable {
background: inherit;
color: inherit;
padding: 0;
}
.note-editor.note-frame .note-statusbar {
background-color: #444444;
border-top: 1px solid rgba(0, 0, 0, 0.125);
}
+9 -3
View File
@@ -1,5 +1,3 @@
.s-border-t {
border-top: gray solid 1px;
}
@@ -11,4 +9,12 @@
.s-border-tb {
border-top: gray solid 1px;
border-bottom: gray solid 1px;
}
}
.avoid-wrap {
display: inline-block;
}
.center {
text-align: center;
}
+2 -2
View File
@@ -27,8 +27,8 @@ shuffle($nationList);
$nationList = Util::convertArrayToDict($nationList, 'nation');
//NOTE: join 안할것임
$scoutMsgs = KVStorage::getValuesFromInterNamespace($db, 'nation_env', 'scout_msg');
foreach ($scoutMsgs as $nationID => $scoutMsg) {
$nationList[$nationID]['scoutmsg'] = $scoutMsg;
foreach ($scoutMsgs as $destNationID => $scoutMsg) {
$nationList[$destNationID]['scoutmsg'] = $scoutMsg;
}
$characterAll = []; //선택용
+2 -2
View File
@@ -24,8 +24,8 @@ shuffle($nationList);
$nationList = Util::convertArrayToDict($nationList, 'nation');
//NOTE: join 안할것임
$scoutMsgs = KVStorage::getValuesFromInterNamespace($db, 'nation_env', 'scout_msg');
foreach ($scoutMsgs as $nationID => $scoutMsg) {
$nationList[$nationID]['scoutmsg'] = $scoutMsg;
foreach ($scoutMsgs as $destNationID => $scoutMsg) {
$nationList[$destNationID]['scoutmsg'] = $scoutMsg;
}
?>
<!DOCTYPE html>
+1 -1
View File
@@ -3,7 +3,7 @@
<?=$btnBegin??''?><a href='b_troop.php' class='commandButton <?= ($meLevel >= 1 && $nationLevel >= 1) ? '' : 'disabled' ?> <?=$btnClass??""?>'>부대 편성</a><?=$btnEnd??''?>
<?=$btnBegin??''?><a href='t_diplomacy.php' class='commandButton <?= $showSecret ? '' : 'disabled' ?> <?=$btnClass??""?>'>외 교 부</a><?=$btnEnd??''?>
<?=$btnBegin??''?><a href='b_myBossInfo.php' class='commandButton <?= $meLevel >= 1 ? '' : 'disabled' ?> <?=$btnClass??""?>'>인 사 부</a><?=$btnEnd??''?>
<?=$btnBegin??''?><a href='b_dipcenter.php' class='commandButton <?= $showSecret ? '' : 'disabled' ?> <?=$btnClass??""?>'>내 무 부</a><?=$btnEnd??''?>
<?=$btnBegin??''?><a href='v_nationStratFinan.php' class='commandButton <?= $showSecret ? '' : 'disabled' ?> <?=$btnClass??""?>'>내 무 부</a><?=$btnEnd??''?>
<?=$btnBegin??''?><a href='v_chiefCenter.php' class='commandButton <?= $showSecret ? '' : 'disabled' ?> <?=$btnClass??""?>'>사 령 부</a><?=$btnEnd??''?>
<?=$btnBegin??''?><a href='v_NPCControl.php' class='commandButton <?= $showSecret ? '' : 'disabled' ?> <?=$btnClass??""?>'>NPC 정책</a><?=$btnEnd??''?>
<?=$btnBegin??''?><a href='b_genList.php' target='_blank' class='open-window commandButton <?=$btnClass??""?> <?= $showSecret ? '' : 'disabled' ?>'>암 행 부</a><?=$btnEnd??''?>
+37 -109
View File
@@ -1,18 +1,12 @@
<template>
<top-back-bar :title="title" />
<div id="container">
<TopBackBar :title="title" />
<table id="newArticle" class="bg0">
<thead>
<tr>
<td colspan="2" class="newArticleHeader bg2"> 게시물 작성</td>
</tr>
</thead>
<tbody>
<tr>
<th class="bg1" style="width: 66px">
<span class="articleTitle">제목</span>
</th>
<td>
<div id="newArticle" class="bg0">
<div class="newArticleHeader bg2 center"> 게시물 작성</div>
<div class="row gx-0">
<div class="col-2 col-md-1 articleTitle bg1 center">제목</div>
<div class="col-10 col-md-11">
<input
class="titleInput"
type="text"
@@ -20,11 +14,11 @@
placeholder="제목"
v-model="newArticle.title"
/>
</td>
</tr>
<tr>
<th class="bg1">내용</th>
<td class="boardArticle">
</div>
</div>
<div class="row gx-0">
<div class="col-2 col-md-1 bg1 center">내용</div>
<div class="col-10 col-md-11">
<textarea
class="contentInput autosize"
ref="newArticleTextForm"
@@ -32,40 +26,35 @@
v-model="newArticle.text"
@input="autoResizeTextarea"
/>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<button type="button" id="submitArticle" @click="submitArticle">
등록
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="row">
<div class="col-8 col-md-10"></div>
<div class="col-4 col-md-2 d-grid">
<b-button id="submitArticle" @click="submitArticle"> 등록 </b-button>
</div>
</div>
</div>
<div id="board">
<template v-if="articles && articles.length">
<board-article
v-for="article in articles"
:key="article.no"
:article="article"
@submit-comment="reloadArticles"
/>
</template>
<template v-else> 게시물이 없습니다. </template>
</div>
<div id="board">
<template v-if="articles && articles.length">
<board-article
v-for="article in articles"
:key="article.no"
:article="article"
@submit-comment="reloadArticles"
/>
</template>
<template v-else> 게시물이 없습니다. </template>
<BottomBar/>
</div>
</template>
<script lang="ts">
import "@scss/common/bootstrap5.scss";
import "@scss/game_bg.scss";
import "../../css/config.css";
import { defineComponent, onMounted, reactive, ref } from "vue";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import BoardArticle from "@/components/BoardArticle.vue";
import { convertFormData } from "@util/convertFormData";
import axios from "axios";
@@ -105,6 +94,7 @@ export default defineComponent({
name: "PageBoard",
components: {
TopBackBar,
BottomBar,
BoardArticle,
},
props: {
@@ -155,7 +145,7 @@ export default defineComponent({
this.newArticle = { title: "", text: "" };
const newArticleTextForm = unwrap(this.newArticleTextForm);
newArticleTextForm.style.height = 'auto';
newArticleTextForm.style.height = "auto";
await this.reloadArticles();
},
@@ -188,7 +178,6 @@ export default defineComponent({
return;
}
articles.length = 0;
articles.push(...Object.values(boardResponse.articles));
articles.reverse();
@@ -205,65 +194,4 @@ export default defineComponent({
};
},
});
</script>
<style>
#newArticle {
width: 1000px;
margin: 0 auto;
}
.titleInput {
width: 100%;
color: white;
background-color: transparent;
border: none;
margin: 1px 5px;
}
.contentInput {
width: 100%;
min-height: 3em;
color: white;
background-color: transparent;
border: none;
padding: 1px 5px;
}
.articleFrame {
width: 1000px;
margin: 20px auto;
}
.commentText {
width: 100%;
}
.authorName,
.comment .author {
width: 110px;
font-size: 85%;
}
.date {
width: 125px;
font-size: 85%;
}
.text {
text-align: left;
padding: 1px 5px;
}
.submitComment {
width: 100%;
}
.commentText {
color: white;
background-color: transparent;
border: none;
padding: 1px 5px;
}
</style>
</script>
+18 -10
View File
@@ -88,6 +88,9 @@
</div>
<b-dropdown class="col-md-4" left text="턴 선택">
<b-dropdown-item @click="selectNone()"
>해제</b-dropdown-item
>
<b-dropdown-item @click="selectAll(true)"
>모든턴</b-dropdown-item
>
@@ -227,6 +230,9 @@
</template>
</div>
</div>
<div id="bottomBar">
<BottomBar />
</div>
</template>
<script lang="ts">
import "@scss/common/bootstrap5.scss";
@@ -236,10 +242,10 @@ import "../../css/config.css";
import { computed, defineComponent, reactive, ref, toRefs, watch } from "vue";
import ChiefReservedCommand from "@/components/ChiefReservedCommand.vue";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import VueTypes from "vue-types";
import { isString, range } from "lodash";
import { mb_strwidth } from "./util/mb_strwidth";
import { sammoAPI } from "./util/sammoAPI";
import { entriesWithType } from "./util/entriesWithType";
import { addMilliseconds } from "date-fns";
import { formatTime } from "./util/formatTime";
@@ -248,6 +254,7 @@ import { getNpcColor } from "./common_legacy";
import TopItem from "@/ChiefCenter/TopItem.vue";
import BottomItem from "@/ChiefCenter/BottomItem.vue";
import { ChiefResponse, OptionalFull } from "./defs";
import { SammoAPI } from "./SammoAPI";
function isDropdownChildren(e?: Event): boolean {
if (!e) {
@@ -269,6 +276,7 @@ function isDropdownChildren(e?: Event): boolean {
export default defineComponent({
components: {
TopBackBar,
BottomBar,
ChiefReservedCommand,
TopItem,
BottomItem,
@@ -293,6 +301,9 @@ export default defineComponent({
this.turnList.clear();
this.turnList.add(turnIdx);
},
selectNone() {
this.turnList.clear();
},
selectAll(e: Event | true) {
//NOTE: split 구현에 버그가 있어서, 수동으로 구분해야함
if (e !== true && isDropdownChildren(e)) {
@@ -317,7 +328,7 @@ export default defineComponent({
},
async repeatNationCommand(amount: number) {
try {
await sammoAPI(`NationCommand/RepeatCommand`, { amount });
await SammoAPI.NationCommand.RepeatCommand({ amount });
} catch (e) {
console.error(e);
alert(`실패했습니다: ${e}`);
@@ -327,7 +338,7 @@ export default defineComponent({
},
async pushNationCommand(amount: number) {
try {
await sammoAPI("NationCommand/PushCommand", { amount });
await SammoAPI.NationCommand.PushCommand({ amount });
} catch (e) {
console.error(e);
alert(`실패했습니다: ${e}`);
@@ -384,9 +395,8 @@ export default defineComponent({
async function reloadTable(): Promise<void> {
try {
const response = await sammoAPI<ChiefResponse>(
"NationCommand/GetReservedCommand"
);
const response =
await SammoAPI.NationCommand.GetReservedCommand<ChiefResponse>();
console.log(response);
for (const [key, value] of entriesWithType(response)) {
if (key === "result") {
@@ -411,13 +421,11 @@ export default defineComponent({
timeDiff.value = serverNowObj.getTime() - clientNowObj.getTime();
if (viewTarget.value === undefined) {
if(!tableObj.officerLevel){
if (!tableObj.officerLevel) {
viewTarget.value = 12;
}
else{
} else {
viewTarget.value = tableObj.officerLevel;
}
}
} catch (e) {
if (isString(e)) {
+5 -7
View File
@@ -262,10 +262,8 @@ import "@scss/common/bootstrap5.scss";
import "@scss/game_bg.scss";
import TopBackBar from "@/components/TopBackBar.vue";
import _ from "lodash";
import { InvalidResponse } from "@/defs";
import axios from "axios";
import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue";
import { sammoAPI } from "./util/sammoAPI";
import { SammoAPI } from "./SammoAPI";
type InheritanceType =
| "previous"
@@ -507,7 +505,7 @@ export default defineComponent({
}
try {
await sammoAPI("InheritAction/BuyHiddenBuff", {
await SammoAPI.InheritAction.BuyHiddenBuff({
type: buffKey,
level,
});
@@ -550,7 +548,7 @@ export default defineComponent({
}
try {
await sammoAPI(`InheritAction/${type}`, {});
await SammoAPI.InheritAction[type]({});
} catch (e) {
console.error(e);
alert(`실패했습니다: ${e}`);
@@ -584,7 +582,7 @@ export default defineComponent({
}
try {
await sammoAPI(`InheritAction/SetNextSpecialWar`, {
await SammoAPI.InheritAction.SetNextSpecialWar({
type: this.nextSpecialWar,
});
} catch (e) {
@@ -616,7 +614,7 @@ export default defineComponent({
}
try {
await sammoAPI(`InheritAction/BuySpecificUnique`, {
await SammoAPI.InheritAction.BuySpecificUnique({
item: this.specificUnique,
amount,
});
+5 -5
View File
@@ -315,10 +315,8 @@ import {
abilityRand,
} from "@util/generalStats";
import { clone, shuffle, sum } from "lodash";
import axios from "axios";
import { InvalidResponse } from "@/defs";
import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue";
import { sammoAPI } from "./util/sammoAPI";
import { SammoAPI } from "./SammoAPI";
declare const nationList: {
nation: number;
@@ -536,7 +534,7 @@ export default defineComponent({
//검증은 언제 되어야 하는가?
const args = clone(this.args);
try {
await sammoAPI(["General", "Join"], args);
await SammoAPI.General.Join(args);
} catch (e) {
console.error(e);
alert(`실패했습니다: ${e}`);
@@ -551,7 +549,9 @@ export default defineComponent({
},
});
</script>
<style>
<style lang="scss">
@import "@scss/common/bootstrap5.scss";
@import "@scss/editor_component.scss";
#container {
width: 100%;
max-width: 1000px;
+655
View File
@@ -0,0 +1,655 @@
<template>
<MyToast v-model="toasts" />
<div id="container" class="pageNationStratFinan bg0">
<TopBackBar title="내무부" />
<div class="diplomacyTitle">외교관계</div>
<div class="diplomacyTable">
<div class="diplomacyHeader tRow bg1">
<div>국가명</div>
<div>국력</div>
<div>장수</div>
<div>속령</div>
<div>상태</div>
<div>기간</div>
<div>종료 시점</div>
</div>
<div
:class="['diplomacyItem', 'tRow', 's-border-b']"
v-for="nation in nationsList"
:key="nation.nation"
>
<div :class="`sam-nation-bg-${nation.color.slice(1)}`">
{{ nation.name }}
</div>
<div>{{ nation.power.toLocaleString() }}</div>
<div>{{ nation.gennum.toLocaleString() }}</div>
<div>{{ nation.cityCnt.toLocaleString() }}</div>
<template v-if="nation.nation == nationID">
<div>-</div>
</template>
<template v-else>
<div
:style="{
color:
diplomacyStateInfo[nation.diplomacy.state].color ?? undefined,
}"
>
{{ diplomacyStateInfo[nation.diplomacy.state].name }}
</div>
<div>
{{
nation.diplomacy.term == 0 ? "-" : `${nation.diplomacy.term}개월`
}}
</div>
<div
v-for="([endYear, endMonth], _idx) in [
parseYearMonth(
joinYearMonth(year, month) + nation.diplomacy.term
),
]"
:key="_idx"
>
{{
nation.diplomacy.term == 0 ? "-" : `${endYear}년 ${endMonth}월`
}}
</div>
</template>
</div>
</div>
<div class="noticeTitle">국가 방침 &amp; 임관 권유 메시지</div>
<div id="noticeForm">
<div class="bg1" style="display: flex; justify-content: space-around">
<div style="flex: 1 1 auto">국가 방침</div>
<div>
<b-button
@click="enableEditNationMsg"
v-if="editable && !inEditNationMsg"
>국가방침 수정</b-button
>
<b-button @click="saveNationMsg" v-if="editable && inEditNationMsg"
>저장</b-button
>
<b-button
v-if="editable && inEditNationMsg"
@click="rollbackNationMsg"
>취소</b-button
>
</div>
</div>
<TipTap
v-model="nationMsg"
:editable="inEditNationMsg"
@ready="trackNationMsgHeight"
@update:modelValue="trackNationMsgHeight"
/>
</div>
<div id="scoutMsgForm">
<div class="bg1" style="display: flex; justify-content: space-around">
<div style="flex: 1 1 auto">임관 권유</div>
<div>
<b-button
@click="enableEditScoutMsg"
v-if="editable && !inEditScoutMsg"
>임관 권유문 수정</b-button
>
<b-button @click="saveScoutMsg" v-if="editable && inEditScoutMsg"
>저장</b-button
>
<b-button v-if="editable && inEditScoutMsg" @click="rollbackScoutMsg"
>취소</b-button
>
</div>
</div>
<div style="border-bottom: solid gray 0.5px">
870px x 200px를 넘어서는 내용은 표시되지 않습니다.
</div>
<div style="width: 870px; margin-left: auto">
<TipTap
v-model="scoutMsg"
:editable="inEditScoutMsg"
@ready="trackScoutMsgHeight"
@update:modelValue="trackScoutMsgHeight"
/>
</div>
</div>
<div class="financeTitle">예산&amp;정책</div>
<div class="row gx-0 center">
<div class="col-6">
<div class="row gx-0">
<div class="col-12 bg2">자금 예산</div>
<div class="col-4 bg1">현 재</div>
<div class="col-8">{{ gold.toLocaleString() }}</div>
<div class="col-4 bg1">단기수입</div>
<div class="col-8">{{ income.gold.war.toLocaleString() }}</div>
<div class="col-4 bg1">세 금</div>
<div class="col-8">
{{ Math.floor(incomeGoldCity).toLocaleString() }}
</div>
<div class="col-4 bg1">수입/지출</div>
<div class="col-8">
+{{ Math.floor(incomeGold).toLocaleString() }} /
{{ Math.floor(-outcomeByBill).toLocaleString() }}
</div>
<div class="col-4 bg1">국고 예산</div>
<div class="col-8">
{{ Math.floor(gold + incomeGold - outcomeByBill).toLocaleString() }}
({{ incomeGold >= outcomeByBill ? "+" : ""
}}{{ Math.floor(incomeGold - outcomeByBill).toLocaleString() }})
</div>
</div>
</div>
<div class="col-6">
<div class="row gx-0">
<div class="col-12 bg2">군량 예산</div>
<div class="col-4 bg1">현 재</div>
<div class="col-8">{{ rice.toLocaleString() }}</div>
<div class="col-4 bg1">둔전수입</div>
<div class="col-8">
{{ Math.floor(incomeRiceWall).toLocaleString() }}
</div>
<div class="col-4 bg1">세 금</div>
<div class="col-8">
{{ Math.floor(incomeRiceCity).toLocaleString() }}
</div>
<div class="col-4 bg1">수입/지출</div>
<div class="col-8">
+{{ Math.floor(incomeRice).toLocaleString() }} /
{{ Math.floor(-outcomeByBill).toLocaleString() }}
</div>
<div class="col-4 bg1">국고 예산</div>
<div class="col-8">
{{ Math.floor(rice + incomeRice - outcomeByBill).toLocaleString() }}
({{ incomeRice >= outcomeByBill ? "+" : ""
}}{{ Math.floor(incomeRice - outcomeByBill).toLocaleString() }})
</div>
</div>
</div>
<div class="col-6">
<div class="row gx-0">
<div class="col-4 bg1 d-grid">
<div class="align-self-center">
세율 <span class="avoid-wrap">(5 ~ 30%)</span>
</div>
</div>
<div class="col-8 row gx-0">
<div class="col-md-6 offset-md-3 align-self-center">
<div class="input-group my-0">
<input
type="number"
class="form-control py-1 f_tnum px-0 text-end"
v-model="policy.rate"
min="5"
max="30"
/><span class="input-group-text py-1 f_tnum">%</span
><b-button
variant="primary"
size="sm"
@click="setRate"
v-if="editable"
>변경</b-button
><b-button size="sm" @click="rollbackRate" v-if="editable"
>취소</b-button
>
</div>
</div>
</div>
</div>
</div>
<div class="col-6">
<div class="row gx-0">
<div class="col-4 bg1 d-grid">
<div class="align-self-center">
지급률 <span class="avoid-wrap">(20 ~ 200%)</span>
</div>
</div>
<div class="col-8 row gx-0">
<div class="col-md-6 offset-md-3 align-self-center">
<div class="input-group my-0">
<input
type="number"
class="form-control py-1 f_tnum px-0 text-end"
v-model="policy.bill"
min="20"
max="200"
/><span class="input-group-text py-1 f_tnum">%</span
><b-button
variant="primary"
size="sm"
@click="setBill"
v-if="editable"
>변경</b-button
><b-button size="sm" @click="rollbackBill" v-if="editable"
>취소</b-button
>
</div>
</div>
</div>
</div>
</div>
<div class="col-6">
<div class="row gx-0">
<div class="col-4 bg1 d-grid">
<div class="align-self-center">
기밀 권한 <span class="avoid-wrap">(1 ~ 99년)</span>
</div>
</div>
<div class="col-8 row gx-0">
<div class="col-md-6 offset-md-3 align-self-center">
<div class="input-group my-0">
<input
type="number"
class="form-control py-1 f_tnum px-0 text-end"
v-model="policy.secretLimit"
min="1"
max="99"
/><span class="input-group-text py-1 f_tnum">년</span
><b-button
variant="primary"
size="sm"
@click="setSecretLimit"
v-if="editable"
>변경</b-button
><b-button
size="sm"
@click="rollbackSecretLimit"
v-if="editable"
>취소</b-button
>
</div>
</div>
</div>
</div>
</div>
<div class="col-6 d-grid">
<div class="row gx-0">
<div class="col-4 bg1 d-grid">
<div class="align-self-center">전쟁 금지 설정</div>
</div>
<div class="col-8 d-grid">
<div class="align-self-center">{{warSettingCnt.remain}} 회(월 +{{warSettingCnt.inc}}회, 최대{{warSettingCnt.max}}회)</div>
</div>
</div>
</div>
<div class="col-3 col-md-4"></div>
<div class="col-3 col-md-2 row gx-0">
<div class="col-9 col-md-8 text-end p-2">전쟁 금지</div>
<div class="col-3 col-md-4 py-2">
<b-form-checkbox
v-model="policy.blockWar"
@change="setBlockWar"
switch
/>
</div>
</div>
<div class="col-3 col-md-2 row gx-0">
<div class="col-9 col-md-8 text-end p-2">임관 금지</div>
<div class="col-3 col-md-4 py-2">
<b-form-checkbox
v-model="policy.blockScout"
@change="setBlockScout"
switch
/>
</div>
</div>
</div>
<div>추가 설정</div>
<BottomBar title="내무부" />
</div>
</template>
<script lang="ts">
import TipTap from "./components/TipTap.vue";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import { computed, defineComponent, reactive, ref, toRefs } from "vue";
import { isString } from "lodash";
import {
diplomacyState,
diplomacyStateInfo,
NationStaticItem,
ToastType,
} from "./defs";
import { SammoAPI } from "./SammoAPI";
import { joinYearMonth } from "@/util/joinYearMonth";
import { parseYearMonth } from "@/util/parseYearMonth";
import MyToast from "@/components/MyToast.vue";
import { ValidResponse } from "./util/callSammoAPI";
type NationItem = NationStaticItem & {
cityCnt: number;
diplomacy: {
state: diplomacyState;
term: number | null;
};
};
type SetBlockWarResponse = ValidResponse & {
availableCnt: number;
}
declare const staticValues: {
editable: boolean;
nationMsg: string;
scoutMsg: string;
nationID: number;
officerLevel: number;
year: number;
month: number;
nationsList: Record<number, NationItem>;
gold: number;
rice: number;
income: {
gold: {
city: number;
war: number;
};
rice: {
city: number;
wall: number;
};
};
outcome: number;
policy: {
rate: number;
bill: number;
secretLimit: number;
blockScout: boolean;
blockWar: boolean;
};
warSettingCnt: {
remain: number;
inc: number;
max: number;
};
};
export default defineComponent({
components: {
TopBackBar,
BottomBar,
MyToast,
TipTap,
},
setup() {
const toasts = ref<ToastType[]>([]);
const self = reactive(staticValues);
let oldNationMsg = staticValues.nationMsg;
const inEditNationMsg = ref(false);
function enableEditNationMsg() {
inEditNationMsg.value = true;
}
function rollbackNationMsg() {
inEditNationMsg.value = false;
self.nationMsg = oldNationMsg;
}
async function saveNationMsg() {
const msg = self.nationMsg;
try {
await SammoAPI.Nation.SetNotice({
msg,
});
oldNationMsg = msg;
inEditNationMsg.value = false;
toasts.value.push({
title: "변경",
content: "국가 방침을 변경했습니다.",
});
} catch (e) {
if (isString(e)) {
toasts.value.push({
title: "에러",
content: e,
type: "danger",
});
}
console.error(e);
}
}
let oldScoutMsg = staticValues.scoutMsg;
const inEditScoutMsg = ref(false);
function enableEditScoutMsg() {
inEditScoutMsg.value = true;
}
function rollbackScoutMsg() {
inEditScoutMsg.value = false;
self.scoutMsg = oldScoutMsg;
}
async function saveScoutMsg() {
const msg = self.scoutMsg;
try {
await SammoAPI.Nation.SetScoutMsg({
msg,
});
oldScoutMsg = msg;
inEditScoutMsg.value = false;
toasts.value.push({
title: "변경",
content: "임관 권유문을 변경했습니다.",
});
} catch (e) {
if (isString(e)) {
toasts.value.push({
title: "에러",
content: e,
type: "danger",
});
}
console.error(e);
}
}
const trackTiptapFormHeight = (target: string) => {
let form: HTMLElement | null = null;
let outerForm: HTMLElement | null = null;
function handler() {
if (!form) {
form = document.querySelector(`${target} .ProseMirror`);
}
if (!outerForm) {
outerForm = document.querySelector(`${target} .tiptap-editor`);
}
if (!form || !outerForm) {
return;
}
const { height: clientHeight } = form.getBoundingClientRect();
const { height: parentHeight } = outerForm.getBoundingClientRect();
if (parentHeight != clientHeight) {
outerForm.style.height = `${clientHeight}px`;
}
}
window.addEventListener("orientationchange", handler, true);
return handler;
};
const incomeGoldCity = computed(() => {
return (self.income.gold.city * self.policy.rate) / 100;
});
const incomeGold = computed(() => {
return incomeGoldCity.value + self.income.gold.war;
});
const incomeRiceCity = computed(() => {
return (self.income.rice.city * self.policy.rate) / 100;
});
const incomeRiceWall = computed(() => {
return (self.income.rice.wall * self.policy.rate) / 100;
});
const incomeRice = computed(() => {
return incomeRiceCity.value + incomeRiceWall.value;
});
const outcomeByBill = computed(() => {
return (self.outcome * self.policy.bill) / 100;
});
let oldRate = staticValues.policy.rate;
async function setRate() {
const rate = self.policy.rate;
try {
await SammoAPI.Nation.SetRate({ amount: rate });
oldRate = rate;
toasts.value.push({
title: "변경",
content: "세율을 변경했습니다.",
});
} catch (e) {
if (isString(e)) {
toasts.value.push({
title: "에러",
content: e,
type: "danger",
});
}
console.error(e);
}
}
function rollbackRate() {
self.policy.rate = oldRate;
}
let oldBill = staticValues.policy.bill;
async function setBill() {
const bill = self.policy.bill;
try {
await SammoAPI.Nation.SetBill({ amount: bill });
oldBill = bill;
toasts.value.push({
title: "변경",
content: "지급률을 변경했습니다.",
});
} catch (e) {
if (isString(e)) {
toasts.value.push({
title: "에러",
content: e,
type: "danger",
});
}
console.error(e);
}
}
function rollbackBill() {
self.policy.bill = oldBill;
}
let oldSecretLimit = staticValues.policy.secretLimit;
async function setSecretLimit() {
const secretLimit = self.policy.secretLimit;
try {
await SammoAPI.Nation.SetSecretLimit({ amount: secretLimit });
oldSecretLimit = secretLimit;
toasts.value.push({
title: "변경",
content: "기밀 권한을 변경했습니다.",
});
} catch (e) {
if (isString(e)) {
toasts.value.push({
title: "에러",
content: e,
type: "danger",
});
}
self.policy.secretLimit = oldSecretLimit;
console.error(e);
}
}
function rollbackSecretLimit() {
self.policy.secretLimit = oldSecretLimit;
}
async function setBlockWar() {
try {
const result = await SammoAPI.Nation.SetBlockWar<SetBlockWarResponse>({ value: self.policy.blockWar });
self.warSettingCnt.remain = result.availableCnt;
toasts.value.push({
title: "변경",
content: "전쟁 금지 설정을 변경했습니다.",
});
} catch (e) {
if (isString(e)) {
toasts.value.push({
title: "에러",
content: e,
type: "danger",
});
}
self.policy.blockWar = !self.policy.blockWar;
console.error(e);
}
}
async function setBlockScout() {
try {
await SammoAPI.Nation.SetBlockScout({ value: self.policy.blockScout });
toasts.value.push({
title: "변경",
content: "임관 설정을 변경했습니다.",
});
} catch (e) {
if (isString(e)) {
toasts.value.push({
title: "에러",
content: e,
type: "danger",
});
}
self.policy.blockScout = !self.policy.blockScout;
console.error(e);
}
}
return {
toasts,
...toRefs(self),
inEditNationMsg,
inEditScoutMsg,
enableEditNationMsg,
rollbackNationMsg,
saveNationMsg,
enableEditScoutMsg,
rollbackScoutMsg,
saveScoutMsg,
diplomacyStateInfo,
joinYearMonth,
parseYearMonth,
trackNationMsgHeight: trackTiptapFormHeight("#noticeForm"),
trackScoutMsgHeight: trackTiptapFormHeight("#scoutMsgForm"),
incomeGoldCity,
incomeGold,
incomeRiceCity,
incomeRiceWall,
incomeRice,
outcomeByBill,
setRate,
rollbackRate,
setBill,
rollbackBill,
setSecretLimit,
rollbackSecretLimit,
setBlockWar,
setBlockScout,
};
},
methods: {},
});
</script>
-124
View File
@@ -1,124 +0,0 @@
<template>
<div> &amp; 임관 권유 메시지</div>
<div id="noticeForm">
<div class="bg1" style="display: flex; justify-content: space-around">
<div style="flex: 1 1 auto">국가 방침</div>
<div>
<b-button
@click="enableEditNationMsg"
v-if="editable && !inEditNationMsg"
>국가방침 수정</b-button
>
<b-button @click="saveNationMsg" v-if="editable && inEditNationMsg"
>저장</b-button
>
<b-button v-if="editable && inEditNationMsg" @click="rollbackNationMsg"
>취소</b-button
>
</div>
</div>
<TipTap v-model="nationMsg" :editable="inEditNationMsg" />
</div>
<div id="scoutMsgForm">
<div class="bg1" style="display: flex; justify-content: space-around">
<div style="flex: 1 1 auto">임관 권유</div>
<div>
<b-button
@click="enableEditScoutMsg"
v-if="editable && !inEditScoutMsg"
>임관 권유문 수정</b-button
>
<b-button @click="saveScoutMsg" v-if="editable && inEditScoutMsg"
>저장</b-button
>
<b-button v-if="editable && inEditScoutMsg" @click="rollbackScoutMsg"
>취소</b-button
>
</div>
</div>
<div style="border-bottom: solid gray 0.5px">
870px x 200px를 넘어서는 내용은 표시되지 않습니다.
</div>
<div style="width: 870px; margin-left: auto">
<TipTap v-model="scoutMsg" :editable="inEditScoutMsg" />
</div>
</div>
</template>
<script lang="ts">
import "@scss/dipcenter.scss";
import "@scss/common_legacy.scss";
import "@scss/editor_component.scss";
import TipTap from "./components/TipTap.vue";
import { defineComponent } from "vue";
import { sammoAPI } from "./util/sammoAPI";
import { isString } from "lodash";
declare const editable: boolean;
declare const nationMsg: string;
declare const scoutMsg: string;
export default defineComponent({
name: "PartialDipcenter",
components: {
TipTap,
},
data() {
return {
editable,
oldNationMsg: nationMsg,
oldScoutMsg: scoutMsg,
nationMsg,
scoutMsg,
inEditNationMsg: false,
inEditScoutMsg: false,
};
},
methods: {
enableEditNationMsg() {
this.inEditNationMsg = true;
},
rollbackNationMsg() {
this.inEditNationMsg = false;
this.nationMsg = this.oldNationMsg;
},
async saveNationMsg() {
const msg = this.nationMsg;
try {
await sammoAPI("Nation/SetNotice", {
msg,
});
this.oldNationMsg = msg;
this.inEditNationMsg = false;
} catch (e) {
if (isString(e)) {
alert(e);
}
console.error(e);
}
},
enableEditScoutMsg() {
this.inEditScoutMsg = true;
},
rollbackScoutMsg() {
this.inEditScoutMsg = false;
this.scoutMsg = this.oldScoutMsg;
},
async saveScoutMsg() {
const msg = this.scoutMsg;
try {
await sammoAPI("Nation/SetScoutMsg", {
msg,
});
this.oldScoutMsg = msg;
this.inEditScoutMsg = false;
} catch (e) {
if (isString(e)) {
alert(e);
}
console.error(e);
}
},
},
});
</script>
+129 -29
View File
@@ -7,6 +7,7 @@
<div class="row gx-1">
<div class="col d-grid">
<b-dropdown left text="턴 선택">
<b-dropdown-item @click="selectTurn()">해제</b-dropdown-item>
<b-dropdown-item @click="selectAll(true)">모든턴</b-dropdown-item>
<b-dropdown-item @click="selectStep(0, 2)">홀수턴</b-dropdown-item>
<b-dropdown-item @click="selectStep(1, 2)">짝수턴</b-dropdown-item>
@@ -50,17 +51,29 @@
</div>
<div class="commandTable">
<template
v-for="(turnObj, turnIdx) in reservedCommandList.slice(
0,
Math.min(maxTurn, viewMaxTurn)
)"
:key="turnIdx"
<DragSelect
:style="rowGridStyle"
attribute="turnIdx"
@dragStart="isDragToggle = true"
@dragDone="
isDragToggle = false;
toggleTurn(...$event);
"
v-slot="{ selected }"
>
<div class="idx_pad center d-grid" @click="toggleTurn(turnIdx)">
<div
v-for="(turnObj, turnIdx) in reservedCommandList.slice(
0,
viewMaxTurn
)"
:turnIdx="turnIdx"
:key="turnIdx"
class="idx_pad center d-grid"
>
<b-button
size="sm"
:variant="
(isDragToggle && selected.has(`${turnIdx}`))?'light':
turnList.has(turnIdx)
? 'info'
: turnList.size == 0 && prevTurnList.has(turnIdx)
@@ -70,10 +83,26 @@
>{{ turnIdx + 1 }}</b-button
>
</div>
</DragSelect>
<DragSelect
:style="rowGridStyle"
attribute="turnIdx"
@dragStart="isDragSingle = true"
@dragDone="
isDragSingle = false;
selectTurn(...$event);
"
v-slot="{ selected }"
>
<div
@click="selectTurn(turnIdx)"
v-for="(turnObj, turnIdx) in reservedCommandList.slice(
0,
viewMaxTurn
)"
:key="turnIdx"
height="24"
class="month_pad center"
:turnIdx="turnIdx"
:style="{
'white-space': 'nowrap',
'font-size': `${Math.min(
@@ -81,18 +110,41 @@
(75 / (`${turnObj.year ?? 1}`.length + 8)) * 1.8
)}px`,
overflow: 'hidden',
color:
isDragSingle && selected.has(`${turnIdx}`) ? 'cyan' : undefined,
}"
>
{{ turnObj.year ? `${turnObj.year}年` : "" }}
{{ turnObj.month ? `${turnObj.month}月` : "" }}
</div>
</DragSelect>
<div :style="rowGridStyle">
<div
v-for="(turnObj, turnIdx) in reservedCommandList.slice(
0,
viewMaxTurn
)"
:key="turnIdx"
class="time_pad center"
style="background-color: black; white-space: nowrap; overflow: hidden"
:style="{
backgroundColor: 'black',
whiteSpace: 'nowrap',
overflow: 'hidden',
}"
>
{{ turnObj.time }}
</div>
<div class="turn_pad center">
</div>
<div :style="rowGridStyle">
<div
v-for="(turnObj, turnIdx) in reservedCommandList.slice(
0,
viewMaxTurn
)"
:key="turnIdx"
class="turn_pad center"
@click="chooseCommand(turnObj.action)"
>
<span
class="turn_text"
:style="turnObj.style"
@@ -101,7 +153,7 @@
v-html="turnObj.brief"
></span>
</div>
</template>
</div>
</div>
<div class="row gx-1">
<div class="col d-grid">
@@ -200,16 +252,18 @@
<script lang="ts">
import addMilliseconds from "date-fns/esm/addMilliseconds";
import addMinutes from "date-fns/esm/addMinutes";
import { range } from "lodash";
import { isString, range } from "lodash";
import { stringifyUrl } from "query-string";
import { defineComponent } from "vue";
import { defineComponent, ref, watch } from "vue";
import { formatTime } from "@util/formatTime";
import { joinYearMonth } from "@util/joinYearMonth";
import { mb_strwidth } from "@util/mb_strwidth";
import { parseTime } from "@util/parseTime";
import { parseYearMonth } from "@util/parseYearMonth";
import { sammoAPI } from "@util/sammoAPI";
import { convertSearch초성 } from "./util/convertSearch초성";
import DragSelect from "@/components/DragSelect.vue";
import { SammoAPI } from "./SammoAPI";
type commandItem = {
value: string;
title: string;
@@ -286,7 +340,9 @@ const searchModeKey = `sammo_searchModeOn`;
export default defineComponent({
name: "PartialReservedCommand",
components: {
DragSelect,
},
methods: {
updateNow() {
const serverNow = addMilliseconds(new Date(), this.timeDiff);
@@ -295,16 +351,27 @@ export default defineComponent({
this.updateNow();
}, 1000 - serverNow.getMilliseconds());
},
toggleTurn(turnIdx: number) {
if (this.turnList.has(turnIdx)) {
this.turnList.delete(turnIdx);
} else {
this.turnList.add(turnIdx);
toggleTurn(...turnList: number[] | string[]) {
for (let turnIdx of turnList) {
if (isString(turnIdx)) {
turnIdx = parseInt(turnIdx);
}
if (this.turnList.has(turnIdx)) {
this.turnList.delete(turnIdx);
} else {
this.turnList.add(turnIdx);
}
}
},
selectTurn(turnIdx: number) {
selectTurn(...turnList: number[] | string[]) {
this.turnList.clear();
this.turnList.add(turnIdx);
for (const turnIdx of turnList) {
if (isString(turnIdx)) {
this.turnList.add(parseInt(turnIdx));
} else {
this.turnList.add(turnIdx);
}
}
},
selectAll(e: Event | true) {
//NOTE: split 구현에 버그가 있어서, 수동으로 구분해야함
@@ -337,7 +404,7 @@ export default defineComponent({
},
async repeatGeneralCommand(amount: number) {
try {
await sammoAPI(`Command/RepeatCommand`, { amount });
await SammoAPI.Command.RepeatCommand({ amount });
} catch (e) {
console.error(e);
alert(`실패했습니다: ${e}`);
@@ -347,7 +414,7 @@ export default defineComponent({
},
async pushGeneralCommand(amount: number) {
try {
await sammoAPI("Command/PushCommand", { amount });
await SammoAPI.Command.PushCommand({ amount });
} catch (e) {
console.error(e);
alert(`실패했습니다: ${e}`);
@@ -372,7 +439,7 @@ export default defineComponent({
async reloadCommandList() {
let result: ReservedCommandResponse;
try {
result = await sammoAPI("Command/GetReservedCommand");
result = await SammoAPI.Command.GetReservedCommand();
} catch (e) {
console.error(e);
alert(`실패했습니다: ${e}`);
@@ -457,7 +524,7 @@ export default defineComponent({
}
try {
await sammoAPI("Command/ReserveCommand", {
await SammoAPI.Command.ReserveCommand({
turnList,
action: commandName,
});
@@ -481,6 +548,9 @@ export default defineComponent({
this.searchModeOn = searchModeOn;
localStorage.setItem(searchModeKey, searchModeOn ? "1" : "0");
},
chooseCommand(val: string){
this.selectedCommand = this.invCommandMap[val];
}
},
data() {
const serverNowObj = parseTime(serverNow);
@@ -519,8 +589,7 @@ export default defineComponent({
return {
maxTurn,
flippedMaxTurn: 15,
viewMaxTurn: 15,
maxPushTurn,
commandList,
serverNow: formatTime(serverNowObj, "HH:mm:ss"),
@@ -533,6 +602,37 @@ export default defineComponent({
searchModeOn,
};
},
setup() {
const flippedMaxTurn = 15;
const viewMaxTurn = ref(flippedMaxTurn);
const rowGridStyle = ref({
display: "grid",
gridTemplateRows: `repeat(${viewMaxTurn.value}, 29.4px)`,
});
watch(viewMaxTurn, (val) => {
rowGridStyle.value.gridTemplateRows = `repeat(${val}, 29.4px)`;
});
const isDragSingle = ref(false);
const isDragToggle = ref(false);
const invCommandMap: Record<string, commandItem> = {};
for(const category of commandList){
for(const command of category.values){
invCommandMap[command.value] = command;
}
}
return {
isDragSingle,
isDragToggle,
flippedMaxTurn,
viewMaxTurn,
rowGridStyle,
invCommandMap,
};
},
mounted() {
void this.reloadCommandList();
},
@@ -618,7 +718,7 @@ export default defineComponent({
background-color: $nbase2color;
}
.turn_pad:nth-child(8n) {
.turn_pad:nth-child(2n) {
background-color: color.adjust($nbase2color, $lightness: -5%);
}
+58
View File
@@ -0,0 +1,58 @@
import { InvalidResponse } from "./defs";
import { APIPathGen } from "./util/APIPathGen";
import { callSammoAPI, ValidResponse } from "./util/callSammoAPI";
export type { ValidResponse, InvalidResponse };
async function done<ResultType extends ValidResponse>(args?: Record<string, unknown>): Promise<ResultType>;
async function done<ResultType extends ValidResponse>(args: Record<string, unknown> | undefined, returnError: false): Promise<ResultType>;
async function done<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(args: Record<string, unknown> | undefined, returnError: true): Promise<ResultType | ErrorType>;
async function done<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(args?: Record<string, unknown>, returnError = false): Promise<ResultType | ErrorType> {
console.error(`Can't directly call. ${args}, ${returnError}. Use auto-generated path API.`);
return callSammoAPI<ResultType, ErrorType>([], args, true);
}
const apiRealPath = {
Command: {
GetReservedCommand: done,
PushCommand: done,
RepeatCommand: done,
ReserveCommand: done,
},
General: {
Join: done,
},
InheritAction: {
BuyHiddenBuff: done,
BuyRandomUnique: done,
BuySpecificUnique: done,
ResetSpecialWar: done,
ResetTurnTime: done,
SetNextSpecialWar: done,
},
Misc: { UploadImage: done },
NationCommand: {
GetReservedCommand: done,
PushCommand: done,
RepeatCommand: done,
ReserveCommand: done,
},
Nation: {
SetNotice: done,
SetScoutMsg: done,
SetBill: done,
SetRate: done,
SetSecretLimit: done,
SetBlockWar: done,
SetBlockScout: done,
},
} as const;
export const SammoAPI = APIPathGen<typeof done, typeof apiRealPath>(apiRealPath, (path: string[]) => {
return (args?: Record<string, unknown>, returnError?: boolean) => {
if (returnError) {
return callSammoAPI(path.join('/'), args, true);
}
return callSammoAPI(path.join('/'), args);
};
});
+30
View File
@@ -0,0 +1,30 @@
import { InvalidResponse } from "./defs";
import { APIPathGen } from "./util/APIPathGen";
import { callSammoAPI, ValidResponse } from "./util/callSammoAPI";
export type { ValidResponse, InvalidResponse };
async function done<ResultType extends ValidResponse>(args?: Record<string, unknown>): Promise<ResultType>;
async function done<ResultType extends ValidResponse>(args: Record<string, unknown> | undefined, returnError: false): Promise<ResultType>;
async function done<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(args: Record<string, unknown> | undefined, returnError: true): Promise<ResultType | ErrorType>;
async function done<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(args?: Record<string, unknown>, returnError = false): Promise<ResultType | ErrorType>{
console.error(`Can't directly call. ${args}, ${returnError}. Use auto-generated path API.`);
return callSammoAPI<ResultType, ErrorType>([], args, true);
}
const apiRealPath = {
Login: {
LoginByID: done,
LoginByToken: done,
ReqNonce: done,
},
} as const;
export const SammoRootAPI = APIPathGen<typeof done, typeof apiRealPath>(apiRealPath, (path: string[]) => {
return (args?: Record<string, unknown>, returnError?: boolean) => {
if (returnError) {
return callSammoAPI(path.join('/'), args, true);
}
return callSammoAPI(path.join('/'), args);
};
});
+1 -2
View File
@@ -1,6 +1,5 @@
{
"ingame": {
"chiefCenter": "chiefCenter.ts",
"troop": "troop.ts",
"map": "map.ts",
"install_db": "install_db.ts",
@@ -27,7 +26,7 @@
"v_NPCControl": "v_NPCControl.ts",
"v_join": "v_join.ts",
"v_main": "v_main.ts",
"v_dipcenter": "v_dipcenter.ts",
"v_nationStratFinan": "v_nationStratFinan.ts",
"v_processing": "v_processing.ts"
}
}
-264
View File
@@ -1,264 +0,0 @@
import axios from 'axios';
import $ from 'jquery';
import { range } from 'lodash';
import { addMinutes } from 'date-fns';
import { errUnknown, getNpcColor } from '@/common_legacy';
import { InvalidResponse } from '@/defs';
import { unwrap } from "@util/unwrap";
import { convertFormData } from '@util/convertFormData';
import { unwrap_any } from "@util/unwrap_any";
import { parseTime } from '@util/parseTime';
import { formatTime } from '@util/formatTime';
import { stringifyUrl } from 'query-string';
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
setAxiosXMLHttpRequest();
declare const maxChiefTurn: number;
type TurnDOMObj = {
turnTime: JQuery<HTMLElement>,
turnPad: JQuery<HTMLElement>,
turnText: JQuery<HTMLElement>
};
type TableObj = {
btns: JQuery<HTMLElement>;
[key: number]: {
officerLevelText: JQuery<HTMLElement>,
name: JQuery<HTMLElement>,
turn: TurnDOMObj[],
}
}
type ChiefResponse = InvalidResponse | {
result: true,
date: string,
nationTurnBrief: {
name: string | null,
turnTime: string | null,
officerLevelText: string,
npcType: number,
turn: string
}[],
isChief: boolean,
turnTerm: number
}
let filledChiefList: Record<number, boolean> = {};
let chiefTableObj: TableObj = undefined as unknown as TableObj;//TODO: 매우 지저분하다. class 기반으로 고치던가 할 것
/*
function clearTable() {
$('.chiefLevelText').html('-');
$('.chiefTurnTime, .chiefTurnText, .chiefName').html('&nbsp;');
}
*/
function genChiefTableObj(): TableObj {
const objTable: TableObj = {
btns: $('#turnPush,#turnPull,#setCommand')
};
for (const chiefIdx of range(5, 13)) {
const $plate = $(`#chief_${chiefIdx}`);
const $officerLevelText = $plate.find('.chiefLevelText');
const $name = $plate.find('.chiefName');
const turn: TurnDOMObj[] = [];
for (const turnIdx of range(maxChiefTurn)) {
const $turn = $plate.find(`.turn${turnIdx}`);
const $turnTime = $turn.find('.chiefTurnTime');
const $turnPad = $turn.find('.chiefTurnPad');
const $turnText = $turn.find('.chiefTurnText');
turn.push({ turnTime: $turnTime, turnPad: $turnPad, turnText: $turnText });
}
objTable[chiefIdx] = {
officerLevelText: $officerLevelText,
name: $name,
turn: turn
};
}
return objTable;
}
function clearChief(chiefIdx: number): void {
const $plate = $(`#chief_${chiefIdx}`);
$plate.find('.chiefLevelText').html('-');
$plate.find('.chiefTurnTime, .chiefTurnText, .chiefName').html('&nbsp;');
}
async function reloadTable() {
const data: ChiefResponse = await (async () => {
try {
const response = await axios({
url: 'j_getChiefTurn.php',
responseType: 'json'
});
return response.data;
}
catch (e) {
console.error(e);
errUnknown();
}
})();
if (!data.result) {
alert(data.reason);
return;
}
const turnTerm = data.turnTerm;
const tmpFilledChiefList: Record<number, boolean> = {};
if (data.isChief) {
chiefTableObj.btns.css('visibility', 'visible');
}
else {
chiefTableObj.btns.css('visibility', 'hidden');
}
$.each(data.nationTurnBrief, function (chiefIdx, chiefInfo) {
tmpFilledChiefList[chiefIdx] = true;
filledChiefList[chiefIdx] = true;
const plateObj = chiefTableObj[chiefIdx];
if (chiefInfo.name) {
const $name = $(`<span>${chiefInfo.name}</span>`);
const nameColor = getNpcColor(chiefInfo.npcType);
if (nameColor) {
$name.css('color', nameColor);
}
plateObj.name.empty().append($name);
}
else {
plateObj.name.html('');
}
plateObj.officerLevelText.text(chiefInfo.officerLevelText);
let turnTimeObj: Date | undefined;
if (chiefInfo.turnTime) {
turnTimeObj = parseTime(chiefInfo.turnTime);
}
const turnList = plateObj.turn;
$.each(chiefInfo.turn, function (turnIdx, turnText) {
if (turnTimeObj) {
turnList[turnIdx].turnTime.text(formatTime(turnTimeObj, 'HH:mm'));
}
else {
turnList[turnIdx].turnTime.text('');
}
turnList[turnIdx].turnText.html(turnText).css('font-size', '14px');
const oWidth = unwrap(turnList[turnIdx].turnPad.innerWidth());
const iWidth = unwrap(turnList[turnIdx].turnText.outerWidth());
if (iWidth > oWidth * 0.95) {
const newFontSize = 14 * oWidth / iWidth * 0.9;
turnList[turnIdx].turnText.css('font-size', `${newFontSize}px`);
}
if (turnTimeObj) {
turnTimeObj = addMinutes(turnTimeObj, turnTerm);
}
});
});
for (const idx of range(5, 13)) {
if (idx in tmpFilledChiefList) {
continue;
}
if (idx in filledChiefList) {
clearChief(idx);
}
}
filledChiefList = tmpFilledChiefList;
}
async function reserveTurn(turnList: number[], command: string) {
console.log(turnList, command);
try {
const response = await axios({
url: 'j_set_chief_command.php',
responseType: 'json',
method: 'post',
data: convertFormData({
turnList,
action: command
})
});
const data: InvalidResponse = response.data;
if (!data.result) {
alert(data.reason);
}
await reloadTable();
}
catch (e) {
console.error(e);
errUnknown();
}
}
async function pushTurn(turnCnt: number) {
try {
const response = await axios({
url: 'j_chief_turn.php',
responseType: 'json',
method: 'post',
data: convertFormData({
amount: turnCnt
})
});
const data = response.data;
if (!data.result) {
alert(data.reason);
}
await reloadTable();
}
catch (e) {
console.error(e);
errUnknown();
}
}
$(function ($) {
chiefTableObj = genChiefTableObj();
void reloadTable();
$('#reloadTable').on('click', reloadTable);
$('#setCommand').on('click', function () {
const turnList = unwrap_any<string[]>($('#chiefTurnSelector').val()).map(function (v) { return parseInt(v); });
const $command = $('#chiefCommandList option:selected');
if ($command.data('reqarg')) {
document.location.href = stringifyUrl({
url: 'v_processing.php',
query: {
command: unwrap_any<string>($command.val()),
turnList: turnList.join('_'),
is_chief: true
}
});
}
else {
void reserveTurn(turnList, unwrap_any<string>($command.val()));
}
return false;
});
$('#turnPush').on('click', function () {
void pushTurn(1);
return false;
});
$('#turnPull').on('click', function () {
void pushTurn(-1);
return false;
});
})
+40 -44
View File
@@ -1,53 +1,49 @@
<template>
<table class="articleFrame bg0">
<thead>
<tr class="bg1">
<th class="authorName">{{ article.author }}</th>
<th class="articleTitle">{{ article.title }}</th>
<th class="date">{{ article.date }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<img
class="authorIcon generalIcon"
width="64"
height="64"
:src="article.author_icon"
/>
</td>
<td class="text" colspan="2">{{ article.text }}</td>
</tr>
</tbody>
<tbody class="commentList">
<div class="articleFrame bg0">
<div class="bg1 row gx-0">
<div class="authorName center">{{ article.author }}</div>
<div class="col articleTitle center">{{ article.title }}</div>
<div class="col-2 col-md-1 date center">{{ article.date.slice(5, 16) }}</div>
</div>
<div class="row gx-0 s-border-b">
<div class="col-2 col-md-1 authorIcon center">
<img
class="generalIcon"
width="64"
height="64"
:src="article.author_icon"
/>
</div>
<div class="col text">{{ article.text }}</div>
</div>
<div class="commentList">
<board-comment
v-for="comment in article.comment"
:key="comment.no"
:comment="comment"
/>
</tbody>
<tfoot>
<tr>
<td class="bg2 inputCommentHeader">댓글 달기</td>
<td>
<input
class="commentText"
type="text"
maxlength="250"
placeholder="새 댓글 내용"
v-model.trim="newCommentText"
@keyup.enter="submitComment"
/>
</td>
<td>
<button type="button" class="submitComment" @click="submitComment">
등록
</button>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="row gx-0">
<div class="bg2 inputCommentHeader center d-grid">
<div class="align-self-center">댓글 달기</div>
</div>
<div class="col d-grid">
<input
class="commentText"
type="text"
maxlength="250"
placeholder="새 댓글 내용"
v-model.trim="newCommentText"
@keyup.enter="submitComment"
/>
</div>
<div class="col-2 col-md-1 d-grid">
<b-button class="submitComment" @click="submitComment" size="sm"
>등록</b-button
>
</div>
</div>
</div>
</template>
<script lang="ts">
import { BoardArticleItem } from "@/PageBoard.vue";
+5 -6
View File
@@ -1,10 +1,9 @@
<template>
<tr class="comment">
<th class="author">{{ comment.author }}</th>
<td class="text">{{ comment.text }}</td>
<td class="date">{{ comment.date }}</td>
<!-- 삭제도 나중에 추가? -->
</tr>
<div class="row gx-0 comment s-border-b">
<div class="authorName center d-grid"><div class="align-self-center">{{ comment.author }}</div></div>
<div class="col text">{{ comment.text }}</div>
<div class="col-2 col-md-1 date center d-grid"><div class="align-self-center">{{ comment.date.slice(5, 16) }}</div></div>
</div>
</template>
<script lang="ts">
import { BoardCommentItem } from "@/PageBoard.vue";
-5
View File
@@ -12,7 +12,6 @@ import { defineComponent, PropType } from "vue";
import "@scss/game_bg.scss";
export default defineComponent({
name: "TopBackBar",
methods: {
back(){
if(this.type === 'normal'){
@@ -28,10 +27,6 @@ export default defineComponent({
}
},
props: {
title: {
type: String,
required: true,
},
type: {
type: String as PropType<"normal"|"chief"|"close">,
default: "normal",
+93 -18
View File
@@ -1,22 +1,54 @@
<template>
<div class="commandPad chiefReservedCommand">
<div class="commandTable">
<template
v-for="(turnObj, turnIdx) in reservedCommandList"
:key="turnIdx"
<DragSelect
:style="rowGridStyle"
attribute="turnIdx"
@dragStart="isDragSingle = true"
@dragDone="
isDragSingle = false;
selectTurn(...$event);
"
v-slot="{ selected }"
>
<div
@click="selectTurn(turnIdx)"
v-for="(turnObj, turnIdx) in reservedCommandList"
:key="turnIdx"
:turnIdx="turnIdx"
class="time_pad center f_tnum"
style="background-color: black; white-space: nowrap; overflow: hidden"
:style="{
backgroundColor: 'black',
whiteSpace: 'nowrap',
overflow: 'hidden',
color:
isDragSingle && selected.has(`${turnIdx}`) ? 'cyan' : undefined,
}"
>
{{ turnObj.time }}
</div>
<div class="idx_pad center d-grid" @click="toggleTurn(turnIdx)">
</DragSelect>
<DragSelect
:style="rowGridStyle"
attribute="turnIdx"
@dragStart="isDragToggle = true"
@dragDone="
isDragToggle = false;
toggleTurn(...$event);
"
v-slot="{ selected }"
>
<div
v-for="(turnObj, turnIdx) in reservedCommandList"
:key="turnIdx"
:turnIdx="turnIdx"
class="idx_pad center d-grid"
>
<b-button
size="sm"
:variant="
turnList.has(turnIdx)
isDragToggle && selected.has(`${turnIdx}`)
? 'light'
: turnList.has(turnIdx)
? 'info'
: turnList.size == 0 && prevTurnList.has(turnIdx)
? 'success'
@@ -25,7 +57,14 @@
>{{ turnIdx + 1 }}</b-button
>
</div>
<div class="turn_pad center">
</DragSelect>
<div :style="rowGridStyle">
<div
v-for="(turnObj, turnIdx) in reservedCommandList"
:key="turnIdx"
class="turn_pad center"
@click="chooseCommand(turnObj.action)"
>
<span
class="turn_text"
:style="turnObj.style"
@@ -34,7 +73,7 @@
v-html="turnObj.brief"
></span>
</div>
</template>
</div>
</div>
<div class="row gx-0">
<div class="col-2 d-grid">
@@ -112,9 +151,12 @@ import { joinYearMonth } from "@util/joinYearMonth";
import { mb_strwidth } from "@util/mb_strwidth";
import { parseTime } from "@util/parseTime";
import { parseYearMonth } from "@util/parseYearMonth";
import { sammoAPI } from "@util/sammoAPI";
import { convertSearch초성 } from "@util/convertSearch초성";
import VueTypes from "vue-types";
import DragSelect from "@/components/DragSelect.vue";
import { isString } from "lodash";
import { SammoAPI } from "@/SammoAPI";
type commandItem = {
value: string;
title: string;
@@ -147,6 +189,9 @@ const searchModeKey = `sammo_searchModeOn`;
export default defineComponent({
name: "NationReservedCommand",
components: {
DragSelect
},
props: {
maxTurn: VueTypes.integer.isRequired,
maxPushTurn: VueTypes.integer.isRequired,
@@ -218,17 +263,28 @@ export default defineComponent({
this.updateCommandList();
}, 1);
},
toggleTurn(val: number) {
if (this.turnList.has(val)) {
this.turnList.delete(val);
} else {
this.turnList.add(val);
toggleTurn(...turnList: number[] | string[]) {
for (let turnIdx of turnList) {
if (isString(turnIdx)) {
turnIdx = parseInt(turnIdx);
}
if (this.turnList.has(turnIdx)) {
this.turnList.delete(turnIdx);
} else {
this.turnList.add(turnIdx);
}
}
this.$emit("update:selectedTurn", this.turnList);
},
selectTurn(val: number) {
selectTurn(...turnList: number[] | string[]) {
this.turnList.clear();
this.turnList.add(val);
for (const turnIdx of turnList) {
if (isString(turnIdx)) {
this.turnList.add(parseInt(turnIdx));
} else {
this.turnList.add(turnIdx);
}
}
this.$emit("update:selectedTurn", this.turnList);
},
updateCommandList() {
@@ -319,7 +375,7 @@ export default defineComponent({
}
try {
await sammoAPI("NationCommand/ReserveCommand", {
await SammoAPI.NationCommand.ReserveCommand({
turnList,
action: commandName,
});
@@ -343,6 +399,9 @@ export default defineComponent({
this.searchModeOn = searchModeOn;
localStorage.setItem(searchModeKey, searchModeOn ? "1" : "0");
},
chooseCommand(val: string){
this.selectedCommand = this.invCommandMap[val];
},
},
data() {
const serverNowObj = parseTime(this.date);
@@ -372,6 +431,13 @@ export default defineComponent({
}
}
const invCommandMap: Record<string, commandItem> = {};
for(const category of this.commandList){
for(const command of category.values){
invCommandMap[command.value] = command;
}
}
const searchModeOn = (localStorage.getItem(searchModeKey) ?? "0") != "0";
const emptyTurn: TurnObjWithTime[] = Array.from<TurnObjWithTime>({
@@ -387,6 +453,11 @@ export default defineComponent({
const prevTurnList = new Set([0]);
const rowGridStyle = {
display: "grid",
gridTemplateRows: `repeat(${this.maxTurn}, 30px)`,
};
return {
updated: false,
listReqArgCommand,
@@ -398,6 +469,10 @@ export default defineComponent({
reservedCommandList: emptyTurn,
autorun_limit: null as null | number,
searchModeOn,
rowGridStyle,
isDragSingle: false,
isDragToggle: false,
invCommandMap,
};
},
mounted() {
+200
View File
@@ -0,0 +1,200 @@
<template>
<div
ref="container"
style="
position: relative;
user-select: none;
overflow: hidden;
touch-action: none;
"
>
<slot :selected="intersected" />
</div>
</template>
<script lang="ts">
/// https://github.com/andi23rosca/drag-select-vue/blob/master/src/DragSelect.vue
import {
defineComponent,
ref,
watch,
onMounted,
onBeforeUnmount,
PropType,
} from "vue";
import VueTypes from "vue-types";
function getDimensions(p1: coord, p2: coord): rect {
return {
width: Math.abs(p1.x - p2.x),
height: Math.abs(p1.y - p2.y),
};
}
function collisionCheck(node1: DOMRect, node2: DOMRect): boolean {
return (
node1.left < node2.left + node2.width &&
node1.left + node1.width > node2.left &&
node1.top < node2.top + node2.height &&
node1.top + node1.height > node2.top
);
}
type coord = { x: number; y: number };
type rect = { width: number; height: number };
export default defineComponent({
props: {
attribute: VueTypes.string.isRequired,
color: {
...VueTypes.string.def("#4299E1"),
required: false,
},
opacity: {
...VueTypes.number.def(0.7),
required: false,
},
modelValue: {
type: Object as PropType<Set<string>>,
required: false,
default: () => ref(new Set()),
},
},
emits: ["update:modelValue", "dragDone", "dragStart"],
setup(props, { emit }) {
const intersected = ref<Set<string>>(props.modelValue);
const container = ref<HTMLElement>();
watch(intersected, (val) => {
emit("update:modelValue", val);
});
watch(props.modelValue, (val) => {
if (intersected.value === val) {
return;
}
intersected.value = val;
});
onMounted(() => {
if (!container.value) {
console.error(`Container is not referenced.`);
return;
}
const uContainer = container.value;
let containerRect = uContainer.getBoundingClientRect();
function getCoords(e: MouseEvent | Touch): coord {
return {
x: e.clientX - containerRect.left,
y: e.clientY - containerRect.top,
};
}
let children: HTMLCollection;
let box = document.createElement("div");
box.setAttribute("data-drag-box-component", "");
box.style.position = "absolute";
box.style.backgroundColor = props.color;
box.style.opacity = `${props.opacity}`;
let start = { x: 0, y: 0 };
let end = { x: 0, y: 0 };
function intersection() {
const rect = box.getBoundingClientRect();
const localIntersected = new Set<string>();
for (let i = 0; i < children.length; i++) {
if (collisionCheck(rect, children[i].getBoundingClientRect())) {
const attr = children[i].getAttribute(props.attribute);
if (children[i].hasAttribute(props.attribute)) {
localIntersected.add(attr as string);
}
}
}
let dismatch = false;
for (const oldVal of intersected.value) {
if (!localIntersected.has(oldVal)) {
dismatch = true;
break;
}
}
if (!dismatch) {
for (const newVal of localIntersected) {
if (!intersected.value.has(newVal)) {
dismatch = true;
break;
}
}
}
if (dismatch) {
intersected.value = localIntersected;
}
}
function touchStart(e: TouchEvent) {
e.preventDefault();
startDrag(e.touches[0]);
}
function touchMove(e: TouchEvent) {
e.preventDefault();
drag(e.touches[0]);
}
let isMine = false;
function startDrag(e: MouseEvent | Touch) {
containerRect = uContainer.getBoundingClientRect();
children = uContainer.children;
start = getCoords(e);
end = start;
document.addEventListener("mousemove", drag);
document.addEventListener("touchmove", touchMove);
box.style.top = start.y + "px";
box.style.left = start.x + "px";
uContainer.prepend(box);
intersection();
isMine = true;
emit("dragStart");
}
function drag(e: MouseEvent | Touch) {
end = getCoords(e);
const dimensions = getDimensions(start, end);
if (end.x < start.x) {
box.style.left = end.x + "px";
}
if (end.y < start.y) {
box.style.top = end.y + "px";
}
box.style.width = dimensions.width + "px";
box.style.height = dimensions.height + "px";
intersection();
}
function endDrag() {
start = { x: 0, y: 0 };
end = { x: 0, y: 0 };
box.style.width = "0";
box.style.height = "0";
document.removeEventListener("mousemove", drag);
document.removeEventListener("touchmove", touchMove);
box.remove();
if(isMine){
emit("dragDone", intersected.value);
}
isMine = false;
}
uContainer.addEventListener("mousedown", startDrag);
uContainer.addEventListener("touchstart", touchStart);
document.addEventListener("mouseup", endDrag);
document.addEventListener("touchend", endDrag);
onBeforeUnmount(() => {
uContainer.removeEventListener("mousedown", startDrag);
uContainer.removeEventListener("touchstart", touchStart);
document.removeEventListener("mouseup", endDrag);
document.removeEventListener("touchend", endDrag);
});
});
return {
intersected,
container,
};
},
});
</script>
+76 -24
View File
@@ -1,34 +1,34 @@
<template>
<CToaster class="z99" placement="top-end">
<CToast
v-for="(toast, index) in modelValue"
:key="index"
:delay="toast.delay??delay"
>
<CToastHeader
:class="['text-white', `bg-${toast.type ?? 'primary'}`]"
close
>
<span class="me-auto fw-bold">{{ toast.title }}</span>
</CToastHeader>
<CToastBody>
{{ toast.content }}
</CToastBody>
</CToast>
<CToaster class="my-toaster" placement="top-end" v-if="toasts.length > 0">
<template v-for="(toast, index) in toasts" :key="index">
<CToast :delay="toast.delay ?? delay">
<CToastHeader :class="['text-white', `bg-${toast.type ?? 'primary'}`]">
<span class="me-auto fw-bold">{{ toast.title }}</span>
<CToastClose class="my-close" color="secondary" size="sm" />
</CToastHeader>
<CToastBody> {{ toast.content }} {{ toast.visible }} </CToastBody>
</CToast>
</template>
</CToaster>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { defineComponent, PropType, ref, watch } from "vue";
import { ToastType } from "@/defs";
import { CToaster, CToast, CToastHeader, CToastBody } from "@coreui/vue/src/components/toast";
import {
CToaster,
CToast,
CToastHeader,
CToastBody,
CToastClose,
} from "@coreui/vue/src/components/toast";
export default defineComponent({
name: "MyToast",
components: {
CToaster,
CToast,
CToastHeader,
CToastBody,
CToastClose,
},
emits: ["update:modelValue"],
props: {
@@ -37,15 +37,67 @@ export default defineComponent({
required: true,
},
delay: {
type: Number,
required:false,
default:5000,
type: Number,
required: false,
default: 5000,
},
},
setup(props, { emit }) {
let doneCnt = 0;
const doneMap = new Map<ToastType, boolean>();
let toasts = ref(props.modelValue);
function onClose(toast: ToastType) {
const status = doneMap.get(toast);
if (status === undefined) {
return;
}
if (status) {
return;
}
doneCnt -= 1;
if (doneCnt != 0) {
doneMap.set(toast, true);
return;
}
doneMap.clear();
toasts.value.length = 0;
emit("update:modelValue", toasts);
}
watch(props.modelValue, (values) => {
for (const item of values) {
if (!doneMap.has(item)) {
doneMap.set(item, false);
doneCnt += 1;
setTimeout(() => {
onClose(item);
}, (item.delay ?? props.delay) + 1000);
}
}
if (toasts.value !== values) {
toasts.value = values;
}
});
return {
onClose,
toasts,
};
},
});
</script>
<style scoped>
.z99 {
<style>
.my-toaster {
z-index: 199;
}
.my-toaster .btn.btn-close.my-close,
.my-toaster .btn.btn-close.my-close:hover,
.my-toaster .btn.btn-close.my-close:active {
border: 0;
padding: 0;
}
</style>
+13 -5
View File
@@ -223,7 +223,9 @@
</b-button-group>
<b-button-group class="mx-1">
<b-button
@click="editor.chain().focus().setImage({ align: 'float-left' }).run()"
@click="
editor.chain().focus().setImage({ align: 'float-left' }).run()
"
:class="{
'is-active': editor.isActive('custom-image', {
float: 'float-left',
@@ -267,7 +269,9 @@
><i class="bi bi-align-end"></i
></b-button>
<b-button
@click="editor.chain().focus().setImage({ align: 'float-right' }).run()"
@click="
editor.chain().focus().setImage({ align: 'float-right' }).run()
"
:class="{
'is-active': editor.isActive('custom-image', {
float: 'float-right',
@@ -280,7 +284,7 @@
</b-button-group>
</b-button-toolbar>
</bubble-menu>
<editor-content :editor="editor" />
<editor-content :editor="editor" class="tiptap-editor" />
<b-modal
v-model="showImageModal"
title="이미지 추가"
@@ -351,9 +355,9 @@ import {
import { v4 as uuidv4 } from "uuid";
import { unwrap } from "@/util/unwrap";
import { getBase64FromFileObject } from "@/util/getBase64FromFileObject";
import { sammoAPI } from "@/util/sammoAPI";
import { isObject, isString } from "lodash";
import { AxiosError } from "axios";
import { SammoAPI } from "@/SammoAPI";
const compoment = defineComponent({
components: {
@@ -367,6 +371,7 @@ const compoment = defineComponent({
BDropdownItem,
BDropdownDivider,
},
emits: ["ready", "update:modelValue"],
methods: {
chooseImage(e: Event) {
const target = unwrap(e.target) as HTMLInputElement;
@@ -403,7 +408,7 @@ const compoment = defineComponent({
};
try {
const base64Binary = await getBase64FromFileObject(targetImage);
imageResult = await sammoAPI("Misc/UploadImage", {
imageResult = await SammoAPI.Misc.UploadImage({
imageData: base64Binary,
});
} catch (e) {
@@ -515,6 +520,9 @@ const compoment = defineComponent({
onUpdate: () => {
this.$emit("update:modelValue", this.editor.getHTML());
},
onCreate: () => {
this.$emit("ready");
}
});
this.editor = editor;
},
+16 -3
View File
@@ -163,11 +163,11 @@ export const NoneValue = 'None' as const;
export type Optional<Type> = {
[Property in keyof Type]+?: Type[Property];
};
};
export type OptionalFull<Type> = {
[Property in keyof Type]: Type[Property]|undefined;
};
[Property in keyof Type]: Type[Property] | undefined;
};
export type commandItem = {
value: string;
@@ -210,3 +210,16 @@ export type ChiefResponse = {
values: commandItem[];
}[];
};
type diplomacyInfo = {
name: string,
color?: string,
}
export type diplomacyState = 0 | 1 | 2 | 7;
export const diplomacyStateInfo: Record<diplomacyState, diplomacyInfo> = {
0: { name: '교전', color: 'red' },
1: { name: '선포중', color: 'magenta' },
2: { name: '통상' },
7: { name: '불가침', color: 'green' },
}
+4 -4
View File
@@ -12,8 +12,8 @@ import { InvalidResponse } from '@/defs';
import { delay } from '@util/delay';
import { Modal } from 'bootstrap';
import '@/gateway/common';
import { sammoAPI } from '@/util/sammoAPI';
import { isString } from 'lodash';
import { SammoRootAPI } from '@/SammoRootAPI';
type LoginResponse = {
result: true,
@@ -101,7 +101,7 @@ async function tryAutoLogin() {
const [tokenID, token] = tokenInfo;
const result = await sammoAPI<AutoLoginNonceResponse, AutoLoginFailed>(["Login", "ReqNonce"], {}, true);
const result = await SammoRootAPI.Login.ReqNonce<AutoLoginNonceResponse, AutoLoginFailed>({}, true);
if (!result) {
//api 에러.
@@ -116,7 +116,7 @@ async function tryAutoLogin() {
const nonce = result.loginNonce;
const hashedToken = sha512(token + nonce);
const loginResult = await sammoAPI<AutoLoginResponse, AutoLoginFailed>(["Login", "LoginByToken"], {
const loginResult = await SammoRootAPI.Login.LoginByToken<AutoLoginResponse, AutoLoginFailed>({
'hashedToken': hashedToken,
'token_id': tokenID,
}, true);
@@ -312,7 +312,7 @@ $(async function ($) {
let result: LoginResponse | LoginFailed;
try {
result = await sammoAPI<LoginResponse, LoginFailed>(["Login", "LoginByID"], {
result = await SammoRootAPI.Login.LoginByID<LoginResponse, LoginFailed>({
username: values.username,
password: hash_pw,
+26
View File
@@ -0,0 +1,26 @@
type SubValue<V extends (...args: unknown[]) => unknown> = V | { [property: string]: SubValue<V> };
export function APIPathGen<V extends () => unknown, T extends { [property: string]: SubValue<V> }>(obj: T, callback: (path: string[]) => V, path?: string[]): T {
return new Proxy(obj, {
get(target, key: string) {
let nextPath: string[];
if (path === undefined) {
nextPath = [key];
}
else {
nextPath = path;
nextPath.push(key);
}
if (!(key in target)) {
throw `${nextPath} is not exists`;
}
const next = target[key];
if (typeof (next) === 'function') {
return callback(nextPath);
}
return APIPathGen(next, callback, nextPath);
}
})
}
+35
View File
@@ -0,0 +1,35 @@
import axios from "axios";
import { isArray } from "lodash";
import { InvalidResponse } from '@/defs';
export type ValidResponse = {
result: true
}
export async function callSammoAPI<ResultType extends ValidResponse>(path: string | string[], args?: Record<string, unknown>): Promise<ResultType>;
export async function callSammoAPI<ResultType extends ValidResponse>(path: string | string[], args: Record<string, unknown> | undefined, returnError: false): Promise<ResultType>;
export async function callSammoAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(path: string | string[], args: Record<string, unknown> | undefined, returnError: true): Promise<ResultType | ErrorType>;
export async function callSammoAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(path: string | string[], args?: Record<string, unknown>, returnError = false): Promise<ResultType | ErrorType> {
if (isArray(path)) {
path = path.join('/');
}
const response = await axios({
url: "api.php",
method: "post",
responseType: "json",
data: {
path,
args,
},
});
const result: ErrorType | ResultType = response.data;
if (!result.result) {
if (returnError) {
return result;
}
throw result.reason;
}
return result;
}
-35
View File
@@ -1,35 +0,0 @@
import axios from "axios";
import { isArray } from "lodash";
import { InvalidResponse } from '@/defs';
export type ValidResponse = {
result: true
}
export async function sammoAPI<ResultType extends ValidResponse>(path: string | string[], args?: Record<string, unknown>): Promise<ResultType>;
export async function sammoAPI<ResultType extends ValidResponse>(path: string | string[], args: Record<string, unknown> | undefined, returnError: false): Promise<ResultType>;
export async function sammoAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(path: string | string[], args: Record<string, unknown> | undefined, returnError: true): Promise<ResultType | ErrorType>;
export async function sammoAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(path: string | string[], args?: Record<string, unknown>, returnError = false): Promise<ResultType | ErrorType> {
if (isArray(path)) {
path = path.join('/');
}
const response = await axios({
url: "api.php",
method: "post",
responseType: "json",
data: {
path,
args,
},
});
const result: ErrorType | ResultType = response.data;
if (!result.result) {
if (returnError) {
return result;
}
throw result.reason;
}
return result;
}
+5 -2
View File
@@ -1,14 +1,17 @@
import "@scss/board.scss";
import { createApp } from 'vue'
import PageBoard from '@/PageBoard.vue';
import BootstrapVue3 from 'bootstrap-vue-3'
import "@scss/common/bootstrap5.scss";
import 'bootstrap-vue-3/dist/bootstrap-vue-3.css'
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
import { auto500px } from "./util/auto500px";
declare const isSecretBoard: boolean;
setAxiosXMLHttpRequest();
auto500px();
createApp(PageBoard, {
isSecretBoard
}).use(BootstrapVue3).mount('#app')
-16
View File
@@ -1,16 +0,0 @@
import { createApp } from 'vue'
import PartialDipcenter from '@/PartialDipcenter.vue';
import BootstrapVue3 from 'bootstrap-vue-3';
import { htmlReady } from './util/htmlReady';
//import { activateFlip } from './legacy/activateFlip';
//import { activateFlip } from "@/legacy/activateFlip";
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
//import { htmlReady } from './util/htmlReady';
createApp(PartialDipcenter).use(BootstrapVue3).mount('#editorForm');
setAxiosXMLHttpRequest();
htmlReady(function(){
//activateFlip();
})
-2
View File
@@ -1,7 +1,5 @@
import { createApp } from 'vue'
import PageJoin from '@/PageJoin.vue';
import BootstrapVue3 from 'bootstrap-vue-3'
import "@scss/common/bootstrap5.scss";
import "@scss/editor_component.scss";
createApp(PageJoin).use(BootstrapVue3).mount('#app')
+12
View File
@@ -0,0 +1,12 @@
import "@scss/nationStratFinan.scss";
import { createApp } from 'vue'
import PageNationStratFinan from '@/PageNationStratFinan.vue';
import BootstrapVue3 from 'bootstrap-vue-3';
import { auto500px } from './util/auto500px';
auto500px();
createApp(PageNationStratFinan).use(BootstrapVue3).mount('#app');
+3 -3
View File
@@ -9,12 +9,12 @@ import { App, createApp } from 'vue';
import { auto500px } from './util/auto500px';
import { isString } from 'lodash';
import { Args, testSubmitArgs } from './processing/args';
import { sammoAPI, ValidResponse } from './util/sammoAPI';
import { SammoAPI, ValidResponse } from './SammoAPI';
declare const turnList: number[];
async function submitCommand<T extends ValidResponse>(isChiefTurn: boolean, turnList: number[], action: string, arg: Args): Promise<T> {
const target = isChiefTurn ? 'NationCommand/ReserveCommand' : 'Command/ReserveCommand';
const targetAPI = isChiefTurn ? SammoAPI.NationCommand.ReserveCommand : SammoAPI.Command.ReserveCommand;
try {
const testResult = testSubmitArgs(arg);
@@ -22,7 +22,7 @@ async function submitCommand<T extends ValidResponse>(isChiefTurn: boolean, turn
throw new TypeError(`Invalied Type ${testResult[0]}, ${testResult[2]} should be ${testResult[1]}`);
}
console.log('trySubmit', arg);
const response = await sammoAPI(target, {
const response = await targetAPI({
action,
turnList,
arg,
+2 -2
View File
@@ -44,8 +44,8 @@ $nationList = $db->query('SELECT nation,`name`,color,scout FROM nation');
$nationList = Util::convertArrayToDict($nationList, 'nation');
//NOTE: join 안할것임
$scoutMsgs = KVStorage::getValuesFromInterNamespace($db, 'nation_env', 'scout_msg');
foreach ($scoutMsgs as $nationID => $scoutMsg) {
$nationList[$nationID]['scoutmsg'] = $scoutMsg;
foreach ($scoutMsgs as $destNationID => $scoutMsg) {
$nationList[$destNationID]['scoutmsg'] = $scoutMsg;
}
+162
View File
@@ -0,0 +1,162 @@
<?php
namespace sammo;
include "lib.php";
include "func.php";
//로그인 검사
$session = Session::requireGameLogin()->setReadOnly();
$userID = Session::getUserID();
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
increaseRefresh("내무부", 1);
$me = $db->queryFirstRow('SELECT no, nation, officer_level, con, turntime, belong, permission, penalty FROM general WHERE owner=%i', $userID);
$con = checkLimit($me['con']);
if ($con >= 2) {
printLimitMsg($me['turntime']);
exit();
}
$permission = checkSecretPermission($me);
if ($permission < 0) {
echo '국가에 소속되어있지 않습니다.';
die();
} else if ($permission < 1) {
echo "권한이 부족합니다. 수뇌부가 아니거나 사관년도가 부족합니다.";
die();
}
$nationID = $me['nation'];
$nation = $db->queryFirstRow('SELECT nation,level,name,color,type,gold,rice,bill,rate,scout,war,secretlimit,capital FROM nation WHERE nation = %i', $nationID);
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
[$year, $month] = $gameStor->getValuesAsArray(['year', 'month']);
$nationStor->cacheValues(['notice', 'scout_msg', 'available_war_setting_cnt']);
$cityCntList = Util::convertPairArrayToDict($db->queryAllLists('SELECT nation, count(city) FROM city GROUP BY nation'));
$dipStateList = Util::convertArrayToDict($db->query('SELECT you,state,term FROM diplomacy WHERE me = %i', $nationID), 'you');
$nationsList = getAllNationStaticInfo();
$cityList = $db->query('SELECT * FROM city WHERE nation=%i', $nationID);
$dedicationList = $db->query('SELECT dedication FROM general WHERE nation=%i AND npc!=5', $nationID);
foreach ($nationsList as &$nationItem) {
$staticNationID = $nationItem['nation'];
//속령수
$nationItem['cityCnt'] = $cityCntList[$staticNationID] ?? 0;
if ($staticNationID !== $nationID) {
$diplomacyItem = $dipStateList[$staticNationID];
$nationItem['diplomacy'] = [
'state' => $diplomacyItem['state'],
'term' => $diplomacyItem['term'],
];
} else {
$nationItem['diplomacy'] = [
'state' => 7,
'term' => null,
];
}
}
// 수입 연산
$goldIncome = getGoldIncome(
$nation['nation'],
$nation['level'],
100,
$nation['capital'],
$nation['type'],
$cityList
);
$warIncome = getWarGoldIncome($nation['type'], $cityList);
$riceIncome = getRiceIncome(
$nation['nation'],
$nation['level'],
100,
$nation['capital'],
$nation['type'],
$cityList
);
$wallIncome = getWallIncome(
$nation['nation'],
$nation['level'],
100,
$nation['capital'],
$nation['type'],
$cityList
);
$income = [
'gold' => [
'city' => $goldIncome,
'war' => $warIncome,
],
'rice' => [
'city' => $riceIncome,
'wall' => $wallIncome,
],
];
$outcome = getOutcome(100, $dedicationList);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=500" />
<title><?= UniqueConst::$serverName ?>: 내무부</title>
<?= WebUtil::printStaticValues([
'staticValues' => [
'editable' => ($me['officer_level'] >= 5 || $permission == 4),
'nationMsg' => $nationStor->notice ?? '',
'scoutMsg' => $nationStor->scout_msg ?? '',
'nationID' => $nationID,
'officerLevel' => $me['officer_level'],
'year' => $year,
'month' => $month,
'nationsList' => $nationsList,
'gold' => $nation['gold'],
'rice' => $nation['rice'],
'income' => $income,
'outcome' => $outcome,
'policy' => [
'rate' => $nation['rate'],
'bill' => $nation['bill'],
'secretLimit' => $nation['secretlimit'],
'blockScout' => $nation['scout'] != 0,
'blockWar' => $nation['war'] != 0,
],
'warSettingCnt' => [
'remain' => $nationStor->getValue('available_war_setting_cnt'),
'inc' => GameConst::$incAvailableWarSettingCnt,
'max' => GameConst::$maxAvailableWarSettingCnt
],
]
], false) ?>
<?= WebUtil::printJS('../d_shared/common_path.js') ?>
<?= WebUtil::printCSS('../d_shared/common.css') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css" />
<?= WebUtil::printDist('vue', 'v_nationStratFinan', true) ?>
</head>
<body>
<div id="app"></div>
</body>
</html>
+3 -3
View File
@@ -5,9 +5,9 @@ set_time_limit(600);
function tryNpmInstall()
{
$npmResultPath = '../npm_recent.json.log';
$packageJsonPath = '../package.json';
$packageJsonLockPath = '../package-lock.json';
$npmResultPath = 'npm_recent.json.log';
$packageJsonPath = 'package.json';
$packageJsonLockPath = 'package-lock.json';
$packageJsonHash = hash_file('sha512', $packageJsonPath);
$timestamp = time();
+5
View File
@@ -68,16 +68,21 @@
"@typescript-eslint/parser": "^5.8.0",
"@vue/compiler-sfc": "^3.2.26",
"@vue/eslint-config-typescript": "^10.0.0",
"autoprefixer": "^10.4.1",
"babel-plugin-lodash": "^3.3.4",
"babel-preset-modern-browsers": "^15.0.2",
"bootswatch": "^5.1.3",
"clean-terminal-webpack-plugin": "^3.0.0",
"css-loader": "^6.5.1",
"cssnano": "^5.0.14",
"esbuild-loader": "^2.18.0",
"eslint": "^8.5.0",
"eslint-plugin-vue": "^8.2.0",
"file-loader": "^6.2.0",
"mini-css-extract-plugin": "^2.4.5",
"postcss-discard-duplicates": "^5.0.1",
"postcss-import": "^14.0.2",
"postcss-loader": "^6.2.1",
"pre-commit": "^1.2.2",
"sass": "^1.45.1",
"sass-loader": "^12.4.0",
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
plugins: [
require('postcss-import'),
require('postcss-discard-duplicates'),
require('autoprefixer'),
]
}
+4
View File
@@ -0,0 +1,4 @@
//TODO: API 경로를 읽어서 SammoAPI.ts, SammoRootAPI에 쉽게 채울 수 있도록 값 만들어주기
export {}
console.log('Hello World!');
+1 -1
View File
@@ -19,7 +19,7 @@
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
"noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
"isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
+1 -1
View File
@@ -1000,7 +1000,7 @@ class util
*/
public static function starts_with($string, $starts_with)
{
return strpos($string, $starts_with) === 0;
return str_starts_with($string, $starts_with);
}
/**
+4 -4
View File
@@ -160,11 +160,11 @@ module.exports = (env, argv) => {
},
{
test: /\.vue\.(s?[ac]ss)$/,
use: ['vue-style-loader', 'css-loader', 'sass-loader']
use: ['vue-style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
},
{
test: /(?<!\.vue)\.(s?[ac]ss)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader']
},
{
test: /\.(png|jpe?g|gif|webp)$/,
@@ -261,7 +261,7 @@ module.exports = (env, argv) => {
},
{
test: /.(s?[ac]ss)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader']
},]
},
plugins: [
@@ -342,7 +342,7 @@ module.exports = (env, argv) => {
]
}, {
test: /.(s?[ac]ss)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader']
}]
},
plugins: [