Compare commits

...
101 Commits
Author SHA1 Message Date
Hide_D 7a3642296a feat: 전투 시뮬레이터에 히든 버프 반영 가능 2023-02-24 00:25:29 +09:00
Hide_D 87e57aea0e feat: 전투 시뮬레이터에 시드 값 입력 가능
- 시드 값 입력시 반복 횟수는 1회로 고정
- 스킬 성공 여부는 동일하나, 실제 수치가 조금 다른 경우가 관측 이유는 추후 확인
- 히든 버프는 아직 미 반영
2023-02-23 23:48:09 +09:00
Hide_D ea256790ec game: 계략에서 도시 인구별 방어 계산식을 제곱근에서 로그로 변경
- (log(장수수 + 1, 2) - 1.25) * 0.04
- 8명까지는 기존 계산식과 큰 차이 없음
2023-02-23 22:59:10 +09:00
Hide_D 4cbd824ee0 fix: 초기 갱신 없이 국가 삭제시 버그 수정 2023-02-23 00:30:42 +09:00
Hide_D 4db1a4b32e fix: 접속자, 접속량 계산에 문제가 있던 부분 수정 2023-02-23 00:25:44 +09:00
Hide_D c060e05553 refac: 메시지 전달 j_ 코드를 API로 변경
- 메시지 전송
- 최근 메시지 수신
- 이전 메시지 수신
- 메시지 응답
- 연락처 수신
- 메시지 삭제
2023-02-22 01:29:00 +09:00
Hide_D 92cd195276 fix: 사령부 타 수뇌 텍스트 복사의 턴 순서도 수정 2023-02-21 02:32:18 +09:00
Hide_D f9b270fef3 fix: 텍스트 복사에서 턴 번호가 제대로 옮겨지지 않는 버그 수정 2023-02-21 02:29:23 +09:00
Hide_D 3f12dfd8ba feat: 턴 조작기에 '텍스트 복사' 추가 2023-02-21 02:26:52 +09:00
Hide_D d7e57f30eb fix: 사령부 복사하기 순서 지연 수정 시도 2023-02-21 02:04:03 +09:00
Hide_D ff822561cd game: 기병의 수비시 전투력 향상
- 기존 대비 전투력 5% 향상
2023-02-21 01:59:59 +09:00
Hide_D 273973f4b6 fix: Uri 기본 사용 방법 변경 2023-02-20 02:23:54 +09:00
Hide_D c648e2df85 fix: WebUtil Namespace 2023-02-20 01:27:52 +09:00
Hide_D 9b7e0ef169 fix, dep: 사용불가능한 phpuri를 league/uri로 대체
- 테스트 아직 없음
2023-02-20 01:02:18 +09:00
Hide_D 6eccd248ea dep: composer 2.5.4 2023-02-20 00:28:04 +09:00
Hide_D eaaa1c5da8 game: 초토화가 치안, 수비, 성벽도 낮추도록 변경 2023-02-05 15:59:33 +09:00
Hide_D 95776cda5c fix: 궁병이 아닌 경우에도 '맞 선제'가 정상 작동하도록 수정
- 궁병선제사격을 선제사격시도, 선제사격발동으로 분리
- 맞선제의 기준을 궁병이 아니라 '선제'의 활성화 여부로 구분
2023-01-28 03:09:44 +09:00
Hide_D 8ae0323dcc fix: 선제 사격에 저격을 쓰지 못하게 해야함 2023-01-28 02:56:35 +09:00
Hide_D f2d0020d92 fix: 피장파장은 피장파장을 대상으로 할 수 없음 2023-01-27 00:51:12 +09:00
Hide_D 67f3004eb0 fix: 이미 다른 부위에 입찰중인 경우 경매가 열리다 마는 버그 수정 2023-01-27 00:43:11 +09:00
Hide_D f933c0616b fix: 턴시간 고정이 0분만 가능한 버그 수정 2023-01-26 14:36:59 +09:00
Hide_D a240c6f901 game: 아시아 명장전(빙의전용) 생성 2023-01-02 20:05:07 +09:00
Hide_D e2c2ae0360 feat, refact: 메시지 응답 2022-12-05 01:55:38 +09:00
Hide_D bcd0117cdd misc: 메시지 삭제 API 정리 2022-12-05 01:42:54 +09:00
Hide_D 83f233cc99 feat, refac: 메시지 삭제 2022-12-05 01:40:12 +09:00
Hide_D 4f21eab7b1 feat, refact: 메시지 연락 리스트 수신 2022-12-05 01:10:41 +09:00
Hide_D 864ddf7efe feat, refac: 메시지 전송 2022-12-05 00:46:16 +09:00
Hide_D 9d3636cc49 misc: APIHelper에서 array가 반환된 경우 result: true 자동 삽입 2022-12-05 00:42:09 +09:00
Hide_D ce4f656030 fix: 정상 깃수 기준을 1 이상으로 변경 2022-12-02 00:43:43 +09:00
Hide_D a94f080cde fix: 미접속 유산 포인트 부여에서 '이벤트 깃수'를 카운트하는 버그 수정 2022-12-02 00:41:44 +09:00
Hide_D 71ca873c7a fix: msgOption은 null을 허용해야함. 2022-12-02 00:23:07 +09:00
Hide_D 6e0d13e1fb fix: 장수 생성시 보너스 스탯 표기 오류 2022-12-01 22:24:09 +09:00
Hide_D 059c4d999a game: 맞 선제시 이전 패치에서 변경된 전투력 반영 2022-12-01 01:13:43 +09:00
Hide_D f52e357e8a fix: 디버그용 코드 수정 2022-12-01 01:02:13 +09:00
Hide_D 761496be5c game: 정란, 벽력에 선제사격 추가, 벽력 공격력 약화
- 6페이즈 전투 기준으론 공격력 유지
- 벽력기준 돌격, 수비전 등에서 상대적 약화
2022-12-01 01:00:30 +09:00
Hide_D 551972d876 feat, refac: 오래된 메시지 불러오기 2022-11-30 01:32:17 +09:00
Hide_D 81c2fc4d43 game: 궁병의 선제 사격을 일반 공격력의 50%에서 2/3로 변경 2022-11-30 00:14:24 +09:00
Hide_D 3e542fcbfa game: 궁기병의 보병 상성 상향 수준 억제
- 공격력: 12.5% 향상
- 방어력: 9.1% 향상
2022-11-30 00:10:44 +09:00
Hide_D c9ab2c593d game: 궁기병의 보병 상대 전투력 향상
- 공격력 25% 향상 (20% 열세)
- 방어력 20% 향상 (25% 열세)
2022-11-30 00:07:46 +09:00
Hide_D 0c9e00e90a game: 목우의 수비 상성 약화
- 1.2(초기) -> 0.8(무상성) -> 1(현재)
- 25% 아군 피해 증가
2022-11-30 00:04:24 +09:00
Hide_D ea17972478 fix: 제거한 troop.ts 파일 빌드 수정 2022-11-29 02:13:43 +09:00
Hide_D 3d40df9755 feat, refac: 최근 메시지 API 2022-11-29 02:06:57 +09:00
Hide_D b4ac348645 Refac: 메시지 타입 지정으로 MessageType Enum 사용 2022-11-29 01:53:55 +09:00
Hide_D 374611f66c refac: MessageType enum 2022-11-29 01:49:19 +09:00
Hide_D 39b66fd626 misc: 사용되지 않는 구버전 부대편성 코드 제거 2022-11-28 23:51:21 +09:00
Hide_D 75dca322b4 fix: UserID가 null인 경우 대응 2022-11-10 17:14:21 +09:00
Hide_D cfdae1ddfa misc: 관리를 위해 mkdir에 group permission 부여 2022-11-05 15:17:57 +09:00
Hide_D 054a6c7073 feat: 모든 API를 기록하는 시스템
- sqlite3에 기록
- 느려지지 않기를...
2022-11-05 15:00:13 +09:00
Hide_D e93d9e6cf5 feat: API에서 민감한 argument 지정 2022-11-05 14:59:52 +09:00
Hide_D 30d99c6fc3 fix: DummySession에서 get이 잘못됨. 2022-11-05 14:58:22 +09:00
Hide_D 56c9b44c93 game: 이벤트 유니크 분실 메시지를 흥미롭게 변경 2022-11-02 21:17:04 +09:00
Hide_D 919ae5a863 game: 무작위 세계 이벤트 시나리오 가오픈 재생성 주기 단축 2022-11-01 23:00:59 +09:00
Hide_D 43b8e546f6 feat: 시나리오 별 사전 생성 시 재 생성 주기 결정 가능 2022-11-01 23:00:25 +09:00
Hide_D b90aef662b game: 무작위 세계 시나리오 초기 등록 2022-10-30 22:13:39 +09:00
Hide_D 1da47863ae feat: 전역 아이템 망실 이벤트 추가 2022-10-30 22:13:25 +09:00
Hide_D 81bd5c1914 feat: 무작위건국, 무작위수도이전 커맨드 추가 2022-10-30 21:47:24 +09:00
Hide_D b14f9a638f fix: 무작위장수명 모드에서 장수명이 제대로 지정되지 않음 2022-10-30 21:41:12 +09:00
Hide_D 8ff3a5a7a8 feat: GameConst에서 유니크 획득 확률 조정 가능 2022-10-30 21:29:48 +09:00
Hide_D 48c77bd6a2 feat: 장수명 무작위 생성 모드 가능 2022-10-30 15:16:40 +09:00
35a017c1dc fix: 천통 후 서버시드 출력 시 괄호를 열지 않고 닫았던 문제 수정 (#227)
Co-authored-by: Jinhyun Kim <lewha0@gmail.com>
Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/227
Reviewed-by: Hide_D <hided62@gmail.com>
Co-authored-by: lewha0 <lewha0@noreply.storage.hided.net>
Co-committed-by: lewha0 <lewha0@noreply.storage.hided.net>
2022-10-10 03:29:48 +09:00
Hide_D a3e8a10da9 game: 건국 시점 유산 포인트 베팅 제공량 상향 2022-10-10 01:39:42 +09:00
Hide_D 142b96836e refac: 신규/복귀 유저 유산 포인트 지급 로직을 조금더 최적화 2022-10-05 00:26:04 +09:00
Hide_D 9be0d2ec2d refac: storeOldGeneral을 General::kill로 이동 2022-10-04 20:38:18 +09:00
Hide_D fd817650e7 fix: 지급 버그 수정 2022-10-04 11:32:52 +09:00
Hide_D 3c6cb96a9b feat: 신규/복귀 유저에게 장수 생성 시 유산 포인트 제공
- 이벤트 깃수 제외 500포인트
- 8기까지 적립
2022-10-04 11:25:40 +09:00
Hide_D c4537454f9 fix: 최대 임관년도 수 계산 버그 재 수정 2022-10-04 10:02:39 +09:00
Hide_D 82cdaa433e fix: 최대 임관년도 수 계산 버그 2022-10-04 01:07:11 +09:00
Hide_D 32dd3ff43f misc: AI에서 삭턴NPC 구분 기준을 1턴 추가 2022-10-04 00:13:52 +09:00
Hide_D 6a5840faaa fix: AI 후방 발령 누락분 수정 2022-10-03 23:38:53 +09:00
Hide_D 5b0a3b064d fix: AI 후방 발령 로직 버그 수정
- 징병시 인구 소모가 없는 경우 후방 발령 제외
- NPC 수뇌일때와 유저 수뇌일 때 도시 인구 계산식 다른 버그 수정
2022-10-03 23:37:07 +09:00
Hide_D 14b8bb9790 fix: 2번 연속 멸망 시 유산 포인트 계산 오류 2022-10-03 11:32:22 +09:00
Hide_D 650f4a5903 fix: 구조상 베팅 당첨 포인트는 은퇴시 반영되어야 함 2022-10-03 00:37:18 +09:00
Hide_D c50410a6da game: 은퇴시 토너먼트 보정치를 100%로
- 실수였음!
2022-10-02 23:27:41 +09:00
Hide_D b2a676cd8b game: 옥란백용구 향상 최대 30% -> 50% 2022-10-02 23:25:00 +09:00
Hide_D 50a75e4543 refac, game: 은퇴 시 포인트 반영 방식 변경
- 반영하지 않고 은퇴 이후로 넘기는 포인트 추가
- 은퇴시 계수를 Obj로 이동
2022-10-02 19:41:38 +09:00
Hide_D 22172ac66f fix: null coalesing 수정 2022-10-02 19:39:35 +09:00
Hide_D 392aaa343d fix: calcGenType을 updateInstance()로 이동 2022-10-02 19:36:17 +09:00
Hide_D d924f47d58 fix: warning 발생 제거 2022-10-02 19:35:05 +09:00
Hide_D 3b70cbe724 fix: GeneralAI env 설정 관련 버그 수정 2022-10-02 17:46:20 +09:00
Hide_D 5c3233f883 misc: 서버 리셋 페이지에서 기본 설정값 변경
- 자율 행동 전부 선택
- 자율 행동 기본 시간 24시간
- 사실모드: 가상
2022-10-02 17:43:40 +09:00
Hide_D b570bfa9d4 fix: NPC선전포고, 긴급포상에서 내부 NPC 국가 상태를 업데이트하도록 수정 2022-10-02 17:37:40 +09:00
Hide_D 270cb25285 refac: GeneralAI에서 상태를 추가 업데이트할 수 있도록 구조 변경 2022-10-02 17:33:23 +09:00
Hide_D 7d3b0db924 game: 계략 성공률을 5%p 하향
- 계략 성공률에 인구수를 반영하기 이전과 비교하여 1명있을 때에는 동일
- 한명도 없을때는 5%p 상승
- 두명 이상일 때에는 이전 대비 성공률 하향
2022-10-02 15:59:30 +09:00
Hide_D 74d310e113 fix: 불가침에서 '까지'는 해당 월을 포함하여야 함 2022-10-02 15:55:31 +09:00
Hide_D 45afb1c090 fix: 수동 은퇴 시 명예의전당에 등록되지 않는 버그 2022-09-27 10:00:44 +09:00
Hide_D 40fc2f8bbf fix: NPC 순간이동이 보급이 끊긴 도시에도 갈 수 있는 버그 수정 2022-09-24 22:29:58 +09:00
Hide_D a9de5a92e1 build: pre-commit에 lint 제거 2022-09-24 20:58:56 +09:00
Hide_D c592b00c3f game: 피장파장에 추가되었던 글로벌 전략 1턴 제거 2022-09-24 20:57:44 +09:00
Hide_D 2c81be4d12 game: 계략 방어 확률에 도시 거주 장수 수 반영
- sqrt(장수 수) * 5%p
2022-09-23 21:59:51 +09:00
Hide_D f10289f7b6 game: 피장파장은 이제 전략
- 글로벌 쿨 1턴
- 피장파장 쿨 8턴
2022-09-23 21:56:15 +09:00
Hide_D c1f06b4ce5 fix: 최대 임관 년도 수 제어 규약 우회 2022-09-22 11:54:47 +09:00
Hide_D 9ad3dbba90 game: 배신 관련 최종 수정, 20%부터, +10%p씩
- 공지 추가
2022-09-21 23:25:20 +09:00
Hide_D 12c62f87f7 game: 초기 배신 수치 추가 보정
- 배신 수치를 '2'로 변경.
- 명성 패널티를 30%로 시작해서 45%, 60%
2022-09-21 23:16:29 +09:00
Hide_D 635f1ae077 feat, game: 초반 '배신 수치' 1 증가, 배신 패널티 증가
- 184년 1월부터 배신수치 1 증가.
- 배신 패널티 당 15%, 최대 6회
2022-09-21 23:12:56 +09:00
Hide_D c89bd2f63f fix: 하야, 망명 시 최대 임관 년수가 반영되지 않는 버그 수정 2022-09-21 22:48:55 +09:00
Hide_D 2f94a8a2ca fix: 가오픈 기간 중 사망 메시지에서 조사가 중복되어 나오던 문제 수정 (#226)
Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/226
Reviewed-by: Hide_D <hided62@gmail.com>
2022-09-14 12:07:08 +09:00
Jinhyun Kim 85b0d35af0 fix: 가오픈 기간 중 사망 메시지에서 조사가 중복되어 나오던 문제 수정 2022-09-14 11:42:19 +09:00
Hide_D b25acacb1e fix: 징병 특기에 '비용 감소' 제거 누락 2022-09-10 19:04:46 +09:00
Hide_D c05a43f3b1 fix(oauth): 이메일 변경 시 oauthID를 받아오지 못하는 분기 수정 2022-09-09 03:28:18 +09:00
Hide_D d99976daa9 fix(oauth): 카카오로그인에서 이메일만 바뀐 경우 이메일 갱신 2022-09-09 03:11:34 +09:00
Hide_D 62f53d09bf feat, game: 징병 전특 리밸런싱, 징병/소집해제시 인구 변동 트리거 설정 2022-09-06 19:48:52 +09:00
115 changed files with 5075 additions and 2346 deletions
+2
View File
@@ -10,8 +10,10 @@
*.log
logs/*.txt
api_log.db
d_log/*.txt
d_log/*.zip
d_log/*.db
sess_*
*/logs/*.txt
*/logs/*/*.txt
-7
View File
@@ -98,12 +98,6 @@ return [
'hwe/j_load_scenarios.php',
'hwe/j_map.php',
'hwe/j_map_recent.php',
'hwe/j_msg_contact_list.php',
'hwe/j_msg_decide_opt.php',
'hwe/j_msg_delete.php',
'hwe/j_msg_get_old.php',
'hwe/j_msg_get_recent.php',
'hwe/j_msg_submit.php',
'hwe/j_myBossInfo.php',
'hwe/j_raise_event.php',
'hwe/j_select_npc.php',
@@ -112,7 +106,6 @@ return [
'hwe/j_set_my_setting.php',
'hwe/j_set_npc_control.php',
'hwe/j_simulate_battle.php',
'hwe/j_troop.php',
'hwe/j_update_picked_general.php',
'hwe/j_vacation.php',
'hwe/lib.php',
+2 -2
View File
@@ -17,7 +17,6 @@
"league/plates": "^3.3",
"vlucas/valitron": "^1.4",
"php-extended/php-tail": "^2.1",
"pguardiario/phpuri": "^1.0",
"symfony/lock": "^6.0",
"ezyang/htmlpurifier": "^4.10",
"xemlock/htmlpurifier-html5": "^0.1.7",
@@ -27,7 +26,8 @@
"illuminate/database": "^9.12",
"illuminate/events": "^9.12",
"php-ds/php-ds": "^1.4",
"hided62/ldto": "^1.0"
"hided62/ldto": "^1.0",
"league/uri": "^6.8"
},
"autoload": {
"psr-4": {
Generated
+225 -29
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "fe4387d1f2df3d3162c053a1d50a733a",
"content-hash": "d7acaa437c3c6f48c9a643c9dc4ef421",
"packages": [
{
"name": "brandonwamboldt/utilphp",
@@ -1194,6 +1194,177 @@
},
"time": "2020-12-25T05:00:37+00:00"
},
{
"name": "league/uri",
"version": "6.8.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri.git",
"reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri/zipball/a700b4656e4c54371b799ac61e300ab25a2d1d39",
"reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39",
"shasum": ""
},
"require": {
"ext-json": "*",
"league/uri-interfaces": "^2.3",
"php": "^8.1",
"psr/http-message": "^1.0.1"
},
"conflict": {
"league/uri-schemes": "^1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^v3.9.5",
"nyholm/psr7": "^1.5.1",
"php-http/psr7-integration-tests": "^1.1.1",
"phpbench/phpbench": "^1.2.6",
"phpstan/phpstan": "^1.8.5",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.1.1",
"phpstan/phpstan-strict-rules": "^1.4.3",
"phpunit/phpunit": "^9.5.24",
"psr/http-factory": "^1.0.1"
},
"suggest": {
"ext-fileinfo": "Needed to create Data URI from a filepath",
"ext-intl": "Needed to improve host validation",
"league/uri-components": "Needed to easily manipulate URI objects",
"psr/http-factory": "Needed to use the URI factory"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Uri\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://nyamsprod.com"
}
],
"description": "URI manipulation library",
"homepage": "https://uri.thephpleague.com",
"keywords": [
"data-uri",
"file-uri",
"ftp",
"hostname",
"http",
"https",
"middleware",
"parse_str",
"parse_url",
"psr-7",
"query-string",
"querystring",
"rfc3986",
"rfc3987",
"rfc6570",
"uri",
"uri-template",
"url",
"ws"
],
"support": {
"docs": "https://uri.thephpleague.com",
"forum": "https://thephpleague.slack.com",
"issues": "https://github.com/thephpleague/uri/issues",
"source": "https://github.com/thephpleague/uri/tree/6.8.0"
},
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2022-09-13T19:58:47+00:00"
},
{
"name": "league/uri-interfaces",
"version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri-interfaces.git",
"reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
"reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.19",
"phpstan/phpstan": "^0.12.90",
"phpstan/phpstan-phpunit": "^0.12.19",
"phpstan/phpstan-strict-rules": "^0.12.9",
"phpunit/phpunit": "^8.5.15 || ^9.5"
},
"suggest": {
"ext-intl": "to use the IDNA feature",
"symfony/intl": "to use the IDNA feature via Symfony Polyfill"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Uri\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://nyamsprod.com"
}
],
"description": "Common interface for URI representation",
"homepage": "http://github.com/thephpleague/uri-interfaces",
"keywords": [
"rfc3986",
"rfc3987",
"uri",
"url"
],
"support": {
"issues": "https://github.com/thephpleague/uri-interfaces/issues",
"source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0"
},
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2021-06-28T04:27:21+00:00"
},
{
"name": "microsoft/tolerant-php-parser",
"version": "v0.1.1",
@@ -1610,34 +1781,6 @@
},
"time": "2022-01-24T11:29:14+00:00"
},
{
"name": "pguardiario/phpuri",
"version": "1.0",
"source": {
"type": "git",
"url": "https://github.com/monkeysuffrage/phpuri.git",
"reference": "ad0a5ec033fe616cfef55578b9c7f2458be8fbfc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/monkeysuffrage/phpuri/zipball/ad0a5ec033fe616cfef55578b9c7f2458be8fbfc",
"reference": "ad0a5ec033fe616cfef55578b9c7f2458be8fbfc",
"shasum": ""
},
"type": "project",
"autoload": {
"files": [
"phpuri.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"description": "A php library for converting relative urls to absolute.",
"support": {
"issues": "https://github.com/monkeysuffrage/phpuri/issues",
"source": "https://github.com/monkeysuffrage/phpuri/tree/1.0.0"
},
"time": "2015-05-24T09:13:17+00:00"
},
{
"name": "phan/phan",
"version": "5.3.2",
@@ -2077,6 +2220,59 @@
},
"time": "2021-11-05T16:47:00+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/master"
},
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "psr/log",
"version": "3.0.0",
BIN
View File
Binary file not shown.
+4 -4
View File
@@ -90,19 +90,19 @@ if (
//기본 파일 생성
if (!file_exists(AppConf::getUserIconPathFS())) {
mkdir(AppConf::getUserIconPathFS());
mkdir(AppConf::getUserIconPathFS(), 0775);
}
if (!file_exists(ROOT . '/d_log')) {
mkdir(ROOT . '/d_log');
mkdir(ROOT . '/d_log', 0775);
}
if (!file_exists(ROOT . '/d_setting')) {
mkdir(ROOT . '/d_setting');
mkdir(ROOT . '/d_setting', 0775);
}
if (!file_exists(ROOT . '/d_shared')) {
mkdir(ROOT . '/d_shared');
mkdir(ROOT . '/d_shared', 0775);
}
}
+17
View File
@@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS "api_log" (
"id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"ip" TEXT NOT NULL,
"date" TEXT NOT NULL,
"path" TEXT NOT NULL,
"arg" TEXT,
"aux" TEXT,
PRIMARY KEY("id" AUTOINCREMENT)
);
CREATE INDEX IF NOT EXISTS "by_date" ON "api_log" (
"date" DESC
);
CREATE INDEX IF NOT EXISTS "by_user" ON "api_log" (
"user_id" ASC,
"date" DESC
);
+10 -8
View File
@@ -2,6 +2,8 @@
namespace sammo;
use sammo\Enums\MessageType;
include "lib.php";
include "func.php";
@@ -111,7 +113,7 @@ switch ($btn) {
case "경험치1000":
$text = $btn . " 지급!";
foreach ($genlist as $generalID) {
$msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg->send(true);
}
$db->update('general', [
@@ -122,7 +124,7 @@ switch ($btn) {
case "공헌치1000":
$text = $btn . " 지급!";
foreach ($genlist as $generalID) {
$msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg->send(true);
}
$db->update('general', [
@@ -133,7 +135,7 @@ switch ($btn) {
case "보숙10000":
$text = "보병숙련도+10000 지급!";
foreach ($genlist as $generalID) {
$msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg->send(true);
}
$db->update('general', [
@@ -143,7 +145,7 @@ switch ($btn) {
case "궁숙10000":
$text = "궁병숙련도+10000 지급!";
foreach ($genlist as $generalID) {
$msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg->send(true);
}
$db->update('general', [
@@ -154,7 +156,7 @@ switch ($btn) {
$src = MessageTarget::buildQuick($session->generalID);
$text = "기병숙련도+10000 지급!";
foreach ($genlist as $generalID) {
$msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg->send(true);
}
$db->update('general', [
@@ -165,7 +167,7 @@ switch ($btn) {
$src = MessageTarget::buildQuick($session->generalID);
$text = "귀병숙련도+10000 지급!";
foreach ($genlist as $generalID) {
$msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg->send(true);
}
$db->update('general', [
@@ -176,7 +178,7 @@ switch ($btn) {
$src = MessageTarget::buildQuick($session->generalID);
$text = "차병숙련도+10000 지급!";
foreach ($genlist as $generalID) {
$msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg->send(true);
}
$db->update('general', [
@@ -196,7 +198,7 @@ switch ($btn) {
case "메세지 전달":
$text = $msg ?? '';
foreach ($genlist as $generalID) {
$msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg = new Message(MessageType::private, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
$msg->send(true);
}
break;
+4 -2
View File
@@ -2,6 +2,8 @@
namespace sammo;
use sammo\Enums\MessageType;
include "lib.php";
include "func.php";
//로그인 검사
@@ -18,7 +20,7 @@ $msg = Util::getPost('msg', 'string', '메시지');
$db = DB::db();
//NOTE: 왜 기능이 admin2와 admin4가 같이 있는가?
//NOTE: 왜 기능이 admin2와 admin4가 같이 있는가?
//NOTE: 왜 블럭 시 admin4에선 금쌀을 없애지 않는가?
switch ($btn) {
case "블럭 해제":
@@ -66,7 +68,7 @@ switch ($btn) {
$src = MessageTarget::buildQuick($session->generalID);
foreach($genlist as $generalID){
$msgObj = new Message(
Message::MSGTYPE_PRIVATE,
MessageType::private,
$src,
MessageTarget::buildQuick($generalID),
$msg,
+1 -2
View File
@@ -27,7 +27,6 @@ foreach ($db->queryFirstColumn(
$inheritPointManager = InheritancePointManager::getInstance();
foreach(General::createGeneralObjListFromDB($db->queryFirstColumn('SELECT `no` FROM general WHERE npc = 0')) as $genObj){
$inheritPointManager->mergeTotalInheritancePoint($genObj, true);
$inheritPointManager->mergeTotalInheritancePoint($genObj);
$inheritPointManager->applyInheritanceUser($genObj->getVar('owner'));
$inheritPointManager->clearInheritancePoint($genObj);
}
+1 -1
View File
@@ -8,7 +8,7 @@ include "func.php";
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
increaseRefresh("갱신정보", 2);
increaseRefresh("갱신정보", 1);
$admin = $gameStor->getValues(['year', 'month', 'refresh', 'maxrefresh', 'maxonline']);
+1 -1
View File
@@ -29,7 +29,7 @@ if ($myset > 0) {
$submit = 'hidden';
}
$targetTime = addTurn($me->getVar('lastrefresh'), $gameStor->turnterm, 2);
$targetTime = addTurn($me->getVar('lastrefresh'), $gameStor->turnterm, GameConst::$minTurnDieOnPrestart);
if ($gameStor->turntime <= $gameStor->opentime) {
//서버 가오픈시 할 수 있는 행동
if ($me->getNPCType() == 0 && $me->getNationID() == 0) {
+30 -3
View File
@@ -65,8 +65,8 @@ if ($nationID) {
전역 설정
</div>
<div class="card-body dragpad_battle">
<div class="row">
<div class="col-sm-6">
<div class="row gx-0">
<div class="col-sm-4">
<div class="input-group">
<input type="number" class="form-control" aria-describedby="text_year" value="<?= $startYear ?>" disabled>
<div class="input-group-text"> 년 시작 </div>
@@ -76,9 +76,13 @@ if ($nationID) {
<div class="input-group-text"> 월 </div>
</div>
</div>
<div class="col-sm-6">
<div class="col-sm-5 px-2">
<div class="btn-toolbar" role="toolbar">
<div class="input-group me-2" role="group">
<div class="input-group-text">
시드
</div>
<input type="text" class="form-control" id="seed" aria-describedby="text_seed">
<div class="input-group-text">
반복 횟수
</div>
@@ -87,6 +91,10 @@ if ($nationID) {
<option value="1000">1000회 (요약 표기)</option>
</select>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group me-2" role="group">
<button type="button" class="btn btn-danger btn-begin_battle">전투</button>
</div>
@@ -426,6 +434,25 @@ if ($nationID) {
</div>
<input type="number" class="form-control form_killcrew" value="0" step="1">
</div>
<div class="input-group mb-0">
<div class="input-group-text">자신</div>
<div class="input-group-text">회피 확률</div>
<input type="number" class="form-control form_war_avoid_ratio" value="0" step="1" min="0" max="5">
<div class="input-group-text">필살 확률</div>
<input type="number" class="form-control form_war_critical_ratio" value="0" step="1" min="0" max="5">
<div class="input-group-text">계략 시도 확률</div>
<input type="number" class="form-control form_war_magic_trial_prob" value="0" step="1" min="0" max="5">
</div>
<div class="input-group mb-1">
<div class="input-group-text">상대</div>
<div class="input-group-text">회피 확률</div>
<input type="number" class="form-control form_oppose_war_avoid_ratio" value="0" step="1" min="0" max="5">
<div class="input-group-text">필살 확률</div>
<input type="number" class="form-control form_oppose_war_critical_ratio" value="0" step="1" min="0" max="5">
<div class="input-group-text">계략 시도 확률</div>
<input type="number" class="form-control form_oppose_war_magic_trial_prob" value="0" step="1" min="0" max="5">
</div>
</div>
</div>
+38 -59
View File
@@ -886,7 +886,7 @@ function generalInfo2(General $generalObj)
function getOnlineNum(): int
{
return KVStorage::getStorage(DB::db(), 'game_env')->getValue('online') ?? 0;
return KVStorage::getStorage(DB::db(), 'game_env')->getValue('online_user_cnt') ?? 0;
}
function onlinegen(General $general)
@@ -1003,19 +1003,31 @@ function increaseRefresh($type = "", $cnt = 1)
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->refresh = $gameStor->refresh + $cnt; //TODO: +로 증가하는 값은 별도로 분리
$isunited = $gameStor->isunited;
$opentime = $gameStor->opentime;
if ($isunited != 2 && $generalID && $userGrade < 6 && $opentime <= TimeUtil::now()) {
$db->update('general', [
'lastrefresh' => $date,
'con' => $db->sqleval('con + %i', $cnt),
'connect' => $db->sqleval('connect + %i', $cnt),
'refcnt' => $db->sqleval('refcnt + %i', $cnt),
'refresh' => $db->sqleval('refresh + %i', $cnt)
], 'owner=%i', $userID);
if($userGrade == 6){
return;
}
if($isunited == 2){
return;
}
if(!$generalID){
return;
}
if($opentime > $date){
return;
}
$gameStor->refresh = $gameStor->refresh + $cnt; //TODO: +로 증가하는 값은 별도로 분리
$db->update('general', [
'lastrefresh' => $date,
'con' => $db->sqleval('con + %i', $cnt),
'connect' => $db->sqleval('connect + %i', $cnt),
'refcnt' => $db->sqleval('refcnt + %i', $cnt),
'refresh' => $db->sqleval('refresh + %i', $cnt)
], 'owner=%i', $userID);
$date = date('Y_m_d H:i:s');
$date2 = substr($date, 0, 10);
@@ -1033,42 +1045,6 @@ function increaseRefresh($type = "", $cnt = 1)
),
FILE_APPEND
);
$proxy_headers = array(
'HTTP_VIA',
'HTTP_X_FORWARDED_FOR',
'HTTP_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_FORWARDED',
'HTTP_CLIENT_IP',
'HTTP_FORWARDED_FOR_IP',
'VIA',
'X_FORWARDED_FOR',
'FORWARDED_FOR',
'X_FORWARDED',
'FORWARDED',
'CLIENT_IP',
'FORWARDED_FOR_IP',
'HTTP_PROXY_CONNECTION'
);
$str = "";
foreach ($proxy_headers as $x) {
if (isset($_SERVER[$x])) $str .= "//{$x}:{$_SERVER[$x]}";
}
if ($str != "") {
file_put_contents(
__DIR__ . "/logs/" . UniqueConst::$serverID . "/_{$date2}_ipcheck.txt",
sprintf(
"%s, %s, %s%s\n",
$session->userName,
$session->generalName,
$_SERVER['REMOTE_ADDR'],
$str
),
FILE_APPEND
);
}
}
function updateTraffic()
@@ -1223,10 +1199,9 @@ function updateOnline()
$nationname[$nation['nation']] = $nation['name'];
}
//동접수
$before5Min = TimeUtil::nowAddMinutes(-5);
$onlineUser = $db->query('SELECT no,name,nation FROM general WHERE lastrefresh > %s AND npc < 2', $before5Min);
$startTurn = cutTurn($gameStor->turntime, $gameStor->turnterm, false);
$onlineUser = $db->query('SELECT no,name,nation FROM general WHERE lastrefresh >= %s AND npc < 2', $startTurn);
$onlineNum = count($onlineUser);
$onlineNationUsers = Util::arrayGroupBy($onlineUser, 'nation');
@@ -1745,7 +1720,8 @@ function tryUniqueItemLottery(RandUtil $rng, General $general, string $acquireTy
$prob = 1 / ($genCount * $itemTypeCnt / 10 / 4); // 건국시 4개(20%) 등장(200명시 20국 정도 됨)
}
$prob = Util::valueFit($prob, null, 1 / 4); //최대치 감소
$prob *= GameConst::$uniqueTrialCoef;
$prob = Util::valueFit($prob, null, GameConst::$maxUniqueTrialProb); //최대치 감소
$result = false;
$prob /= sqrt(7);
@@ -1805,7 +1781,7 @@ function deleteNation(General $lord, bool $applyDB): array
$nationID,
$lordID
),
['npc', 'gold', 'rice', 'experience', 'explevel', 'dedication', 'dedlevel', 'belong', 'aux'],
['npc', 'owner', 'gold', 'rice', 'experience', 'explevel', 'dedication', 'dedlevel', 'belong', 'aux'],
1
);
$nationGeneralList[$lordID] = $lord;
@@ -1814,7 +1790,7 @@ function deleteNation(General $lord, bool $applyDB): array
$nation['aux'] = Json::decode($nation['aux']);
$nation['msg'] = $nationStor->nationNotice['msg'] ?? '';
$nation['scout_msg'] = $nationStor->scout_msg;
$nation['aux'] += $nationStor->max_power;
$nation['aux'] += $nationStor->max_power ?? [];
$nation['history'] = getNationHistoryLogAll($nationID);
$josaYi = JosaUtil::pick($nationName, '이');
@@ -1823,13 +1799,16 @@ function deleteNation(General $lord, bool $applyDB): array
// 전 장수 재야로
foreach ($nationGeneralList as $general) {
$general->setAuxVar(
InheritanceKey::max_belong->value,
max(
$general->getVar('belong'),
$general->getAuxVar(InheritanceKey::max_belong->value) ?? 0
)
);
if($general->getNPCType() < 2){
$general->setAuxVar(
InheritanceKey::max_belong->value,
max(
$general->getVar('belong'),
$general->getAuxVar(InheritanceKey::max_belong->value) ?? 0
)
);
}
$general->setVar('belong', 0);
$general->setVar('troop', 0);
$general->setVar('officer_level', 0);
+3 -4
View File
@@ -1092,9 +1092,8 @@ function checkEmperior()
}
processSumInheritPointRank();
foreach ($allUserGenerals as $genObj) {
$inheritPointManager->mergeTotalInheritancePoint($genObj, true);
$inheritPointManager->mergeTotalInheritancePoint($genObj);
$inheritPointManager->applyInheritanceUser($genObj->getVar('owner'));
$inheritPointManager->clearInheritancePoint($genObj);
}
$gameStor->isunited = 2;
@@ -1165,7 +1164,7 @@ function checkEmperior()
$nation['aux'] = Json::decode($nation['aux']);
$nation['msg'] = $nationStor->nationNotice['msg'] ?? '';;
$nation['scout_msg'] = $nationStor->scout_msg;
$nation['aux'] += $nationStor->max_power;
$nation['aux'] += $nationStor->max_power ?? [];
$nation['history'] = getNationHistoryLogAll($nation['nation']);
storeOldGenerals(0, $admin['year'], $admin['month']);
@@ -1242,7 +1241,7 @@ function checkEmperior()
]);
$hiddenSeed = UniqueConst::$hiddenSeed;
$history = ["<C>●</>{$admin['year']}{$admin['month']}월:<Y><b>【통일】</b></><D><b>{$nation['name']}</b></>{$josaYi} 전토를 통일하였습니다. <span class='hidden_but_copyable'>(서버시드: $hiddenSeed})</span>"];
$history = ["<C>●</>{$admin['year']}{$admin['month']}월:<Y><b>【통일】</b></><D><b>{$nation['name']}</b></>{$josaYi} 전토를 통일하였습니다. <span class='hidden_but_copyable'>(서버시드: {$hiddenSeed})</span>"];
pushGlobalHistoryLog($history, $admin['year'], $admin['month']);
//연감 월결산
+11 -10
View File
@@ -93,8 +93,8 @@ if ($session->userGrade < 5 && !$allowReset) {
<label for="fiction" class="col-sm-3 col-form-label">NPC 상성</label>
<div class="col-sm-9">
<div id="fiction" class="btn-group btn-group-toggle" data-bs-toggle="buttons">
<input type="radio" id="fiction_0" class="btn-check" name="fiction" value="0" checked><label for="fiction_0" class="btn btn-secondary"> 연의 </label>
<input type="radio" id="fiction_1" class="btn-check" name="fiction" value="1"><label for="fiction_1" class="btn btn-secondary"> 가상 </label>
<input type="radio" id="fiction_0" class="btn-check" name="fiction" value="0"><label for="fiction_0" class="btn btn-secondary"> 연의 </label>
<input type="radio" id="fiction_1" class="btn-check" name="fiction" value="1" checked><label for="fiction_1" class="btn btn-secondary"> 가상 </label>
</div>
</div>
</div>
@@ -114,6 +114,7 @@ if ($session->userGrade < 5 && !$allowReset) {
<div class="col-sm-9">
<div id="block_general_create" class="btn-group btn-group-toggle" data-bs-toggle="buttons">
<input type="radio" class="btn-check" id="block_general_create_0" name="block_general_create" value="0" checked><label for="block_general_create_0" class="btn btn-secondary">가능</label>
<input type="radio" class="btn-check" id="block_general_create_2" name="block_general_create" value="2"><label for="block_general_create_2" class="btn btn-secondary">장수명무작위</label>
<input type="radio" class="btn-check" id="block_general_create_1" name="block_general_create" value="1"><label for="block_general_create_1" class="btn btn-secondary">불가</label>
</div>
</div>
@@ -135,13 +136,13 @@ if ($session->userGrade < 5 && !$allowReset) {
<div class="col-sm-9">
<div class="input-group">
<div class="btn-group btn-group-toggle" data-bs-toggle="buttons">
<input type="checkbox" id="autorun_develop" class="autorun_user_chk btn-check" data-key="develop" name="autorun_user[]" value="develop"><label class="btn btn-secondary" for="autorun_develop">내정</label>
<input type="checkbox" id="autorun_warp" class="autorun_user_chk btn-check" data-key="warp" name="autorun_user[]" value="warp"><label class="btn btn-secondary" for="autorun_warp">순간이동</label>
<input type="checkbox" id="autorun_recruit" class="autorun_user_chk btn-check" data-key="recruit" name="autorun_user[]" value="recruit"><label class="btn btn-secondary" for="autorun_recruit">징병</label>
<input type="checkbox" id="autorun_recruit_high" class="autorun_user_chk btn-check" data-key="recruit_high" name="autorun_user[]" value="recruit_high"><label class="btn btn-secondary" for="autorun_recruit_high">모병</label>
<input type="checkbox" id="autorun_train" class="autorun_user_chk btn-check" data-key="train" name="autorun_user[]" value="train"><label class="btn btn-secondary" for="autorun_train">훈사</label>
<input type="checkbox" id="autorun_battle" class="autorun_user_chk btn-check" data-key="battle" name="autorun_user[]" value="battle"><label class="btn btn-secondary" for="autorun_battle">출병</label>
<input type="checkbox" id="autorun_chief" class="autorun_user_chk btn-check" data-key="chief" name="autorun_user[]" value="chief"><label class="btn btn-secondary" for="autorun_chief">기본 사령턴</label>
<input type="checkbox" id="autorun_develop" class="autorun_user_chk btn-check" data-key="develop" name="autorun_user[]" value="develop" checked><label class="btn btn-secondary" for="autorun_develop">내정</label>
<input type="checkbox" id="autorun_warp" class="autorun_user_chk btn-check" data-key="warp" name="autorun_user[]" value="warp" checked><label class="btn btn-secondary" for="autorun_warp">순간이동</label>
<input type="checkbox" id="autorun_recruit" class="autorun_user_chk btn-check" data-key="recruit" name="autorun_user[]" value="recruit" checked><label class="btn btn-secondary" for="autorun_recruit">징병</label>
<input type="checkbox" id="autorun_recruit_high" class="autorun_user_chk btn-check" data-key="recruit_high" name="autorun_user[]" value="recruit_high" checked><label class="btn btn-secondary" for="autorun_recruit_high">모병</label>
<input type="checkbox" id="autorun_train" class="autorun_user_chk btn-check" data-key="train" name="autorun_user[]" value="train" checked><label class="btn btn-secondary" for="autorun_train">훈사</label>
<input type="checkbox" id="autorun_battle" class="autorun_user_chk btn-check" data-key="battle" name="autorun_user[]" value="battle" checked><label class="btn btn-secondary" for="autorun_battle">출병</label>
<input type="checkbox" id="autorun_chief" class="autorun_user_chk btn-check" data-key="chief" name="autorun_user[]" value="chief" checked><label class="btn btn-secondary" for="autorun_chief">기본 사령턴</label>
</div>
</div>
</div>
@@ -163,7 +164,7 @@ if ($session->userGrade < 5 && !$allowReset) {
<option value="480">8시간</option>
<option value="600">10시간</option>
<option value="720">12시간</option>
<option value="1440">24시간</option>
<option value="1440" selected>24시간</option>
<option value="2160">36시간</option>
<option value="2880">48시간</option>
<option value="3600">60시간</option>
+3 -1
View File
@@ -1,6 +1,8 @@
<?php
namespace sammo;
use sammo\Enums\MessageType;
include "lib.php";
include "func.php";
@@ -119,7 +121,7 @@ else{
}
$msg = new Message(
Message::MSGTYPE_DIPLOMACY,
MessageType::diplomacy,
$src,
$dest,
$msgText,
+4 -2
View File
@@ -1,6 +1,8 @@
<?php
namespace sammo;
use sammo\Enums\MessageType;
include "lib.php";
include "func.php";
@@ -104,7 +106,7 @@ else{
}
$msg = new Message(
Message::MSGTYPE_DIPLOMACY,
MessageType::diplomacy,
$src,
$dest,
$msgText,
@@ -115,7 +117,7 @@ $msg = new Message(
$msgID = $msg->send();
$msg = new Message(
Message::MSGTYPE_NATIONAL,
MessageType::national,
$src,
$dest,
$msgText,
+3 -1
View File
@@ -1,6 +1,8 @@
<?php
namespace sammo;
use sammo\Enums\MessageType;
include "lib.php";
include "func.php";
@@ -73,7 +75,7 @@ $db->update('ng_diplomacy', [
$msgText = "외교 서신(#{$letterNo})이 회수되었습니다.";
$msg = new Message(
Message::MSGTYPE_DIPLOMACY,
MessageType::diplomacy,
$src,
$dest,
$msgText,
+3 -1
View File
@@ -1,6 +1,8 @@
<?php
namespace sammo;
use sammo\Enums\MessageType;
include "lib.php";
include "func.php";
@@ -176,7 +178,7 @@ else{
$msg = new Message(
Message::MSGTYPE_DIPLOMACY,
MessageType::diplomacy,
$src,
$dest,
$msgText,
+1 -1
View File
@@ -109,7 +109,7 @@ $scenario = (int)$_POST['scenario'];
$fiction = (int)$_POST['fiction'];
$extend = (int)$_POST['extend'];
$npcmode = (int)$_POST['npcmode'];
$block_general_create = (bool)$_POST['block_general_create'];
$block_general_create = (int)$_POST['block_general_create'];
$show_img_level = (int)$_POST['show_img_level'];
$tournament_trig = !!(int)$_POST['tournament_trig'];
$join_mode = $_POST['join_mode'];
-19
View File
@@ -1,19 +0,0 @@
<?php
namespace sammo;
include('lib.php');
include('func.php');
$session = Session::requireGameLogin([])->setReadOnly();
if(!Session::getInstance()->generalID){
Json::die([
"nation"=>[]
]);
}
//NOTE: 모든 국가, 모든 장수에 대해서 같은 결과라면 캐싱 가능하지 않을까?
Json::die([
"nation"=>getMailboxList()
]);
-63
View File
@@ -1,63 +0,0 @@
<?php
namespace sammo;
include 'lib.php';
include 'func.php';
WebUtil::requireAJAX();
//{msgID: 1206, response: true}
$session = Session::requireGameLogin([])->setReadOnly();
$generalID = Session::getInstance()->generalID;
if (!$generalID) {
Json::die([
'result'=>false,
'reason'=>'로그인하지 않음'
]);
}
$jsonPost = Json::decode(Util::getPost('data', 'string', '{}'));
$msgID = Util::toInt($jsonPost['msgID']??null);
$msgResponse = $jsonPost['response']??null;
if ($msgID === null || !is_bool($msgResponse)) {
Json::die([
'result'=>false,
'reason'=>'올바르지 않은 인자'
]);
}
$general = DB::db()->queryFirstRow('SELECT `no`, `name`, `nation`, `officer_level`, `npc`, `gold`, `rice`, `troop`, `aux` from `general` where `no` = %i', $generalID);
if(!$general){
Json::die([
'result'=>false,
'reason'=>'존재하지 않는 장수'
]);
}
$msg = Message::getMessageByID($msgID);
if($msg === null){
Json::die([
'result'=>false,
'reason'=>'올바르지 않은 메시지'
]);
}
$reason = 'success';
$gameStor = KVStorage::getStorage(DB::db(), 'game_env');
$gameStor->cacheAll();
if($msgResponse){
$result = $msg->agreeMessage($general['no'], $reason);
}
else{
$result = $msg->declineMessage($general['no'], $reason);
}
Json::die([
'result' => $result!==DiplomaticMessage::INVALID,
'reason' => $reason
]);
-74
View File
@@ -1,74 +0,0 @@
<?php
namespace sammo;
include('lib.php');
include('func.php');
WebUtil::requireAJAX();
$session = Session::requireGameLogin([]);
$userID = Session::getUserID();
$msgID = Util::getPost('msgID', 'int');
if($msgID === null){
Json::die([
'result'=>false,
'reason'=>'올바르지 않은 범위 입니다.'
]);
}
$lastMsgGet = Json::decode($session->lastMsgGet)??[];
$now = new \DateTime();
$delayTime = false;
if(count($lastMsgGet) >= 10){
try{
if($lastMsgGet[0] !== 'string'){
throw new \Exception('Why not string?');
}
$first = new \DateTime($lastMsgGet[0]);
$diff = $first->diff($now);
if($diff->days == 0 && $diff->h > 0 && $diff->i == 0 && $diff->s <= 5){
$delayTime = true;
}
array_shift($lastMsgGet);
}
catch(\Exception $e){
$lastMsgGet = [];
}
}
$lastMsgGet[] = $now;
$session->lastMsgGet = Json::encode($lastMsgGet);
if($delayTime){
sleep(1);
}
$session->setReadOnly();
list($generalID, $nationID, $generalName) = DB::db()->queryFirstList(
'select `no`, `nation`, `name` from `general` where owner = %i',
$userID
);
if($nationID === null){
Json::die([
'result'=>false,
'reason'=>'장수가 사망했습니다.'
]);
}
$reason = Message::deleteMsg($msgID, $generalID);
if($reason === null){
$result = [
'result'=>true,
'reason'=>'success'
];
}
else{
$result = [
'result'=>false,
'reason'=>$reason
];
}
Json::die($result);
-118
View File
@@ -1,118 +0,0 @@
<?php
namespace sammo;
include('lib.php');
include('func.php');
$session = Session::requireGameLogin([]);
$userID = Session::getUserID();
$reqTo = Util::getPost('to', 'int');
$reqType = Util::getPost('type', 'string');
if($reqTo === null){
Json::die([
'result'=>false,
'reason'=>'올바르지 않은 범위 입니다.'
]);
}
if($reqType === null || !in_array($reqType, ['private', 'public', 'national', 'diplomacy'])){
Json::die([
'result'=>false,
'reason'=>'올바르지 않은 타입입니다.'
]);
}
$lastMsgGet = Json::decode($session->lastMsgGet)??[];
$now = new \DateTime();
$delayTime = false;
if(count($lastMsgGet) >= 10){
try{
if($lastMsgGet[0] !== 'string'){
throw new \Exception('Why not string?');
}
$first = new \DateTime($lastMsgGet[0]);
$diff = $first->diff($now);
if($diff->days == 0 && $diff->h > 0 && $diff->i == 0 && $diff->s <= 1){
$delayTime = true;
}
array_shift($lastMsgGet);
}
catch(\Exception $e){
$lastMsgGet = [];
}
}
$lastMsgGet[] = $now;
$session->lastMsgGet = Json::encode($lastMsgGet);
if($delayTime){
sleep(1);
}
$session->setReadOnly();
$me = DB::db()->queryFirstRow('SELECT `no`,`name`,`nation`,`officer_level`,`con`,`picture`,`imgsvr`,penalty,permission FROM general WHERE `owner`=%i', $userID);
if($me === null){
Json::die([
'result'=>false,
'reason'=>'장수가 사망했습니다.'
]);
}
[$generalID, $nationID, $generalName] = [$me['no'], $me['nation'], $me['name']];
$permission = checkSecretPermission($me, false);
$result = [
'private'=>[],
'public'=>[],
'national'=>[],
'diplomacy'=>[],
'result'=>true,
'keepRecent'=>true,
'sequence'=>0,
'nationID'=>$nationID,
'generalName'=>$generalName,
];
$result['result'] = true;
$nextSequence = $reqTo;
if($reqType == 'private'){
$result['private'] = array_map(function(Message $msg) use (&$nextSequence){
if($msg->id > $nextSequence){
$nextSequence = $msg->id;
}
return $msg->toArray();
}, Message::getMessagesFromMailBoxOld($generalID, Message::MSGTYPE_PRIVATE, $reqTo, 15));
}
else if($reqType == 'public'){
$result['public'] = array_map(function(Message $msg)use (&$nextSequence){
if($msg->id > $nextSequence){
$nextSequence = $msg->id;
}
return $msg->toArray();
}, Message::getMessagesFromMailBoxOld(Message::MAILBOX_PUBLIC, Message::MSGTYPE_PUBLIC, $reqTo, 15));
}
else if($reqType == 'national'){
$result['national'] = array_map(function(Message $msg)use (&$nextSequence){
if($msg->id > $nextSequence){
$nextSequence = $msg->id;
}
return $msg->toArray();
}, Message::getMessagesFromMailBoxOld(Message::MAILBOX_NATIONAL + $nationID, Message::MSGTYPE_NATIONAL, $reqTo, 15));
}
else{
$result['diplomacy'] = array_map(function(Message $msg)use (&$nextSequence, $permission){
if($msg->id > $nextSequence){
$nextSequence = $msg->id;
}
$values = $msg->toArray();
if($msg->dest->nationID != 0 && $permission < 3){
$values['text'] = '(외교 메시지입니다)';//TODO: 외교서신이라 읽을 수 없음을 보여줘야함
$values['option']['invalid'] = true;
}
return $values;
}, Message::getMessagesFromMailBoxOld(Message::MAILBOX_NATIONAL + $nationID, Message::MSGTYPE_DIPLOMACY, $reqTo, 15));
}
Json::die($result);
-120
View File
@@ -1,120 +0,0 @@
<?php
namespace sammo;
include('lib.php');
include('func.php');
$session = Session::requireGameLogin([]);
$userID = Session::getUserID();
$reqSequence = Util::getPost('sequence', 'int', -1);
$lastMsgGet = Json::decode($session->lastMsgGet)??[];
$now = new \DateTime();
$delayTime = false;
if(count($lastMsgGet) >= 10){
try{
if(!is_string($lastMsgGet[0])){
throw new \Exception('Why not string?');
}
$first = new \DateTime($lastMsgGet[0]);
$diff = $first->diff($now);
if($diff->days == 0 && $diff->h > 0 && $diff->i == 0 && $diff->s <= 1){
$delayTime = true;
}
array_shift($lastMsgGet);
}
catch(\Exception $e){
$lastMsgGet = [];
}
}
$lastMsgGet[] = TimeUtil::now();
$session->lastMsgGet = Json::encode($lastMsgGet);
if($delayTime){
usleep(200);
}
$session->setReadOnly();
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,`name`,`nation`,`officer_level`,`con`,`picture`,`imgsvr`,penalty,permission FROM general WHERE `owner`=%i', $userID);
if($me === null){
Json::die([
'result'=>false,
'reason'=>'장수가 사망했습니다.'
]);
}
[$generalID, $nationID, $generalName] = [$me['no'], $me['nation'], $me['name']];
$permission = checkSecretPermission($me, false);
$result = [];
$result['result'] = true;
$result['keepRecent'] = false;
$nextSequence = $reqSequence;
$minSequence = $reqSequence;
$lastType = null;
$result['private'] = array_map(function(Message $msg) use (&$nextSequence, &$minSequence, &$lastType){
if($msg->id > $nextSequence){
$nextSequence = $msg->id;
}
if($msg->id <= $minSequence){
$minSequence = $msg->id;
$lastType = 'private';
}
return $msg->toArray();
}, Message::getMessagesFromMailBox($generalID, Message::MSGTYPE_PRIVATE, 15, $reqSequence));
$result['public'] = array_map(function(Message $msg)use (&$nextSequence, &$minSequence, &$lastType){
if($msg->id > $nextSequence){
$nextSequence = $msg->id;
}
if($msg->id <= $minSequence){
$minSequence = $msg->id;
$lastType = 'public';
}
return $msg->toArray();
}, Message::getMessagesFromMailBox(Message::MAILBOX_PUBLIC, Message::MSGTYPE_PUBLIC, 15, $reqSequence));
$result['national'] = array_map(function(Message $msg)use (&$nextSequence, &$minSequence, &$lastType){
if($msg->id > $nextSequence){
$nextSequence = $msg->id;
}
if($msg->id <= $minSequence){
$minSequence = $msg->id;
$lastType = 'national';
}
return $msg->toArray();
}, Message::getMessagesFromMailBox(Message::MAILBOX_NATIONAL + $nationID, Message::MSGTYPE_NATIONAL, 15, $reqSequence));
$result['diplomacy']= array_map(function(Message $msg)use (&$nextSequence, &$minSequence, &$lastType, $permission){
if($msg->id > $nextSequence){
$nextSequence = $msg->id;
}
if($msg->id <= $minSequence){
$minSequence = $msg->id;
$lastType = 'diplomacy';
}
$values = $msg->toArray();
if($msg->dest->nationID != 0 && $permission < 3){
$values['text'] = '(외교 메시지입니다)';//TODO: 외교서신이라 읽을 수 없음을 보여줘야함
$values['option']['invalid'] = true;
}
return $values;
}, Message::getMessagesFromMailBox(Message::MAILBOX_NATIONAL + $nationID, Message::MSGTYPE_DIPLOMACY, 15, $reqSequence));
if($lastType !== null){
array_pop($result[$lastType]);
$result['keepRecent'] = true;
}
else if($reqSequence <= 0){
$result['keepRecent'] = true;
}
$result['sequence'] = $nextSequence;
$result['nationID'] = $nationID;
$result['generalName'] = $generalName;
Json::die($result);
-206
View File
@@ -1,206 +0,0 @@
<?php
namespace sammo;
include 'lib.php';
include 'func.php';
WebUtil::requireAJAX();
$post = Json::decode(Util::getPost('data', 'string', '{}'));
'@phan-var mixed[] $post';
$v = new Validator($post);
$v->rule('required', ['mailbox','text']);
$v->rule('integer', 'mailbox');
$v->rule('lengthMin', 'text', 1);
if(!$v->validate()){
Json::die([
'result'=>false,
'reason'=>$v->errorStr(),
'msgID'=>null
]);
}
$mailbox = (int)$post['mailbox'];
$text = trim(StringUtil::cutStringForWidth($post['text'], 199, ''));
$session = Session::requireGameLogin([
'msgID'=>null
]);
$userID = Session::getUserID();
//NOTE: 전송 메시지 시간 계산을 위해 Session을 쓰기 가능 상태로 열어둠.
increaseRefresh('서신전달', 1);
if(getBlockLevel() == 1 || getBlockLevel() == 3) {
Json::die([
'result' => false,
'reason' => '차단되었습니다.',
'msgID'=>null
]);
}
/**
* 메시지 전송 코드.
*
* TODO: 장기적으로 ajax는 한곳에 모을 필요가 있을 듯.
*/
$now = new \DateTime();
$unlimited = new \DateTime('9999-12-31');
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,`name`,`nation`,`officer_level`,`con`,`picture`,`imgsvr`,penalty,permission,belong FROM general WHERE `owner`=%i', $userID);
if(!$me){
$session->logoutGame();
Json::die([
'result' => false,
'reason' => '로그인되지 않았습니다.',
'msgID' => null
]);
}
$con = checkLimit($me['con']);
if($con >= 2) {
Json::die([
'result' => false,
'reason' => '접속 제한입니다.',
'msgID' => null
]);
}
$me['icon'] = GetImageURL($me['imgsvr'], $me['picture']);
$permission = checkSecretPermission($me);
$srcNation = getNationStaticInfo($me['nation']);
$src = new MessageTarget($me['no'], $me['name'], $srcNation['nation'], $srcNation['name'], $srcNation['color'], $me['icon']);
// 전체 메세지
if($mailbox == Message::MAILBOX_PUBLIC) {
$msg = new Message(
Message::MSGTYPE_PUBLIC,
$src,
$src,
$text,
$now,
$unlimited,
[]
);
$msgID = $msg->send();
Json::die([
'result' => true,
'reason' => 'SUCCESS',
'msgID' => $msgID
]);
}
// 국가 메세지
if($mailbox >= Message::MAILBOX_NATIONAL) {
if($permission < 4){
$destNationID = $me['nation'];
}
else{
$destNationID = $mailbox - Message::MAILBOX_NATIONAL;
}
if($destNationID == $me['nation']){
$msgType = Message::MSGTYPE_NATIONAL;
}
else{
$msgType = Message::MSGTYPE_DIPLOMACY;
}
$destNation = getNationStaticInfo($destNationID);
$dest = new MessageTarget(0, '', $destNation['nation'], $destNation['name'], $destNation['color']);
$msg = new Message(
$msgType,
$src,
$dest,
$text,
$now,
$unlimited,
[]
);
$msgID = $msg->send();
Json::die([
'result' => true,
'reason' => 'SUCCESS',
'msgID' => $msgID
]);
}
// 개인 메세지
if($mailbox > 0) {
$lastMsg = new \DateTime($session->lastMsg??'0000-00-00');
$msg_interval = $now->getTimestamp() - $lastMsg->getTimestamp();
//NOTE: 여기서 유저 레벨을 구별할 코드가 필요할까?
if($msg_interval < 2){
Json::die([
'result' => false,
'reason' => '개인메세지는 2초당 1건만 보낼 수 있습니다!',
'msgID' => null
]);
}
$session->lastMsg = $now->format('Y-m-d H:i:s');
$destUser = $db->queryFirstRow('SELECT `no`,`name`,`nation`,`officer_level`,`con`,`picture`,`imgsvr`,permission,penalty FROM general WHERE `no`=%i',$mailbox);
if(!$destUser){
Json::die([
'result' => false,
'reason' => '존재하지 않는 유저입니다.',
'msgID' => null
]);
}
$destPermission = checkSecretPermission($destUser, false);
if($permission == 4 && $destPermission == 4 && $destUser['nation'] != $me['nation']){
Json::die([
'result' => false,
'reason' => '외교권자끼리는 메시지를 보낼 수 없습니다.',
'msgID' => null
]);
}
$destNation = getNationStaticInfo($destUser['nation']);
$dest = new MessageTarget(
$destUser['no'],
$destUser['name'],
$destNation['nation'],
$destNation['name'],
$destNation['color'],
GetImageURL($destUser['imgsvr'], $destUser['picture'])
);
$msg = new Message(
Message::MSGTYPE_PRIVATE,
$src,
$dest,
$text,
$now,
$unlimited,
[]
);
$msgID = $msg->send();
Json::die([
'result' => true,
'reason' => 'SUCCESS',
'msgID' => $msgID
]);
}
Json::die([
'result' => false,
'reason' => '알 수 없는 에러',
'msgID' => null
]);
+5 -3
View File
@@ -1,6 +1,8 @@
<?php
namespace sammo;
use sammo\Enums\MessageType;
include "lib.php";
include "func.php";
@@ -226,8 +228,8 @@ function do추방(General $general, int $myOfficerLevel):?string{
}
else{
$betrayCnt = $general->getVar('betray');
$general->addExperience(-$general->getVar('experience')*0.1*$betrayCnt);
$general->addDedication(-$general->getVar('dedication')*0.1*$betrayCnt);
$general->addExperience(-$general->getVar('experience')*0.15*$betrayCnt);
$general->addDedication(-$general->getVar('dedication')*0.15*$betrayCnt);
$general->increaseVarWithLimit('betray', 1, null, GameConst::$maxBetrayCnt);
}
@@ -268,7 +270,7 @@ function do추방(General $general, int $myOfficerLevel):?string{
GetImageURL($general->getVar('imgsvr'), $general->getVar('picture'))
);
$msg = new Message(
Message::MSGTYPE_PUBLIC,
MessageType::public,
$src,
$src,
$str,
+2 -2
View File
@@ -56,7 +56,7 @@ if(file_exists(__DIR__.'/.htaccess')){
'scenarioName'=>$options['scenarioName'],
'turnterm'=>$options['turnterm'],
'fictionMode'=>($options['fiction']?'가상':'사실'),
'block_general_create'=>(!!$options['block_general_create']),
'block_general_create'=>Util::toInt($options['block_general_create']),
'npcMode'=>([0=>'불가',1=>'가능',2=>'선택 생성'][$options['npcmode']]),
'openDatetime'=>$reserved['date'],
'starttime'=>$options['starttime'],
@@ -90,7 +90,7 @@ $admin['scenario'] = $admin['scenario_text'];
$admin['userCnt'] = $genCnt;
$admin['npcCnt'] = $npcCnt;
$admin['nationCnt'] = $nationCnt;
$admin['block_general_create'] = !!$admin['block_general_create'];
$admin['block_general_create'] = $admin['block_general_create'];
$admin['npcMode'] = [0=>'불가',1=>'가능',2=>'선택 생성'][$admin['npcMode']];
$admin['fictionMode'] = $admin['fiction']?'가상':'사실';
+51 -3
View File
@@ -84,6 +84,7 @@ $rawDefenderList = $query['defenderGenerals'];
$rawDefenderCity = $query['defenderCity'];
$rawDefenderNation = $query['defenderNation'];
$warSeed = $query['seed'] ?? '';
$generalCheck = [
'required'=>[
@@ -132,6 +133,19 @@ $generalCheck = [
['weapon', array_merge(array_keys(GameConst::$allItems['weapon']), ['None'])],
['book', array_merge(array_keys(GameConst::$allItems['book']), ['None'])],
['item', array_merge(array_keys(GameConst::$allItems['item']), ['None'])],
],
'array'=>['inheritBuff'],
];
$inheritBuffCheck = [
'between'=>[
[TriggerInheritBuff::WAR_AVOID_RATIO, [0, 5]],
[TriggerInheritBuff::WAR_CRITICAL_RATIO, [0, 5]],
[TriggerInheritBuff::WAR_MAGIC_TRIAL_PROB, [0, 5]],
[TriggerInheritBuff::OPPOSE_WAR_AVOID_RATIO, [0, 5]],
[TriggerInheritBuff::OPPOSE_WAR_CRITICAL_RATIO, [0, 5]],
[TriggerInheritBuff::OPPOSE_WAR_MAGIC_TRIAL_PROB, [0, 5]],
]
];
@@ -143,6 +157,19 @@ if(!$v->validate()){
'reason'=>'[출병자]'.$v->errorStr()
]);
}
$rawAttacker['aux'] = [];
if(key_exists('inheritBuff', $rawAttacker)){
$v = new Validator($rawAttacker['inheritBuff']);
$v->rules($inheritBuffCheck);
if(!$v->validate()){
Json::die([
'result'=>false,
'reason'=>'[출병자]'.$v->errorStr()
]);
}
$rawAttacker['aux']['inheritBuff'] = $rawAttacker['inheritBuff'];
}
$rawAttacker['aux'] = Json::encode($rawAttacker['aux']);
$rawAttacker['owner'] = 0;
$defenderList = [];
@@ -156,6 +183,20 @@ foreach($rawDefenderList as $idx=>$rawDefenderGeneral){
'reason'=>"[수비자{$idx}]".$v->errorStr()
]);
}
$rawDefenderGeneral['aux'] = [];
if(key_exists('inheritBuff', $rawDefenderGeneral)){
$v = new Validator($rawDefenderGeneral['inheritBuff']);
$v->rules($inheritBuffCheck);
if(!$v->validate()){
$idx+=1;
Json::die([
'result'=>false,
'reason'=>"[수비자{$idx}]".$v->errorStr()
]);
}
$rawDefenderGeneral['aux']['inheritBuff'] = $rawDefenderGeneral['inheritBuff'];
}
$rawDefenderGeneral['aux'] = Json::encode($rawDefenderGeneral['aux']);
$rawDefenderGeneral['owner'] = 0;
$defenderList[] = new General($rawDefenderGeneral, extractRankVar($rawDefenderGeneral), $rawDefenderCity, $rawAttackerNation, $year, $month, true);
@@ -297,10 +338,13 @@ function extractRankVar(array $rawVal):Map{
function simulateBattle(
$rawAttacker, $rawAttackerCity, $rawAttackerNation,
$rawDefenderList, $rawDefenderCity, $rawDefenderNation,
$startYear, $year, $month
$startYear, $year, $month, $warSeed
){
$warSeed = bin2hex(random_bytes(16));
if(!$warSeed){
$warSeed = bin2hex(random_bytes(16));
}
$warRng = new RandUtil(new LiteHashDRBG($warSeed));
$attacker = new WarUnitGeneral(
@@ -400,12 +444,16 @@ $avgWar = 0;
$attackerActivatedSkills = [];
$defendersActivatedSkills = [];
if($warSeed){
$repeatCnt = 1;
}
foreach(Util::range($repeatCnt) as $repeatIdx){
/** @var WarUnit $attacker */
[$attacker, $city, $battleResult, $conquerCity, $attackerRice, $defenderRice] = simulateBattle(
$rawAttacker, $rawAttackerCity, $rawAttackerNation,
$rawDefenderList, $rawDefenderCity, $rawDefenderNation,
$startYear, $year, $month
$startYear, $year, $month, $warSeed
);
$lastWarLog = Util::mapWithKey(function($key, $values){
return ConvertLog(join('<br>', $values));
-146
View File
@@ -1,146 +0,0 @@
<?php
namespace sammo;
include "lib.php";
include "func.php";
WebUtil::requireAJAX();
// $btn, $name, $troop
$action = Util::getPost('action');
$name = Util::getPost('name');
$gen = Util::getPost('gen', 'int');
$troop = Util::getPost('troop', 'int');
//로그인 검사
$session = Session::requireGameLogin([])->setReadOnly();
$userID = $session::getUserID();
increaseRefresh("부대명령", 0);
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`, name, nation, troop FROM general WHERE `owner`=%i', $userID);
$generalID = $me['no'];
$nationID = $me['nation'];
if($action == '부대창설'){
$name = StringUtil::neutralize($name);
if(!$name){
Json::die([
'result'=>false,
'reason'=>'부대 이름이 없습니다.'
]);
}
if($me['troop'] != 0){
Json::die([
'result'=>false,
'reason'=>'이미 부대에 가입해있습니다.'
]);
}
$db->insert('troop',[
'troop_leader'=>$generalID,
'name'=>$name,
'nation'=>$nationID,
]);
$db->update('general', [
'troop'=>$generalID,
], 'no=%i',$generalID);
Json::die([
'result'=>true,
'reason'=>'success'
]);
}
if($action == '부대추방'){
if (!$gen){
Json::die([
'result'=>false,
'reason'=>'장수를 지정해야 합니다.'
]);
}
$db->update('general', [
'troop'=>0
], 'no=%i AND troop = %i', $gen, $generalID);
if($db->affectedRows() == 0){
Json::die([
'result'=>false,
'reason'=>'부대장이 아니거나, 장수를 잘못 지정했습니다.'
]);
}
Json::die([
'result'=>true,
'reason'=>'success'
]);
}
if ($action == '부대가입') {
if(!$troop){
Json::die([
'result'=>false,
'reason'=>'부대를 지정해야 합니다.'
]);
}
if($me['troop'] === $generalID){
Json::die([
'result'=>false,
'reason'=>'부대장입니다.'
]);
}
$troopExists = $db->queryFirstField('SELECT `troop_leader` FROM `troop` WHERE `troop_leader` = %i AND `nation` = %i', $troop, $nationID);
if (!$troopExists) {
Json::die([
'result'=>false,
'reason'=>'올바른 부대장이 아닙니다.'
]);
}
$db->update('general', [
'troop'=>$troop
], 'no=%i', $generalID);
Json::die([
'result'=>true,
'reason'=>'success'
]);
}
if($action == "부대탈퇴") {
if($me['troop'] == 0){
Json::die([
'result'=>false,
'reason'=>'부대에 속해있지 않습니다.'
]);
}
//부대장일 경우
if($me['troop'] === $generalID){
$db->update('general', [
'troop'=>0
], 'troop=%i',$generalID);
$db->delete('troop', 'troop_leader=%i', $generalID);
}
else{
$db->update('general', [
'troop'=>0
], 'no=%i', $generalID);
}
Json::die([
'result'=>true,
'reason'=>'success'
]);
}
Json::die([
'result'=>false,
'reason'=>'올바르지 않은 명령입니다.'
]);
+2 -2
View File
@@ -63,8 +63,8 @@ function prepareDir(string $dirPath, bool $forceCreate=true):bool{
if(!unlink($dirPath)){
return false;
}
return mkdir($dirPath);
return mkdir($dirPath, 0775);
}
return mkdir($dirPath, 0777, true);
return mkdir($dirPath, 0775, true);
}
+3 -3
View File
@@ -4,7 +4,7 @@ namespace sammo\API\General;
use sammo\DB;
use sammo\DummyGeneral;
use sammo\GameConst;
use sammo\Session;
use sammo\General;
use sammo\JosaUtil;
@@ -54,7 +54,7 @@ class DieOnPrestart extends \sammo\BaseAPI
}
//서버 가오픈시 할 수 있는 행동
$targetTime = addTurn($general['lastrefresh'], $gameStor->turnterm, 2);
$targetTime = addTurn($general['lastrefresh'], $gameStor->turnterm, GameConst::$minTurnDieOnPrestart);
if ($targetTime > TimeUtil::now()) {
$targetTimeShort = substr($targetTime, 0, 19);
return "아직 삭제할 수 없습니다. {$targetTimeShort} 부터 가능합니다.";
@@ -67,7 +67,7 @@ class DieOnPrestart extends \sammo\BaseAPI
$generalName = $generalObj->getName();
$josaYi = JosaUtil::pick($generalName, '이');
$generalObj->kill($db, true, "<Y>{$generalName}</>{$josaYi} 홀연히 모습을 <R>감추었습니다</>");
$generalObj->kill($db, true, "<Y>{$generalName}</>{$josaYi} 홀연히 모습을 <R>감추었습니다</>");
$session->logoutGame();
return null;
+78 -10
View File
@@ -2,7 +2,9 @@
namespace sammo\API\General;
use Ds\Set;
use sammo\ActionLogger;
use sammo\Auction;
use sammo\CityConst;
use sammo\DB;
use sammo\Enums\RankColumn;
@@ -84,6 +86,40 @@ class Join extends \sammo\BaseAPI
return static::REQ_LOGIN | static::REQ_READ_ONLY;
}
public function calcRestInheritPoint(int $userID): int
{
$db = DB::db();
if (Util::toInt(UniqueConst::$seasonIdx) < 1) {
//이벤트 시즌은 카운트하지 않음
return 0;
}
$gameID = UniqueConst::$serverID;
$alreadyJoined = $db->queryFirstField('SELECT count(*) FROM `storage` WHERE `namespace` = %s AND `key` LIKE %s', 'inheritance_result', "{$gameID}_{$userID}_%");
if ($alreadyJoined) {
//이미 받았음
return 0;
}
$targetBonusPointCnt = 0;
//정상적으로 끝난 깃수만 제공
$oldGames = $db->queryFirstColumn('SELECT server_id FROM ng_games WHERE `winner_nation` IS NOT NULL AND `season` >= 1 AND `server_id` != %s ORDER BY `date` DESC LIMIT 8', $gameID);
if(!$oldGames){
return 0;
}
//이전에 장수를 생성한 적이 있는지
$oldGamesCnt = new Set($db->queryFirstColumn('SELECT `server_id` FROM ng_old_generals WHERE server_id IN %ls AND `owner` = %i GROUP BY `server_id`', $oldGames, $userID));
foreach ($oldGames as $oldGameID) {
if($oldGamesCnt->contains($oldGameID)) {
break;
}
$targetBonusPointCnt += 1;
}
return $targetBonusPointCnt;
}
public function launch(Session $session, ?\DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$userID = $session->userID;
@@ -105,7 +141,7 @@ class Join extends \sammo\BaseAPI
$inheritCity = $this->args['inheritCity'] ?? null;
$inheritBonusStat = $this->args['inheritBonusStat'] ?? null;
if($inheritTurntime !== null && $inheritCity !== null){
if ($inheritTurntime !== null && $inheritCity !== null) {
return '턴과 도시를 동시에 지정할 수 없습니다.';
}
@@ -119,9 +155,16 @@ class Join extends \sammo\BaseAPI
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->cacheValues(['year', 'month', 'maxgeneral', 'scenario', 'show_img_level', 'turnterm', 'turntime', 'genius', 'npcmode']);
$gameStor->cacheValues(['year', 'month', 'maxgeneral', 'scenario', 'show_img_level', 'block_general_create', 'turnterm', 'turntime', 'genius', 'npcmode']);
########## 동일 정보 존재여부 확인. ##########
$block_general_create = $gameStor->getValue('block_general_create');
if($block_general_create & 1){
return '장수 직접 생성이 불가능한 모드입니다.';
}
$blockCustomGeneralName = $gameStor->getValue('block_general_create') & 2;
$gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2');
$oldGeneral = $db->queryFirstField('SELECT `no` FROM general WHERE `owner`=%i', $userID);
$oldName = $db->queryFirstField('SELECT `no` FROM general WHERE `name`=%s', $name);
@@ -305,7 +348,7 @@ class Join extends \sammo\BaseAPI
if ($inheritTurntime !== null) {
$inheritTurntime = $inheritTurntime % ($admin['turnterm'] * 60);
$userLogger->push(sprintf("턴 시간 %02d:%02d 로 지정", intdiv($inheritTurntime, 60), $inheritTurntime%60), "inheritPoint");
$userLogger->push(sprintf("턴 시간 %02d:%02d 로 지정", intdiv($inheritTurntime, 60), $inheritTurntime % 60), "inheritPoint");
$inheritTurntime += $rng->nextRangeInt(0, 999999) / 1000000;
$turntime = new \DateTimeImmutable(cutTurn($admin['turntime'], $admin['turnterm']));
@@ -337,6 +380,15 @@ class Join extends \sammo\BaseAPI
//상성 랜덤
$affinity = $rng->nextRangeInt(1, 150);
$betray = 0;
if ($relYear >= 4) {
$betray += 2;
}
if($blockCustomGeneralName){
$name = bin2hex(random_bytes(5));
}
########## 회원정보 테이블에 입력값을 등록한다. ##########
$db->insert('general', [
'owner' => $userID,
@@ -365,6 +417,7 @@ class Join extends \sammo\BaseAPI
'lastrefresh' => $now,
'crewtype' => GameUnitConst::DEFAULT_CREWTYPE,
'makelimit' => 0,
'betray' => $betray,
'age' => $age,
'startage' => $age,
'personal' => $character,
@@ -374,6 +427,15 @@ class Join extends \sammo\BaseAPI
'special2' => $special2
]);
$generalID = $db->insertId();
if($blockCustomGeneralName){
//XXX: 클래스가 이게 맞나?
$name = Auction::genObfuscatedName($generalID);
$db->update('general', [
'name' => $name
], 'no=%i', $generalID);
}
$turnRows = [];
foreach (Util::range(GameConst::$maxTurn) as $turnIdx) {
$turnRows[] = [
@@ -402,19 +464,25 @@ class Join extends \sammo\BaseAPI
if ($inheritRequiredPoint > 0) {
$userLogger->push("장수 생성으로 포인트 {$inheritRequiredPoint} 소모", "inheritPoint");
$inheritStor = KVStorage::getStorage(DB::db(), "inheritance_{$userID}");
$inheritStor->setValue('previous', [$inheritTotalPoint - $inheritRequiredPoint, null]);
$inheritTotalPoint -= $inheritRequiredPoint;
$inheritStor->setValue('previous', [$inheritTotalPoint, null]);
$userLogger->flush();
$db->update('rank_data', [
'value'=>$db->sqleval('value + %i', $inheritRequiredPoint)
'value' => $db->sqleval('value + %i', $inheritRequiredPoint)
], 'general_id = %i AND type = %s', $generalID, RankColumn::inherit_point_spent_dynamic->value);
}
$me = [
'no' => $generalID
];
$log = [];
$mylog = [];
$restInheritPoint = $this->calcRestInheritPoint($userID) * 500;
if($restInheritPoint > 0){
$userLogger->push("신규/복귀 생성으로 포인트 {$restInheritPoint} 지급", "inheritPoint");
$inheritStor = KVStorage::getStorage(DB::db(), "inheritance_{$userID}");
$inheritTotalPoint += $restInheritPoint;
$inheritStor->setValue('previous', [$inheritTotalPoint, null]);
$userLogger->flush();
}
$logger = new ActionLogger($generalID, 0, $gameStor->year, $gameStor->month);
@@ -0,0 +1,61 @@
<?php
namespace sammo\API\Message;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\DiplomaticMessage;
use sammo\KVStorage;
use sammo\Message;
use sammo\Validator;
class DecideMessageResponse extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', ['msgID', 'response']);
$v->rule('integer', 'msgID')
->rule('boolean', 'response');
if (!$v->validate()) {
return $v->errorStr();
}
$this->args['msgID'] = (int)($this->args['msgID'] ?? -1);
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$msgID = $this->args['msgID'];
$msgResponse = $this->args['response'];
$generalID = $session->generalID;
$msg = Message::getMessageByID($msgID);
if ($msg === null) {
return '존재하지 않는 메시지입니다.';
}
$reason = 'success';
$gameStor = KVStorage::getStorage(DB::db(), 'game_env');
$gameStor->cacheAll();
if ($msgResponse) {
$result = $msg->agreeMessage($generalID, $reason);
} else {
$result = $msg->declineMessage($generalID, $reason);
}
return [
'result' => $result !== DiplomaticMessage::INVALID,
'reason' => $reason
];
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace sammo\API\Message;
use sammo\Session;
use DateTimeInterface;
use sammo\Message;
use sammo\Validator;
class DeleteMessage extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', ['msgID']);
$v->rule('integer', 'msgID');
if (!$v->validate()) {
return $v->errorStr();
}
$this->args['msgID'] = (int)($this->args['msgID'] ?? -1);
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$msgID = $this->args['msgID'];
$generalID = $session->generalID;
return Message::deleteMsg($msgID, $generalID);
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace sammo\API\Message;
use sammo\Session;
use DateTimeInterface;
use function sammo\getMailboxList;
class GetContactList extends \sammo\BaseAPI{
public function validateArgs(): ?string
{
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
if(!$session->generalID){
return [
"nation"=>[]
];
}
//NOTE: 모든 국가, 모든 장수에 대해서 같은 결과라면 캐싱 가능하지 않을까?
return [
"nation"=>getMailboxList()
];
}
}
+143
View File
@@ -0,0 +1,143 @@
<?php
namespace sammo\API\Message;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\Enums\MessageType;
use sammo\Json;
use sammo\Message;
use sammo\TimeUtil;
use sammo\Util;
use sammo\Validator;
use function sammo\checkSecretPermission;
class GetOldMessage extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', [
'to',
'type',
])->rule('integer', 'to')
->rule('in', 'type', ['private', 'public', 'national', 'diplomacy']);
if (!$v->validate()) {
return $v->errorStr();
}
$this->args['to'] = (int)($this->args['to'] ?? 0);
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN;
}
private function delayFrequentCall(Session $session): void
{
$lastMsgGet = Json::decode($session->lastMsgGet) ?? [];
$now = new \DateTimeImmutable();
$delayTime = false;
if (count($lastMsgGet) >= 10) {
try {
if (!is_string($lastMsgGet[0])) {
throw new \Exception('Why not string?');
}
$first = new \DateTimeImmutable($lastMsgGet[0]);
$diffSec = TimeUtil::DateIntervalToSeconds($first->diff($now));
if ($diffSec < 1) {
$delayTime = true;
}
array_shift($lastMsgGet);
} catch (\Exception $e) {
$lastMsgGet = [];
}
}
$lastMsgGet[] = TimeUtil::format($now, true);
$session->lastMsgGet = Json::encode($lastMsgGet);
if ($delayTime) {
usleep(200);
}
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$this->delayFrequentCall($session);
$session->setReadOnly();
$userID = $session->getUserID();
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,`name`,`nation`,`officer_level`,`con`,`picture`,`imgsvr`,penalty,permission FROM general WHERE `owner`=%i', $userID);
if ($me === null) {
return '장수가 사망했습니다.';
}
[$generalID, $nationID, $generalName] = [$me['no'], $me['nation'], $me['name']];
$permission = checkSecretPermission($me, false);
$reqTo = $this->args['to'];
$result = [
MessageType::private->value => [],
MessageType::public->value => [],
MessageType::national->value => [],
MessageType::diplomacy->value => [],
'result' => true,
'keepRecent' => true,
'sequence' => 0,
'nationID' => $nationID,
'generalName' => $generalName,
];
$result['result'] = true;
$nextSequence = $reqTo;
$reqType = MessageType::tryFrom($this->args['type']);
if ($reqType === null) {
return '잘못된 요청입니다.';
}
if ($reqType === MessageType::private) {
$result[MessageType::private->value] = array_map(function (Message $msg) use (&$nextSequence) {
if ($msg->id > $nextSequence) {
$nextSequence = $msg->id;
}
return $msg->toArray();
}, Message::getMessagesFromMailBoxOld($generalID, MessageType::private, $reqTo, 15));
} else if ($reqType === MessageType::public) {
$result[MessageType::public->value] = array_map(function (Message $msg) use (&$nextSequence) {
if ($msg->id > $nextSequence) {
$nextSequence = $msg->id;
}
return $msg->toArray();
}, Message::getMessagesFromMailBoxOld(Message::MAILBOX_PUBLIC, MessageType::public, $reqTo, 15));
} else if ($reqType === MessageType::national) {
$result[MessageType::national->value] = array_map(function (Message $msg) use (&$nextSequence) {
if ($msg->id > $nextSequence) {
$nextSequence = $msg->id;
}
return $msg->toArray();
}, Message::getMessagesFromMailBoxOld(Message::MAILBOX_NATIONAL + $nationID, MessageType::national, $reqTo, 15));
} else {
$result[MessageType::diplomacy->value] = array_map(function (Message $msg) use (&$nextSequence, $permission) {
if ($msg->id > $nextSequence) {
$nextSequence = $msg->id;
}
$values = $msg->toArray();
if ($msg->dest->nationID != 0 && $permission < 3) {
$values['text'] = '(외교 메시지입니다)'; //TODO: 외교서신이라 읽을 수 없음을 보여줘야함
$values['option']['invalid'] = true;
}
return $values;
}, Message::getMessagesFromMailBoxOld(Message::MAILBOX_NATIONAL + $nationID, MessageType::diplomacy, $reqTo, 15));
}
return $result;
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
namespace sammo\API\Message;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\Enums\MessageType;
use sammo\Json;
use sammo\Message;
use sammo\TimeUtil;
use sammo\Util;
use sammo\Validator;
use function sammo\checkSecretPermission;
class GetRecentMessage extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('integer', 'sequence');
if (!$v->validate()) {
return $v->errorStr();
}
$this->args['sequence'] = (int)($this->args['sequence'] ?? -1);
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN;
}
private function delayFrequentCall(Session $session): void
{
$lastMsgGet = Json::decode($session->lastMsgGet) ?? [];
$now = new \DateTimeImmutable();
$delayTime = false;
if (count($lastMsgGet) >= 10) {
try {
if (!is_string($lastMsgGet[0])) {
throw new \Exception('Why not string?');
}
$first = new \DateTimeImmutable($lastMsgGet[0]);
$diffSec = TimeUtil::DateIntervalToSeconds($first->diff($now));
if ($diffSec < 1) {
$delayTime = true;
}
array_shift($lastMsgGet);
} catch (\Exception $e) {
$lastMsgGet = [];
}
}
$lastMsgGet[] = TimeUtil::format($now, true);
$session->lastMsgGet = Json::encode($lastMsgGet);
if ($delayTime) {
usleep(200);
}
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$this->delayFrequentCall($session);
$session->setReadOnly();
$userID = $session->getUserID();
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,`name`,`nation`,`officer_level`,`con`,`picture`,`imgsvr`,penalty,permission FROM general WHERE `owner`=%i', $userID);
if ($me === null) {
return '장수가 사망했습니다.';
}
[$generalID, $nationID, $generalName] = [$me['no'], $me['nation'], $me['name']];
$permission = checkSecretPermission($me, false);
$reqSequence = $this->args['sequence'];
$result = [];
$result['result'] = true;
$result['keepRecent'] = false;
$nextSequence = $reqSequence;
$minSequence = $reqSequence;
$lastType = null;
$result['private'] = array_map(function (Message $msg) use (&$nextSequence, &$minSequence, &$lastType) {
if ($msg->id > $nextSequence) {
$nextSequence = $msg->id;
}
if ($msg->id <= $minSequence) {
$minSequence = $msg->id;
$lastType = 'private';
}
return $msg->toArray();
}, Message::getMessagesFromMailBox($generalID, MessageType::private, 15, $reqSequence));
$result['public'] = array_map(function (Message $msg) use (&$nextSequence, &$minSequence, &$lastType) {
if ($msg->id > $nextSequence) {
$nextSequence = $msg->id;
}
if ($msg->id <= $minSequence) {
$minSequence = $msg->id;
$lastType = 'public';
}
return $msg->toArray();
}, Message::getMessagesFromMailBox(Message::MAILBOX_PUBLIC, MessageType::public, 15, $reqSequence));
$result['national'] = array_map(function (Message $msg) use (&$nextSequence, &$minSequence, &$lastType) {
if ($msg->id > $nextSequence) {
$nextSequence = $msg->id;
}
if ($msg->id <= $minSequence) {
$minSequence = $msg->id;
$lastType = 'national';
}
return $msg->toArray();
}, Message::getMessagesFromMailBox(Message::MAILBOX_NATIONAL + $nationID, MessageType::national, 15, $reqSequence));
$result['diplomacy'] = array_map(function (Message $msg) use (&$nextSequence, &$minSequence, &$lastType, $permission) {
if ($msg->id > $nextSequence) {
$nextSequence = $msg->id;
}
if ($msg->id <= $minSequence) {
$minSequence = $msg->id;
$lastType = 'diplomacy';
}
$values = $msg->toArray();
if ($msg->dest->nationID != 0 && $permission < 3) {
$values['text'] = '(외교 메시지입니다)'; //TODO: 외교서신이라 읽을 수 없음을 보여줘야함
$values['option']['invalid'] = true;
}
return $values;
}, Message::getMessagesFromMailBox(Message::MAILBOX_NATIONAL + $nationID, MessageType::diplomacy, 15, $reqSequence));
if ($lastType !== null) {
array_pop($result[$lastType]);
$result['keepRecent'] = true;
} else if ($reqSequence <= 0) {
$result['keepRecent'] = true;
}
$result['sequence'] = $nextSequence;
$result['nationID'] = $nationID;
$result['generalName'] = $generalName;
return $result;
}
}
+240
View File
@@ -0,0 +1,240 @@
<?php
namespace sammo\API\Message;
use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\Enums\MessageType;
use sammo\Message;
use sammo\MessageTarget;
use sammo\Validator;
use function sammo\checkLimit;
use function sammo\checkSecretPermission;
use function sammo\getBlockLevel;
use function sammo\GetImageURL;
use function sammo\getNationStaticInfo;
use function sammo\increaseRefresh;
class SendMessage extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('required', ['mailbox', 'text']);
$v->rule('integer', 'mailbox');
$v->rule('lengthMin', 'text', 1);
if (!$v->validate()) {
return $v->errorStr();
}
$this->args['mailbox'] = (int)($this->args['mailbox'] ?? Message::MAILBOX_PUBLIC);
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN;
}
private function genPublicMessage(MessageTarget $src, string $text): Message
{
$now = new \DateTime();
$unlimited = new \DateTime('9999-12-31');
$msg = new Message(
MessageType::public,
$src,
$src,
$text,
$now,
$unlimited,
[]
);
return $msg;
}
private function genNationalMessage(MessageTarget $src, string $text): Message
{
$now = new \DateTime();
$unlimited = new \DateTime('9999-12-31');
$dest = new MessageTarget(0, '', $src->nationID, $src->nationName, $src->color);
$msg = new Message(
MessageType::national,
$src,
$dest,
$text,
$now,
$unlimited,
[]
);
return $msg;
}
private function genDiplomacyMessage(MessageTarget $src, int $destNationID, string $text): Message|string
{
$now = new \DateTime();
$unlimited = new \DateTime('9999-12-31');
$destNation = getNationStaticInfo($destNationID);
if ($destNation === null) {
return '존재하지 않는 국가입니다.';
}
$dest = new MessageTarget(0, '', $destNation['nation'], $destNation['name'], $destNation['color']);
$msg = new Message(
MessageType::diplomacy,
$src,
$dest,
$text,
$now,
$unlimited,
[]
);
return $msg;
}
private function genPrivateMessage(MessageTarget $src, int $destGeneralID, int $permission, string $text): Message|string
{
$now = new \DateTime();
$unlimited = new \DateTime('9999-12-31');
$db = DB::db();
$destUser = $db->queryFirstRow('SELECT `no`,`name`,`nation`,`officer_level`,`con`,`picture`,`imgsvr`,permission,penalty FROM general WHERE `no`=%i', $destGeneralID);
if (!$destUser) {
return '존재하지 않는 유저입니다.';
}
$destPermission = checkSecretPermission($destUser, false);
if ($permission == 4 && $destPermission == 4 && $destUser['nation'] != $src->nationID) {
return '외교권자끼리는 메시지를 보낼 수 없습니다.';
}
$destNation = getNationStaticInfo($destUser['nation']);
if ($destNation === null) {
$destNation = getNationStaticInfo(0);
}
$dest = new MessageTarget(
$destUser['no'],
$destUser['name'],
$destNation['nation'],
$destNation['name'],
$destNation['color'],
GetImageURL($destUser['imgsvr'], $destUser['picture'])
);
$msg = new Message(
MessageType::private,
$src,
$dest,
$text,
$now,
$unlimited,
[]
);
return $msg;
}
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
$userID = $session->userID;
$mailbox = $this->args['mailbox'];
$text = $this->args['text'];
increaseRefresh('서신전달', 1);
$blockLevel = getBlockLevel();
if ($blockLevel == 1 || $blockLevel == 3) {
return '차단되었습니다.';
}
$db = DB::db();
$me = $db->queryFirstRow('SELECT `no`,`name`,`nation`,`officer_level`,`con`,`picture`,`imgsvr`,penalty,permission,belong FROM general WHERE `owner`=%i', $userID);
if (!$me) {
$session->logoutGame();
return '장수가 없습니다.';
}
$con = checkLimit($me['con']);
if ($con >= 2) {
return '접속 제한입니다.';
}
$nationID = $me['nation'];
$iconPath = GetImageURL($me['imgsvr'], $me['picture']);
$srcNation = getNationStaticInfo($me['nation']);
if ($srcNation === null) {
return '존재하지 않는 국가입니다.';
}
$src = new MessageTarget($me['no'], $me['name'], $srcNation['nation'], $srcNation['name'], $srcNation['color'], $iconPath);
// 전체 메세지
if ($mailbox === Message::MAILBOX_PUBLIC) {
$msgID = $this->genPublicMessage($src, $text)->send();
return [
'msgID' => $msgID
];
}
$permission = checkSecretPermission($me);
if ($mailbox >= Message::MAILBOX_NATIONAL) {
if ($permission < 4) {
$destNationID = $nationID;
} else {
$destNationID = $mailbox - Message::MAILBOX_NATIONAL;
}
if ($destNationID === $nationID) {
$msgID = $this->genNationalMessage($src, $text)->send();
return [
'msgID' => $msgID
];
}
$msgObjOrError = $this->genDiplomacyMessage($src, $destNationID, $text);
if(is_string($msgObjOrError)) {
return $msgObjOrError;
}
$msgID = $msgObjOrError->send();
return [
'msgID' => $msgID
];
}
if ($mailbox > 0) {
$now = new \DateTime();
$lastMsg = new \DateTime($session->lastMsg ?? '0000-00-00');
$msg_interval = $now->getTimestamp() - $lastMsg->getTimestamp();
if ($msg_interval < 2) {
return '개인메세지는 2초당 1건만 보낼 수 있습니다!';
}
$session->lastMsg = $now->format('Y-m-d H:i:s');
$msgObjOrError = $this->genPrivateMessage($src, $mailbox, $permission, $text);
if(is_string($msgObjOrError)) {
return $msgObjOrError;
}
$msgID = $msgObjOrError->send();
return [
'msgID' => $msgID
];
}
return '알 수 없는 에러입니다.';
}
}
@@ -10,7 +10,7 @@ class che_명마_12_옥란백용구 extends \sammo\BaseStatItem{
public function __construct()
{
parent::__construct();
$this->info .= "<br>[전투] 남은 병력이 적을수록 회피 확률 증가. 최대 +30%p";
$this->info .= "<br>[전투] 남은 병력이 적을수록 회피 확률 증가. 최대 +50%p";
}
public function onCalcStat(General $general, string $statName, $value, $aux = null)
@@ -20,7 +20,7 @@ class che_명마_12_옥란백용구 extends \sammo\BaseStatItem{
$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 + \sammo\Util::valueFit((1 - $crewL / $leadership) * 0.5, 0, 0.5);
}
return $value;
}
+5 -1
View File
@@ -9,7 +9,7 @@ class che_징병_낙주 extends \sammo\BaseItem{
protected $rawName = '낙주';
protected $name = '낙주(징병)';
protected $info = '[군사] 징·모병비 -30%, 통솔 순수 능력치 보정 +15%';
protected $info = '[군사] 징·모병비 -30%<br>[기타] 통솔 순수 능력치 보정 +15%, 징병/모병/소집해제 시 인구 변동 없음';
protected $cost = 200;
protected $consumable = false;
@@ -18,6 +18,10 @@ class che_징병_낙주 extends \sammo\BaseItem{
if($varType == 'cost') return $value * 0.7;
}
if($turnType == '징집인구' && $varType == 'score'){
return 0;
}
return $value;
}
@@ -9,7 +9,7 @@ class event_전투특기_징병 extends \sammo\BaseItem{
protected $rawName = '비급';
protected $name = '비급(징병)';
protected $info = '[군사] 징·모병비 -50%, 통솔 순수 능력치 보정 +25%';
protected $info = '[군사] 징병/모병 시 훈사 70/84 제공<br>[기타] 통솔 순수 능력치 보정 +25%, 징병/모병/소집해제 시 인구 변동 없음';
protected $cost = 100;
protected $buyable = true;
protected $consumable = false;
@@ -17,7 +17,18 @@ class event_전투특기_징병 extends \sammo\BaseItem{
public function onCalcDomestic(string $turnType, string $varType, float $value, $aux=null):float{
if(in_array($turnType, ['징병', '모병'])){
if($varType == 'cost') return $value * 0.5;
if($varType == 'train' || $varType == 'atmos'){
if($turnType === '징병'){
return 70;
}
else{
return 84;
}
}
}
if($turnType == '징집인구' && $varType == 'score'){
return 0;
}
return $value;
@@ -9,7 +9,7 @@ class che_event_징병 extends \sammo\BaseSpecial{
protected $id = 72;
protected $name = '징병';
protected $info = '[군사] 징·모병비 -50%, 통솔 순수 능력치 보정 +25%';
protected $info = '[군사] 징병/모병 시 훈사 70/84 제공<br>[기타] 통솔 순수 능력치 보정 +25%, 징병/모병/소집해제 시 인구 변동 없음';
static $selectWeightType = SpecialityHelper::WEIGHT_NORM;
static $selectWeight = 1;
@@ -21,9 +21,20 @@ class che_event_징병 extends \sammo\BaseSpecial{
public function onCalcDomestic(string $turnType, string $varType, float $value, $aux=null):float{
if(in_array($turnType, ['징병', '모병'])){
if($varType == 'cost') return $value * 0.5;
if($varType == 'train' || $varType == 'atmos'){
if($turnType === '징병'){
return 70;
}
else{
return 84;
}
}
}
if($turnType == '징집인구' && $varType == 'score'){
return 0;
}
return $value;
}
+14 -3
View File
@@ -9,7 +9,7 @@ class che_징병 extends \sammo\BaseSpecial{
protected $id = 72;
protected $name = '징병';
protected $info = '[군사] 징·모병비 -50%, 통솔 순수 능력치 보정 +25%';
protected $info = '[군사] 징병/모병 시 훈사 70/84 제공<br>[기타] 통솔 순수 능력치 보정 +25%, 징병/모병/소집해제 시 인구 변동 없음';
static $selectWeightType = SpecialityHelper::WEIGHT_NORM;
static $selectWeight = 1;
@@ -21,9 +21,20 @@ class che_징병 extends \sammo\BaseSpecial{
public function onCalcDomestic(string $turnType, string $varType, float $value, $aux=null):float{
if(in_array($turnType, ['징병', '모병'])){
if($varType == 'cost') return $value * 0.5;
if($varType == 'train' || $varType == 'atmos'){
if($turnType === '징병'){
return 70;
}
else{
return 84;
}
}
}
if($turnType == '징집인구' && $varType == 'score'){
return 0;
}
return $value;
}
+3 -2
View File
@@ -10,6 +10,7 @@ use sammo\DTO\AuctionBidItemData;
use sammo\DTO\AuctionInfo;
use sammo\Enums\AuctionType;
use sammo\Enums\InheritanceKey;
use sammo\Enums\MessageType;
use sammo\Enums\RankColumn;
use sammo\Enums\ResourceType;
@@ -245,7 +246,7 @@ abstract class Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$msg = new Message(
Message::MSGTYPE_PRIVATE,
MessageType::private,
$src,
$dest,
$reason,
@@ -509,7 +510,7 @@ abstract class Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$msg = new Message(
Message::MSGTYPE_PRIVATE,
MessageType::private,
$src,
$dest,
$failReason,
+2 -1
View File
@@ -7,6 +7,7 @@ use sammo\DTO\AuctionBidItem;
use sammo\DTO\AuctionInfo;
use sammo\DTO\AuctionInfoDetail;
use sammo\Enums\AuctionType;
use sammo\Enums\MessageType;
use sammo\Enums\ResourceType;
abstract class AuctionBasicResource extends Auction
@@ -145,7 +146,7 @@ abstract class AuctionBasicResource extends Auction
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$msg = new Message(
Message::MSGTYPE_PRIVATE,
MessageType::private,
$src,
$dest,
"{$this->auctionID}{$hostResName} 경매에 입찰이 없어 취소되었습니다.",
+5 -1
View File
@@ -110,7 +110,11 @@ class AuctionUniqueItem extends Auction
}
$auction = new static($auctionID, $general);
try {
$auction->bid($startAmount, false);
$failReason = $auction->bid($startAmount, false);
if($failReason){
$auction->closeAuction();
return "경매를 시작했지만, 첫 입찰에 실패했습니다: {$failReason}";
}
} catch (\Exception $e) {
//실패해선 안된다.
$msg = $e->getMessage();
@@ -179,6 +179,15 @@ class che_등용수락 extends Command\GeneralCommand{
$db->update('nation', $setScoutNationValues, 'nation=%i', $destNationID);
$general->increaseInheritancePoint(InheritanceKey::active_action, 1);
if($general->getNPCType() < 2){
$general->setAuxVar(
InheritanceKey::max_belong->value,
max(
$general->getVar('belong'),
$general->getAuxVar(InheritanceKey::max_belong->value) ?? 0
)
);
}
$general->setVar('permission', 'normal');
$general->setVar('belong', 1);
$general->setVar('officer_level', 1);
@@ -0,0 +1,246 @@
<?php
namespace sammo\Command\General;
use \sammo\DB;
use \sammo\Util;
use \sammo\JosaUtil;
use \sammo\General;
use \sammo\ActionLogger;
use \sammo\GameConst;
use \sammo\GameUnitConst;
use \sammo\LastTurn;
use \sammo\Command;
use \sammo\Json;
use function \sammo\tryUniqueItemLottery;
use function \sammo\getAllNationStaticInfo;
use \sammo\Constraint\ConstraintHelper;
use sammo\Enums\InheritanceKey;
use function sammo\buildNationTypeClass;
use function sammo\genGenericUniqueRNGFromGeneral;
use function sammo\refreshNationStaticInfo;
use function sammo\GetNationColors;
class che_무작위건국 extends Command\GeneralCommand
{
static protected $actionName = '무작위 도시 건국';
static public $reqArg = true;
protected function argTest(): bool
{
if ($this->arg === null) {
return false;
}
$nationName = $this->arg['nationName'] ?? null;
$nationType = $this->arg['nationType'] ?? null;
$colorType = $this->arg['colorType'] ?? null;
if ($nationName === null || $nationType === null || $colorType === null) {
return false;
}
if (!is_string($nationName) || !is_string($nationType) || !is_int($colorType)) {
return false;
}
if (mb_strwidth($nationName) > 18 || $nationName == '') {
return false;
}
if (!key_exists($colorType, GetNationColors())) {
return false;
}
try {
$nationTypeClass = buildNationTypeClass($nationType);
} catch (\InvalidArgumentException $e) {
return false;
}
$this->arg = [
'nationName' => $nationName,
'nationType' => $nationType,
'colorType' => $colorType
];
return true;
}
protected function init()
{
$env = $this->env;
$this->setNation(['gennum', 'aux']);
$relYear = $env['year'] - $env['startyear'];
$this->minConditionConstraints = [
ConstraintHelper::BeOpeningPart($relYear + 1),
ConstraintHelper::ReqNationValue('level', '국가규모', '==', 0, '정식 국가가 아니어야합니다.')
];
}
protected function initWithArg()
{
$env = $this->env;
$relYear = $env['year'] - $env['startyear'];
$nationName = $this->arg['nationName'];
$nationType = $this->arg['nationType'];
$colorType = $this->arg['colorType'];
$this->fullConditionConstraints = [
ConstraintHelper::BeLord(),
ConstraintHelper::WanderingNation(),
ConstraintHelper::ReqNationValue('gennum', '수하 장수', '>=', 2),
ConstraintHelper::BeOpeningPart($relYear + 1),
ConstraintHelper::CheckNationNameDuplicate($nationName),
ConstraintHelper::AllowJoinAction(),
];
}
public function getBrief(): string
{
$nationName = $this->arg['nationName'];
$josaUl = JosaUtil::pick($nationName, '을');
return "{$nationName}{$josaUl} 무작위 도시에 건국";
}
public function getCost(): array
{
return [0, 0];
}
public function getPreReqTurn(): int
{
return 0;
}
public function getPostReqTurn(): int
{
return 0;
}
public function run(\Sammo\RandUtil $rng): bool
{
if (!$this->hasFullConditionMet()) {
throw new \RuntimeException('불가능한 커맨드를 강제로 실행 시도');
}
$db = DB::db();
$env = $this->env;
$general = $this->generalObj;
$date = $general->getTurnTime($general::TURNTIME_HM);
$generalName = $general->getName();
$logger = $general->getLogger();
$initYearMonth = Util::joinYearMonth($env['init_year'], $env['init_month']);
$yearMonth = Util::joinYearMonth($env['year'], $env['month']);
if($yearMonth <= $initYearMonth){
$logger->pushGeneralActionLog("다음 턴부터 건국할 수 있습니다. <1>$date</>");
$this->alternative = new che_인재탐색($general, $this->env, null);
return false;
}
$cities = $db->queryFirstColumn('SELECT city FROM city where `level`>=5 and `level`<=6 and nation=0');
if(!$cities){
$logger->pushGeneralActionLog("건국할 수 있는 도시가 없습니다. <1>$date</>");
$this->alternative = new che_해산($general, $this->env, null);
return false;
}
$cityID = $rng->choice($cities);
if($general->getCityID() == $cityID){
$this->setCity();
}
else{
$this->generalObj->setVar('city', $cityID);
$this->setCity();
$db->update('general', [
'city' => $cityID
], 'nation = %i', $general->getNationID());
}
$josaYi = JosaUtil::pick($generalName, '이');
$nationName = $this->arg['nationName'];
$nationType = $this->arg['nationType'];
$colorType = GetNationColors()[$this->arg['colorType']];
$cityName = $this->city['name'];
$josaUl = JosaUtil::pick($nationName, '을');
$nationTypeClass = buildNationTypeClass($nationType);
$nationTypeName = $nationTypeClass->getName();
$logger->pushGeneralActionLog("<D><b>{$nationName}</b></>{$josaUl} 건국하였습니다. <1>$date</>");
$logger->pushGlobalActionLog("<Y>{$generalName}</>{$josaYi} <G><b>{$cityName}</b></>에 국가를 건설하였습니다.");
$josaNationYi = JosaUtil::pick($nationName, '이');
$logger->pushGlobalHistoryLog("<Y><b>【건국】</b></>{$nationTypeName} <D><b>{$nationName}</b></>{$josaNationYi} 새로이 등장하였습니다.");
$logger->pushGeneralHistoryLog("<D><b>{$nationName}</b></>{$josaUl} 건국");
$logger->pushNationalHistoryLog("<Y>{$generalName}</>{$josaYi} <D><b>{$nationName}</b></>{$josaUl} 건국");
$exp = 1000;
$ded = 1000;
$general->addExperience($exp);
$general->addDedication($ded);
$aux = Json::decode($this->nation['aux']) ?? [];
$aux['can_국기변경'] = 1;
$aux['can_무작위수도이전'] = 1;
$db->update('city', [
'nation' => $general->getNationID(),
'conflict' => '{}'
], 'city=%i', $general->getCityID());
$db->update('nation', [
'name' => $nationName,
'color' => $colorType,
'level' => 1,
'type' => $nationType,
'capital' => $general->getCityID(),
'aux' => Json::encode($aux)
], 'nation=%i', $general->getNationID());
refreshNationStaticInfo();
$general->increaseInheritancePoint(InheritanceKey::active_action, 1);
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
$general->checkStatChange();
tryUniqueItemLottery(genGenericUniqueRNGFromGeneral($general), $general, '건국');
$general->applyDB($db);
return true;
}
public function exportJSVars(): array
{
$nationTypes = [];
foreach (GameConst::$availableNationType as $nationType) {
$nationClass = buildNationTypeClass($nationType);
$nationTypes[$nationType] = [
'type' => $nationType,
'name' => $nationClass->getName(),
'pros' => $nationClass::$pros,
'cons' => $nationClass::$cons
];
}
return [
'procRes' => [
'available건국' => count(getAllNationStaticInfo()) < $this->env['maxnation'],
'nationTypes' => $nationTypes,
'colors' => GetNationColors(),
]
];
}
}
@@ -62,8 +62,6 @@ class che_소집해제 extends Command\GeneralCommand{
$general = $this->generalObj;
$date = $general->getTurnTime($general::TURNTIME_HM);
$crew = $general->getVar('crew');
$logger = $general->getLogger();
$logger->pushGeneralActionLog("병사들을 <R>소집해제</>하였습니다. <1>$date</>");
@@ -71,8 +69,10 @@ class che_소집해제 extends Command\GeneralCommand{
$exp = 70;
$ded = 100;
$crewUp = $general->onCalcDomestic('징집인구', 'score', $general->getVar('crew'));
$db->update('city', [
'pop'=>$db->sqleval('pop + %i', $crew)
'pop'=>$db->sqleval('pop + %i', $crewUp)
], 'city=%i', $general->getCityID());
$general->setVar('crew', 0);
+6
View File
@@ -10,6 +10,7 @@ use \sammo\{
use \sammo\Constraint\ConstraintHelper;
use function sammo\tryUniqueItemLottery;
use function sammo\CheckHall;
class che_은퇴 extends Command\GeneralCommand{
static protected $actionName = '은퇴';
@@ -59,12 +60,17 @@ class che_은퇴 extends Command\GeneralCommand{
}
$db = DB::db();
$env = $this->env;
$general = $this->generalObj;
$date = $general->getTurnTime($general::TURNTIME_HM);
$logger = $general->getLogger();
if ($env['isunited'] == 0) {
CheckHall($general->getID());
}
$general->rebirth();
$logger->pushGeneralActionLog("은퇴하였습니다. <1>$date</>");
+11 -6
View File
@@ -184,10 +184,13 @@ class che_징병 extends Command\GeneralCommand
$logger = $general->getLogger();
$setTrain = $general->onCalcDomestic(static::$actionName, 'train', static::$defaultTrain);
$setAtmos = $general->onCalcDomestic(static::$actionName, 'atmos', static::$defaultAtmos);
if ($reqCrewType->id == $currCrewType->id && $currCrew > 0) {
$logger->pushGeneralActionLog("{$crewTypeName} <C>{$reqCrewText}</>명을 추가{$this->getName()}했습니다. <1>$date</>");
$train = ($currCrew * $general->getVar('train') + $reqCrew * static::$defaultTrain) / ($currCrew + $reqCrew);
$atmos = ($currCrew * $general->getVar('atmos') + $reqCrew * static::$defaultAtmos) / ($currCrew + $reqCrew);
$train = ($currCrew * $general->getVar('train') + $reqCrew * $setTrain) / ($currCrew + $reqCrew);
$atmos = ($currCrew * $general->getVar('atmos') + $reqCrew * $setAtmos) / ($currCrew + $reqCrew);
$general->increaseVar('crew', $reqCrew);
$general->setVar('train', $train);
@@ -196,15 +199,17 @@ class che_징병 extends Command\GeneralCommand
$logger->pushGeneralActionLog("{$crewTypeName} <C>{$reqCrewText}</>명을 {$this->getName()}했습니다. <1>$date</>");
$general->setVar('crewtype', $reqCrewType->id);
$general->setVar('crew', $reqCrew);
$general->setVar('train', static::$defaultTrain);
$general->setVar('atmos', static::$defaultAtmos);
$general->setVar('train', $setTrain);
$general->setVar('atmos', $setAtmos);
}
$newTrust = Util::valueFit($this->city['trust'] - ($reqCrew / $this->city['pop']) / static::$costOffset * 100, 0);
$reqCrewDown = $general->onCalcDomestic('징집인구', 'score', $reqCrew);
$newTrust = Util::valueFit($this->city['trust'] - ($reqCrewDown / $this->city['pop']) / static::$costOffset * 100, 0);
$db->update('city', [
'trust' => $newTrust,
'pop' => $this->city['pop'] - $reqCrew
'pop' => $this->city['pop'] - $reqCrewDown
], 'city=%i', $general->getCityID());
$exp = Util::round($reqCrew / 100);
+9
View File
@@ -113,6 +113,15 @@ class che_하야 extends Command\GeneralCommand{
$general->setVar('troop', 0);
$general->increaseInheritancePoint(InheritanceKey::active_action, 1);
if($general->getNPCType() < 2){
$general->setAuxVar(
InheritanceKey::max_belong->value,
max(
$general->getVar('belong'),
$general->getAuxVar(InheritanceKey::max_belong->value) ?? 0
)
);
}
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
$general->checkStatChange();
+1
View File
@@ -94,6 +94,7 @@ class che_화계 extends Command\GeneralCommand
$prob = $maxGenScore / GameConst::$sabotageProbCoefByStat;
$prob += $probCorrection;
$prob += (log(count($destCityGeneralList) + 1, 2) - 1.25) * GameConst::$sabotageDefenceCoefByGeneralCnt;
$prob += $destCity['secu'] / $destCity['secu_max'] / 5; //최대 20%p
$prob += $destCity['supply'] ? 0.1 : 0;
+2 -1
View File
@@ -19,6 +19,7 @@ use function \sammo\GetImageURL;
use \sammo\Constraint\Constraint;
use \sammo\Constraint\ConstraintHelper;
use sammo\Enums\MessageType;
class che_몰수 extends Command\NationCommand
{
@@ -186,7 +187,7 @@ class che_몰수 extends Command\NationCommand
GetImageURL($destGeneral->getVar('imgsvr'), $destGeneral->getVar('picture'))
);
$msg = new Message(
Message::MSGTYPE_PUBLIC,
MessageType::public,
$src,
$src,
$text,
@@ -0,0 +1,165 @@
<?php
namespace sammo\Command\Nation;
use \sammo\DB;
use \sammo\Util;
use \sammo\JosaUtil;
use \sammo\General;
use \sammo\DummyGeneral;
use \sammo\ActionLogger;
use \sammo\GameConst;
use \sammo\LastTurn;
use \sammo\GameUnitConst;
use \sammo\Command;
use \sammo\MessageTarget;
use \sammo\Message;
use \sammo\CityConst;
use \sammo\Constraint\Constraint;
use \sammo\Constraint\ConstraintHelper;
use sammo\Enums\InheritanceKey;
use sammo\Event\Action;
use sammo\Json;
class che_무작위수도이전 extends Command\NationCommand
{
static protected $actionName = '무작위 수도 이전';
protected function argTest(): bool
{
$this->arg = [];
return true;
}
protected function init()
{
$env = $this->env;
$relYear = $env['year'] - $env['startyear'];
$this->setCity();
$this->setNation(['capital', 'aux']);
$this->fullConditionConstraints = [
ConstraintHelper::OccupiedCity(),
ConstraintHelper::BeLord(),
ConstraintHelper::SuppliedCity(),
ConstraintHelper::BeOpeningPart($relYear + 1),
ConstraintHelper::ReqNationAuxValue("can_무작위수도이전", 0, '>', 0, '더이상 변경이 불가능합니다.')
];
}
public function getCommandDetailTitle():string{
$name = $this->getName();
$reqTurn = $this->getPreReqTurn()+1;
return "{$name}/{$reqTurn}";
}
public function getCost(): array
{
return [0, 0];
}
public function getPreReqTurn(): int
{
return 1;
}
public function getPostReqTurn(): int
{
return 0;
}
public function run(\Sammo\RandUtil $rng): bool
{
if (!$this->hasFullConditionMet()) {
throw new \RuntimeException('불가능한 커맨드를 강제로 실행 시도');
}
$db = DB::db();
$general = $this->generalObj;
$generalID = $general->getID();
$generalName = $general->getName();
$date = $general->getTurnTime($general::TURNTIME_HM);
$year = $this->env['year'];
$month = $this->env['month'];
$logger = $general->getLogger();
$oldCityID = $this->nation['capital'];
$cities = $db->queryFirstColumn('SELECT city FROM city where `level`>=5 and `level`<=6 and nation=0');
if (!$cities) {
$logger->pushGeneralActionLog("이동할 수 있는 도시가 없습니다. <1>$date</>");
return false;
}
$destCityID = $rng->choice($cities);
$this->setDestCity($destCityID, true);
$destCity = $this->destCity;
$destCityID = $destCity['city'];
$destCityName = $destCity['name'];
$nationID = $general->getNationID();
$nationName = $this->nation['name'];
$josaRo = JosaUtil::pick($destCityName, '로');
$general->addExperience(5 * ($this->getPreReqTurn() + 1));
$general->addDedication(5 * ($this->getPreReqTurn() + 1));
$josaYi = JosaUtil::pick($generalName, '이');
$josaYiNation = JosaUtil::pick($nationName, '이');
$aux = Json::decode($this->nation['aux']);
$aux["can_무작위수도이전"] -= 1;
$db->update('city', [
'nation' => $nationID,
'conflict' => '{}'
], 'city=%i', $destCityID);
$db->update('nation', [
'capital' => $destCityID,
'aux'=>Json::encode($aux),
], 'nation=%i', $nationID);
$db->update('city', [
'nation' => 0,
'front' => 0,
'conflict' => '{}',
'officer_set' => 0,
], 'city=%i', $oldCityID);
$general->setVar('city', $destCityID);
$generalList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND no!=%i', $general->getNationID(), $general->getID());
if ($generalList) {
$db->update('general', [
'city' => $destCityID
], 'no IN %li', $generalList);
}
foreach ($generalList as $targetGeneralID) {
$targetLogger = new ActionLogger($targetGeneralID, $general->getNationID(), $year, $month);
$targetLogger->pushGeneralActionLog("국가 수도를 <G><b>{$destCityName}</b></>{$josaRo} 옮겼습니다.", ActionLogger::PLAIN);
$targetLogger->flush();
}
\sammo\refreshNationStaticInfo();
$general->increaseInheritancePoint(InheritanceKey::active_action, 1);
$logger->pushGeneralActionLog("<G><b>{$destCityName}</b></>{$josaRo} 국가를 옮겼습니다. <1>$date</>");
$logger->pushGeneralHistoryLog("<G><b>{$destCityName}</b></>{$josaRo} <M>무작위 수도 이전</>");
$logger->pushNationalHistoryLog("<Y>{$generalName}</>{$josaYi} <G><b>{$destCityName}</b></>{$josaRo} <M>무작위 수도 이전</>");
$logger->pushGlobalActionLog("<Y>{$generalName}</>{$josaYi} <G><b>{$destCityName}</b></>{$josaRo} <M>수도 이전</>하였습니다.");
$logger->pushGlobalHistoryLog("<S><b>【무작위 수도 이전】</b></><D><b>{$nationName}</b></>{$josaYiNation} <G><b>{$destCityName}</b></>{$josaRo} <M>수도 이전</>하였습니다.");
$this->setResultTurn(new LastTurn($this->getName(), $this->arg, 0));
$general->applyDB($db);
return true;
}
}
@@ -112,7 +112,7 @@ class che_불가침수락 extends Command\NationCommand
$month = $this->arg['month'];
$currentMonth = $env['year'] * 12 + $env['month'] - 1;
$reqMonth = $year * 12 + $month - 1;
$reqMonth = $year * 12 + $month;
$nationID = $this->nation['nation'];
@@ -199,7 +199,7 @@ class che_불가침수락 extends Command\NationCommand
$destLogger = $this->destGeneralObj->getLogger();
$currentMonth = $env['year'] * 12 + $env['month'] - 1;
$reqMonth = $year * 12 + $month - 1;
$reqMonth = $year * 12 + $month;
$db->update(
'diplomacy',
@@ -22,6 +22,7 @@ use function \sammo\getNationStaticInfo;
use \sammo\Constraint\Constraint;
use \sammo\Constraint\ConstraintHelper;
use sammo\Enums\MessageType;
class che_불가침제의 extends Command\NationCommand
{
@@ -202,7 +203,7 @@ class che_불가침제의 extends Command\NationCommand
$josaWa = JosaUtil::pick($nationName, '와');
$msg = new DiplomaticMessage(
Message::MSGTYPE_DIPLOMACY,
MessageType::diplomacy,
$src,
$dest,
"{$nationName}{$josaWa} {$year}{$month}월까지 불가침 제의 서신",
@@ -20,6 +20,7 @@ use function \sammo\GetImageURL;
use \sammo\Constraint\Constraint;
use \sammo\Constraint\ConstraintHelper;
use sammo\Enums\MessageType;
class che_불가침파기제의 extends Command\NationCommand{
static protected $actionName = '불가침 파기 제의';
@@ -150,7 +151,7 @@ class che_불가침파기제의 extends Command\NationCommand{
$validUntil->add(new \DateInterval("PT{$validMinutes}M"));
$msg = new DiplomaticMessage(
Message::MSGTYPE_DIPLOMACY,
MessageType::diplomacy,
$src,
$dest,
"{$nationName}의 불가침 파기 제의 서신",
@@ -21,6 +21,7 @@ use function \sammo\GetImageURL;
use \sammo\Constraint\Constraint;
use \sammo\Constraint\ConstraintHelper;
use sammo\Enums\MessageType;
class che_선전포고 extends Command\NationCommand
{
@@ -175,7 +176,7 @@ class che_선전포고 extends Command\NationCommand
$destNation['color']
);
$msg = new Message(
Message::MSGTYPE_NATIONAL,
MessageType::national,
$src,
$dest,
$text,
@@ -20,6 +20,7 @@ use function \sammo\GetImageURL;
use \sammo\Constraint\Constraint;
use \sammo\Constraint\ConstraintHelper;
use sammo\Enums\MessageType;
class che_종전제의 extends Command\NationCommand{
static protected $actionName = '종전 제의';
@@ -148,7 +149,7 @@ class che_종전제의 extends Command\NationCommand{
$validUntil->add(new \DateInterval("PT{$validMinutes}M"));
$msg = new DiplomaticMessage(
Message::MSGTYPE_DIPLOMACY,
MessageType::diplomacy,
$src,
$dest,
"{$nationName}의 종전 제의 서신",
@@ -171,6 +171,9 @@ class che_초토화 extends Command\NationCommand{
'pop'=>$db->sqleval('greatest(pop_max*0.1, pop*0.2)'),
'agri'=>$db->sqleval('greatest(agri_max*0.1, agri*0.2)'),
'comm'=>$db->sqleval('greatest(comm_max*0.1, comm*0.2)'),
'secu'=>$db->sqleval('greatest(secu_max*0.1, secu*0.2)'),
'def'=>$db->sqleval('greatest(def_max*0.1, def*0.2)'),
'wall'=>$db->sqleval('greatest(wall_max*0.1, wall*0.5)'),
'nation'=>0,
'front'=>0,
'conflict'=>'{}'
+14 -3
View File
@@ -59,6 +59,9 @@ class che_피장파장 extends Command\NationCommand
if (!in_array($commandType, GameConst::$availableChiefCommand['전략'])) {
return false;
}
if($commandType == Util::getClassNameFromObj($this)){
return false;
}
$this->arg = [
@@ -75,7 +78,7 @@ class che_피장파장 extends Command\NationCommand
$env = $this->env;
$this->setCity();
$this->setNation();
$this->setNation(['strategic_cmd_limit']);
$this->minConditionConstraints = [
ConstraintHelper::OccupiedCity(),
@@ -113,6 +116,7 @@ class che_피장파장 extends Command\NationCommand
[0, 1],
'선포, 전쟁중인 상대국에게만 가능합니다.'
),
ConstraintHelper::AvailableStrategicCommand(),
];
}
@@ -120,8 +124,9 @@ class che_피장파장 extends Command\NationCommand
{
$name = $this->getName();
$reqTurn = $this->getPreReqTurn() + 1;
$postReqTurn = $this->getPostReqTurn();
return "{$name}/{$reqTurn}턴(대상 재사용 대기 {$this->getTargetPostReqTurn()})";
return "{$name}/{$reqTurn}턴(재사용 대기 $postReqTurn, 대상 재사용 대기 {$this->getTargetPostReqTurn()})";
}
public function getCost(): array
@@ -136,7 +141,7 @@ class che_피장파장 extends Command\NationCommand
public function getPostReqTurn(): int
{
return 0;
return 8;
}
public function getTargetPostReqTurn(): int
@@ -251,7 +256,13 @@ class che_피장파장 extends Command\NationCommand
$oneAvailableCommandName = null;
$thisClassName = Util::getClassNameFromObj($this);
foreach (GameConst::$availableChiefCommand['전략'] as $commandType) {
if($commandType == $thisClassName){
continue;
}
$cmd = buildNationCommandClass($commandType, $generalObj, $this->env, new LastTurn());
$cmdName = $cmd->getName();
$remainTurn = 0;
+6 -4
View File
@@ -1,6 +1,8 @@
<?php
namespace sammo;
use sammo\Enums\MessageType;
class DiplomaticMessage extends Message{
const ACCEPTED = 1;
@@ -18,7 +20,7 @@ class DiplomaticMessage extends Message{
protected $validDiplomacy = true;
public function __construct(
string $msgType,
MessageType $msgType,
MessageTarget $src,
MessageTarget $dest,
string $msg,
@@ -27,7 +29,7 @@ class DiplomaticMessage extends Message{
array $msgOption
)
{
if ($msgType !== self::MSGTYPE_DIPLOMACY){
if ($msgType !== MessageType::diplomacy){
throw new \InvalidArgumentException('DiplomaticMessage msgType');
}
@@ -208,7 +210,7 @@ class DiplomaticMessage extends Message{
$josaYi = JosaUtil::pick($this->src->nationName, '이');
$newMsg = new Message(
self::MSGTYPE_NATIONAL,
MessageType::national,
$this->dest,
$this->src,
"【외교】{$year}{$month}월: {$this->src->nationName}{$josaYi} {$this->dest->nationName}에게 제안한 {$this->diplomacyDetail}",
@@ -224,7 +226,7 @@ class DiplomaticMessage extends Message{
$newMsg->send();
$newMsg = new Message(
self::MSGTYPE_DIPLOMACY,
MessageType::diplomacy,
$this->dest,
$this->src,
"【외교】{$year}{$month}월: {$this->src->nationName}{$josaYi} {$this->dest->nationName}에게 제안한 {$this->diplomacyDetail}",
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace sammo\Enums;
enum MessageType: string
{
case private = 'private';
case public = 'public';
case national = 'national';
case diplomacy = 'diplomacy';
//TODO: System을 정식으로 추가해야함.
}
@@ -0,0 +1,20 @@
<?php
namespace sammo\Event\Action;
use sammo\DB;
class AddGlobalBetray extends \sammo\Event\Action
{
public function __construct(private int $cnt = 1, private int $ifMax = 0)
{
}
public function run(array $env)
{
$db = DB::db();
$db->update('general', [
'betray' => $db->sqleval('betray + %i', $this->cnt),
], 'betray <= %i', $this->ifMax);
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace sammo\Event\Action;
use sammo\ActionLogger;
use sammo\CityConst;
use sammo\DB;
use sammo\KVStorage;
use sammo\Util;
use sammo\General;
use sammo\JosaUtil;
use sammo\LiteHashDRBG;
use sammo\RandUtil;
use sammo\UniqueConst;
class LostUniqueItem extends \sammo\Event\Action
{
public function __construct(
private float $lostProb = 0.1,
) {
}
public function run(array $env)
{
$db = DB::db();
$generalIDList = $db->queryFirstColumn("SELECT `no` FROM general WHERE npc <= 1");
if (!$generalIDList) {
return;
}
$generals = General::createGeneralObjListFromDB($generalIDList);
$lostItems = [];
$totalLostCnt = 0;
$year = $env['year'];
$month = $env['month'];
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'LostUniqueItem',
$year,
$month,
)));
$maxLostByGenCnt = 0;
$maxLostGenList = [];
foreach ($generals as $general) {
$itemList = $general->getItems();
$didLoseItem = false;
$lostByGenCnt = 0;
foreach ($itemList as $itemType => $item) {
if ($item->isBuyable()) {
continue;
}
$logger = $general->getLogger();
if ($rng->nextBool($this->lostProb)) {
$itemName = $item->getName();
$itemRawName = $item->getRawName();
$josaUl = JosaUtil::pick($itemRawName, '을');
$lostItems[$itemName] = 1;
$totalLostCnt++;
$lostByGenCnt++;
$general->setItem($itemType, 'None');
$didLoseItem = true;
$logger->pushGeneralActionLog("<C>{$itemName}</>{$josaUl} 잃었습니다.");
}
}
if (!$didLoseItem) {
continue;
}
$general->applyDB($db);
if ($maxLostByGenCnt < $lostByGenCnt) {
$maxLostByGenCnt = $lostByGenCnt;
$maxLostGenList = [$general->getName()];
} else if ($maxLostByGenCnt === $lostByGenCnt) {
$maxLostGenList[] = $general->getName();
}
}
$logger = new ActionLogger(0, 0, $year, $month);
if ($totalLostCnt == 0) {
$logger->pushGlobalHistoryLog("<R><b>【망실】</b></>어떤 아이템도 잃지 않았습니다!");
} else {
$genCnt = count($maxLostGenList);
if($genCnt > 4){
$maxLostGenList = array_slice($maxLostGenList, 0, 4);
}
$maxLostGenListStr = implode(', ', $maxLostGenList);
if($genCnt > 4){
$maxLostGenListStr .= ' 외 ' . ($genCnt - 4) . '명';
}
$josaYi = JosaUtil::pick($maxLostGenListStr, '이');
$logger->pushGlobalHistoryLog("<R><b>【망실】</b></>불운하게도 <Y>{$maxLostGenListStr}</>{$josaYi} 한 번에 유니크 <C>{$maxLostByGenCnt}</>종을 잃었습니다! (총 <C>{$totalLostCnt}</>개)");
}
$logger->flush();
}
}
+2 -1
View File
@@ -9,6 +9,7 @@ use \sammo\Util;
use \sammo\DB;
use sammo\DTO\BettingInfo;
use sammo\DTO\SelectItem;
use sammo\Enums\MessageType;
use sammo\Json;
use sammo\KVStorage;
use sammo\Message;
@@ -138,7 +139,7 @@ class OpenNationBetting extends \sammo\Event\Action
GetImageURL($general['imgsvr'], $general['picture'])
);
$msg = new Message(
Message::MSGTYPE_PRIVATE,
MessageType::private,
$src,
$dest,
$text,
+16 -1
View File
@@ -46,6 +46,8 @@ class GameConstBase
public static $sabotageDefaultProb = 0.35;
/** @var int 계략시 확률 가중치(수치가 클수록 변화가 적음 : (지력차/$firing + $basefiring)*/
public static $sabotageProbCoefByStat = 300;
/** @var float 인원수 별 계략 방어 가중치 */
public static $sabotageDefenceCoefByGeneralCnt = 0.04;
/** @var int 계략시 최소 수치 감소량*/
public static $sabotageDamageMin = 100;
/** @var int 계략시 최대 수치 감소량*/
@@ -202,6 +204,11 @@ class GameConstBase
[20, 4]
];
public static $minTurnDieOnPrestart = 2;
public static $uniqueTrialCoef = 1;
public static $maxUniqueTrialProb = 0.25;
public static $maxAvailableWarSettingCnt = 10;
public static $incAvailableWarSettingCnt = 2;
@@ -368,9 +375,9 @@ class GameConstBase
'che_의병모집',
'che_이호경식',
'che_급습',
'che_피장파장',
],
'기타' => [
'che_피장파장',
'che_국기변경',
'che_국호변경',
]
@@ -431,5 +438,13 @@ class GameConstBase
["NoticeToHistoryLog", "<S>출병 제한이 풀렸습니다.</>", ActionLogger::EVENT_YEAR_MONTH],
["DeleteEvent"]
],
[
"month", 2000,
["DateRelative", "==", 4, 1],
["NoticeToHistoryLog", "<S>이제부터 하야, 망명시 패널티가 적용됩니다.</>", ActionLogger::EVENT_YEAR_MONTH],
["AddGlobalBetray", 1, 0],
["AddGlobalBetray", 1, 1],
["DeleteEvent"]
],
];
}
+12 -12
View File
@@ -115,16 +115,16 @@ class GameUnitConstBase{
[self::T_CAVALRY=>1.2, self::T_FOOTMAN=>0.8, self::T_SIEGE=>1.2],
[self::T_CAVALRY=>0.8, self::T_FOOTMAN=>1.2, self::T_SIEGE=>0.8],
['표준적인 궁병입니다.','궁병은 선제사격을 하는 병종입니다.'],
null, ['che_궁병선제사격']
null, ['che_선제사격시도', 'che_선제사격발동']
],
[
1201, self::T_ARCHER, '궁기병',
100, 100, 8, 20, 0, 11, 12,
1000, null, ['동이'], 0,
[self::T_CAVALRY=>1.2, self::T_FOOTMAN=>0.8, self::T_SIEGE=>1.2],
[self::T_CAVALRY=>0.8, self::T_FOOTMAN=>1.2, self::T_SIEGE=>0.8],
['말을 타고 잘 피합니다.'],
null, ['che_궁병선제사격']
[self::T_CAVALRY=>1.2, self::T_FOOTMAN=>0.9, self::T_SIEGE=>1.2],
[self::T_CAVALRY=>0.8, self::T_FOOTMAN=>1.1, self::T_SIEGE=>0.8],
['말을 타고 잘 피합니다. 특히 다른 궁병보다 보병에게 조금 더 강합니다.'],
null, ['che_선제사격시도', 'che_선제사격발동']
],
[
1202, self::T_ARCHER, '연노병',
@@ -133,7 +133,7 @@ class GameUnitConstBase{
[self::T_CAVALRY=>1.2, self::T_FOOTMAN=>0.8, self::T_SIEGE=>1.2],
[self::T_CAVALRY=>0.8, self::T_FOOTMAN=>1.2, self::T_SIEGE=>0.8],
['화살을 연사합니다.'],
null, ['che_궁병선제사격']
null, ['che_선제사격시도', 'che_선제사격발동']
],
[
1203, self::T_ARCHER, '강궁병',
@@ -142,7 +142,7 @@ class GameUnitConstBase{
[self::T_CAVALRY=>1.2, self::T_FOOTMAN=>0.8, self::T_SIEGE=>1.2],
[self::T_CAVALRY=>0.8, self::T_FOOTMAN=>1.2, self::T_SIEGE=>0.8],
['강건한 궁병입니다.'],
null, ['che_궁병선제사격']
null, ['che_선제사격시도', 'che_선제사격발동']
],
[
1204, self::T_ARCHER, '석궁병',
@@ -151,7 +151,7 @@ class GameUnitConstBase{
[self::T_CAVALRY=>1.2, self::T_FOOTMAN=>0.8, self::T_SIEGE=>1.2],
[self::T_CAVALRY=>0.8, self::T_FOOTMAN=>1.2, self::T_SIEGE=>0.8],
['강력한 화살을 쏩니다.'],
null, ['che_궁병선제사격']
null, ['che_선제사격시도', 'che_선제사격발동']
],
[
@@ -316,7 +316,7 @@ class GameUnitConstBase{
[self::T_FOOTMAN=>1.25, self::T_ARCHER=>1.25, self::T_CAVALRY=>1.25, self::T_WIZARD=>1.25, self::T_CASTLE=>1.8, 1106=>1.112],
[self::T_FOOTMAN=>1.2, self::T_ARCHER=>1.2, self::T_CAVALRY=>1.2, self::T_WIZARD=>1.2],
['높은 구조물 위에서 공격합니다.'],
['che_성벽부상무효'], null
['che_성벽부상무효'], ['che_선제사격시도', 'che_선제사격발동']
],
[
1501, self::T_SIEGE, '충차',
@@ -329,19 +329,19 @@ class GameUnitConstBase{
],
[
1502, self::T_SIEGE, '벽력거',
200, 100, 6, 5, 0, 20, 5,
150, 100, 6, 5, 0, 20, 5,
3000, ['업'], null, 0,
[self::T_FOOTMAN=>1.25, self::T_ARCHER=>1.25, self::T_CAVALRY=>1.25, self::T_WIZARD=>1.25, self::T_CASTLE=>1.8, 1106=>1.112],
[self::T_FOOTMAN=>0.833, self::T_ARCHER=>0.833, self::T_CAVALRY=>0.833, self::T_WIZARD=>0.833, 1106=>0.909],
['상대에게 돌덩이를 날립니다.'],
['che_성벽부상무효'], null
['che_성벽부상무효'], ['che_선제사격시도', 'che_선제사격발동']
],
[
1503, self::T_SIEGE, '목우',
50, 200, 5, 0, 0, 15, 5,
3000, ['성도'], null, 0,
[self::T_FOOTMAN=>1.25, self::T_ARCHER=>1.25, self::T_CAVALRY=>1.25, self::T_WIZARD=>1.25, self::T_CASTLE=>1.8, 1106=>1.112],
[self::T_FOOTMAN=>0.833, self::T_ARCHER=>0.833, self::T_CAVALRY=>0.833, self::T_WIZARD=>0.833, 1106=>0.909],
[self::T_FOOTMAN=>1, self::T_ARCHER=>1, self::T_CAVALRY=>1, self::T_WIZARD=>1, 1106=>1],
['상대를 저지하는 특수병기입니다.'],
['che_성벽부상무효'], ['che_저지시도', 'che_저지발동']
]
+5 -5
View File
@@ -626,7 +626,6 @@ class General implements iAction
$inheritPointManager = InheritancePointManager::getInstance();
$inheritPointManager->mergeTotalInheritancePoint($this);
$inheritPointManager->applyInheritanceUser($this->getVar('owner'));
$inheritPointManager->clearInheritancePoint($this);
}
@@ -666,6 +665,8 @@ class General implements iAction
'reserved_until' => null,
], 'general_id=%i', $generalID);
storeOldGeneral($generalID, $logger->getYear(), $logger->getMonth());
$db->delete('general', 'no=%i', $generalID);
$db->delete('general_turn', 'general_id=%i', $generalID);
$db->delete('rank_data', 'general_id=%i', $generalID);
@@ -686,9 +687,8 @@ class General implements iAction
$ownerID = $this->getVar('owner');
if ($ownerID) {
$inheritPointManager->mergeTotalInheritancePoint($this);
$inheritPointManager->mergeTotalInheritancePoint($this, true);
$inheritPointManager->applyInheritanceUser($ownerID, true);
$inheritPointManager->clearInheritancePoint($this);
}
$this->multiplyVarWithLimit('leadership', 0.85, 10);
@@ -1331,8 +1331,8 @@ class General implements iAction
return InheritancePointManager::getInstance()->increaseInheritancePoint($this, $key, $value, $aux);
}
public function mergeTotalInheritancePoint(bool $isEnd = false)
public function mergeTotalInheritancePoint(bool $isRebirth = false)
{
InheritancePointManager::getInstance()->mergeTotalInheritancePoint($this, $isEnd);
InheritancePointManager::getInstance()->mergeTotalInheritancePoint($this, $isRebirth);
}
}
+64 -35
View File
@@ -4,6 +4,7 @@ namespace sammo;
use sammo\Command\GeneralCommand;
use sammo\Command\NationCommand;
use sammo\Enums\MessageType;
use sammo\Enums\RankColumn;
use sammo\Scenario\NPC;
@@ -11,8 +12,6 @@ class GeneralAI
{
protected RandUtil $rng;
/** @var General */
protected $general;
protected array $city;
protected array $nation;
protected int $genType;
@@ -65,6 +64,9 @@ class GeneralAI
/** @var General[] */
protected $chiefGenerals;
/** @var bool */
protected $reqUpdateInstance = true;
/** @var General[] */
protected $lostGenerals;
/** @var General[] 이번 턴에 '집합'하는 부대장 목록 */
@@ -80,15 +82,16 @@ class GeneralAI
const d직전 = 3;
const d전쟁 = 4;
//수뇌용
protected function updateInstance(): void{
if(!$this->reqUpdateInstance){
return;
}
$this->reqUpdateInstance = false;
public function __construct(General $general)
{
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$this->env = $gameStor->getAll(true);
$this->baseDevelCost = $this->env['develcost'] * 12;
$this->general = $general;
$general = $this->general;
$city = $general->getRawCity();
if ($city === null) {
$city = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $general->getCityID());
@@ -96,14 +99,6 @@ class GeneralAI
}
$this->city = $city;
$this->rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'GeneralAI',
$this->env['year'],
$this->env['month'],
$general->getID(),
)));
$this->nation = $db->queryFirstRow(
'SELECT nation,name,color,capital,capset,gennum,gold,rice,bill,rate,rate_tmp,scout,war,strategic_cmd_limit,surlimit,tech,power,level,chief_set,type,aux FROM nation WHERE nation = %i',
$general->getNationID()
@@ -123,8 +118,8 @@ class GeneralAI
$nationStor = KVStorage::getStorage($db, $this->nation['nation'], 'nation_env');
$nationStor->cacheValues(['npc_nation_policy', 'npc_general_policy', 'prev_income_gold', 'prev_income_rice']);
$this->nationPolicy = new AutorunNationPolicy($general, $this->env['autorun_user']['options'] ?? null, $nationStor->getValue('npc_nation_policy'), $gameStor->getValue('npc_nation_policy'), $this->nation, $this->env);
$this->generalPolicy = new AutorunGeneralPolicy($general, $this->env['autorun_user']['options'] ?? null, $nationStor->getValue('npc_general_policy'), $gameStor->getValue('npc_general_policy'), $this->nation, $this->env);
$this->nationPolicy = new AutorunNationPolicy($general, $this->env['autorun_user']['options'] ?? null, $nationStor->getValue('npc_nation_policy'), ($this->env['npc_nation_policy']) ?? null, $this->nation, $this->env);
$this->generalPolicy = new AutorunGeneralPolicy($general, $this->env['autorun_user']['options'] ?? null, $nationStor->getValue('npc_general_policy'), ($this->env['npc_general_policy']) ?? null, $this->nation, $this->env);
$prevIncomeGold = $nationStor->prev_income_gold ?? 1000;
$prevIncomeRice = $nationStor->prev_income_rice ?? 1000;
@@ -142,6 +137,25 @@ class GeneralAI
$this->nation['aux'] = Json::decode($this->nation['aux'] ?? '{}');
$this->calcDiplomacyState();
$this->genType = $this->calcGenType($general);
}
public function __construct(protected General $general)
{
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$this->env = $gameStor->getAll(true);
$this->rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'GeneralAI',
$this->env['year'],
$this->env['month'],
$general->getID(),
)));
$this->leadership = $general->getLeadership();
$this->strength = $general->getStrength();
$this->intel = $general->getIntel();
@@ -149,11 +163,6 @@ class GeneralAI
$this->fullLeadership = $general->getLeadership(false);
$this->fullStrength = $general->getStrength(false);
$this->fullIntel = $general->getIntel(false);
$this->genType = $this->calcGenType($general);
$this->calcDiplomacyState();
}
public function getGeneralObj(): General
@@ -572,6 +581,9 @@ class GeneralAI
if ($userGeneral->getVar('crew') >= $this->nationPolicy->minWarCrew) {
continue;
}
if ($userGeneral->onCalcDomestic('징집인구', 'score', 100) <= 1) {
continue;
}
$generalTurnTime = $userGeneral->getTurnTime();
$troopTurnTime = $troopLeader->getTurnTime();
@@ -665,6 +677,9 @@ class GeneralAI
if ($userGeneral->getVar('crew') >= $this->nationPolicy->minWarCrew) {
continue;
}
if ($userGeneral->onCalcDomestic('징집인구', 'score', 100) <= 1) {
continue;
}
$generalCadidates[$generalID] = $userGeneral;
}
@@ -678,10 +693,8 @@ class GeneralAI
/** @var General */
$pickedGeneral = $this->rng->choice($generalCadidates);
$minRecruitPop = $this->fullLeadership * 100 + GameConst::$minAvailableRecruitPop;
if (!$this->generalPolicy->can한계징병) {
$minRecruitPop = max($minRecruitPop, $this->fullLeadership * 100 + $this->nationPolicy->minNPCRecruitCityPopulation);
}
$pickedGeneralLeadership = $pickedGeneral->getLeadership(false, true, true, true);
$minRecruitPop = $pickedGeneralLeadership * 100 + GameConst::$minAvailableRecruitPop;
$recruitableCityList = [];
@@ -964,6 +977,9 @@ class GeneralAI
if ($npcGeneral->getVar('crew') >= $this->nationPolicy->minWarCrew) {
continue;
}
if ($npcGeneral->onCalcDomestic('징집인구', 'score', 100) <= 1) {
continue;
}
$generalCadidates[$generalID] = $npcGeneral;
}
@@ -977,10 +993,9 @@ class GeneralAI
/** @var General */
$pickedGeneral = $this->rng->choice($generalCadidates);
$minRecruitPop = $this->fullLeadership * 100 + GameConst::$minAvailableRecruitPop;
if (!$this->generalPolicy->can한계징병) {
$minRecruitPop = max($minRecruitPop, $this->fullLeadership * 100 + $this->nationPolicy->minNPCRecruitCityPopulation);
}
$pickedGeneralLeadership = $pickedGeneral->getLeadership(false, true, true, true);
$minRecruitPop = $pickedGeneralLeadership * 100 + GameConst::$minAvailableRecruitPop;
$minRecruitPop = max($minRecruitPop, $pickedGeneralLeadership * 100 + $this->nationPolicy->minNPCRecruitCityPopulation);
$recruitableCityList = [];
@@ -1289,6 +1304,7 @@ class GeneralAI
return null;
}
$this->reqUpdateInstance = true;
return $cmd;
}
@@ -1489,6 +1505,7 @@ class GeneralAI
return null;
}
$this->reqUpdateInstance = true;
return $cmd;
}
@@ -1951,6 +1968,7 @@ class GeneralAI
return null;
}
$this->reqUpdateInstance = true;
return $cmd;
}
@@ -1968,6 +1986,7 @@ class GeneralAI
$cmd = buildNationCommandClass('che_천도', $this->general, $this->env, $lastTurn, $lastTurn->getArg());
if ($cmd->hasFullConditionMet()) {
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()];
$this->reqUpdateInstance = true;
return $cmd;
}
}
@@ -2090,6 +2109,7 @@ class GeneralAI
$nationStor->last천도Trial = [$general->getVar('officer_level'), $general->getTurnTime()];
$this->reqUpdateInstance = true;
return $cmd;
}
@@ -2653,7 +2673,7 @@ class GeneralAI
return $this->rng->choiceUsingWeightPair($cmdList);
}
public function do소집해제(): ?GeneralCommand
protected function do소집해제(): ?GeneralCommand
{
if ($this->attackable) {
return null;
@@ -2972,6 +2992,9 @@ class GeneralAI
}
foreach ($this->frontCities as $frontCity) {
if(!$frontCity['supply']){
continue;
}
$candidateCities[$frontCity['city']] = $frontCity['important'];
}
@@ -3454,7 +3477,7 @@ class GeneralAI
}
if ($nationCity['front']) {
$frontCities[$cityID] = $nationCity;
} else {
} else if($nationCity['supply']) {
$backupCities[$cityID] = $nationCity;
}
@@ -3532,7 +3555,7 @@ class GeneralAI
} else if ($nationGeneral->getVar('troop') === $generalID && $nationGeneral->getReservedTurn(0, $this->env)->getName() === 'che_집합') {
//비 NPC부대장임
$troopLeaders[$generalID] = $nationGeneral;
} else if ($nationGeneral->getVar('killturn') < 5) {
} else if ($nationGeneral->getVar('killturn') <= 5) {
//삭턴이 몇 안남은 장수는 '내정장 npc'로 처리
$npcCivilGenerals[$generalID] = $nationGeneral;
} else if ($npcType < 2) {
@@ -3570,6 +3593,8 @@ class GeneralAI
public function chooseNationTurn(NationCommand $reservedCommand): NationCommand
{
$this->updateInstance();
//TODO: NationTurn과 InstantNationTurn 구분 필요
$lastTurn = $reservedCommand->getLastTurn();
$general = $this->general;
@@ -3641,6 +3666,8 @@ class GeneralAI
return $reservedCommand;
}
$this->updateInstance();
foreach ($this->nationPolicy->priority as $actionName) {
/** @var ?NationCommand */
if (!key_exists($actionName, $this->nationPolicy::$availableInstantTurn)) {
@@ -3663,6 +3690,8 @@ class GeneralAI
$npcType = $general->getNPCType();
$nationID = $general->getNationID();
$this->updateInstance();
//특별 메세지 있는 경우 출력
$term = $this->env['turnterm'];
if ($general->getVar('npcmsg') && $this->rng->nextBool(GameConst::$npcMessageFreqByDay * $term / (60 * 24))) {
@@ -3675,7 +3704,7 @@ class GeneralAI
GetImageURL($general->getVar('imgsvr'), $general->getVar('picture'))
);
$msg = new Message(
Message::MSGTYPE_PUBLIC,
MessageType::public,
$src,
$src,
$general->getVar('npcmsg'),
@@ -23,8 +23,9 @@ class che_병력군량소모 extends BaseGeneralTrigger{
}
else{
$db = DB::db();
$crewUp = $general->onCalcDomestic('징집인구', 'score', $general->getVar('crew'));
$db->update('city', [
'pop'=>$db->sqleval('pop + %i', $general->getVar('crew'))
'pop'=>$db->sqleval('pop + %i', $crewUp)
], 'city=%i', $general->getCityID());
$general->setVar('crew', 0);
+37 -26
View File
@@ -17,18 +17,18 @@ class InheritancePointManager
private function __construct()
{
$inheritanceKey = new Map();
$inheritanceKey->put(InheritanceKey::previous, new InheritancePointType(true, 1, '기존 포인트'));
$inheritanceKey->put(InheritanceKey::lived_month, new InheritancePointType(true, 1, '생존'));
$inheritanceKey->put(InheritanceKey::max_belong, new InheritancePointType(false, 10, '최대 임관년 수'));
$inheritanceKey->put(InheritanceKey::max_domestic_critical, new InheritancePointType(true, 1, '최대 연속 내정 성공'));
$inheritanceKey->put(InheritanceKey::active_action, new InheritancePointType(true, 3, '능동 행동 수'));
$inheritanceKey->put(InheritanceKey::previous, new InheritancePointType(true, 1, '기존 포인트', 1));
$inheritanceKey->put(InheritanceKey::lived_month, new InheritancePointType(true, 1, '생존', 1));
$inheritanceKey->put(InheritanceKey::max_belong, new InheritancePointType(false, 10, '최대 임관년 수', null));
$inheritanceKey->put(InheritanceKey::max_domestic_critical, new InheritancePointType(true, 1, '최대 연속 내정 성공', null));
$inheritanceKey->put(InheritanceKey::active_action, new InheritancePointType(true, 3, '능동 행동 수', 1));
//$inheritanceKey->put(InheritanceKey::snipe_combat, new InheritancePointType(true, 10, '병종 상성 우위 횟수'));
$inheritanceKey->put(InheritanceKey::combat, new InheritancePointType(['rank', RankColumn::warnum], 5, '전투 횟수'));
$inheritanceKey->put(InheritanceKey::sabotage, new InheritancePointType(['rank', RankColumn::firenum], 20, '계략 성공 횟수'));
$inheritanceKey->put(InheritanceKey::unifier, new InheritancePointType(true, 1, '천통 기여'));
$inheritanceKey->put(InheritanceKey::dex, new InheritancePointType(false, 0.001, '숙련도'));
$inheritanceKey->put(InheritanceKey::tournament, new InheritancePointType(true, 1, '토너먼트'));
$inheritanceKey->put(InheritanceKey::betting, new InheritancePointType(false, 10, '베팅 당첨'));
$inheritanceKey->put(InheritanceKey::combat, new InheritancePointType(['rank', RankColumn::warnum], 5, '전투 횟수', 1));
$inheritanceKey->put(InheritanceKey::sabotage, new InheritancePointType(['rank', RankColumn::firenum], 20, '계략 성공 횟수', 1));
$inheritanceKey->put(InheritanceKey::unifier, new InheritancePointType(true, 1, '천통 기여', null));
$inheritanceKey->put(InheritanceKey::dex, new InheritancePointType(false, 0.001, '숙련도', 0.5));
$inheritanceKey->put(InheritanceKey::tournament, new InheritancePointType(true, 1, '토너먼트', 1));
$inheritanceKey->put(InheritanceKey::betting, new InheritancePointType(false, 10, '베팅 당첨', 1));
$this->inheritanceKey = $inheritanceKey;
}
@@ -258,9 +258,8 @@ class InheritancePointManager
$inheritStor->setValue($key->value, [$newValue, $aux]);
}
public function clearInheritancePoint(General $general)
public function clearInheritancePoint(?int $ownerID)
{
$ownerID = $general->getVar('owner');
if (!$ownerID) {
return;
}
@@ -281,7 +280,7 @@ class InheritancePointManager
$inheritStor->setValue(InheritanceKey::previous->value, $previousPointInfo);
}
public function mergeTotalInheritancePoint(General $general, bool $isEnd = false)
public function mergeTotalInheritancePoint(General $general, bool $isRebirth = false)
{
$ownerID = $general->getVar('owner');
if (!$ownerID) {
@@ -296,7 +295,7 @@ class InheritancePointManager
$gameStor->cacheValues(['year', 'startyear', 'month']);
if ($general->getVar('npc') == 1) {
if (!$isEnd) {
if ($isRebirth) {
return;
}
@@ -331,18 +330,14 @@ class InheritancePointManager
}
function applyInheritanceUser(int $userID, bool $isRebirth = false): float
function applyInheritanceUser(int $ownerID, bool $isRebirth = false): float
{
if ($userID === 0) {
if ($ownerID === 0) {
return 0;
}
//FIXME: 굳이 merge, apply, clean 3단계를 거쳐야 할 이유가 없음
/** @var Map<InheritanceKey,float> */
$rebirthDegraded = new Map();
$rebirthDegraded[InheritanceKey::dex] = 0.5;
$inheritStor = KVStorage::getStorage(DB::db(), "inheritance_{$userID}");
$inheritStor = KVStorage::getStorage(DB::db(), "inheritance_{$ownerID}");
$totalPoint = 0;
/** @var array<string,array{0:float,1:string|float}> */
$allPoints = $inheritStor->getAll();
@@ -355,15 +350,27 @@ class InheritancePointManager
return $allPoints[InheritanceKey::previous->value][0];
}
$userLogger = new UserLogger($userID);
$userLogger = new UserLogger($ownerID);
$previousPoint = ($allPoints[InheritanceKey::previous->value] ?? [0, 0])[0];
foreach ($allPoints as $rKey => [$value,]) {
$keepValues = [];
foreach ($allPoints as $rKey => [$value, $auxV]) {
$key = InheritanceKey::from($rKey);
if ($isRebirth && $rebirthDegraded->hasKey($key)) {
$value *= $rebirthDegraded[$key];
/** @var InheritancePointType */
$keyTypeObj = $this->inheritanceKey->get($key);
if ($isRebirth) {
if ($keyTypeObj->rebirthStoreCoeff === null) {
$keepValues[$rKey] = [$value, $auxV];
continue;
}
$value *= $keyTypeObj->rebirthStoreCoeff;
}
$keyText = $this->getInheritancePointType($key)->info;
$userLogger->push("{$keyText} 포인트 {$value} 증가", "inheritPoint");
$totalPoint += $value;
@@ -373,6 +380,10 @@ class InheritancePointManager
$userLogger->flush();
$inheritStor->resetValues();
foreach ($keepValues as $rKey => $pointPair) {
$inheritStor->setValue($rKey, $pointPair);
}
$inheritStor->setValue(InheritanceKey::previous->value, [$totalPoint, null]);
return $totalPoint;
}
+36 -82
View File
@@ -1,16 +1,13 @@
<?php
namespace sammo;
use sammo\Enums\MessageType;
class Message
{
const MAILBOX_PUBLIC = 9999;
const MAILBOX_NATIONAL = 9000;
const MSGTYPE_PUBLIC = 'public';
const MSGTYPE_PRIVATE = 'private';
const MSGTYPE_NATIONAL = 'national';
const MSGTYPE_DIPLOMACY = 'diplomacy';
//기본 정보
public $mailbox = null;
public $id = null;
@@ -18,39 +15,15 @@ class Message
protected $sendCnt = 0;
public $msgType;
/** @var \sammo\MessageTarget */
public $src;
/** @var \sammo\MessageTarget */
public $dest;
public $msg;
/** @var \DateTime */
public $date;
/** @var \DateTime */
public $validUntil;
public $msgOption;
public function __construct(
string $msgType,
MessageTarget $src,
MessageTarget $dest,
string $msg,
\DateTime $date,
\DateTime $validUntil,
array $msgOption
public MessageType $msgType,
public MessageTarget $src,
public MessageTarget $dest,
public string $msg,
public \DateTime $date,
public \DateTime $validUntil,
public ?array $msgOption
) {
if (!static::isValidMsgType($msgType)) {
throw new \InvalidArgumentException('올바르지 않은 msgType');
}
$this->msgType = $msgType;
$this->src = $src;
$this->dest = $dest;
$this->msg = $msg;
$this->date = $date;
$this->validUntil = $validUntil;
$this->msgOption = $msgOption;
}
public function setSentInfo(int $mailbox, int $messageID) : self
@@ -61,19 +34,19 @@ class Message
do{
if($mailbox === Message::MAILBOX_PUBLIC){
if($this->msgType !== Message::MSGTYPE_PUBLIC){
throw new \InvalidArgumentException('올바르지 않은 mailbox, msgType !== MSGTYPE_PUBLIC');
if($this->msgType !== MessageType::public){
throw new \InvalidArgumentException('올바르지 않은 mailbox, msgType !== MessageType::public');
}
$this->isInboxMail = true;
break;
}
if($mailbox >= Message::MAILBOX_NATIONAL){
if($this->msgType === Message::MSGTYPE_DIPLOMACY){
if($this->msgType === MessageType::diplomacy){
$this->isInboxMail = true;
break;
}
if ($this->msgType !== Message::MSGTYPE_NATIONAL) {
throw new \InvalidArgumentException('올바르지 않은 mailbox, msgType not in (MSGTYPE_DIPLOMACY, MSGTYPE_NATIONAL)');
if ($this->msgType !== MessageType::national) {
throw new \InvalidArgumentException('올바르지 않은 mailbox, msgType not in (MessageType::diplomacy, MessageType::national)');
}
if($this->dest->nationID + Message::MAILBOX_NATIONAL === $mailbox){
$this->isInboxMail = true;
@@ -85,7 +58,7 @@ class Message
}
throw new \InvalidArgumentException('송신, 수신국 둘 중의 어느 메일함도 아닙니다');
}
if($this->msgType !== Message::MSGTYPE_PRIVATE){
if($this->msgType !== MessageType::private){
throw new \InvalidArgumentException('올바르지 않은 mailbox, msgType !== MSGTYPE_PRIVATE');
}
if($this->dest->generalID === $mailbox){
@@ -105,11 +78,11 @@ class Message
}
public function toArray():array{
if($this->msgType === Message::MSGTYPE_PUBLIC){
if($this->msgType === MessageType::public){
$src = $this->src->toArray();
$dest = null;
}
else if($this->msgType === Message::MSGTYPE_NATIONAL || $this->msgType === Message::MSGTYPE_DIPLOMACY){
else if($this->msgType === MessageType::national || $this->msgType === MessageType::diplomacy){
$src = $this->src->toArray();
$dest = $this->dest->toArray();
}
@@ -120,7 +93,7 @@ class Message
return [
'id'=>$this->id,
'msgType'=>$this->msgType,
'msgType'=>$this->msgType->value,
'src'=>$src,
'dest'=>$dest,
'text'=>$this->msg,
@@ -133,7 +106,7 @@ class Message
{
$dbMessage = Json::decode($row['message']);
$msgType = $row['type'];
$msgType = MessageType::from($row['type']);
$src = MessageTarget::buildFromArray($dbMessage['src']);
$dest = MessageTarget::buildFromArray($dbMessage['dest']);
$option = Util::array_get($dbMessage['option'], []);
@@ -149,7 +122,7 @@ class Message
];
$action = Util::array_get($option['action'], null);
if ($msgType === self::MSGTYPE_DIPLOMACY && $action !== null) {
if ($msgType === MessageType::diplomacy && $action !== null) {
$objMessage = new DiplomaticMessage(...$args);
} elseif ($action === 'scout') {
$objMessage = new ScoutMessage(...$args);
@@ -164,7 +137,7 @@ class Message
protected static function isValidMailBox(int $mailbox): bool
{
if ($mailbox > self::MAILBOX_PUBLIC) {
if ($mailbox > MessageType::public) {
return false;
}
if ($mailbox <= 0) {
@@ -173,17 +146,6 @@ class Message
return true;
}
protected static function isValidMsgType(string $msgType): bool
{
switch ($msgType) {
case static::MSGTYPE_PUBLIC: return true;
case static::MSGTYPE_PRIVATE: return true;
case static::MSGTYPE_NATIONAL: return true;
case static::MSGTYPE_DIPLOMACY: return true;
}
return false;
}
public static function getMessageByID(int $messageID) : ?Message
{
$db = DB::db();
@@ -197,24 +159,20 @@ class Message
/**
* @param int $mailbox 메일 사서함.
* @param string $msgType 메일 타입. MSGTYPE 중 하나.
* @param MessageType $msgType 메일 타입.
* @param int $limit 가져오고자 하는 수. 0 이하의 값이면 모두.
* @param int $fromSeq 가져오고자 하는 위치. $fromSeq보다 '큰' seq만 가져온다. 따라서 0 이하이면 모두 가져옴.
* @return Message[]
*/
public static function getMessagesFromMailBox(int $mailbox, string $msgType, int $limit=30, int $fromSeq = 0)
public static function getMessagesFromMailBox(int $mailbox, MessageType $msgType, int $limit=30, int $fromSeq = 0)
{
$db = DB::db();
if (!static::isValidMsgType($msgType)) {
throw new \InvalidArgumentException('올바르지 않은 $msgType');
}
$date = (new \DateTime())->format('Y-m-d H:i:s');
$where = new \WhereClause('and');
$where->add('mailbox = %i', $mailbox);
$where->add('type = %s', $msgType);
$where->add('type = %s', $msgType->value);
$where->add('valid_until > %s', $date);
if ($fromSeq > 0) {
$where->add('id >= %i', $fromSeq);
@@ -233,24 +191,20 @@ class Message
/**
* @param int $mailbox 메일 사서함.
* @param string $msgType 메일 타입. MSGTYPE 중 하나.
* @param MessageType $msgType 메일 타입.
* @param int $toSeq 가져오고자 하는 위치. $toSeq보다 '작은' seq만 가져온다.
* @param int $limit 가져오고자 하는 수.
* @return Message[]
*/
public static function getMessagesFromMailBoxOld(int $mailbox, string $msgType, int $toSeq, int $limit = 20)
public static function getMessagesFromMailBoxOld(int $mailbox, MessageType $msgType, int $toSeq, int $limit = 20)
{
$db = DB::db();
if (!static::isValidMsgType($msgType)) {
throw new \InvalidArgumentException('올바르지 않은 $msgType');
}
$date = (new \DateTime())->format('Y-m-d H:i:s');
$where = new \WhereClause('and');
$where->add('mailbox = %i', $mailbox);
$where->add('type = %s', $msgType);
$where->add('type = %s', $msgType->value);
$where->add('valid_until > %s', $date);
$where->add('id < %i', $toSeq);
@@ -296,7 +250,7 @@ class Message
'overwrite'=>[$msgObj->id]
];
if($msgObj->msgType == Message::MSGTYPE_PRIVATE || $msgObj->msgType == Message::MSGTYPE_NATIONAL){
if($msgObj->msgType === MessageType::private || $msgObj->msgType === MessageType::national){
$receiveID = $msgObj->msgOption['receiverMessageID']??null;
if($receiveID !== null){
$msgObj2 = static::getMessageByID($receiveID);
@@ -345,7 +299,7 @@ class Message
$db = DB::db();
$db->insert('message', [
'mailbox' => $mailbox,
'type' => $this->msgType,
'type' => $this->msgType->value,
'src' => $src_id,
'dest' => $dest_id,
'time' => $this->date->format('Y-m-d H:i:s'),
@@ -364,13 +318,13 @@ class Message
if($this->sendCnt > 1){
throw new \RuntimeException('이미 전송한 메일입니다.');
}
if($this->msgType === self::MSGTYPE_PRIVATE && $this->src->generalID !== $this->dest->generalID){
if($this->msgType === MessageType::private && $this->src->generalID !== $this->dest->generalID){
return $this->sendRaw($this->src->generalID);
}
if($this->msgType === self::MSGTYPE_NATIONAL && $this->src->nationID !== $this->dest->nationID){
if($this->msgType === MessageType::national && $this->src->nationID !== $this->dest->nationID){
return $this->sendRaw($this->src->nationID + self::MAILBOX_NATIONAL);
}
if($this->msgType === self::MSGTYPE_DIPLOMACY){
if($this->msgType === MessageType::diplomacy){
if(key_exists('action', $this->msgOption)){
$tmp = $this->msgOption;
$this->msgOption = null;
@@ -391,7 +345,7 @@ class Message
throw new \RuntimeException('이미 전송한 메일입니다.');
}
if($this->msgType === self::MSGTYPE_PRIVATE){
if($this->msgType === MessageType::private){
if(!($this->msgOption['silence']??false)){
//XXX: 알림을 이런식으로 보내는게 맞는가에 대한 의문 있음
DB::db()->update('general', [
@@ -401,11 +355,11 @@ class Message
return $this->sendRaw($this->dest->generalID);
}
if($this->msgType === self::MSGTYPE_NATIONAL){
if($this->msgType === MessageType::national){
return $this->sendRaw($this->dest->nationID + self::MAILBOX_NATIONAL);
}
if($this->msgType === self::MSGTYPE_DIPLOMACY){
if($this->msgType === MessageType::diplomacy){
if(!($this->msgOption['silence']??false)){
//XXX: 알림을 이런식으로 보내는게 맞는가에 대한 의문 있음
DB::db()->update('general', [
@@ -415,7 +369,7 @@ class Message
return $this->sendRaw($this->dest->nationID + self::MAILBOX_NATIONAL);
}
if($this->msgType === self::MSGTYPE_PUBLIC){
if($this->msgType === MessageType::public){
return $this->sendRaw(self::MAILBOX_PUBLIC);
}
+6 -6
View File
@@ -18,8 +18,8 @@ class ResetHelper{
'reason'=>'logs, data 디렉토리를 생성할 권한이 없습니다.'
];
}
mkdir($servRoot.'/logs', 0755);
mkdir($servRoot.'/data', 0755);
mkdir($servRoot.'/logs', 0775);
mkdir($servRoot.'/data', 0775);
}
if(!is_writable($servRoot.'/logs')){
@@ -44,7 +44,7 @@ class ResetHelper{
}
if(!file_exists($servRoot.'/logs/preserved')){
mkdir($servRoot.'/logs/preserved', 0755);
mkdir($servRoot.'/logs/preserved', 0775);
}
if(!file_exists($servRoot.'/logs/.htaccess')){
@@ -80,8 +80,8 @@ class ResetHelper{
$serverID = DB::prefix().'_'.date("ymd").'_'.Util::randomStr(4);
mkdir($servRoot.'/logs/'.$serverID, 0755);
mkdir($servRoot.'/data/'.$serverID, 0755);
mkdir($servRoot.'/logs/'.$serverID, 0775);
mkdir($servRoot.'/data/'.$serverID, 0775);
$seasonIdx = 1;
if($db->queryFirstField('SHOW TABLES LIKE %s', 'storage') !== null){
@@ -160,7 +160,7 @@ class ResetHelper{
int $scenario,
int $fiction,
int $extend,
bool $block_general_create,
int $block_general_create,
int $npcmode,
int $show_img_level,
bool $tournament_trig,
+7 -5
View File
@@ -1,6 +1,8 @@
<?php
namespace sammo;
use sammo\Enums\MessageType;
class ScoutMessage extends Message{
const ACCEPTED = 1;
@@ -9,7 +11,7 @@ class ScoutMessage extends Message{
protected $validScout = true;
public function __construct(
string $msgType,
MessageType $msgType,
MessageTarget $src,
MessageTarget $dest,
string $msg,
@@ -18,7 +20,7 @@ class ScoutMessage extends Message{
array $msgOption
)
{
if ($msgType !== self::MSGTYPE_PRIVATE){
if ($msgType !== MessageType::private){
throw new \InvalidArgumentException('DiplomaticMessage msgType');
}
@@ -98,7 +100,7 @@ class ScoutMessage extends Message{
$josaRo = JosaUtil::pick($this->src->nationName, '로');
$newMsg = new Message(
self::MSGTYPE_PRIVATE,
MessageType::private,
$this->src,
$this->dest,
"{$this->src->nationName}{$josaRo} 등용 제의 수락",
@@ -120,7 +122,7 @@ class ScoutMessage extends Message{
$josaRo = JosaUtil::pick($this->src->nationName, '로');
$newMsg = new Message(
self::MSGTYPE_PRIVATE,
MessageType::private,
$this->src,
$this->dest,
"{$this->src->nationName}{$josaRo} 등용 제의 거부",
@@ -223,6 +225,6 @@ class ScoutMessage extends Message{
'action'=>'scout'
];
return new ScoutMessage(Message::MSGTYPE_PRIVATE, $src, $dest, $msg, $date, $validUntil, $msgOption);
return new ScoutMessage(MessageType::private, $src, $dest, $msg, $date, $validUntil, $msgOption);
}
}
-1
View File
@@ -195,7 +195,6 @@ class TurnExecutionHelper
$general->setVar('owner_name', null);
} else {
$general->applyDB($db);
storeOldGeneral($generalID, $gameStor->year, $gameStor->month);
$general->kill($db);
return;
}
+1
View File
@@ -5,6 +5,7 @@ class InheritancePointType{
public bool|array $storeType,
public int|float $pointCoeff,
public string $info,
public ?float $rebirthStoreCoeff,
)
{
@@ -12,6 +12,7 @@ use sammo\GameUnitDetail;
use sammo\Util;
use sammo\ObjectTrigger;
/** @deprecated che_선제사격시도, che_선제사격발동 으로 이전*/
class che_궁병선제사격 extends BaseWarUnitTrigger
{
protected $priority = ObjectTrigger::PRIORITY_BEGIN + 50;
@@ -39,15 +40,15 @@ class che_궁병선제사격 extends BaseWarUnitTrigger
if ($oppose->getCrewType()->armType == GameUnitConst::T_ARCHER) {
$oppose->activateSkill('특수', '선제');
$self->multiplyWarPowerMultiply(0.5);
$oppose->multiplyWarPowerMultiply(0.5);
$self->multiplyWarPowerMultiply(2/3);
$oppose->multiplyWarPowerMultiply(2/3);
$oppose->getLogger()->pushGeneralBattleDetailLog('서로 <C>선제 사격</>을 주고 받았다!</>', ActionLogger::PLAIN);
$self->getLogger()->pushGeneralBattleDetailLog('서로 <C>선제 사격</>을 주고 받았다!</>', ActionLogger::PLAIN);
return true;
}
$oppose->multiplyWarPowerMultiply(0);
$self->multiplyWarPowerMultiply(0.5);
$self->multiplyWarPowerMultiply(2/3);
$self->activateSkill('회피불가', '필살불가', '계략불가');
$oppose->activateSkill('회피불가', '필살불가', '격노불가', '계략불가');
@@ -13,8 +13,8 @@ class che_기병병종전투 extends BaseWarUnitTrigger{
protected function actionWar(WarUnit $self, WarUnit $oppose, array &$selfEnv, array &$opposeEnv):bool{
if(!$self->isAttacker()){
$oppose->multiplyWarPowerMultiply(1/0.95);
$self->multiplyWarPowerMultiply(0.95);
$oppose->multiplyWarPowerMultiply(1.02);
$self->multiplyWarPowerMultiply(0.97);
}
else if($oppose instanceof WarUnitCity){
$self->multiplyWarPowerMultiply(0.9);
@@ -0,0 +1,50 @@
<?php
namespace sammo\WarUnitTrigger;
use sammo\ActionLogger;
use sammo\BaseWarUnitTrigger;
use sammo\GameUnitConst;
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_BEGIN + 51;
protected function actionWar(WarUnit $self, WarUnit $oppose, array &$selfEnv, array &$opposeEnv): bool
{
assert($self instanceof WarUnitGeneral, 'General만 발동 가능');
if (!$self->hasActivatedSkill('선제')) {
return true;
}
if ($oppose->hasActivatedSkill('선제') && $oppose->isAttacker()){
//맞 선제라면 공격자가 처리
return true;
}
$self->addPhase(-1);
$oppose->addPhase(-1);
if ($oppose->hasActivatedSkill('선제')) {
$self->multiplyWarPowerMultiply(2/3);
$oppose->multiplyWarPowerMultiply(2/3);
$oppose->getLogger()->pushGeneralBattleDetailLog('서로 <C>선제 사격</>을 주고 받았다!</>', ActionLogger::PLAIN);
$self->getLogger()->pushGeneralBattleDetailLog('서로 <C>선제 사격</>을 주고 받았다!</>', ActionLogger::PLAIN);
return true;
}
$oppose->multiplyWarPowerMultiply(0);
$self->multiplyWarPowerMultiply(2/3);
$self->activateSkill('회피불가', '필살불가', '계략불가');
$oppose->activateSkill('회피불가', '필살불가', '격노불가', '계략불가');
$oppose->getLogger()->pushGeneralBattleDetailLog('상대에게 <R>선제 사격</>을 받았다!</>', ActionLogger::PLAIN);
$self->getLogger()->pushGeneralBattleDetailLog('상대에게 <C>선제 사격</>을 했다!</>', ActionLogger::PLAIN);
return true;
}
}
@@ -0,0 +1,36 @@
<?php
namespace sammo\WarUnitTrigger;
use sammo\ActionLogger;
use sammo\BaseWarUnitTrigger;
use sammo\GameUnitConst;
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_BEGIN + 50;
protected function actionWar(WarUnit $self, WarUnit $oppose, array &$selfEnv, array &$opposeEnv): bool
{
assert($self instanceof WarUnitGeneral, 'General만 발동 가능');
if ($self->getPhase() !== 0 && $oppose->getPhase() !== 0) {
return true;
}
if ($self->hasActivatedSkill('선제')) {
return true;
}
if ($self->hasActivatedSkillOnLog('선제')) {
return true;
}
$self->activateSkill('특수', '선제');
return true;
}
}
@@ -30,6 +30,9 @@ class che_저격시도 extends BaseWarUnitTrigger{
if($self->getPhase() !== 0 && $oppose->getPhase() !== 0){
return true;
}
if($oppose->getPhase() < 0){
return true;
}
if($self->hasActivatedSkill('저격')){
return true;
}
+2 -2
View File
@@ -23,8 +23,8 @@
[
"month", 999,
["Date", "==", 181, 1],
["OpenNationBetting", 4, 1000],
["OpenNationBetting", 1, 1000],
["OpenNationBetting", 4, 5000],
["OpenNationBetting", 1, 2000],
["DeleteEvent"]
],
[
+2 -2
View File
@@ -26,8 +26,8 @@
[
"month", 999,
["Date", "==", 181, 1],
["OpenNationBetting", 4, 1000],
["OpenNationBetting", 1, 1000],
["OpenNationBetting", 4, 5000],
["OpenNationBetting", 1, 2000],
["DeleteEvent"]
],
[
+2 -2
View File
@@ -30,8 +30,8 @@
[
"month", 999,
["Date", "==", 181, 1],
["OpenNationBetting", 4, 1000],
["OpenNationBetting", 1, 1000],
["OpenNationBetting", 4, 5000],
["OpenNationBetting", 1, 2000],
["DeleteEvent"]
],
[
+1078
View File
@@ -0,0 +1,1078 @@
{
"title": "【가상모드27-c】 아시아 명장전(빙의전용)",
"startYear": 180,
"history": [
"<C>●</>180년 1월:<L><b>【가상모드27】</b>아시아 명장전!(빙의전용)</>",
"<C>●</>180년 1월:<L><b>【이벤트】</b></>빙의전용 시나리오. 아시아 명장 일부 스탯 유저수준 버프"
],
"stat": {
"total": 310,
"min": 40,
"max": 140
},
"iconPath": ".",
"fiction": 1,
"nation":[],
"diplomacy":[],
"const": {
"npcBanMessageProb": 0,
"npcSeizureMessageProb": 0,
"npcMessageFreqByDay": 8,
"availableSpecialWar": [
"che_귀병",
"che_신산",
"che_환술",
"che_집중",
"che_신중",
"che_반계",
"che_보병",
"che_궁병",
"che_기병",
"che_공성",
"che_돌격",
"che_무쌍",
"che_견고",
"che_위압",
"che_저격",
"che_필살",
"che_징병",
"che_의술",
"che_격노",
"che_척사"
],
"allItems": {
"horse": {
"che_명마_01_노기": 0,
"che_명마_02_조랑": 0,
"che_명마_03_노새": 0,
"che_명마_04_나귀": 0,
"che_명마_05_갈색마": 0,
"che_명마_06_흑색마": 0,
"che_명마_07_백마": 2,
"che_명마_07_기주마": 2,
"che_명마_07_오환마": 2,
"che_명마_07_백상": 2,
"che_명마_08_양주마": 2,
"che_명마_08_흉노마": 2,
"che_명마_09_과하마": 2,
"che_명마_09_의남백마": 2,
"che_명마_10_대완마": 2,
"che_명마_10_옥추마": 2,
"che_명마_11_서량마": 2,
"che_명마_11_화종마": 2,
"che_명마_12_사륜거": 2,
"che_명마_12_옥란백용구": 2,
"che_명마_13_절영": 2,
"che_명마_13_적로": 2,
"che_명마_14_적란마": 2,
"che_명마_14_조황비전": 2,
"che_명마_15_한혈마": 2,
"che_명마_15_적토마": 2
},
"weapon": {
"che_무기_01_단도": 0,
"che_무기_02_단궁": 0,
"che_무기_03_단극": 0,
"che_무기_04_목검": 0,
"che_무기_05_죽창": 0,
"che_무기_06_소부": 0,
"che_무기_07_동추": 2,
"che_무기_07_철편": 2,
"che_무기_07_철쇄": 2,
"che_무기_07_맥궁": 2,
"che_무기_08_유성추": 2,
"che_무기_08_철질여골": 2,
"che_무기_09_쌍철극": 2,
"che_무기_09_동호비궁": 2,
"che_무기_10_삼첨도": 2,
"che_무기_10_대부": 2,
"che_무기_11_고정도": 2,
"che_무기_11_이광궁": 2,
"che_무기_12_철척사모": 2,
"che_무기_12_칠성검": 2,
"che_무기_13_사모": 2,
"che_무기_13_양유기궁": 2,
"che_무기_14_언월도": 2,
"che_무기_14_방천화극": 2,
"che_무기_15_청홍검": 2,
"che_무기_15_의천검": 2
},
"book": {
"che_서적_01_효경전": 0,
"che_서적_02_회남자": 0,
"che_서적_03_변도론": 0,
"che_서적_04_건상역주": 0,
"che_서적_05_여씨춘추": 0,
"che_서적_06_사민월령": 0,
"che_서적_07_위료자": 2,
"che_서적_07_사마법": 2,
"che_서적_07_한서": 2,
"che_서적_07_논어": 2,
"che_서적_08_전론": 2,
"che_서적_08_사기": 2,
"che_서적_09_장자": 2,
"che_서적_09_역경": 2,
"che_서적_10_시경": 2,
"che_서적_10_구국론": 2,
"che_서적_11_상군서": 2,
"che_서적_11_춘추전": 2,
"che_서적_12_산해경": 2,
"che_서적_12_맹덕신서": 2,
"che_서적_13_관자": 2,
"che_서적_13_병법24편": 2,
"che_서적_14_한비자": 2,
"che_서적_14_오자병법": 2,
"che_서적_15_노자": 2,
"che_서적_15_손자병법": 2
},
"item": {
"che_치료_환약": 0,
"che_저격_수극": 0,
"che_사기_탁주": 0,
"che_훈련_청주": 0,
"che_계략_이추": 0,
"che_계략_향낭": 0,
"event_전투특기_격노": 0,
"event_전투특기_견고": 0,
"event_전투특기_공성": 0,
"event_전투특기_궁병": 0,
"event_전투특기_귀병": 0,
"event_전투특기_기병": 0,
"event_전투특기_돌격": 0,
"event_전투특기_무쌍": 0,
"event_전투특기_반계": 0,
"event_전투특기_보병": 0,
"event_전투특기_신산": 0,
"event_전투특기_신중": 0,
"event_전투특기_위압": 0,
"event_전투특기_의술": 0,
"event_전투특기_저격": 0,
"event_전투특기_집중": 0,
"event_전투특기_징병": 0,
"event_전투특기_척사": 0,
"event_전투특기_필살": 0,
"event_전투특기_환술": 0
}
}
},
"general":[
[0, "아회남", null, 0, null, 65, 74, 26, 0, 160, 300, null, null],
[0, "이적", null, 0, null, 40, 24, 84, 0, 160, 300, null, null],
[0, "윤상", null, 0, null, 51, 44, 62, 0, 160, 300, null, null],
[0, "윤묵", null, 0, null, 26, 15, 66, 0, 160, 300, null, null],
[0, "우금", null, 0, null, 85, 82, 75, 0, 160, 300, null, null],
[0, "위관", null, 0, null, 69, 46, 79, 0, 160, 300, null, null],
[0, "원윤", null, 0, null, 32, 14, 39, 0, 160, 300, null, null],
[0, "원희", null, 0, null, 66, 51, 63, 0, 160, 300, null, null],
[0, "염행", null, 0, null, 78, 90, 58, 0, 160, 300, null, null],
[0, "염유", null, 0, null, 71, 57, 71, 0, 160, 300, null, null],
[0, "원술", null, 0, null, 76, 65, 75, 0, 160, 300, null, null],
[0, "원상", null, 0, null, 63, 75, 42, 0, 160, 300, null, null],
[0, "원소", null, 0, null, 90, 69, 81, 0, 160, 300, null, null],
[0, "염상", null, 0, null, 40, 27, 71, 0, 160, 300, null, null],
[0, "원담", null, 0, null, 60, 78, 30, 0, 160, 300, null, null],
[0, "염포", null, 0, null, 40, 25, 82, 0, 160, 300, null, null],
[0, "왕위", null, 0, null, 60, 70, 62, 0, 160, 300, null, null],
[0, "왕윤", null, 0, null, 26, 6, 68, 0, 160, 300, null, null],
[0, "왕항", null, 0, null, 76, 60, 61, 0, 160, 300, null, null],
[0, "왕찬", null, 0, null, 15, 3, 79, 0, 160, 300, null, null],
[0, "왕수", null, 0, null, 54, 27, 77, 0, 160, 300, null, null],
[0, "왕숙", null, 0, null, 36, 22, 76, 0, 160, 300, null, null],
[0, "왕준", null, 0, null, 85, 73, 84, 0, 160, 300, null, null],
[0, "왕쌍", null, 0, null, 67, 88, 19, 0, 160, 300, null, null],
[0, "왕창", null, 0, null, 74, 64, 79, 0, 160, 300, null, null],
[0, "왕평", null, 0, null, 88, 81, 70, 0, 160, 300, null, null],
[0, "왕보", null, 0, null, 62, 41, 79, 0, 160, 300, null, null],
[0, "왕문", null, 0, null, 65, 64, 31, 0, 160, 300, null, null],
[0, "왕릉1", null, 0, null, 74, 64, 71, 0, 160, 300, null, null],
[0, "왕루", null, 0, null, 28, 30, 79, 0, 160, 300, null, null],
[0, "왕랑", null, 0, null, 47, 35, 79, 0, 160, 300, null, null],
[0, "온회", null, 0, null, 62, 36, 74, 0, 160, 300, null, null],
[0, "하안", null, 0, null, 15, 30, 72, 0, 160, 300, null, null],
[0, "괴월", null, 0, null, 50, 38, 85, 0, 160, 300, null, null],
[0, "괴량", null, 0, null, 68, 33, 88, 0, 160, 300, null, null],
[0, "가화", null, 0, null, 49, 65, 41, 0, 160, 300, null, null],
[0, "가규", null, 0, null, 78, 61, 84, 0, 160, 300, null, null],
[0, "하의", null, 0, null, 56, 69, 36, 0, 160, 300, null, null],
[0, "화흠", null, 0, null, 51, 33, 88, 0, 160, 300, null, null],
[0, "가후", null, 0, null, 81, 22, 98, 0, 160, 300, null, null],
[0, "곽가", null, 0, null, 78, 16, 99, 0, 160, 300, null, null],
[0, "악환", null, 0, null, 65, 80, 42, 0, 160, 300, null, null],
[0, "곽사", null, 0, null, 64, 76, 13, 0, 160, 300, null, null],
[0, "악취", null, 0, null, 53, 66, 61, 0, 160, 300, null, null],
[0, "곽준", null, 0, null, 73, 66, 73, 0, 160, 300, null, null],
[0, "학소", null, 0, null, 90, 71, 88, 0, 160, 300, null, null],
[0, "악진", null, 0, null, 80, 85, 56, 0, 160, 300, null, null],
[0, "곽도", null, 0, null, 62, 50, 82, 0, 160, 300, null, null],
[0, "학맹", null, 0, null, 62, 67, 42, 0, 160, 300, null, null],
[0, "곽유지", null, 0, null, 28, 9, 69, 0, 160, 300, null, null],
[0, "곽익", null, 0, null, 73, 69, 69, 0, 160, 300, null, null],
[0, "곽회", null, 0, null, 90, 77, 85, 0, 160, 300, null, null],
[0, "하후위", null, 0, null, 71, 73, 49, 0, 160, 300, null, null],
[0, "하후연", null, 0, null, 95, 92, 74, 0, 160, 300, null, null],
[0, "하후은", null, 0, null, 63, 72, 53, 0, 160, 300, null, null],
[0, "하후화", null, 0, null, 48, 51, 71, 0, 160, 300, null, null],
[0, "하후혜", null, 0, null, 50, 44, 76, 0, 160, 300, null, null],
[0, "하후현", null, 0, null, 54, 39, 76, 0, 160, 300, null, null],
[0, "하후상", null, 0, null, 79, 72, 75, 0, 160, 300, null, null],
[0, "하후덕", null, 0, null, 69, 73, 32, 0, 160, 300, null, null],
[0, "하후돈", null, 0, null, 91, 88, 68, 0, 160, 300, null, null],
[0, "하후패", null, 0, null, 79, 85, 77, 0, 160, 300, null, null],
[0, "하후무", null, 0, null, 20, 7, 4, 0, 160, 300, null, null],
[0, "가충", null, 0, null, 54, 31, 89, 0, 160, 300, null, null],
[0, "하진", null, 0, null, 68, 54, 75, 0, 160, 300, null, null],
[0, "화웅", null, 0, null, 78, 90, 55, 0, 160, 300, null, null],
[0, "한윤", null, 0, null, 26, 29, 65, 0, 160, 300, null, null],
[0, "관우", null, 0, null, 96, 99, 76, 0, 160, 300, null, null],
[0, "환계", null, 0, null, 25, 26, 68, 0, 160, 300, null, null],
[0, "관해", null, 0, null, 70, 82, 10, 0, 160, 300, null, null],
[0, "관구검", null, 0, null, 84, 86, 62, 0, 160, 300, null, null],
[0, "한현", null, 0, null, 22, 33, 7, 0, 160, 300, null, null],
[0, "한호", null, 0, null, 73, 72, 84, 0, 160, 300, null, null],
[0, "관흥", null, 0, null, 76, 84, 67, 0, 160, 300, null, null],
[0, "관색", null, 0, null, 74, 83, 56, 0, 160, 300, null, null],
[0, "한수", null, 0, null, 78, 75, 79, 0, 160, 300, null, null],
[0, "한숭", null, 0, null, 25, 15, 71, 0, 160, 300, null, null],
[0, "관정", null, 0, null, 36, 52, 73, 0, 160, 300, null, null],
[0, "한섬", null, 0, null, 69, 66, 36, 0, 160, 300, null, null],
[0, "감택", null, 0, null, 51, 49, 83, 0, 160, 300, null, null],
[0, "한충", null, 0, null, 64, 67, 18, 0, 160, 300, null, null],
[0, "한당", null, 0, null, 76, 85, 61, 0, 160, 300, null, null],
[0, "한덕", null, 0, null, 63, 76, 25, 0, 160, 300, null, null],
[0, "감녕", null, 0, null, 92, 95, 76, 0, 160, 300, null, null],
[0, "한복", null, 0, null, 28, 3, 27, 0, 160, 300, null, null],
[0, "관평", null, 0, null, 78, 82, 72, 0, 160, 300, null, null],
[0, "간옹", null, 0, null, 20, 32, 75, 0, 160, 300, null, null],
[0, "안량", null, 0, null, 88, 96, 43, 0, 160, 300, null, null],
[0, "위연", null, 0, null, 91, 90, 75, 0, 160, 300, null, null],
[0, "국의", null, 0, null, 84, 78, 51, 0, 160, 300, null, null],
[0, "희지재", null, 0, null, 63, 35, 91, 0, 160, 300, null, null],
[0, "위속", null, 0, null, 67, 78, 31, 0, 160, 300, null, null],
[0, "위유", null, 0, null, 46, 29, 74, 0, 160, 300, null, null],
[0, "우금2", null, 0, null, 72, 79, 38, 0, 160, 300, null, null],
[0, "우보", null, 0, null, 38, 60, 21, 0, 160, 300, null, null],
[0, "강유", null, 0, null, 92, 80, 93, 0, 160, 300, null, null],
[0, "공지", null, 0, null, 43, 53, 62, 0, 160, 300, null, null],
[0, "교유", null, 0, null, 64, 69, 38, 0, 160, 300, null, null],
[0, "공도", null, 0, null, 59, 70, 23, 0, 160, 300, null, null],
[0, "허공", null, 0, null, 44, 66, 64, 0, 160, 300, null, null],
[0, "허정", null, 0, null, 23, 5, 68, 0, 160, 300, null, null],
[0, "허저", null, 0, null, 74, 97, 36, 0, 160, 300, null, null],
[0, "허유", null, 0, null, 36, 21, 80, 0, 160, 300, null, null],
[0, "기령", null, 0, null, 85, 84, 51, 0, 160, 300, null, null],
[0, "김의", null, 0, null, 32, 38, 65, 0, 160, 300, null, null],
[0, "김배삼결", null, 0, null, 64, 76, 19, 0, 160, 300, null, null],
[0, "김선", null, 0, null, 52, 68, 13, 0, 160, 300, null, null],
[0, "우번", null, 0, null, 46, 46, 87, 0, 160, 300, null, null],
[0, "형도영", null, 0, null, 69, 79, 41, 0, 160, 300, null, null],
[0, "엄안", null, 0, null, 82, 83, 72, 0, 160, 300, null, null],
[0, "엄망", null, 0, null, 68, 71, 40, 0, 160, 300, null, null],
[0, "엄준", null, 0, null, 41, 12, 71, 0, 160, 300, null, null],
[0, "엄정", null, 0, null, 50, 55, 39, 0, 160, 300, null, null],
[0, "엄백호", null, 0, null, 67, 70, 42, 0, 160, 300, null, null],
[0, "엄여", null, 0, null, 61, 78, 44, 0, 160, 300, null, null],
[0, "오의", null, 0, null, 79, 73, 70, 0, 160, 300, null, null],
[0, "황개", null, 0, null, 80, 86, 70, 0, 160, 300, null, null],
[0, "고간", null, 0, null, 74, 56, 48, 0, 160, 300, null, null],
[0, "황권", null, 0, null, 75, 59, 88, 0, 160, 300, null, null],
[0, "고유", null, 0, null, 57, 43, 72, 0, 160, 300, null, null],
[0, "고순", null, 0, null, 87, 89, 60, 0, 160, 300, null, null],
[0, "고상", null, 0, null, 68, 68, 53, 0, 160, 300, null, null],
[0, "고승", null, 0, null, 67, 73, 41, 0, 160, 300, null, null],
[0, "후성", null, 0, null, 74, 75, 68, 0, 160, 300, null, null],
[0, "후선", null, 0, null, 63, 66, 34, 0, 160, 300, null, null],
[0, "황조", null, 0, null, 81, 66, 62, 0, 160, 300, null, null],
[0, "공손월", null, 0, null, 74, 72, 47, 0, 160, 300, null, null],
[0, "공손연", null, 0, null, 65, 70, 60, 0, 160, 300, null, null],
[0, "공손공", null, 0, null, 39, 17, 69, 0, 160, 300, null, null],
[0, "공손강", null, 0, null, 72, 66, 65, 0, 160, 300, null, null],
[0, "공손찬", null, 0, null, 86, 82, 60, 0, 160, 300, null, null],
[0, "공손속", null, 0, null, 63, 68, 57, 0, 160, 300, null, null],
[0, "공손도", null, 0, null, 67, 71, 66, 0, 160, 300, null, null],
[0, "공손범", null, 0, null, 73, 69, 66, 0, 160, 300, null, null],
[0, "황충", null, 0, null, 88, 96, 70, 0, 160, 300, null, null],
[0, "고정", null, 0, null, 55, 63, 43, 0, 160, 300, null, null],
[0, "고패", null, 0, null, 66, 61, 69, 0, 160, 300, null, null],
[0, "황보숭", null, 0, null, 91, 61, 82, 0, 160, 300, null, null],
[0, "공융", null, 0, null, 30, 5, 73, 0, 160, 300, null, null],
[0, "고람", null, 0, null, 76, 82, 68, 0, 160, 300, null, null],
[0, "오경", null, 0, null, 69, 66, 52, 0, 160, 300, null, null],
[0, "호거아", null, 0, null, 70, 82, 41, 0, 160, 300, null, null],
[0, "호진", null, 0, null, 65, 74, 12, 0, 160, 300, null, null],
[0, "올돌골", null, 0, null, 79, 86, 11, 0, 160, 300, null, null],
[0, "호반", null, 0, null, 55, 55, 59, 0, 160, 300, null, null],
[0, "오반", null, 0, null, 74, 71, 59, 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, 40, 54, 2, 0, 160, 300, null, null],
[0, "채모", null, 0, null, 70, 70, 77, 0, 160, 300, null, null],
[0, "착융", null, 0, null, 60, 69, 38, 0, 160, 300, null, null],
[0, "사마의", null, 0, null, 99, 63, 98, 0, 160, 300, null, null],
[0, "사마염", null, 0, null, 69, 59, 77, 0, 160, 300, null, null],
[0, "사마사", null, 0, null, 84, 64, 89, 0, 160, 300, null, null],
[0, "사마소", null, 0, null, 85, 57, 88, 0, 160, 300, null, null],
[0, "사마부", null, 0, null, 70, 37, 77, 0, 160, 300, null, null],
[0, "사마랑", null, 0, null, 20, 21, 72, 0, 160, 300, null, null],
[0, "사마가", null, 0, null, 66, 84, 25, 0, 160, 300, null, null],
[0, "주흔", null, 0, null, 65, 62, 73, 0, 160, 300, null, null],
[0, "주앙", null, 0, null, 74, 65, 65, 0, 160, 300, null, null],
[0, "주지", null, 0, null, 73, 85, 41, 0, 160, 300, null, null],
[0, "주창", null, 0, null, 68, 77, 42, 0, 160, 300, null, null],
[0, "주태", null, 0, null, 84, 90, 48, 0, 160, 300, null, null],
[0, "주방", null, 0, null, 52, 41, 80, 0, 160, 300, null, null],
[0, "주유", null, 0, null, 97, 71, 98, 0, 160, 300, null, null],
[0, "주환", null, 0, null, 87, 84, 77, 0, 160, 300, null, null],
[0, "주거", null, 0, null, 77, 57, 72, 0, 160, 300, null, null],
[0, "축융", null, 0, null, 72, 75, 35, 0, 160, 300, null, null],
[0, "주준", null, 0, null, 89, 65, 76, 0, 160, 300, null, null],
[0, "주연", null, 0, null, 86, 75, 74, 0, 160, 300, null, null],
[0, "주치", null, 0, null, 71, 56, 72, 0, 160, 300, null, null],
[0, "주포", null, 0, null, 68, 73, 34, 0, 160, 300, null, null],
[0, "주령", null, 0, null, 78, 73, 67, 0, 160, 300, null, null],
[0, "순욱", null, 0, null, 78, 38, 97, 0, 160, 300, null, null],
[0, "순우경", null, 0, null, 78, 66, 29, 0, 160, 300, null, null],
[0, "순심", null, 0, null, 45, 25, 78, 0, 160, 300, null, null],
[0, "순유", null, 0, null, 80, 35, 95, 0, 160, 300, null, null],
[0, "장완", null, 0, null, 78, 34, 89, 0, 160, 300, null, null],
[0, "종회", null, 0, null, 88, 62, 93, 0, 160, 300, null, null],
[0, "장간", null, 0, null, 19, 6, 66, 0, 160, 300, null, null],
[0, "장의거", null, 0, null, 71, 58, 59, 0, 160, 300, null, null],
[0, "장흠", null, 0, null, 78, 85, 54, 0, 160, 300, null, null],
[0, "장제3", null, 0, null, 62, 43, 85, 0, 160, 300, null, null],
[0, "초주", null, 0, null, 16, 3, 67, 0, 160, 300, null, null],
[0, "초촉", null, 0, null, 65, 76, 33, 0, 160, 300, null, null],
[0, "향총", null, 0, null, 76, 61, 77, 0, 160, 300, null, null],
[0, "종요", null, 0, null, 79, 24, 90, 0, 160, 300, null, null],
[0, "향랑", null, 0, null, 28, 18, 74, 0, 160, 300, null, null],
[0, "서영", null, 0, null, 92, 80, 62, 0, 160, 300, null, null],
[0, "제갈각", null, 0, null, 72, 47, 90, 0, 160, 300, null, null],
[0, "제갈근", null, 0, null, 75, 34, 85, 0, 160, 300, null, null],
[0, "제갈균", null, 0, null, 29, 25, 52, 0, 160, 300, null, null],
[0, "제갈탄", null, 0, null, 72, 72, 76, 0, 160, 300, null, null],
[0, "제갈량", null, 0, null, 98, 43, 100, 0, 160, 300, null, null],
[0, "서황", null, 0, null, 95, 94, 78, 0, 160, 300, null, null],
[0, "서서", null, 0, null, 90, 64, 94, 0, 160, 300, null, null],
[0, "서성", null, 0, null, 86, 81, 78, 0, 160, 300, null, null],
[0, "서막", null, 0, null, 55, 32, 68, 0, 160, 300, null, null],
[0, "신의", null, 0, null, 55, 59, 60, 0, 160, 300, null, null],
[0, "신탐", null, 0, null, 56, 62, 54, 0, 160, 300, null, null],
[0, "심배", null, 0, null, 80, 60, 84, 0, 160, 300, null, null],
[0, "신비", null, 0, null, 42, 24, 79, 0, 160, 300, null, null],
[0, "신평", null, 0, null, 69, 43, 77, 0, 160, 300, null, null],
[0, "진복", null, 0, null, 31, 7, 73, 0, 160, 300, null, null],
[0, "진랑", null, 0, null, 58, 70, 35, 0, 160, 300, null, null],
[0, "휴고", null, 0, null, 63, 71, 38, 0, 160, 300, null, null],
[0, "추단", null, 0, null, 61, 64, 34, 0, 160, 300, null, null],
[0, "성의", null, 0, null, 74, 72, 41, 0, 160, 300, null, null],
[0, "성공영", null, 0, null, 73, 71, 80, 0, 160, 300, null, null],
[0, "설종", null, 0, null, 34, 16, 73, 0, 160, 300, null, null],
[0, "단경1", null, 0, null, 71, 68, 43, 0, 160, 300, null, null],
[0, "전종", null, 0, null, 78, 72, 75, 0, 160, 300, null, null],
[0, "조예", null, 0, null, 72, 52, 82, 0, 160, 300, null, null],
[0, "조휴", null, 0, null, 84, 78, 62, 0, 160, 300, null, null],
[0, "송헌", null, 0, null, 68, 77, 38, 0, 160, 300, null, null],
[0, "조앙", null, 0, null, 78, 67, 69, 0, 160, 300, null, null],
[0, "조홍", null, 0, null, 80, 75, 55, 0, 160, 300, null, null],
[0, "조순", null, 0, null, 85, 83, 63, 0, 160, 300, null, null],
[0, "조창", null, 0, null, 86, 91, 59, 0, 160, 300, null, null],
[0, "조식", null, 0, null, 45, 22, 86, 0, 160, 300, null, null],
[0, "조진", null, 0, null, 88, 74, 70, 0, 160, 300, null, null],
[0, "조인", null, 0, null, 94, 90, 72, 0, 160, 300, null, null],
[0, "조성", null, 0, null, 54, 74, 38, 0, 160, 300, null, null],
[0, "조조", null, 0, null, 100, 72, 96, 0, 160, 300, null, null],
[0, "조충", null, 0, null, 43, 21, 74, 0, 160, 300, null, null],
[0, "장패", null, 0, null, 78, 86, 53, 0, 160, 300, null, null],
[0, "조비", null, 0, null, 75, 71, 87, 0, 160, 300, null, null],
[0, "조표", null, 0, null, 54, 69, 32, 0, 160, 300, null, null],
[0, "저수", null, 0, null, 86, 46, 95, 0, 160, 300, null, null],
[0, "소비", null, 0, null, 69, 63, 66, 0, 160, 300, null, null],
[0, "조무", null, 0, null, 70, 71, 66, 0, 160, 300, null, null],
[0, "손환", null, 0, null, 82, 73, 76, 0, 160, 300, null, null],
[0, "손견", null, 0, null, 94, 95, 73, 0, 160, 300, null, null],
[0, "손건", null, 0, null, 44, 33, 79, 0, 160, 300, null, null],
[0, "손권", null, 0, null, 86, 69, 83, 0, 160, 300, null, null],
[0, "손교", null, 0, null, 77, 72, 67, 0, 160, 300, null, null],
[0, "손책", null, 0, null, 93, 97, 74, 0, 160, 300, null, null],
[0, "손소", null, 0, null, 82, 79, 76, 0, 160, 300, null, null],
[0, "손정", null, 0, null, 66, 53, 73, 0, 160, 300, null, null],
[0, "손중", null, 0, null, 62, 71, 36, 0, 160, 300, null, null],
[0, "손등", null, 0, null, 62, 37, 79, 0, 160, 300, null, null],
[0, "손유", null, 0, null, 77, 70, 68, 0, 160, 300, null, null],
[0, "손익", null, 0, null, 67, 83, 27, 0, 160, 300, null, null],
[0, "손례", null, 0, null, 78, 81, 67, 0, 160, 300, null, null],
[0, "태사자", null, 0, null, 94, 94, 72, 0, 160, 300, null, null],
[0, "대래동주", null, 0, null, 51, 53, 54, 0, 160, 300, null, null],
[0, "대릉", null, 0, null, 70, 75, 39, 0, 160, 300, null, null],
[0, "타사대왕", null, 0, null, 62, 59, 70, 0, 160, 300, null, null],
[0, "담웅", null, 0, null, 58, 73, 14, 0, 160, 300, null, null],
[0, "장윤", null, 0, null, 72, 67, 42, 0, 160, 300, null, null],
[0, "조운", null, 0, null, 94, 96, 75, 0, 160, 300, null, null],
[0, "장위", null, 0, null, 74, 66, 45, 0, 160, 300, null, null],
[0, "장영", null, 0, null, 75, 74, 41, 0, 160, 300, null, null],
[0, "장연", null, 0, null, 76, 81, 57, 0, 160, 300, null, null],
[0, "장횡", null, 0, null, 59, 70, 23, 0, 160, 300, null, null],
[0, "장온", null, 0, null, 32, 10, 72, 0, 160, 300, null, null],
[0, "장개", null, 0, null, 34, 66, 8, 0, 160, 300, null, null],
[0, "장각", null, 0, null, 87, 35, 90, 0, 160, 300, null, null],
[0, "장기", null, 0, null, 74, 35, 76, 0, 160, 300, null, null],
[0, "장의", null, 0, null, 79, 72, 75, 0, 160, 300, null, null],
[0, "장훈", null, 0, null, 72, 68, 41, 0, 160, 300, null, null],
[0, "장굉", null, 0, null, 24, 22, 86, 0, 160, 300, null, null],
[0, "장합", null, 0, null, 92, 89, 76, 0, 160, 300, null, null],
[0, "조홍2", null, 0, null, 66, 72, 28, 0, 160, 300, null, null],
[0, "장제2", null, 0, null, 70, 66, 55, 0, 160, 300, null, null],
[0, "장수", null, 0, null, 80, 73, 64, 0, 160, 300, null, null],
[0, "장소", null, 0, null, 41, 13, 87, 0, 160, 300, null, null],
[0, "장송", null, 0, null, 40, 6, 85, 0, 160, 300, null, null],
[0, "장임", null, 0, null, 85, 84, 78, 0, 160, 300, null, null],
[0, "장제", null, 0, null, 67, 43, 80, 0, 160, 300, null, null],
[0, "장막", null, 0, null, 52, 53, 71, 0, 160, 300, null, null],
[0, "조범", null, 0, null, 28, 21, 32, 0, 160, 300, null, null],
[0, "장비", null, 0, null, 87, 98, 72, 0, 160, 300, null, null],
[0, "장포", null, 0, null, 78, 84, 48, 0, 160, 300, null, null],
[0, "장보", null, 0, null, 83, 71, 81, 0, 160, 300, null, null],
[0, "장만성", null, 0, null, 75, 81, 45, 0, 160, 300, null, null],
[0, "장양", null, 0, null, 72, 70, 66, 0, 160, 300, null, null],
[0, "장익", null, 0, null, 75, 74, 76, 0, 160, 300, null, null],
[0, "장료", null, 0, null, 96, 94, 78, 0, 160, 300, null, null],
[0, "장량", null, 0, null, 78, 80, 74, 0, 160, 300, null, null],
[0, "조루", null, 0, null, 70, 43, 68, 0, 160, 300, null, null],
[0, "장로", null, 0, null, 65, 42, 81, 0, 160, 300, null, null],
[0, "진횡", null, 0, null, 64, 63, 26, 0, 160, 300, null, null],
[0, "진응", null, 0, null, 62, 72, 45, 0, 160, 300, null, null],
[0, "진기", null, 0, null, 58, 65, 43, 0, 160, 300, null, null],
[0, "진궁", null, 0, null, 84, 55, 90, 0, 160, 300, null, null],
[0, "진교", null, 0, null, 61, 27, 77, 0, 160, 300, null, null],
[0, "진군", null, 0, null, 32, 14, 75, 0, 160, 300, null, null],
[0, "진규", null, 0, null, 30, 4, 82, 0, 160, 300, null, null],
[0, "진식", null, 0, null, 70, 71, 25, 0, 160, 300, null, null],
[0, "진진", null, 0, null, 46, 46, 69, 0, 160, 300, null, null],
[0, "진태", null, 0, null, 86, 77, 89, 0, 160, 300, null, null],
[0, "진등", null, 0, null, 79, 64, 81, 0, 160, 300, null, null],
[0, "진도", null, 0, null, 82, 78, 67, 0, 160, 300, null, null],
[0, "진무", null, 0, null, 76, 87, 43, 0, 160, 300, null, null],
[0, "진란", null, 0, null, 66, 69, 40, 0, 160, 300, null, null],
[0, "진림", null, 0, null, 25, 9, 75, 0, 160, 300, null, null],
[0, "정욱", null, 0, null, 76, 49, 94, 0, 160, 300, null, null],
[0, "정원지", null, 0, null, 69, 74, 17, 0, 160, 300, null, null],
[0, "정은", null, 0, null, 69, 73, 40, 0, 160, 300, null, null],
[0, "정원", null, 0, null, 70, 76, 35, 0, 160, 300, null, null],
[0, "정보", null, 0, null, 84, 79, 79, 0, 160, 300, null, null],
[0, "정봉", null, 0, null, 81, 92, 75, 0, 160, 300, null, null],
[0, "전위", null, 0, null, 79, 98, 42, 0, 160, 300, null, null],
[0, "전해", null, 0, null, 68, 65, 58, 0, 160, 300, null, null],
[0, "전주", null, 0, null, 66, 66, 73, 0, 160, 300, null, null],
[0, "전풍", null, 0, null, 82, 43, 96, 0, 160, 300, null, null],
[0, "전예", null, 0, null, 93, 85, 80, 0, 160, 300, null, null],
[0, "동윤", null, 0, null, 47, 28, 79, 0, 160, 300, null, null],
[0, "동화", null, 0, null, 57, 34, 75, 0, 160, 300, null, null],
[0, "등애", null, 0, null, 92, 80, 94, 0, 160, 300, null, null],
[0, "도겸", null, 0, null, 51, 27, 64, 0, 160, 300, null, null],
[0, "등현", null, 0, null, 61, 73, 45, 0, 160, 300, null, null],
[0, "등지", null, 0, null, 73, 52, 81, 0, 160, 300, null, null],
[0, "동습", null, 0, null, 72, 85, 50, 0, 160, 300, null, null],
[0, "동승", null, 0, null, 56, 53, 65, 0, 160, 300, null, null],
[0, "동소", null, 0, null, 34, 25, 80, 0, 160, 300, null, null],
[0, "동탁", null, 0, null, 91, 87, 72, 0, 160, 300, null, null],
[0, "동도나", null, 0, null, 68, 73, 31, 0, 160, 300, null, null],
[0, "동민", null, 0, null, 49, 60, 25, 0, 160, 300, null, null],
[0, "등무", null, 0, null, 61, 75, 29, 0, 160, 300, null, null],
[0, "두기", null, 0, null, 74, 43, 78, 0, 160, 300, null, null],
[0, "두예", null, 0, null, 96, 69, 95, 0, 160, 300, null, null],
[0, "배원소", null, 0, null, 49, 65, 25, 0, 160, 300, null, null],
[0, "마완", null, 0, null, 71, 74, 16, 0, 160, 300, null, null],
[0, "마휴", null, 0, null, 68, 73, 48, 0, 160, 300, null, null],
[0, "파재", null, 0, null, 74, 74, 55, 0, 160, 300, null, null],
[0, "마준", null, 0, null, 38, 52, 31, 0, 160, 300, null, null],
[0, "마속", null, 0, null, 61, 67, 84, 0, 160, 300, null, null],
[0, "마대", null, 0, null, 79, 85, 62, 0, 160, 300, null, null],
[0, "마충", null, 0, null, 78, 72, 68, 0, 160, 300, null, null],
[0, "마초", null, 0, null, 86, 97, 44, 0, 160, 300, null, null],
[0, "마철", null, 0, null, 70, 62, 58, 0, 160, 300, null, null],
[0, "마등", null, 0, null, 85, 80, 52, 0, 160, 300, null, null],
[0, "마량", null, 0, null, 53, 23, 85, 0, 160, 300, null, null],
[0, "반준", null, 0, null, 61, 46, 82, 0, 160, 300, null, null],
[0, "반장", null, 0, null, 76, 80, 69, 0, 160, 300, null, null],
[0, "번조", null, 0, null, 66, 73, 31, 0, 160, 300, null, null],
[0, "번능", null, 0, null, 69, 61, 36, 0, 160, 300, null, null],
[0, "반봉", null, 0, null, 56, 77, 4, 0, 160, 300, null, null],
[0, "비의", null, 0, null, 82, 56, 88, 0, 160, 300, null, null],
[0, "비연", null, 0, null, 60, 66, 21, 0, 160, 300, null, null],
[0, "비시", null, 0, null, 18, 28, 65, 0, 160, 300, null, null],
[0, "미축", null, 0, null, 33, 29, 78, 0, 160, 300, null, null],
[0, "미방", null, 0, null, 54, 61, 32, 0, 160, 300, null, null],
[0, "비요", null, 0, null, 70, 66, 73, 0, 160, 300, null, null],
[0, "무안국", null, 0, null, 70, 79, 34, 0, 160, 300, null, null],
[0, "풍습", null, 0, null, 72, 71, 39, 0, 160, 300, null, null],
[0, "부사인", null, 0, null, 46, 63, 37, 0, 160, 300, null, null],
[0, "부첨", null, 0, null, 73, 85, 76, 0, 160, 300, null, null],
[0, "부손", null, 0, null, 29, 13, 70, 0, 160, 300, null, null],
[0, "문앙", null, 0, null, 76, 96, 65, 0, 160, 300, null, null],
[0, "문흠", null, 0, null, 74, 79, 43, 0, 160, 300, null, null],
[0, "문추", null, 0, null, 87, 95, 49, 0, 160, 300, null, null],
[0, "문빙", null, 0, null, 88, 85, 69, 0, 160, 300, null, null],
[0, "변희", null, 0, null, 56, 70, 62, 0, 160, 300, null, null],
[0, "망아장", null, 0, null, 45, 79, 3, 0, 160, 300, null, null],
[0, "봉기", null, 0, null, 56, 48, 84, 0, 160, 300, null, null],
[0, "방희", null, 0, null, 60, 38, 69, 0, 160, 300, null, null],
[0, "법정", null, 0, null, 82, 47, 94, 0, 160, 300, null, null],
[0, "방통", null, 0, null, 79, 34, 97, 0, 160, 300, null, null],
[0, "방덕", null, 0, null, 89, 94, 70, 0, 160, 300, null, null],
[0, "포륭", null, 0, null, 58, 76, 49, 0, 160, 300, null, null],
[0, "목순", null, 0, null, 52, 77, 11, 0, 160, 300, null, null],
[0, "목록대왕", null, 0, null, 65, 71, 39, 0, 160, 300, null, null],
[0, "보즐", null, 0, null, 83, 51, 81, 0, 160, 300, null, null],
[0, "만총", null, 0, null, 88, 64, 87, 0, 160, 300, null, null],
[0, "모개", null, 0, null, 63, 39, 60, 0, 160, 300, null, null],
[0, "맹획", null, 0, null, 81, 88, 42, 0, 160, 300, null, null],
[0, "맹체", null, 0, null, 75, 73, 75, 0, 160, 300, null, null],
[0, "맹우", null, 0, null, 50, 68, 22, 0, 160, 300, null, null],
[0, "유섭", null, 0, null, 62, 79, 24, 0, 160, 300, null, null],
[0, "양부", null, 0, null, 62, 68, 68, 0, 160, 300, null, null],
[0, "옹개", null, 0, null, 73, 75, 57, 0, 160, 300, null, null],
[0, "양의", null, 0, null, 68, 40, 82, 0, 160, 300, null, null],
[0, "양호", null, 0, null, 94, 69, 92, 0, 160, 300, null, null],
[0, "양홍", null, 0, null, 33, 15, 77, 0, 160, 300, null, null],
[0, "양앙", null, 0, null, 68, 72, 38, 0, 160, 300, null, null],
[0, "양추", null, 0, null, 52, 65, 40, 0, 160, 300, null, null],
[0, "양수", null, 0, null, 46, 23, 83, 0, 160, 300, null, null],
[0, "양추2", null, 0, null, 67, 64, 60, 0, 160, 300, null, null],
[0, "양송", null, 0, null, 17, 5, 28, 0, 160, 300, null, null],
[0, "양임", null, 0, null, 70, 78, 56, 0, 160, 300, null, null],
[0, "양조", null, 0, null, 53, 61, 36, 0, 160, 300, null, null],
[0, "양백", null, 0, null, 44, 45, 19, 0, 160, 300, null, null],
[0, "양부2", null, 0, null, 67, 50, 83, 0, 160, 300, null, null],
[0, "양봉", null, 0, null, 66, 65, 31, 0, 160, 300, null, null],
[0, "양봉2", null, 0, null, 54, 62, 53, 0, 160, 300, null, null],
[0, "뇌동", null, 0, null, 69, 78, 51, 0, 160, 300, null, null],
[0, "뇌박", null, 0, null, 62, 70, 36, 0, 160, 300, null, null],
[0, "이이", null, 0, null, 63, 77, 19, 0, 160, 300, null, null],
[0, "이회", null, 0, null, 79, 65, 79, 0, 160, 300, null, null],
[0, "이각", null, 0, null, 69, 72, 24, 0, 160, 300, null, null],
[0, "육항", null, 0, null, 94, 68, 93, 0, 160, 300, null, null],
[0, "육적", null, 0, null, 20, 7, 72, 0, 160, 300, null, null],
[0, "육손", null, 0, null, 97, 71, 96, 0, 160, 300, null, null],
[0, "이엄", null, 0, null, 83, 84, 76, 0, 160, 300, null, null],
[0, "이유", null, 0, null, 61, 26, 93, 0, 160, 300, null, null],
[0, "이숙", null, 0, null, 46, 69, 61, 0, 160, 300, null, null],
[0, "이통", null, 0, null, 73, 81, 62, 0, 160, 300, null, null],
[0, "이전", null, 0, null, 78, 80, 79, 0, 160, 300, null, null],
[0, "이풍", null, 0, null, 69, 74, 50, 0, 160, 300, null, null],
[0, "유언", null, 0, null, 61, 38, 80, 0, 160, 300, null, null],
[0, "유괴", null, 0, null, 71, 73, 67, 0, 160, 300, null, null],
[0, "유기", null, 0, null, 52, 12, 63, 0, 160, 300, null, null],
[0, "유우", null, 0, null, 62, 33, 75, 0, 160, 300, null, null],
[0, "유훈", null, 0, null, 47, 63, 35, 0, 160, 300, null, null],
[0, "유현", null, 0, null, 54, 66, 51, 0, 160, 300, null, null],
[0, "유순", null, 0, null, 63, 46, 41, 0, 160, 300, null, null],
[0, "유장", null, 0, null, 31, 5, 34, 0, 160, 300, null, null],
[0, "유심", null, 0, null, 60, 62, 70, 0, 160, 300, null, null],
[0, "유선", null, 0, null, 3, 5, 9, 0, 160, 300, null, null],
[0, "유종", null, 0, null, 24, 22, 65, 0, 160, 300, null, null],
[0, "유도", null, 0, null, 25, 23, 26, 0, 160, 300, null, null],
[0, "유파", null, 0, null, 29, 22, 75, 0, 160, 300, null, null],
[0, "유반", null, 0, null, 69, 76, 47, 0, 160, 300, null, null],
[0, "유비", null, 0, null, 88, 78, 75, 0, 160, 300, null, null],
[0, "유표", null, 0, null, 48, 31, 72, 0, 160, 300, null, null],
[0, "유복", null, 0, null, 64, 49, 74, 0, 160, 300, null, null],
[0, "유벽", null, 0, null, 70, 72, 47, 0, 160, 300, null, null],
[0, "유봉", null, 0, null, 75, 79, 44, 0, 160, 300, null, null],
[0, "유엽", null, 0, null, 56, 32, 92, 0, 160, 300, null, null],
[0, "유요", null, 0, null, 62, 67, 46, 0, 160, 300, null, null],
[0, "여위황", null, 0, null, 58, 63, 29, 0, 160, 300, null, null],
[0, "요화", null, 0, null, 73, 76, 69, 0, 160, 300, null, null],
[0, "양흥", null, 0, null, 61, 65, 19, 0, 160, 300, null, null],
[0, "양습", null, 0, null, 73, 40, 74, 0, 160, 300, null, null],
[0, "양서", null, 0, null, 56, 62, 66, 0, 160, 300, null, null],
[0, "능조", null, 0, null, 75, 81, 42, 0, 160, 300, null, null],
[0, "능통", null, 0, null, 83, 89, 60, 0, 160, 300, null, null],
[0, "여개", null, 0, null, 53, 30, 67, 0, 160, 300, null, null],
[0, "여건", null, 0, null, 57, 70, 62, 0, 160, 300, null, null],
[0, "여광", null, 0, null, 56, 70, 13, 0, 160, 300, null, null],
[0, "여상", null, 0, null, 54, 71, 12, 0, 160, 300, null, null],
[0, "여대", null, 0, null, 80, 71, 70, 0, 160, 300, null, null],
[0, "여범", null, 0, null, 73, 63, 75, 0, 160, 300, null, null],
[0, "여포", null, 0, null, 89, 100, 42, 0, 160, 300, null, null],
[0, "여몽", null, 0, null, 91, 81, 90, 0, 160, 300, null, null],
[0, "윤직", null, 0, null, 46, 42, 73, 0, 160, 300, null, null],
[0, "냉포", null, 0, null, 71, 82, 61, 0, 160, 300, null, null],
[0, "누규", null, 0, null, 52, 13, 87, 0, 160, 300, null, null],
[0, "노숙", null, 0, null, 89, 56, 93, 0, 160, 300, null, null],
[0, "노식", null, 0, null, 86, 63, 82, 0, 160, 300, null, null],
[0, "범강", null, 0, null, 49, 50, 36, 0, 160, 300, null, null],
[0, "장달", null, 0, null, 43, 56, 34, 0, 160, 300, null, null],
[0, "규람", null, 0, null, 55, 70, 58, 0, 160, 300, null, null],
[0, "대원", null, 0, null, 38, 32, 68, 0, 160, 300, null, null],
[0, "왕기", null, 0, null, 81, 72, 86, 0, 160, 300, null, null],
[0, "왕상", null, 0, null, 23, 19, 68, 0, 160, 300, null, null],
[0, "왕돈", null, 0, null, 60, 66, 46, 0, 160, 300, null, null],
[0, "악침", null, 0, null, 73, 75, 43, 0, 160, 300, null, null],
[0, "하제", null, 0, null, 83, 70, 82, 0, 160, 300, null, null],
[0, "환범", null, 0, null, 40, 11, 78, 0, 160, 300, null, null],
[0, "견초", null, 0, null, 72, 79, 73, 0, 160, 300, null, null],
[0, "고당륭", null, 0, null, 35, 24, 76, 0, 160, 300, null, null],
[0, "오찬", null, 0, null, 68, 41, 78, 0, 160, 300, null, null],
[0, "호질", null, 0, null, 73, 46, 74, 0, 160, 300, null, null],
[0, "오질", null, 0, null, 16, 29, 68, 0, 160, 300, null, null],
[0, "호준", null, 0, null, 79, 76, 62, 0, 160, 300, null, null],
[0, "고담", null, 0, null, 53, 23, 77, 0, 160, 300, null, null],
[0, "최림", null, 0, null, 32, 22, 66, 0, 160, 300, null, null],
[0, "사마망", null, 0, null, 78, 58, 83, 0, 160, 300, null, null],
[0, "주이", null, 0, null, 70, 73, 61, 0, 160, 300, null, null],
[0, "전단", null, 0, null, 67, 64, 39, 0, 160, 300, null, null],
[0, "조우", null, 0, null, 50, 43, 37, 0, 160, 300, null, null],
[0, "조상", null, 0, null, 39, 33, 30, 0, 160, 300, null, null],
[0, "조방", null, 0, null, 20, 14, 27, 0, 160, 300, null, null],
[0, "태사향", null, 0, null, 57, 66, 48, 0, 160, 300, null, null],
[0, "장휴", null, 0, null, 63, 27, 74, 0, 160, 300, null, null],
[0, "장호", null, 0, null, 75, 74, 51, 0, 160, 300, null, null],
[0, "조광", null, 0, null, 67, 71, 53, 0, 160, 300, null, null],
[0, "장집", null, 0, null, 76, 29, 80, 0, 160, 300, null, null],
[0, "장승", null, 0, null, 77, 70, 75, 0, 160, 300, null, null],
[0, "장소2", null, 0, null, 23, 56, 31, 0, 160, 300, null, null],
[0, "조통", null, 0, null, 68, 67, 61, 0, 160, 300, null, null],
[0, "진표", null, 0, null, 66, 42, 74, 0, 160, 300, null, null],
[0, "정무", null, 0, null, 56, 35, 75, 0, 160, 300, null, null],
[0, "정봉2", null, 0, null, 66, 66, 53, 0, 160, 300, null, null],
[0, "전만", null, 0, null, 51, 73, 39, 0, 160, 300, null, null],
[0, "마균", null, 0, null, 22, 17, 71, 0, 160, 300, null, null],
[0, "방회", null, 0, null, 69, 75, 49, 0, 160, 300, null, null],
[0, "낙통", null, 0, null, 76, 58, 75, 0, 160, 300, null, null],
[0, "육개", null, 0, null, 65, 61, 79, 0, 160, 300, null, null],
[0, "유찬", null, 0, null, 78, 81, 64, 0, 160, 300, null, null],
[0, "유소", null, 0, null, 68, 53, 74, 0, 160, 300, null, null],
[0, "요립", null, 0, null, 32, 15, 76, 0, 160, 300, null, null],
[0, "여거", null, 0, null, 78, 69, 71, 0, 160, 300, null, null],
[0, "제갈첨", null, 0, null, 70, 57, 76, 0, 160, 300, null, null],
[0, "손휴", null, 0, null, 57, 42, 78, 0, 160, 300, null, null],
[0, "극정", null, 0, null, 42, 23, 68, 0, 160, 300, null, null],
[0, "호분", null, 0, null, 71, 76, 50, 0, 160, 300, null, null],
[0, "손량", null, 0, null, 32, 26, 70, 0, 160, 300, null, null],
[0, "마막", null, 0, null, 34, 12, 44, 0, 160, 300, null, null],
[0, "손호", null, 0, null, 41, 73, 71, 0, 160, 300, null, null],
[0, "조모", null, 0, null, 54, 61, 59, 0, 160, 300, null, null],
[0, "조환", null, 0, null, 30, 28, 30, 0, 160, 300, null, null],
[0, "사마유", null, 0, null, 71, 68, 75, 0, 160, 300, null, null],
[0, "제갈상", null, 0, null, 64, 79, 62, 0, 160, 300, null, null],
[0, "손화", null, 0, null, 31, 39, 68, 0, 160, 300, null, null],
[0, "염우", null, 0, null, 52, 61, 51, 0, 160, 300, null, null],
[0, "왕경", null, 0, null, 67, 63, 71, 0, 160, 300, null, null],
[0, "왕혼", null, 0, null, 74, 71, 63, 0, 160, 300, null, null],
[0, "곽혁", null, 0, null, 33, 29, 70, 0, 160, 300, null, null],
[0, "가범", null, 0, null, 60, 62, 73, 0, 160, 300, null, null],
[0, "서질", null, 0, null, 68, 83, 24, 0, 160, 300, null, null],
[0, "손준", null, 0, null, 64, 70, 65, 0, 160, 300, null, null],
[0, "손침", null, 0, null, 58, 55, 65, 0, 160, 300, null, null],
[0, "우사", null, 0, null, 61, 34, 74, 0, 160, 300, null, null],
[0, "진건", null, 0, null, 72, 71, 65, 0, 160, 300, null, null],
[0, "동궐", null, 0, null, 65, 59, 76, 0, 160, 300, null, null],
[0, "당자", null, 0, null, 61, 71, 50, 0, 160, 300, null, null],
[0, "등충", null, 0, null, 76, 80, 69, 0, 160, 300, null, null],
[0, "황숭", null, 0, null, 67, 63, 72, 0, 160, 300, null, null],
[0, "화핵", null, 0, null, 20, 24, 71, 0, 160, 300, null, null],
[0, "허의", null, 0, null, 51, 74, 15, 0, 160, 300, null, null],
[0, "송겸", null, 0, null, 66, 78, 42, 0, 160, 300, null, null],
[0, "호제", null, 0, null, 57, 39, 68, 0, 160, 300, null, null],
[0, "나헌", null, 0, null, 86, 70, 82, 0, 160, 300, null, null],
[0, "노숙2", null, 0, null, 73, 53, 74, 0, 160, 300, null, null],
[0, "구성", null, 0, null, 63, 70, 38, 0, 160, 300, null, null],
[0, "어부라", null, 0, null, 72, 68, 21, 0, 160, 300, null, null],
[0, "아하소과", null, 0, null, 61, 76, 13, 0, 160, 300, null, null],
[0, "가비능", null, 0, null, 77, 69, 52, 0, 160, 300, null, null],
[0, "구력거", null, 0, null, 80, 68, 56, 0, 160, 300, null, null],
[0, "강단", null, 0, null, 65, 83, 48, 0, 160, 300, null, null],
[0, "황란", null, 0, null, 63, 75, 44, 0, 160, 300, null, null],
[0, "호주천", null, 0, null, 70, 71, 40, 0, 160, 300, null, null],
[0, "종리목", null, 0, null, 84, 72, 82, 0, 160, 300, null, null],
[0, "제갈교", null, 0, null, 62, 27, 75, 0, 160, 300, null, null],
[0, "답돈", null, 0, null, 84, 82, 61, 0, 160, 300, null, null],
[0, "반임", null, 0, null, 66, 79, 38, 0, 160, 300, null, null],
[0, "보도근", null, 0, null, 64, 73, 50, 0, 160, 300, null, null],
[0, "미당대왕", null, 0, null, 59, 69, 21, 0, 160, 300, null, null],
[0, "유표2", null, 0, null, 73, 74, 48, 0, 160, 300, null, null],
[0, "루반", null, 0, null, 65, 76, 39, 0, 160, 300, null, null],
[0, "손관", null, 0, null, 72, 78, 51, 0, 160, 300, null, null],
[0, "곽석", null, 0, null, 59, 71, 48, 0, 160, 300, null, null],
[0, "주조", null, 0, null, 63, 68, 26, 0, 160, 300, null, null],
[0, "장거", null, 0, null, 79, 75, 56, 0, 160, 300, null, null],
[0, "장순", null, 0, null, 73, 81, 60, 0, 160, 300, null, null],
[0, "철리길", null, 0, null, 77, 70, 52, 0, 160, 300, null, null],
[0, "주삭", null, 0, null, 54, 60, 50, 0, 160, 300, null, null],
[0, "황월영", null, 0, null, 46, 33, 78, 0, 160, 300, null, null],
[0, "채염", null, 0, null, 42, 26, 82, 0, 160, 300, null, null],
[0, "소교", null, 0, null, 23, 15, 78, 0, 160, 300, null, null],
[0, "신헌영", null, 0, null, 65, 35, 90, 0, 160, 300, null, null],
[0, "손상향", null, 0, null, 72, 79, 55, 0, 160, 300, null, null],
[0, "대교", null, 0, null, 22, 30, 78, 0, 160, 300, null, null],
[0, "초선", null, 0, null, 12, 22, 71, 0, 160, 300, null, null],
[0, "교모", null, 0, null, 54, 45, 69, 0, 160, 300, null, null],
[0, "관구수", null, 0, null, 58, 62, 33, 0, 160, 300, null, null],
[0, "관구전", null, 0, null, 63, 60, 69, 0, 160, 300, null, null],
[0, "원유", null, 0, null, 56, 40, 73, 0, 160, 300, null, null],
[0, "왕광", null, 0, null, 65, 55, 61, 0, 160, 300, null, null],
[0, "신창", null, 0, null, 50, 26, 42, 0, 160, 300, null, null],
[0, "사마주", null, 0, null, 61, 52, 61, 0, 160, 300, null, null],
[0, "양제", null, 0, null, 67, 68, 71, 0, 160, 300, null, null],
[0, "성만", null, 0, null, 61, 68, 66, 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, 64, 76, 34, 0, 160, 300, null, null],
[0, "심영", null, 0, null, 70, 77, 61, 0, 160, 300, null, null],
[0, "도준", null, 0, null, 65, 66, 46, 0, 160, 300, null, null],
[0, "만욱", null, 0, null, 21, 25, 71, 0, 160, 300, null, null],
[0, "오언", null, 0, null, 76, 81, 65, 0, 160, 300, null, null],
[0, "위막", null, 0, null, 57, 62, 59, 0, 160, 300, null, null],
[0, "시삭", null, 0, null, 38, 65, 47, 0, 160, 300, null, null],
[0, "전속", null, 0, null, 47, 60, 55, 0, 160, 300, null, null],
[0, "장화", null, 0, null, 36, 27, 78, 0, 160, 300, null, null],
[0, "오거", null, 0, null, 49, 61, 23, 0, 160, 300, null, null],
[0, "추정", null, 0, null, 70, 64, 66, 0, 160, 300, null, null],
[0, "장특", null, 0, null, 73, 54, 75, 0, 160, 300, null, null],
[0, "곽마", null, 0, null, 68, 71, 47, 0, 160, 300, null, null],
[0, "석포", null, 0, null, 69, 71, 56, 0, 160, 300, null, null],
[0, "견홍", null, 0, null, 70, 74, 66, 0, 160, 300, null, null],
[0, "한거자", null, 0, null, 51, 59, 51, 0, 160, 300, null, null],
[0, "휴원진", null, 0, null, 52, 68, 45, 0, 160, 300, null, null],
[0, "곽원", null, 0, null, 66, 73, 36, 0, 160, 300, null, null],
[0, "계옹", null, 0, null, 63, 52, 41, 0, 160, 300, null, null],
[0, "이부", null, 0, null, 31, 36, 74, 0, 160, 300, null, null],
[0, "왕업", null, 0, null, 33, 6, 35, 0, 160, 300, null, null],
[0, "좌혁", null, 0, null, 62, 66, 51, 0, 160, 300, null, null],
[0, "화타", null, 0, null, 57, 25, 73, 0, 160, 300, null, null],
[0, "사마휘", null, 0, null, 55, 26, 82, 0, 160, 300, null, null],
[0, "우길", null, 0, null, 23, 15, 90, 0, 160, 300, null, null],
[0, "헌제", null, 0, null, 22, 18, 68, 0, 160, 300, null, null],
[0, "왕검", null, 0, null, 92, 51, 91, 0, 160, 300, null, null],
[0, "위만", null, 0, null, 93, 90, 62, 0, 160, 300, null, null],
[0, "부분노", null, 0, null, 97, 68, 96, 0, 160, 300, null, null],
[0, "무휼", null, 0, null, 132, 141, 43, 0, 160, 300, null, "위압"],
[0, "태조대왕", null, 0, null, 92, 63, 91, 0, 160, 300, null, null],
[0, "명림답부", null, 0, null, 94, 68, 99, 0, 160, 300, null, null],
[0, "달가", null, 0, null, 91, 60, 96, 0, 160, 300, null, null],
[0, "고노자", null, 0, null, 93, 96, 62, 0, 160, 300, null, null],
[0, "미천왕", null, 0, null, 91, 60, 88, 0, 160, 300, null, null],
[0, "고무", null, 0, null, 90, 93, 63, 0, 160, 300, null, null],
[0, "광개토대왕", null, 0, null, 145, 132, 42, 0, 160, 300, null, "필살"],
[0, "장수왕", null, 0, null, 97, 94, 63, 0, 160, 300, null, null],
[0, "을지문덕", null, 0, null, 138, 43, 141, 0, 160, 300, null, "환술"],
[0, "고흘", null, 0, null, 88, 91, 63, 0, 160, 300, null, null],
[0, "온달", null, 0, null, 90, 92, 60, 0, 160, 300, null, null],
[0, "고건무", null, 0, null, 97, 91, 71, 0, 160, 300, null, null],
[0, "양만춘", null, 0, null, 98, 69, 91, 0, 160, 300, null, null],
[0, "연개소문", null, 0, null, 92, 66, 91, 0, 160, 300, null, null],
[0, "근초고왕", null, 0, null, 93, 93, 70, 0, 160, 300, null, null],
[0, "목라근자", null, 0, null, 89, 90, 68, 0, 160, 300, null, null],
[0, "근구수왕", null, 0, null, 90, 63, 89, 0, 160, 300, null, null],
[0, "사법명", null, 0, null, 91, 66, 92, 0, 160, 300, null, null],
[0, "찬수류", null, 0, null, 87, 60, 83, 0, 160, 300, null, null],
[0, "해례곤", null, 0, null, 86, 88, 59, 0, 160, 300, null, null],
[0, "목간나", null, 0, null, 85, 87, 61, 0, 160, 300, null, null],
[0, "윤충", null, 0, null, 90, 87, 59, 0, 160, 300, null, null],
[0, "의직", null, 0, null, 85, 61, 83, 0, 160, 300, null, null],
[0, "계백", null, 0, null, 93, 71, 94, 0, 160, 300, null, null],
[0, "귀실복신", null, 0, null, 83, 57, 82, 0, 160, 300, null, null],
[0, "흑치상지", null, 0, null, 90, 73, 92, 0, 160, 300, null, null],
[0, "이사금", null, 0, null, 98, 93, 71, 0, 160, 300, null, null],
[0, "물계자", null, 0, null, 88, 94, 43, 0, 160, 300, null, null],
[0, "석우로", null, 0, null, 87, 91, 54, 0, 160, 300, null, null],
[0, "이사부", null, 0, null, 90, 72, 93, 0, 160, 300, null, null],
[0, "거칠부", null, 0, null, 92, 73, 90, 0, 160, 300, null, null],
[0, "진흥왕", null, 0, null, 91, 66, 91, 0, 160, 300, null, null],
[0, "사다함", null, 0, null, 86, 91, 66, 0, 160, 300, null, null],
[0, "김세종", null, 0, null, 88, 63, 87, 0, 160, 300, null, null],
[0, "김무력", null, 0, null, 90, 93, 63, 0, 160, 300, null, null],
[0, "알천", null, 0, null, 93, 96, 51, 0, 160, 300, null, null],
[0, "김유신", null, 0, null, 128, 66, 131, 0, 160, 300, null, "환술"],
[0, "김흠돌", null, 0, null, 87, 90, 52, 0, 160, 300, null, null],
[0, "김품일", null, 0, null, 83, 56, 82, 0, 160, 300, null, null],
[0, "김천존", null, 0, null, 84, 60, 82, 0, 160, 300, null, null],
[0, "죽지", null, 0, null, 83, 86, 52, 0, 160, 300, null, null],
[0, "문무왕", null, 0, null, 92, 73, 96, 0, 160, 300, null, null],
[0, "시득", null, 0, null, 98, 67, 90, 0, 160, 300, null, null],
[0, "장보고", null, 0, null, 99, 72, 96, 0, 160, 300, null, null],
[0, "김웅원", null, 0, null, 92, 91, 66, 0, 160, 300, null, null],
[0, "대조영", null, 0, null, 96, 92, 63, 0, 160, 300, null, null],
[0, "대무예", null, 0, null, 90, 97, 59, 0, 160, 300, null, null],
[0, "장문휴", null, 0, null, 88, 89, 53, 0, 160, 300, null, null],
[0, "대인수", null, 0, null, 89, 60, 85, 0, 160, 300, null, null],
[0, "궁예", null, 0, null, 91, 90, 57, 0, 160, 300, null, null],
[0, "왕건", null, 0, null, 141, 43, 138, 0, 160, 300, null, "집중"],
[0, "이흔암", null, 0, null, 83, 82, 49, 0, 160, 300, null, null],
[0, "견훤", null, 0, null, 91, 65, 90, 0, 160, 300, null, null],
[0, "상귀", null, 0, null, 87, 88, 55, 0, 160, 300, null, null],
[0, "상애", null, 0, null, 76, 69, 41, 0, 160, 300, null, null],
[0, "관흔", null, 0, null, 79, 52, 83, 0, 160, 300, null, null],
[0, "유금필", null, 0, null, 87, 94, 58, 0, 160, 300, null, null],
[0, "배현경", null, 0, null, 86, 48, 90, 0, 160, 300, null, null],
[0, "서희", null, 0, null, 71, 26, 99, 0, 160, 300, null, null],
[0, "양규", null, 0, null, 95, 66, 93, 0, 160, 300, null, null],
[0, "김숙흥", null, 0, null, 87, 90, 57, 0, 160, 300, null, null],
[0, "지채문", null, 0, null, 86, 63, 88, 0, 160, 300, null, null],
[0, "강감찬", null, 0, null, 94, 68, 96, 0, 160, 300, null, null],
[0, "강민첨", null, 0, null, 92, 93, 64, 0, 160, 300, null, null],
[0, "윤관", null, 0, null, 87, 58, 86, 0, 160, 300, null, null],
[0, "척준경", null, 0, null, 112, 156, 51, 0, 160, 300, null, "무쌍"],
[0, "허재", null, 0, null, 73, 48, 81, 0, 160, 300, null, null],
[0, "김의원", null, 0, null, 80, 86, 51, 0, 160, 300, null, null],
[0, "김부식", null, 0, null, 51, 21, 92, 0, 160, 300, null, null],
[0, "두경승", null, 0, null, 88, 62, 86, 0, 160, 300, null, null],
[0, "김취려", null, 0, null, 90, 88, 58, 0, 160, 300, null, null],
[0, "박서", null, 0, null, 86, 52, 87, 0, 160, 300, null, null],
[0, "김경손", null, 0, null, 73, 92, 58, 0, 160, 300, null, null],
[0, "김윤후", null, 0, null, 82, 51, 83, 0, 160, 300, null, null],
[0, "김방경", null, 0, null, 93, 56, 87, 0, 160, 300, null, null],
[0, "정세운", null, 0, null, 86, 82, 62, 0, 160, 300, null, null],
[0, "최영", null, 0, null, 96, 92, 71, 0, 160, 300, null, null],
[0, "정지", null, 0, null, 88, 91, 66, 0, 160, 300, null, null],
[0, "최무선", null, 0, null, 87, 59, 93, 0, 160, 300, null, null],
[0, "이성계", null, 0, null, 141, 50, 133, 0, 160, 300, null, "신중"],
[0, "김성우", null, 0, null, 87, 84, 53, 0, 160, 300, null, null],
[0, "김종서", null, 0, null, 93, 66, 92, 0, 160, 300, null, null],
[0, "남이", null, 0, null, 89, 90, 63, 0, 160, 300, null, null],
[0, "권율", null, 0, null, 94, 70, 93, 0, 160, 300, null, null],
[0, "황진", null, 0, null, 87, 96, 53, 0, 160, 300, null, null],
[0, "곽재우", null, 0, null, 132, 144, 44, 0, 160, 300, null, "필살"],
[0, "김시민", null, 0, null, 93, 72, 96, 0, 160, 300, null, null],
[0, "이순신", null, 0, null, 148, 40, 142, 0, 160, 300, null, "집중"],
[0, "정문부", null, 0, null, 87, 56, 83, 0, 160, 300, null, null],
[0, "정충신", null, 0, null, 82, 51, 90, 0, 160, 300, null, null],
[0, "원균", null, 0, null, 1, 1, 1, 0, 160, 300, null, null],
[0, "관중", null, 0, null, 91, 45, 98, 0, 160, 300, null, null],
[0, "몽무", null, 0, null, 88, 96, 53, 0, 160, 300, null, null],
[0, "몽염", null, 0, null, 91, 83, 71, 0, 160, 300, null, null],
[0, "몽오", null, 0, null, 90, 86, 60, 0, 160, 300, null, null],
[0, "방난", null, 0, null, 91, 57, 93, 0, 160, 300, null, null],
[0, "백기", null, 0, null, 100, 63, 92, 0, 160, 300, null, null],
[0, "범려", null, 0, null, 78, 22, 96, 0, 160, 300, null, null],
[0, "사마양저", null, 0, null, 88, 51, 92, 0, 160, 300, null, null],
[0, "사마착", null, 0, null, 93, 91, 66, 0, 160, 300, null, null],
[0, "손빈", null, 0, null, 80, 16, 96, 0, 160, 300, null, null],
[0, "손무", null, 0, null, 98, 32, 100, 0, 160, 300, null, null],
[0, "신릉군", null, 0, null, 81, 39, 90, 0, 160, 300, null, null],
[0, "악양", null, 0, null, 83, 36, 96, 0, 160, 300, null, null],
[0, "악의", null, 0, null, 132, 48, 141, 0, 160, 300, null, "환술"],
[0, "염파", null, 0, null, 98, 97, 80, 0, 160, 300, null, null],
[0, "오기", null, 0, null, 93, 39, 95, 0, 160, 300, null, null],
[0, "오운", null, 0, null, 88, 51, 90, 0, 160, 300, null, null],
[0, "왕분", null, 0, null, 89, 91, 68, 0, 160, 300, null, null],
[0, "왕전", null, 0, null, 95, 66, 93, 0, 160, 300, null, null],
[0, "왕흘", null, 0, null, 93, 92, 60, 0, 160, 300, null, null],
[0, "이목", null, 0, null, 96, 69, 98, 0, 160, 300, null, null],
[0, "이신", null, 0, null, 89, 90, 57, 0, 160, 300, null, null],
[0, "전단2", null, 0, null, 94, 59, 93, 0, 160, 300, null, null],
[0, "조사", null, 0, null, 91, 63, 91, 0, 160, 300, null, null],
[0, "진개", null, 0, null, 88, 87, 50, 0, 160, 300, null, null],
[0, "항연", null, 0, null, 90, 88, 60, 0, 160, 300, null, null],
[0, "한신", null, 0, null, 100, 74, 95, 0, 160, 300, null, null],
[0, "항적", null, 0, null, 118, 153, 47, 0, 160, 300, null, "무쌍"],
[0, "장한", null, 0, null, 88, 66, 87, 0, 160, 300, null, null],
[0, "유방", null, 0, null, 90, 66, 85, 0, 160, 300, null, null],
[0, "조참", null, 0, null, 89, 53, 87, 0, 160, 300, null, null],
[0, "팽월", null, 0, null, 92, 93, 60, 0, 160, 300, null, null],
[0, "영포", null, 0, null, 93, 93, 58, 0, 160, 300, null, null],
[0, "종리말", null, 0, null, 90, 83, 66, 0, 160, 300, null, null],
[0, "주발", null, 0, null, 82, 50, 83, 0, 160, 300, null, null],
[0, "번쾌", null, 0, null, 87, 92, 63, 0, 160, 300, null, null],
[0, "역상", null, 0, null, 83, 52, 83, 0, 160, 300, null, null],
[0, "관영", null, 0, null, 86, 87, 60, 0, 160, 300, null, null],
[0, "부관", null, 0, null, 83, 58, 88, 0, 160, 300, null, null],
[0, "근흡", null, 0, null, 86, 90, 52, 0, 160, 300, null, null],
[0, "왕릉2", null, 0, null, 90, 87, 56, 0, 160, 300, null, null],
[0, "계포", null, 0, null, 83, 62, 90, 0, 160, 300, null, null],
[0, "용저", null, 0, null, 86, 92, 41, 0, 160, 300, null, null],
[0, "곽거병", null, 0, null, 93, 92, 53, 0, 160, 300, null, null],
[0, "마원", null, 0, null, 90, 52, 83, 0, 160, 300, null, null],
[0, "위청", null, 0, null, 92, 91, 52, 0, 160, 300, null, null],
[0, "주아부", null, 0, null, 86, 41, 92, 0, 160, 300, null, null],
[0, "곽자의", null, 0, null, 92, 97, 44, 0, 160, 300, null, null],
[0, "나오에 카네츠구", null, 0, null, 87, 91, 42, 0, 160, 300, null, null],
[0, "다케다 신겐", null, 0, null, 91, 50, 93, 0, 160, 300, null, null],
[0, "도요토미 히데요시", null, 0, null, 98, 70, 97, 0, 160, 300, null, null],
[0, "고니시 유키나가", null, 0, null, 96, 68, 94, 0, 160, 300, null, null],
[0, "가토 기요마사", null, 0, null, 92, 93, 66, 0, 160, 300, null, null],
[0, "도요토미 히데나가", null, 0, null, 94, 70, 92, 0, 160, 300, null, null],
[0, "도도 다카토라", null, 0, null, 90, 92, 62, 0, 160, 300, null, null],
[0, "도쿠가와 이에야스", null, 0, null, 92, 68, 91, 0, 160, 300, null, null],
[0, "모리 모토나리", null, 0, null, 88, 42, 96, 0, 160, 300, null, null],
[0, "우에스기 겐신", null, 0, null, 91, 96, 78, 0, 160, 300, null, null],
[0, "오다 노부나가", null, 0, null, 93, 73, 92, 0, 160, 300, null, null],
[0, "이마가와 요시모토", null, 0, null, 89, 91, 71, 0, 160, 300, null, null],
[0, "사나다 마사유키", null, 0, null, 93, 66, 90, 0, 160, 300, null, null],
[0, "사나다 노부시게", null, 0, null, 94, 92, 70, 0, 160, 300, null, null],
[0, "시마즈 이에히사", null, 0, null, 96, 94, 63, 0, 160, 300, null, null],
[0, "시마즈 요시히로", null, 0, null, 93, 63, 88, 0, 160, 300, null, null],
[0, "코바야카와타카카게", null, 0, null, 90, 61, 92, 0, 160, 300, null, null],
[0, "타치바나 도세츠", null, 0, null, 91, 89, 71, 0, 160, 300, null, null],
[0, "타치바나 무네시게", null, 0, null, 90, 69, 88, 0, 160, 300, null, null],
[0, "타카하시 쇼운", null, 0, null, 92, 91, 70, 0, 160, 300, null, null],
[0, "호조 우지야스", null, 0, null, 89, 58, 91, 0, 160, 300, null, null],
[0, "칭기즈 칸", null, 0, null, 151, 121, 43, 0, 160, 300, null, "위압"],
[0, "오고타이 칸", null, 0, null, 98, 93, 64, 0, 160, 300, null, null],
[0, "자무카", null, 0, null, 97, 96, 60, 0, 160, 300, null, null],
[0, "수부타이", null, 0, null, 94, 71, 91, 0, 160, 300, null, null],
[0, "제베", null, 0, null, 93, 97, 68, 0, 160, 300, null, null]
],
"general_ex":[
],
"initialEvents": [
[
true,
[
"ChangeCity",
"all",
{
"trade": 100
}
]
]
],
"events": [
[
"month",
1000,
[
"Date",
"==",
180,
1
],
[
"ChangeCity",
"all",
{
"trade": 100
}
],
[
"DeleteEvent"
]
],
[
"month",
1000,
[
"Date",
"==",
180,
7
],
[
"ChangeCity",
"all",
{
"trade": 100
}
],
[
"DeleteEvent"
]
],
[
"month",
1000,
[
"Date",
"==",
181,
1
],
[
"ChangeCity",
"all",
{
"trade": 100
}
],
[
"DeleteEvent"
]
],
[
"month",
1000,
[
"Date",
"==",
181,
7
],
[
"ChangeCity",
"all",
{
"trade": 100
}
],
[
"DeleteEvent"
]
],
[
"month",
1000,
[
"Date",
"==",
182,
1
],
[
"ChangeCity",
"all",
{
"trade": 100
}
],
[
"DeleteEvent"
]
],
[
"month",
1000,
[
"Date",
"==",
182,
7
],
[
"ChangeCity",
"all",
{
"trade": 100
}
],
[
"DeleteEvent"
]
],
[
"destroy_nation",
1000,
[
"and",
[
"Date",
">=",
183,
1
],
[
"RemainNation",
"==",
1
]
],
[
"BlockScoutAction"
],
[
"DeleteEvent"
]
]
]
}
+283
View File
@@ -0,0 +1,283 @@
{
"title": "【공백지】 무작위 세계",
"startYear": 180,
"map": {
"mapName": "miniche_b"
},
"history": [
"<C>●</>180년 1월:<L><b>【공백지】</b></>무작위성이 넘치는 이벤트 깃수"
],
"const": {
"joinRuinedNPCProp": 0,
"npcBanMessageProb": 1,
"inheritItemUniqueMinPoint": 75000,
"inheritItemRandomPoint": 75000,
"uniqueTrialCoef": 10000,
"maxUniqueTrialProb": 0.2,
"maxUniqueItemLimit": [
[-1, 4]
],
"minTurnDieOnPrestart": 1,
"availableGeneralCommand": {
"개인": [
"휴식",
"che_요양",
"che_단련",
"che_숙련전환",
"che_견문",
"che_은퇴",
"che_장비매매",
"che_군량매매",
"che_내정특기초기화",
"che_전투특기초기화"
],
"내정": [
"che_농지개간",
"che_상업투자",
"che_기술연구",
"che_수비강화",
"che_성벽보수",
"che_치안강화",
"che_정착장려",
"che_주민선정",
"che_물자조달"
],
"군사": [
"che_징병",
"che_모병",
"che_훈련",
"che_사기진작",
"che_출병",
"che_집합",
"che_소집해제",
"che_첩보"
],
"인사": [
"che_이동",
"che_강행",
"che_인재탐색",
"che_귀환",
"che_랜덤임관"
],
"계략": [
"che_선동",
"che_탈취",
"che_파괴",
"che_화계"
],
"국가": [
"che_증여",
"che_헌납",
"che_하야",
"che_거병",
"che_무작위건국",
"che_선양",
"che_해산"
]
},
"availableChiefCommand": {
"휴식": [
"휴식"
],
"인사": [
"che_발령",
"che_포상",
"che_몰수"
],
"외교": [
"che_물자원조",
"che_불가침제의",
"che_선전포고",
"che_종전제의",
"che_불가침파기제의"
],
"특수": [
"che_초토화",
"che_천도",
"che_증축",
"che_감축"
],
"전략": [
"che_필사즉생",
"che_백성동원",
"che_수몰",
"che_허보",
"che_의병모집",
"che_이호경식",
"che_급습",
"che_피장파장"
],
"기타": [
"che_국기변경",
"che_국호변경",
"che_무작위수도이전"
]
},
"allItems": {
"horse": {
"che_명마_01_노기": 0, "che_명마_02_조랑": 0, "che_명마_03_노새": 0,
"che_명마_04_나귀": 0, "che_명마_05_갈색마": 0, "che_명마_06_흑색마": 0,
"che_명마_07_백마": 6,
"che_명마_07_기주마": 6,
"che_명마_07_오환마": 4,
"che_명마_07_백상": 8,
"che_명마_08_양주마": 4,
"che_명마_08_흉노마": 4,
"che_명마_09_과하마": 4,
"che_명마_09_의남백마": 4,
"che_명마_10_대완마": 4,
"che_명마_10_옥추마": 4,
"che_명마_11_서량마": 4,
"che_명마_11_화종마": 4,
"che_명마_12_사륜거": 8,
"che_명마_12_옥란백용구": 8,
"che_명마_13_절영": 4,
"che_명마_13_적로": 4,
"che_명마_14_적란마": 4,
"che_명마_14_조황비전": 4,
"che_명마_15_한혈마": 6,
"che_명마_15_적토마": 6
},
"weapon": {
"che_무기_01_단도": 0, "che_무기_02_단궁": 0, "che_무기_03_단극": 0,
"che_무기_04_목검": 0, "che_무기_05_죽창": 0, "che_무기_06_소부": 0,
"che_무기_07_동추": 4,
"che_무기_07_철편": 4,
"che_무기_07_철쇄": 4,
"che_무기_07_맥궁": 8,
"che_무기_08_유성추": 4,
"che_무기_08_철질여골": 4,
"che_무기_09_쌍철극": 4,
"che_무기_09_동호비궁": 8,
"che_무기_10_삼첨도": 4,
"che_무기_10_대부": 4,
"che_무기_11_고정도": 4,
"che_무기_11_이광궁": 8,
"che_무기_12_철척사모": 4,
"che_무기_12_칠성검": 4,
"che_무기_13_사모": 4,
"che_무기_13_양유기궁": 8,
"che_무기_14_언월도": 4,
"che_무기_14_방천화극": 4,
"che_무기_15_청홍검": 6,
"che_무기_15_의천검": 6
},
"book": {
"che_서적_01_효경전": 0, "che_서적_02_회남자": 0, "che_서적_03_변도론": 0,
"che_서적_04_건상역주": 0, "che_서적_05_여씨춘추": 0, "che_서적_06_사민월령": 0,
"che_서적_07_위료자": 8,
"che_서적_07_사마법": 6,
"che_서적_07_한서": 4,
"che_서적_07_논어": 6,
"che_서적_08_전론": 6,
"che_서적_08_사기": 4,
"che_서적_09_장자": 4,
"che_서적_09_역경": 4,
"che_서적_10_시경": 4,
"che_서적_10_구국론": 4,
"che_서적_11_상군서": 4,
"che_서적_11_춘추전": 6,
"che_서적_12_산해경": 8,
"che_서적_12_맹덕신서": 4,
"che_서적_13_관자": 4,
"che_서적_13_병법24편": 4,
"che_서적_14_한비자": 4,
"che_서적_14_오자병법": 4,
"che_서적_15_노자": 6,
"che_서적_15_손자병법": 6
},
"item": {
"che_치료_환약": 0, "che_저격_수극": 0, "che_사기_탁주": 0,
"che_훈련_청주": 0, "che_계략_이추": 0, "che_계략_향낭": 0,
"che_의술_정력견혈산": 3,
"che_의술_청낭서": 3,
"che_의술_태평청령": 3,
"che_의술_상한잡병론": 3,
"che_보물_도기": 2,
"che_조달_주판": 2,
"che_내정_납금박산로": 2,
"che_전략_평만지장도": 2,
"che_숙련_동작": 2,
"che_명성_구석": 2,
"che_척사_오악진형도": 3,
"che_격노_구정신단경": 2,
"che_징병_낙주": 2,
"che_저격_매화수전": 3,
"che_저격_비도": 3,
"che_위압_조목삭": 2,
"che_공성_묵자": 3,
"che_집중_전국책": 3,
"che_환술_논어집해": 3,
"che_진압_박혁론": 3,
"che_부적_태현청생부": 2,
"che_저지_삼황내문": 2,
"che_행동_서촉지형도": 2,
"che_간파_노군입산부": 2,
"che_불굴_상편": 4,
"che_약탈_옥벽": 2,
"che_농성_주서음부": 2,
"che_농성_위공자병법": 2,
"che_계략_육도": 3,
"che_계략_삼략": 3,
"che_상성보정_과실주": 3,
"che_능력치_지력_이강주": 2,
"che_능력치_무력_두강주": 2,
"che_능력치_통솔_보령압주": 2,
"che_훈련_철벽서": 3,
"che_훈련_단결도": 3,
"che_사기_춘화첩": 3,
"che_사기_초선화": 3,
"che_회피_태평요술": 2,
"che_필살_둔갑천서": 2
}
}
},
"events":[
[
"month", 1000,
["or", ["Date", "==", null, 12], ["Date", "==", null, 6]],
["CreateManyNPC", 10, 10],
["DeleteEvent"]
],
[
"month", 1000,
["Date", "==", 181, 1],
["RaiseNPCNation"],
["DeleteEvent"]
],
[
"month", 999,
["Date", "==", 181, 1],
["OpenNationBetting", 4, 5000],
["OpenNationBetting", 1, 2000],
["DeleteEvent"]
],
[
"month", 999,
["and",
["Date", ">=", 183, 1],
["RemainNation", "<=", 8]
],
["OpenNationBetting", 1, 1000],
["DeleteEvent"]
],
[
"destroy_nation", 1000,
["and",
["Date", ">=", 183, 1],
["RemainNation", "==", 1]
],
["BlockScoutAction"],
["DeleteEvent"]
],
[
"month", 999,
["Date", "==", null, 1],
["LostUniqueItem", 0.2]
]
]
}
+36 -7
View File
@@ -48,19 +48,18 @@
</div>
</div>
</DragSelect>
<BButton
ref="btnCopy"
<div
class="hoverPanel d-grid"
:style="{
position: 'absolute',
display: isCopyButtonShown ? 'block' : 'none',
display: isCopyButtonShown ? 'grid !important' : 'none !important',
top: `${btnPos * 30 + 25}px`,
right: '10px',
}"
@blur="isCopyButtonShown = false"
@click="tryCopy()"
>
복사하기
</BButton>
<BButton ref="btnCopy" variant="primary" @blur="hideCopyButton()" @click="tryCopy()"> 복사하기 </BButton>
<BButton ref="btnCopy" @blur="hideCopyButton()" @click="tryTextCopy()"> 텍스트 복사</BButton>
</div>
</div>
</template>
<script setup lang="ts">
@@ -117,6 +116,12 @@ function dragStart() {
isDragToggle.value = true;
}
function hideCopyButton() {
setTimeout(() => {
isCopyButtonShown.value = false;
}, 1);
}
function eqSet<T>(as: Set<T>, bs: Set<T>) {
if (as.size !== bs.size) return false;
for (var a of as) if (!bs.has(a)) return false;
@@ -160,6 +165,30 @@ function tryCopy() {
storedActionsHelper.clipboard.value = [...actions];
}
const removeTagRegEx = /<[^>]*>?/g;
function tryTextCopy() {
const rawActions = queryActionHelper.extractQueryActions();
isCopyButtonShown.value = false;
const actions = queryActionHelper.amplifyQueryActions(rawActions, queryActionHelper.getSelectedTurnList());
if (actions.length === 0) {
return;
}
const turnBriefs: [number, string][] = [];
for (const action of actions) {
const [turnIdxList, turnObj] = action;
for (const turnIdx of turnIdxList) {
turnBriefs.push([turnIdx, `${turnIdx + 1}${turnObj.brief.replace(removeTagRegEx, "")}`]);
}
}
turnBriefs.sort((a, b) => a[0] - b[0]);
const text = turnBriefs.map(([, brief]) => brief).join("\n");
void navigator.clipboard.writeText(text);
}
const turnTimes = ref<string[]>([]);
if (!props.officer || !props.officer.turnTime) {
+12 -4
View File
@@ -62,7 +62,8 @@
<div class="row">
<div class="col col-md-4 col-3 a-right align-self-center">장수명</div>
<div class="col col-md-3 col-9 align-self-center">
<input v-model="args.name" class="form-control" />
<input v-if="!blockCustomGeneralName" v-model="args.name" class="form-control" />
<span v-else>무작위</span>
</div>
<div class="col col-md-1 col-3 a-right align-self-center">전콘 사용</div>
<div class="col col-md-4 col-9 align-self-center">
@@ -194,7 +195,7 @@
v-model="inheritTurnTimeMinute"
:readonly="!inheritTurnTimeSet"
:min="0"
:max="1 - turnterm"
:max="turnterm - 1"
:right="true"
title="분"
/>
@@ -267,6 +268,11 @@ declare const staticValues: {
imgsvr: 0 | 1;
};
config: {
show_img_level: number;
blockCustomGeneralName: boolean;
}
serverID: string;
inheritTotalPoint: number;
};
@@ -294,6 +300,8 @@ const { serverID, member, turnterm } = staticValues;
const nationList = ref(shuffle(staticValues.nationList));
const args = ref<JoinArgs>();
const blockCustomGeneralName = staticValues.config.blockCustomGeneralName;
const stats = ref({
min: 50,
max: 50,
@@ -355,8 +363,8 @@ watch(gameConstStore, (gameConst) => {
min: gameConst.gameConst.defaultStatMin,
max: gameConst.gameConst.defaultStatMax,
total: gameConst.gameConst.defaultStatTotal,
bonusMin: gameConst.gameConst.defaultStatMin,
bonusMax: gameConst.gameConst.defaultStatMax,
bonusMin: gameConst.gameConst.bornMinStatBonus,
bonusMax: gameConst.gameConst.bornMaxStatBonus,
};
args.value = {
+32
View File
@@ -81,6 +81,8 @@
<BDropdownItem @click="clipboardCopy"> <i class="bi bi-files" />&nbsp;복사하기 </BDropdownItem>
<BDropdownItem @click="clipboardPaste"> <i class="bi bi-clipboard-fill" />&nbsp;붙여넣기 </BDropdownItem>
<BDropdownDivider />
<BDropdownItem @click="clipboardTextCopy"> <i class="bi bi-files" />&nbsp;텍스트 복사 </BDropdownItem>
<BDropdownDivider />
<BDropdownItem @click="setStoredActions">
<i class="bi bi-bookmark-plus-fill" />&nbsp;보관하기
</BDropdownItem>
@@ -640,6 +642,36 @@ async function clipboardPaste(releaseSelect = true) {
return result;
}
const removeTagRegEx = /<[^>]*>?/g;
function clipboardTextCopy(releaseSelect = true) {
const rawActions = queryActionHelper.extractQueryActions();
if (rawActions.length === 0) {
return;
}
const actions = queryActionHelper.amplifyQueryActions(rawActions, queryActionHelper.getSelectedTurnList());
if (actions.length === 0) {
return;
}
const turnBriefs: [number, string][] = [];
for (const action of actions) {
const [turnIdxList, turnObj] = action;
for (const turnIdx of turnIdxList) {
turnBriefs.push([turnIdx, `${turnIdx + 1}${turnObj.brief.replace(removeTagRegEx, "")}`]);
}
}
turnBriefs.sort((a, b) => a[0] - b[0]);
const text = turnBriefs.map(([, brief]) => brief).join("\n");
void navigator.clipboard.writeText(text);
if (releaseSelect) {
queryActionHelper.releaseSelectedTurnList();
}
}
async function subRepeatCommand(releaseSelect = true): Promise<boolean> {
const reqTurnList = queryActionHelper.getSelectedTurnList();
const selectedMinTurnIdx = reqTurnList[0];
+22
View File
@@ -32,6 +32,7 @@ GetRecentRecordResponse,
import type { CachedMapResult, GeneralListResponse, ItemTypeKey, MapResult } from "./defs";
import type { VoteDetailResult, VoteListResult } from "./defs/API/Vote";
import type { ActiveResourceAuctionList, OpenAuctionResponse, UniqueItemAuctionDetail, UniqueItemAuctionList } from "./defs/API/Auction";
import type { MabilboxListResponse, MsgResponse, MsgType } from "./defs/API/Message";
const apiRealPath = {
Auction: {
@@ -170,6 +171,27 @@ const apiRealPath = {
lastID: number
}, InheritLogResponse>
},
Message: {
DeleteMessage: PATCH as APICallT<{
msgID: number,
}>,
DecideMessageResponse: POST as APICallT<{
msgID: number,
response: boolean,
}>,
GetContactList: GET as APICallT<undefined, MabilboxListResponse>,
GetRecentMessage: GET as APICallT<{
sequence?: number;
}, MsgResponse>,
GetOldMessage: GET as APICallT<{
to: number;
type: MsgType;
}, MsgResponse>,
SendMessage: POST as APICallT<{
mailbox: number;
text: string;
}, ValidResponse & {msgID: number}>
},
Misc: {
UploadImage: POST as APICallT<
{
+1000 -960
View File
@@ -1,1068 +1,1108 @@
import $ from 'jquery';
import 'bootstrap';
import download from 'downloadjs';
import $ from "jquery";
import "bootstrap";
import download from "downloadjs";
import { unwrap } from "@util/unwrap";
import { isInteger } from 'lodash-es';
import { errUnknown } from '@/common_legacy';
import { getNPCColor } from './utilGame';
import { isInteger } from "lodash-es";
import { errUnknown } from "@/common_legacy";
import { getNPCColor } from "./utilGame";
import { combineArray } from "@util/combineArray";
import { isBrightColor } from "@util/isBrightColor";
import { numberWithCommas } from "@util/numberWithCommas";
import { unwrap_any } from '@util/unwrap_any';
import type { BasicGeneralListResponse, InvalidResponse } from '@/defs';
import { formatTime } from '@util/formatTime';
import { Modal } from 'bootstrap';
import { unwrap_any } from "@util/unwrap_any";
import type { BasicGeneralListResponse, InvalidResponse } from "@/defs";
import { formatTime } from "@util/formatTime";
import { Modal } from "bootstrap";
type CityAttackerInfo = {
level: number,
}
level: number;
};
type CityBasicInfo = {
nation?: number,
level: number,
def: number,
wall: number,
city?: number,
}
nation?: number;
level: number;
def: number;
wall: number;
city?: number;
};
type NationBasicInfo = {
level: number,
type: string,
tech: number,
capital: number,
}
level: number;
type: string;
tech: number;
capital: number;
};
type InheritBuff = {
warAvoidRatio: number;
warCriticalRatio: number;
warMagicTrialProb: number;
warAvoidRatioOppose: number;
warCriticalRatioOppose: number;
warMagicTrialProbOppose: number;
};
type GeneralInfo = {
no: number,
npc?: number
name: string,
officer_level: number,
explevel: number,
no: number;
npc?: number;
name: string;
officer_level: number;
explevel: number;
leadership: number,
horse: string,
strength: number,
weapon: string,
intel: number,
book: string,
item: string,
leadership: number;
horse: string;
strength: number;
weapon: string;
intel: number;
book: string;
item: string;
injury: number,
injury: number;
rice: number,
rice: number;
personal: string,
special2: string,
personal: string;
special2: string;
crew: number,
crewtype: number,
atmos: number,
train: number,
crew: number;
crewtype: number;
atmos: number;
train: number;
dex1: number,
dex2: number,
dex3: number,
dex4: number,
dex5: number,
dex1: number;
dex2: number;
dex3: number;
dex4: number;
dex5: number;
defence_train: number,
defence_train: number;
warnum: number,
killnum: number,
killcrew: number,
warnum: number;
killnum: number;
killcrew: number;
officer_city?: number,
officer_city?: number;
inheritBuff?: InheritBuff;
};
type BattleInfo = {
attackerGeneral: GeneralInfo,
attackerCity: CityAttackerInfo | CityBasicInfo,
attackerNation: NationBasicInfo,
defenderGenerals: GeneralInfo[],
defenderCity: CityBasicInfo,
defenderNation: NationBasicInfo,
year: number,
month: number,
repeatCnt: number,
attackerGeneral: GeneralInfo;
attackerCity: CityAttackerInfo | CityBasicInfo;
attackerNation: NationBasicInfo;
defenderGenerals: GeneralInfo[];
defenderCity: CityBasicInfo;
defenderNation: NationBasicInfo;
year: number;
month: number;
repeatCnt: number;
seed?: string;
};
type ExportedGeneralInfo = {
objType: 'general',
data: GeneralInfo,
}
objType: "general";
data: GeneralInfo;
};
type ExportedBattleInfo = {
objType: 'battle',
data: BattleInfo
}
objType: "battle";
data: BattleInfo;
};
type ExportedInfo = ExportedGeneralInfo | ExportedBattleInfo;
type BattleResult = {
result: true,
datetime: string,
lastWarLog: {
generalBattleResultLog: string,
generalBattleDetailLog: string,
},
avgWar: number,
phase: number,
killed: number,
maxKilled: number,
minKilled: number,
dead: number,
maxDead: number,
minDead: number,
attackerRice: number,
defenderRice: number,
attackerSkills: Record<string, number>,
defendersSkills: Record<string, number>[],
}
result: true;
datetime: string;
lastWarLog: {
generalBattleResultLog: string;
generalBattleDetailLog: string;
};
avgWar: number;
phase: number;
killed: number;
maxKilled: number;
minKilled: number;
dead: number;
maxDead: number;
minDead: number;
attackerRice: number;
defenderRice: number;
attackerSkills: Record<string, number>;
defendersSkills: Record<string, number>[];
};
declare global {
interface Window {
nation?: NationBasicInfo,
city?: CityBasicInfo,
defaultSpecialDomestic: string,
}
interface Window {
nation?: NationBasicInfo;
city?: CityBasicInfo;
defaultSpecialDomestic: string;
}
}
let modalImport: Modal|undefined = undefined;
let modalImport: Modal | undefined = undefined;
$(function ($) {
const $generalForm = $(".form_sample .general_detail");
const $defenderHeaderForm = $(".form_sample .card-header");
const $defenderColumn = $(".defender-column");
const defenderNoList: Record<number, JQuery<HTMLElement>> = {};
const $generalForm = $('.form_sample .general_detail');
const $defenderHeaderForm = $('.form_sample .card-header');
const $defenderColumn = $('.defender-column');
const $attackerCard = $(".attacker_form");
const defenderNoList: Record<number, JQuery<HTMLElement>> = {};
const $attackerCard = $('.attacker_form');
const initBasicEvent = function () {
if (window.nation && window.city) {
$('.form_city_level').val(window.city.level);
$('.form_def').val(window.city.def);
$('.form_wall').val(window.city.wall);
$('.form_nation_type').val(window.nation.type);
$('.form_nation_level').val(window.nation.level);
$('.form_tech').val(window.nation.tech / 1000);
if (window.nation.capital == window.city.city) {
$('.attacker_nation .form_is_capital:first').trigger('click');
$('.defender_nation .form_is_capital:first').trigger('click');
} else {
$('.attacker_nation .form_is_capital:last').trigger('click');
$('.defender_nation .form_is_capital:last').trigger('click');
}
} else {
$('.attacker_nation .form_is_capital:last').trigger('click');
$('.defender_nation .form_is_capital:last').trigger('click');
}
$('.form_injury').on('change', function () {
const $this = $(this) as JQuery<HTMLInputElement>;
const $general = getGeneralDetail($this);
const $helptext = $general.find('.injury_helptext');
const injury = parseInt($this.val() as string);
//FIXME: PHP 코드와 항상 일치하도록 변경
let text = '건강';
let color = 'white';
if (injury > 60) {
text = '위독';
color = 'red';
} else if (injury > 40) {
text = '심각';
color = 'magenta';
} else if (injury > 20) {
text = '중상';
color = 'orange';
} else if (injury > 0) {
text = '경상';
color = 'yellow';
}
$helptext.html(text).css('color', color);
});
$('.export_general').on('click', function () {
const $btn = $(this);
const $general = getGeneralDetail($btn);
const values = exportGeneralInfo($general);
console.log(values);
});
$('.delete-defender').on('click', function () {
const $card = getGeneralFrame($(this));
deleteDefender($card);
});
$('.copy-defender').on('click', function () {
const $card = getGeneralFrame($(this));
copyDefender($card);
});
$('.add-defender').on('click', function () {
addDefender();
});
$('.btn-general-load').on('click', function () {
const $file = $(this).prev();
$file[0].click();
})
$<HTMLInputElement>('.form_load_general_file').on('change', function (e) {
e.preventDefault();
const $this = $(this);
const $card = getGeneralFrame($this);
const files = unwrap(e.target.files);
importGeneralInfoByFile(files, $card);
return false;
});
$('.btn-general-import-server').on('click', function () {
const $this = $(this);
const $card = getGeneralFrame($this);
const $modal = $('#importModal');
$modal.data('target', $card);
if(!modalImport){
modalImport = new Modal($modal[0]);
}
modalImport.show();
});
$('.btn-general-save').on('click', function () {
const $this = $(this);
const $general = getGeneralDetail($this);
const generalData = exportGeneralInfo($general);
const filename = `general_${generalData.name}.json`;
const saveData = JSON.stringify({
objType: 'general',
data: generalData
}, null, 4);
download(saveData, filename, 'application/json');
});
$('.btn-battle-load').on('click', function () {
const $file = $(this).prev();
$file[0].click();
})
$<HTMLInputElement>('.form_load_battle_file').on('change', function (e) {
e.preventDefault();
const files = unwrap(e.target.files);
importBattleInfoByFile(files);
return false;
});
$('.btn-battle-save').on('click', function () {
const battleData = exportAllData();
const dateText = formatTime(new Date(), 'yyyyMMdd_HHmmss');
const filename = `battle_${dateText}.json`;
const saveData = JSON.stringify({
objType: 'battle',
data: battleData
}, null, 4);
download(saveData, filename, 'application/json');
})
const $generals = $('.general_detail');
$generals.on('dragover dragleave', function (e) {
e.stopPropagation()
e.preventDefault()
})
$generals.on('drop', function (e) {
e.preventDefault();
const $this = $(this);
const $card = getGeneralFrame($this);
const files = unwrap((unwrap(e.originalEvent) as DragEvent).dataTransfer).files;
importGeneralInfoByFile(files, $card);
return false;
});
const $battlePad = $('.dragpad_battle');
$battlePad.on('dragover dragleave', function (e) {
e.stopPropagation()
e.preventDefault()
})
$battlePad.on('drop', function (e) {
e.preventDefault();
const files = unwrap((unwrap(e.originalEvent) as DragEvent).dataTransfer).files;
importBattleInfoByFile(files);
return false;
});
const initBasicEvent = function () {
if (window.nation && window.city) {
$(".form_city_level").val(window.city.level);
$(".form_def").val(window.city.def);
$(".form_wall").val(window.city.wall);
$(".form_nation_type").val(window.nation.type);
$(".form_nation_level").val(window.nation.level);
$(".form_tech").val(window.nation.tech / 1000);
if (window.nation.capital == window.city.city) {
$(".attacker_nation .form_is_capital:first").trigger("click");
$(".defender_nation .form_is_capital:first").trigger("click");
} else {
$(".attacker_nation .form_is_capital:last").trigger("click");
$(".defender_nation .form_is_capital:last").trigger("click");
}
} else {
$(".attacker_nation .form_is_capital:last").trigger("click");
$(".defender_nation .form_is_capital:last").trigger("click");
}
const importGeneralInfo = function ($general: JQuery<HTMLElement>, data: GeneralInfo) {
const setVal = function (query: string, val: string | number) {
$general.find(query).val(val).trigger('change');
}
$(".form_injury").on("change", function () {
const $this = $(this) as JQuery<HTMLInputElement>;
const $general = getGeneralDetail($this);
const $helptext = $general.find(".injury_helptext");
setVal('.form_general_name', data.name);
const injury = parseInt($this.val() as string);
//FIXME: PHP 코드와 항상 일치하도록 변경
let text = "건강";
let color = "white";
if (injury > 60) {
text = "위독";
color = "red";
} else if (injury > 40) {
text = "심각";
color = "magenta";
} else if (injury > 20) {
text = "중상";
color = "orange";
} else if (injury > 0) {
text = "경상";
color = "yellow";
}
$helptext.html(text).css("color", color);
});
setVal('.form_officer_level', data.officer_level);
setVal('.form_exp_level', data.explevel);
$(".export_general").on("click", function () {
const $btn = $(this);
const $general = getGeneralDetail($btn);
setVal('.form_leadership', data.leadership);
setVal('.form_general_horse', data.horse);
setVal('.form_strength', data.strength);
setVal('.form_general_weap', data.weapon);
setVal('.form_intel', data.intel);
setVal('.form_general_book', data.book);
setVal('.form_general_item', data.item);
const values = exportGeneralInfo($general);
console.log(values);
});
$(".delete-defender").on("click", function () {
const $card = getGeneralFrame($(this));
deleteDefender($card);
});
$(".copy-defender").on("click", function () {
const $card = getGeneralFrame($(this));
copyDefender($card);
});
setVal('.form_injury', data.injury);
$(".add-defender").on("click", function () {
addDefender();
});
setVal('.form_rice', data.rice);
$(".btn-general-load").on("click", function () {
const $file = $(this).prev();
$file[0].click();
});
setVal('.form_general_character', data.personal);
setVal('.form_general_special_war', data.special2);
$<HTMLInputElement>(".form_load_general_file").on("change", function (e) {
e.preventDefault();
const $this = $(this);
const $card = getGeneralFrame($this);
setVal('.form_crew', data.crew);
setVal('.form_crewtype', data.crewtype);
setVal('.form_atmos', data.atmos);
setVal('.form_train', data.train);
const files = unwrap(e.target.files);
setVal('.form_dex1', data.dex1);
setVal('.form_dex2', data.dex2);
setVal('.form_dex3', data.dex3);
setVal('.form_dex4', data.dex4);
setVal('.form_dex5', data.dex5);
setVal('.form_defence_train', data.defence_train);
importGeneralInfoByFile(files, $card);
return false;
});
setVal('.form_warnum', data.warnum);
setVal('.form_killnum', data.killnum);
setVal('.form_killcrew', data.killcrew);
$(".btn-general-import-server").on("click", function () {
const $this = $(this);
const $card = getGeneralFrame($this);
if (!setGeneralNo($general, data.no)) {
setGeneralNo($general, generateNewGeneralNo());
}
const $modal = $("#importModal");
$modal.data("target", $card);
if (!modalImport) {
modalImport = new Modal($modal[0]);
}
modalImport.show();
});
$(".btn-general-save").on("click", function () {
const $this = $(this);
const $general = getGeneralDetail($this);
const generalData = exportGeneralInfo($general);
const filename = `general_${generalData.name}.json`;
const saveData = JSON.stringify(
{
objType: "general",
data: generalData,
},
null,
4
);
download(saveData, filename, "application/json");
});
$(".btn-battle-load").on("click", function () {
const $file = $(this).prev();
$file[0].click();
});
$<HTMLInputElement>(".form_load_battle_file").on("change", function (e) {
e.preventDefault();
const files = unwrap(e.target.files);
importBattleInfoByFile(files);
return false;
});
$(".btn-battle-save").on("click", function () {
const battleData = exportAllData();
const dateText = formatTime(new Date(), "yyyyMMdd_HHmmss");
const filename = `battle_${dateText}.json`;
const saveData = JSON.stringify(
{
objType: "battle",
data: battleData,
},
null,
4
);
download(saveData, filename, "application/json");
});
const $generals = $(".general_detail");
$generals.on("dragover dragleave", function (e) {
e.stopPropagation();
e.preventDefault();
});
$generals.on("drop", function (e) {
e.preventDefault();
const $this = $(this);
const $card = getGeneralFrame($this);
const files = unwrap((unwrap(e.originalEvent) as DragEvent).dataTransfer).files;
importGeneralInfoByFile(files, $card);
return false;
});
const $battlePad = $(".dragpad_battle");
$battlePad.on("dragover dragleave", function (e) {
e.stopPropagation();
e.preventDefault();
});
$battlePad.on("drop", function (e) {
e.preventDefault();
const files = unwrap((unwrap(e.originalEvent) as DragEvent).dataTransfer).files;
importBattleInfoByFile(files);
return false;
});
};
const importGeneralInfo = function ($general: JQuery<HTMLElement>, data: GeneralInfo) {
const setVal = function (query: string, val: string | number) {
$general.find(query).val(val).trigger("change");
};
setVal(".form_general_name", data.name);
setVal(".form_officer_level", data.officer_level);
setVal(".form_exp_level", data.explevel);
setVal(".form_leadership", data.leadership);
setVal(".form_general_horse", data.horse);
setVal(".form_strength", data.strength);
setVal(".form_general_weap", data.weapon);
setVal(".form_intel", data.intel);
setVal(".form_general_book", data.book);
setVal(".form_general_item", data.item);
setVal(".form_injury", data.injury);
setVal(".form_rice", data.rice);
setVal(".form_general_character", data.personal);
setVal(".form_general_special_war", data.special2);
setVal(".form_crew", data.crew);
setVal(".form_crewtype", data.crewtype);
setVal(".form_atmos", data.atmos);
setVal(".form_train", data.train);
setVal(".form_dex1", data.dex1);
setVal(".form_dex2", data.dex2);
setVal(".form_dex3", data.dex3);
setVal(".form_dex4", data.dex4);
setVal(".form_dex5", data.dex5);
setVal(".form_defence_train", data.defence_train);
setVal(".form_warnum", data.warnum);
setVal(".form_killnum", data.killnum);
setVal(".form_killcrew", data.killcrew);
if (data.inheritBuff) {
setVal(".form_war_avoid_ratio", data.inheritBuff.warAvoidRatio);
setVal(".form_war_critical_ratio", data.inheritBuff.warCriticalRatio);
setVal(".form_war_magic_trial_prob", data.inheritBuff.warMagicTrialProb);
setVal(".form_oppose_war_avoid_ratio", data.inheritBuff.warAvoidRatioOppose);
setVal(".form_oppose_war_critical_ratio", data.inheritBuff.warCriticalRatioOppose);
setVal(".form_oppose_war_magic_trial_prob", data.inheritBuff.warMagicTrialProbOppose);
}
const exportGeneralInfo = function ($general: JQuery<HTMLElement>): GeneralInfo {
const getInt = function (query: string): number {
return parseInt(unwrap_any<string>($general.find(query).val()));
}
if (!setGeneralNo($general, data.no)) {
setGeneralNo($general, generateNewGeneralNo());
}
};
const getVal = function (query: string): string {
return unwrap_any<string>($general.find(query).val());
}
const exportGeneralInfo = function ($general: JQuery<HTMLElement>): GeneralInfo {
const getInt = function (query: string): number {
return parseInt(unwrap_any<string>($general.find(query).val()));
};
return {
no: getGeneralNo($general),
name: getVal('.form_general_name'),
officer_level: getInt('.form_officer_level'),
explevel: getInt('.form_exp_level'),
const getVal = function (query: string): string {
return unwrap_any<string>($general.find(query).val());
};
leadership: getInt('.form_leadership'),
horse: getVal('.form_general_horse'),
strength: getInt('.form_strength'),
weapon: getVal('.form_general_weap'),
intel: getInt('.form_intel'),
book: getVal('.form_general_book'),
item: getVal('.form_general_item'),
return {
no: getGeneralNo($general),
name: getVal(".form_general_name"),
officer_level: getInt(".form_officer_level"),
explevel: getInt(".form_exp_level"),
injury: getInt('.form_injury'),
leadership: getInt(".form_leadership"),
horse: getVal(".form_general_horse"),
strength: getInt(".form_strength"),
weapon: getVal(".form_general_weap"),
intel: getInt(".form_intel"),
book: getVal(".form_general_book"),
item: getVal(".form_general_item"),
rice: getInt('.form_rice'),
injury: getInt(".form_injury"),
personal: getVal('.form_general_character'),
special2: getVal('.form_general_special_war'),
rice: getInt(".form_rice"),
crew: getInt('.form_crew'),
crewtype: getInt('.form_crewtype'),
atmos: getInt('.form_atmos'),
train: getInt('.form_train'),
personal: getVal(".form_general_character"),
special2: getVal(".form_general_special_war"),
dex1: getInt('.form_dex1'),
dex2: getInt('.form_dex2'),
dex3: getInt('.form_dex3'),
dex4: getInt('.form_dex4'),
dex5: getInt('.form_dex5'),
crew: getInt(".form_crew"),
crewtype: getInt(".form_crewtype"),
atmos: getInt(".form_atmos"),
train: getInt(".form_train"),
defence_train: getInt('.form_defence_train'),
dex1: getInt(".form_dex1"),
dex2: getInt(".form_dex2"),
dex3: getInt(".form_dex3"),
dex4: getInt(".form_dex4"),
dex5: getInt(".form_dex5"),
warnum: getInt('.form_warnum'),
killnum: getInt('.form_killnum'),
killcrew: getInt('.form_killcrew'),
defence_train: getInt(".form_defence_train"),
};
warnum: getInt(".form_warnum"),
killnum: getInt(".form_killnum"),
killcrew: getInt(".form_killcrew"),
inheritBuff: {
warAvoidRatio: getInt(".form_war_avoid_ratio"),
warCriticalRatio: getInt(".form_war_critical_ratio"),
warMagicTrialProb: getInt(".form_war_magic_trial_prob"),
warAvoidRatioOppose: getInt(".form_oppose_war_avoid_ratio"),
warCriticalRatioOppose: getInt(".form_oppose_war_critical_ratio"),
warMagicTrialProbOppose: getInt(".form_oppose_war_magic_trial_prob"),
},
};
};
const importBattleInfoByFile = function (files: FileList) {
if (files === null) {
alert("파일 에러!");
return false;
}
const importBattleInfoByFile = function (files: FileList) {
if (files === null) {
alert('파일 에러!');
return false;
}
if (files.length < 1) {
alert("파일 에러!");
return false;
}
if (files.length < 1) {
alert("파일 에러!");
return false;
}
const file = files[0];
if (file.size > 1024 * 1024) {
alert("파일이 너무 큽니다!");
return false;
}
if (file.type === "") {
alert("폴더를 업로드할 수 없습니다!");
return false;
}
const reader = new FileReader();
reader.onload = function () {
let battleData: ExportedInfo;
try {
battleData = JSON.parse(unwrap_any<string>(reader.result));
} catch (e) {
alert("올바르지 않은 파일 형식입니다");
return false;
}
const file = files[0];
if (file.size > 1024 * 1024) {
alert('파일이 너무 큽니다!');
return false;
}
if (file.type === '') {
alert('폴더를 업로드할 수 없습니다!');
return false;
}
if (!("objType" in battleData)) {
alert("파일 형식을 확인할 수 없습니다");
return false;
}
const reader = new FileReader();
reader.onload = function () {
let battleData: ExportedInfo;
try {
battleData = JSON.parse(unwrap_any<string>(reader.result));
} catch (e) {
alert('올바르지 않은 파일 형식입니다');
return false;
}
if (battleData.objType != "battle") {
alert("전투 데이터가 아닙니다");
return false;
}
if (!('objType' in battleData)) {
alert('파일 형식을 확인할 수 없습니다');
return false;
}
importBattleInfo(battleData.data);
return true;
};
if (battleData.objType != 'battle') {
alert('전투 데이터가 아닙니다');
return false;
}
try {
reader.readAsText(file);
} catch (e) {
alert("파일을 읽는데 실패했습니다.");
return false;
}
importBattleInfo(battleData.data);
return true;
};
return true;
};
try {
const importGeneralInfoByFile = function (files: FileList, $card: JQuery<HTMLElement>) {
if ($card === undefined) {
$card = addDefender();
}
reader.readAsText(file);
} catch (e) {
alert('파일을 읽는데 실패했습니다.');
return false;
}
if (files === null) {
alert("파일 에러!");
return false;
}
if (files.length < 1) {
alert("파일 에러!");
return false;
}
const file = files[0];
if (file.size > 1024 * 1024) {
alert("파일이 너무 큽니다!");
return false;
}
if (file.type === "") {
alert("폴더를 업로드할 수 없습니다!");
return false;
}
const reader = new FileReader();
reader.onload = function () {
let generalData: ExportedInfo;
try {
generalData = JSON.parse(unwrap_any<string>(reader.result));
} catch (e) {
alert("올바르지 않은 파일 형식입니다");
return false;
}
if (!("objType" in generalData)) {
alert("파일 형식을 확인할 수 없습니다");
return false;
}
if (generalData.objType == "battle") {
importBattleInfo(generalData.data);
return true;
}
if (generalData.objType != "general") {
alert("장수 데이터가 아닙니다");
return false;
}
$card.find(".form_load_general_file").val("");
importGeneralInfo($card, generalData.data);
return true;
};
try {
reader.readAsText(file);
} catch (e) {
alert("파일을 읽는데 실패했습니다.");
return false;
}
const importGeneralInfoByFile = function (files: FileList, $card: JQuery<HTMLElement>) {
if ($card === undefined) {
$card = addDefender();
}
if (files === null) {
alert('파일 에러!');
return false;
}
if (files.length < 1) {
alert("파일 에러!");
return false;
}
const file = files[0];
if (file.size > 1024 * 1024) {
alert('파일이 너무 큽니다!');
return false;
}
if (file.type === '') {
alert('폴더를 업로드할 수 없습니다!');
return false;
}
const reader = new FileReader();
reader.onload = function () {
let generalData: ExportedInfo;
try {
generalData = JSON.parse(unwrap_any<string>(reader.result));
} catch (e) {
alert('올바르지 않은 파일 형식입니다');
return false;
}
if (!('objType' in generalData)) {
alert('파일 형식을 확인할 수 없습니다');
return false;
}
if (generalData.objType == 'battle') {
importBattleInfo(generalData.data);
return true;
}
if (generalData.objType != 'general') {
alert('장수 데이터가 아닙니다');
return false;
}
$card.find('.form_load_general_file').val('');
importGeneralInfo($card, generalData.data);
return true;
};
try {
reader.readAsText(file);
} catch (e) {
alert('파일을 읽는데 실패했습니다.');
return false;
return true;
};
const extendGeneralInfoForDB = function (generalData: GeneralInfo) {
const dbVal = {
nation: generalData.no <= 1 ? 1 : 2,
city: generalData.no <= 1 ? 1 : 3,
turntime: "2018-08-26 12:00",
special: window.defaultSpecialDomestic,
leadership_exp: 0,
strength_exp: 0,
intel_exp: 0,
gold: 10000,
dedication: 0,
recent_war: "2018-08-26 12:00",
experience: Math.pow(generalData.explevel, 2),
};
return $.extend({}, generalData, dbVal);
};
const getGeneralFrame = function ($btn: JQuery<HTMLElement>) {
const $card = $btn.closest(".general_form");
return $card;
};
const getGeneralDetail = function ($btn: JQuery<HTMLElement>) {
const $card = getGeneralFrame($btn);
const $general = $card.find(".general_detail");
return $general;
};
const getGeneralNo = function ($btn: JQuery<HTMLElement>) {
return parseInt(getGeneralFrame($btn).data("general_no"));
};
const setGeneralNo = function ($btn: JQuery<HTMLElement>, value: number) {
if (value == 1) {
//1번은 무조건 공격자임
return false;
}
if (value in defenderNoList) {
return false;
}
const $card = getGeneralFrame($btn);
$card.data("general_no", value);
defenderNoList[value] = $card;
return true;
};
const generateNewGeneralNo = function () {
for (;;) {
const newGeneralNo = Math.floor(Math.random() * (1 << 24)) + 2;
if (newGeneralNo in defenderNoList) {
continue;
}
return newGeneralNo;
}
};
const deleteGeneralNo = function ($btn: JQuery<HTMLElement>) {
const $card = getGeneralFrame($btn);
$card.removeData("general_no");
const generalNo = getGeneralNo($card);
delete defenderNoList[generalNo];
};
const addDefender = function ($cardAfter?: JQuery<HTMLElement>) {
const $newCard = $('<div class="card mb-2 defender_form general_form"></div>');
if ($cardAfter === undefined) {
$defenderColumn.append($newCard);
} else {
$cardAfter.after($newCard);
}
$newCard.append($defenderHeaderForm.clone(true, true));
$newCard.append($generalForm.clone(true, true));
//$newGeneral = getGeneralDetail($newCard);
setGeneralNo($newCard, generateNewGeneralNo());
return $newCard;
};
const deleteDefender = function ($card: JQuery<HTMLElement>) {
deleteGeneralNo($card);
$card.detach();
};
const copyDefender = function ($card: JQuery<HTMLElement>) {
const $general = getGeneralDetail($card);
const generalData = exportGeneralInfo($general);
const $newObj = addDefender($card);
importGeneralInfo(getGeneralDetail($newObj), generalData);
};
const importBattleInfo = function (battleData: BattleInfo) {
$(".form_load_battle_file").val("");
console.log(battleData);
const $attackerNation = $(".attacker_nation");
const $defenderNation = $(".defender_nation");
const attackerGeneral = battleData.attackerGeneral;
const attackerCity = battleData.attackerCity;
const attackerNation = battleData.attackerNation;
const defenderGenerals = battleData.defenderGenerals;
const defenderCity = battleData.defenderCity;
const defenderNation = battleData.defenderNation;
const seed = battleData.seed;
if (seed) {
$("#seed").val(seed);
}
$("#year").val(battleData.year);
$("#month").val(battleData.month);
$("#repeat_cnt").val(battleData.repeatCnt);
$(".delete-defender").trigger("click");
$attackerNation.find(".form_nation_type").val(attackerNation.type);
$attackerNation.find(".form_tech").val(Math.floor(attackerNation.tech / 1000));
$attackerNation.find(".form_nation_level").val(attackerNation.level);
if (attackerNation.capital == 1) {
$attackerNation.find(".form_is_capital:first").trigger("click");
} else {
$attackerNation.find(".form_is_capital:last").trigger("click");
}
$attackerNation.find(".form_city_level").val(attackerCity.level);
importGeneralInfo($(".attacker_form"), attackerGeneral);
$defenderNation.find(".form_nation_type").val(defenderNation.type);
$defenderNation.find(".form_tech").val(Math.floor(defenderNation.tech / 1000));
$defenderNation.find(".form_nation_level").val(defenderNation.level);
if (defenderNation.capital == 1) {
$defenderNation.find(".form_is_capital:first").trigger("click");
} else {
$defenderNation.find(".form_is_capital:last").trigger("click");
}
$defenderNation.find(".form_city_level").val(defenderCity.level);
$("#city_def").val(defenderCity.def);
$("#city_wall").val(defenderCity.wall);
$.each(defenderGenerals, function (idx, defenderGeneral) {
const $card = addDefender();
importGeneralInfo($card, defenderGeneral);
});
};
const exportAllData = function (): BattleInfo {
const $attackerNation = $(".attacker_nation");
const $defenderNation = $(".defender_nation");
const attackerGeneral = exportGeneralInfo($(".attacker_form"));
const attackerCity = {
level: parseInt($attackerNation.find(".form_city_level").val() as string),
};
const attackerNation = {
type: unwrap_any<string>($attackerNation.find(".form_nation_type").val()),
tech: parseInt($attackerNation.find(".form_tech").val() as string) * 1000,
level: parseInt($attackerNation.find(".form_nation_level").val() as string),
capital: $attackerNation.find(".form_is_capital:checked").val() == "1" ? 1 : 2,
};
const defenderGenerals = $(".defender_form")
.map(function () {
return exportGeneralInfo($(this));
})
.toArray();
const defenderCity = {
def: parseInt($("#city_def").val() as string),
wall: parseInt($("#city_wall").val() as string),
level: parseInt($defenderNation.find(".form_city_level").val() as string),
};
const defenderNation = {
type: unwrap_any<string>($defenderNation.find(".form_nation_type").val()),
tech: parseInt(unwrap_any<string>($defenderNation.find(".form_tech").val())) * 1000,
level: parseInt(unwrap_any<string>($defenderNation.find(".form_nation_level").val())),
capital: $defenderNation.find(".form_is_capital:checked").val() == "1" ? 3 : 4,
};
const year = parseInt(unwrap_any<string>($("#year").val()));
const month = parseInt(unwrap_any<string>($("#month").val()));
const repeatCnt = parseInt(unwrap_any<string>($("#repeat_cnt").val()));
const seed = unwrap_any<string>($("#seed").val());
return {
attackerGeneral: attackerGeneral,
attackerCity: attackerCity,
attackerNation: attackerNation,
defenderGenerals: defenderGenerals,
defenderCity: defenderCity,
defenderNation: defenderNation,
year: year,
month: month,
repeatCnt: repeatCnt,
seed,
};
};
const extendAllDataForDB = function (allData: BattleInfo): BattleInfo {
const defaultNation = {
nation: 0,
name: "재야",
capital: 0,
level: 0,
gold: 0,
rice: 2000,
type: "None",
tech: 0,
gennum: 200,
};
const defaultCity = {
nation: 0,
supply: 1,
name: "도시",
pop: 500000,
agri: 10000,
comm: 10000,
secu: 10000,
def: 1000,
wall: 1000,
trust: 100,
pop_max: 600000,
agri_max: 12000,
comm_max: 12000,
secu_max: 10000,
def_max: 12000,
wall_max: 12000,
dead: 0,
state: 0,
conflict: "{}",
};
const attackerNation = $.extend({}, defaultNation, allData.attackerNation);
attackerNation.nation = 1;
attackerNation.name = "출병국";
const attackerCity = $.extend({}, defaultCity, allData.attackerCity) as CityBasicInfo;
attackerCity.nation = 1;
attackerCity.city = 1;
const attackerGeneral = extendGeneralInfoForDB(allData.attackerGeneral);
if (2 <= attackerGeneral.officer_level && attackerGeneral.officer_level <= 4) {
attackerGeneral.officer_city = 1;
} else {
attackerGeneral.officer_city = 0;
}
const defenderNation = $.extend({}, defaultNation, allData.defenderNation);
defenderNation.nation = 2;
defenderNation.name = "수비국";
const defenderCity = $.extend({}, defaultCity, allData.defenderCity);
defenderCity.nation = 2;
defenderCity.city = 3;
defenderCity.wall_max = Math.floor((defenderCity.wall / 5) * 6);
defenderCity.def_max = Math.floor((defenderCity.def / 5) * 6);
const defenderGenerals: GeneralInfo[] = [];
$.each(allData.defenderGenerals, function () {
const defenderGeneral = extendGeneralInfoForDB(this);
if (2 <= defenderGeneral.officer_level && defenderGeneral.officer_level <= 4) {
defenderGeneral.officer_city = 3;
} else {
defenderGeneral.officer_city = 0;
}
defenderGenerals.push(defenderGeneral);
});
return $.extend({}, allData, {
attackerGeneral: attackerGeneral,
attackerCity: attackerCity,
attackerNation: attackerNation,
defenderGenerals: defenderGenerals,
defenderCity: defenderCity,
defenderNation: defenderNation,
});
};
const parseSkillCount = function (skills: Record<string, number>) {
const result: string[] = [];
for (const [key, value] of Object.entries(skills)) {
result.push(`${key}(${toPretty(value)}회)`);
}
if (result.length == 0) {
return "-";
}
return result.join(", ");
};
const toPretty = function (number: number) {
if (isInteger(number)) {
number = Math.floor(number);
} else {
number = parseFloat(number.toFixed(2));
}
return numberWithCommas(number);
};
const showBattleResult = function (result: BattleResult) {
$("#result_datetime").html(result.datetime);
$("#result_warcnt").html(toPretty(result.avgWar));
$("#result_phase").html(toPretty(result.phase));
$("#result_killed").html(toPretty(result.killed));
if (result.minKilled != result.maxKilled) {
$("#result_maxKilled").html(toPretty(result.maxKilled));
$("#result_minKilled").html(toPretty(result.minKilled));
$("#result_varKilled").show();
} else {
$("#result_varKilled").hide();
}
$("#result_dead").html(toPretty(result.dead));
if (result.minDead != result.maxDead) {
$("#result_maxDead").html(toPretty(result.maxDead));
$("#result_minDead").html(toPretty(result.minDead));
$("#result_varDead").show();
} else {
$("#result_varDead").hide();
}
$("#result_attackerRice").html(toPretty(result.attackerRice));
$("#result_defenderRice").html(toPretty(result.defenderRice));
$("#result_attackerSkills").html(parseSkillCount(result.attackerSkills));
$(".result_defenderSkills").detach();
const $summary = $("#battle_result_summary");
for (const [idx, defenderSkills] of Object.entries(result.defendersSkills)) {
console.log(defenderSkills);
const $result = $(`<tr class='result_defenderSkills'><th>수비자${parseInt(idx) + 1} 스킬</th><td></td></tr>`);
$result.find("td").html(parseSkillCount(defenderSkills));
$summary.append($result);
}
$("#generalBattleResultLog").html(result.lastWarLog.generalBattleResultLog);
$("#generalBattleDetailLog").html(result.lastWarLog.generalBattleDetailLog);
};
const beginBattle = function () {
const data = extendAllDataForDB(exportAllData());
console.log(data);
$.ajax({
type: "post",
url: "j_simulate_battle.php",
dataType: "json",
data: {
action: "battle",
query: JSON.stringify(data),
},
}).then(
function (result) {
console.log(result);
if (!result.result) {
alert(result.reason);
return;
}
showBattleResult(result);
},
function () {
alert("전투 개시 실패!");
}
);
};
const reorderDefender = function (defenderOrder: number[]) {
for (const generalNo of defenderOrder) {
if (!(generalNo in defenderNoList)) {
//음..?
alert(`${generalNo}이 수비자 리스트에 없습니다. 버그인 듯 합니다.`);
return true;
}
const $defenderObj = defenderNoList[generalNo];
$defenderObj.detach();
$defenderColumn.append($defenderObj);
}
};
const extendGeneralInfoForDB = function (generalData: GeneralInfo) {
const dbVal = {
nation: (generalData.no) <= 1 ? 1 : 2,
city: (generalData.no) <= 1 ? 1 : 3,
turntime: '2018-08-26 12:00',
special: window.defaultSpecialDomestic,
leadership_exp: 0,
strength_exp: 0,
intel_exp: 0,
gold: 10000,
dedication: 0,
recent_war: '2018-08-26 12:00',
experience: Math.pow(generalData.explevel, 2),
};
return $.extend({}, generalData, dbVal);
}
const getGeneralFrame = function ($btn: JQuery<HTMLElement>) {
const $card = $btn.closest('.general_form');
return $card;
}
const getGeneralDetail = function ($btn: JQuery<HTMLElement>) {
const $card = getGeneralFrame($btn);
const $general = $card.find('.general_detail');
return $general;
}
const getGeneralNo = function ($btn: JQuery<HTMLElement>) {
return parseInt(getGeneralFrame($btn).data('general_no'));
}
const setGeneralNo = function ($btn: JQuery<HTMLElement>, value: number) {
if (value == 1) {
//1번은 무조건 공격자임
return false;
const requestReorderDefender = function () {
const data = extendAllDataForDB(exportAllData());
console.log(data);
$.ajax({
type: "post",
url: "j_simulate_battle.php",
dataType: "json",
data: {
action: "reorder",
query: JSON.stringify(data),
},
}).then(
function (result) {
console.log(result);
if (!result.result) {
alert(result.reason);
return;
}
if (value in defenderNoList) {
return false;
}
const $card = getGeneralFrame($btn);
$card.data('general_no', value);
defenderNoList[value] = $card;
return true;
}
reorderDefender(result.order);
},
function () {
alert("재정렬 실패!");
}
);
};
const generateNewGeneralNo = function () {
for (; ;) {
const newGeneralNo = Math.floor(Math.random() * (1 << 24)) + 2;
if (newGeneralNo in defenderNoList) {
continue;
}
return newGeneralNo;
}
}
let initGeneralList = false;
const deleteGeneralNo = function ($btn: JQuery<HTMLElement>) {
const $card = getGeneralFrame($btn);
$card.removeData('general_no');
const generalNo = getGeneralNo($card);
delete defenderNoList[generalNo];
}
$("#importFromDB").on("click", function () {
const generalID = $("#modalSelector").val();
console.log(generalID);
const addDefender = function ($cardAfter?: JQuery<HTMLElement>) {
const $newCard = $('<div class="card mb-2 defender_form general_form"></div>');
$.post({
url: "j_export_simulator_object.php",
dataType: "json",
data: {
destGeneralID: generalID,
},
}).then(function (data) {
if (!data.result) {
alert(data.reason);
return false;
}
if ($cardAfter === undefined) {
$defenderColumn.append($newCard);
} else {
$cardAfter.after($newCard);
}
const $modal = $("#importModal");
const $card = $modal.data("target");
importGeneralInfo($card, data.general);
$newCard.append($defenderHeaderForm.clone(true, true));
$newCard.append($generalForm.clone(true, true));
if (modalImport) {
modalImport.hide();
}
}, errUnknown);
});
//$newGeneral = getGeneralDetail($newCard);
setGeneralNo($newCard, generateNewGeneralNo());
unwrap(document.querySelector("#importModal")).addEventListener("show.bs.modal", function () {
if (!initGeneralList) {
const $list = $("#modalSelector");
return $newCard;
}
const deleteDefender = function ($card: JQuery<HTMLElement>) {
deleteGeneralNo($card);
$card.detach();
}
const copyDefender = function ($card: JQuery<HTMLElement>) {
const $general = getGeneralDetail($card);
const generalData = exportGeneralInfo($general);
const $newObj = addDefender($card);
importGeneralInfo(getGeneralDetail($newObj), generalData);
}
const importBattleInfo = function (battleData: BattleInfo) {
$('.form_load_battle_file').val('');
console.log(battleData);
const $attackerNation = $('.attacker_nation');
const $defenderNation = $('.defender_nation');
const attackerGeneral = battleData.attackerGeneral;
const attackerCity = battleData.attackerCity;
const attackerNation = battleData.attackerNation;
const defenderGenerals = battleData.defenderGenerals;
const defenderCity = battleData.defenderCity;
const defenderNation = battleData.defenderNation;
$('#year').val(battleData.year);
$('#month').val(battleData.month);
$('#repeat_cnt').val(battleData.repeatCnt);
$('.delete-defender').trigger('click');
$attackerNation.find('.form_nation_type').val(attackerNation.type);
$attackerNation.find('.form_tech').val(Math.floor(attackerNation.tech / 1000));
$attackerNation.find('.form_nation_level').val(attackerNation.level);
if (attackerNation.capital == 1) {
$attackerNation.find('.form_is_capital:first').trigger('click');
} else {
$attackerNation.find('.form_is_capital:last').trigger('click');
}
$attackerNation.find('.form_city_level').val(attackerCity.level);
importGeneralInfo($('.attacker_form'), attackerGeneral);
$defenderNation.find('.form_nation_type').val(defenderNation.type);
$defenderNation.find('.form_tech').val(Math.floor(defenderNation.tech / 1000));
$defenderNation.find('.form_nation_level').val(defenderNation.level);
if (defenderNation.capital == 1) {
$defenderNation.find('.form_is_capital:first').trigger('click');
} else {
$defenderNation.find('.form_is_capital:last').trigger('click');
}
$defenderNation.find('.form_city_level').val(defenderCity.level);
$('#city_def').val(defenderCity.def);
$('#city_wall').val(defenderCity.wall);
$.each(defenderGenerals, function (idx, defenderGeneral) {
const $card = addDefender();
importGeneralInfo($card, defenderGeneral);
const addNation = function (generalList: GeneralInfo[], nationName: string, color: string) {
generalList.sort(function (lhs, rhs) {
if (lhs.npc != rhs.npc) {
return (lhs.npc ?? 0) - (rhs.npc ?? 0);
}
if (lhs.name < rhs.name) {
return -1;
}
if (lhs.name > rhs.name) {
return 1;
}
return 0;
});
}
const $optGroup = $(`<optgroup label="${nationName}"></optgroup>`);
$optGroup.css("background-color", color);
$optGroup.css("color", isBrightColor(color) ? "black" : "white");
const exportAllData = function (): BattleInfo {
const $attackerNation = $('.attacker_nation');
const $defenderNation = $('.defender_nation');
const attackerGeneral = exportGeneralInfo($('.attacker_form'));
const attackerCity = {
level: parseInt($attackerNation.find('.form_city_level').val() as string),
};
const attackerNation = {
type: unwrap_any<string>($attackerNation.find('.form_nation_type').val()),
tech: parseInt($attackerNation.find('.form_tech').val() as string) * 1000,
level: parseInt($attackerNation.find('.form_nation_level').val() as string),
capital: $attackerNation.find('.form_is_capital:checked').val() == '1' ? 1 : 2,
for (const general of generalList) {
const $item = $(`<option value="${general.no}">${general.name}</option>`);
if (general.npc) {
$item.css("color", unwrap(getNPCColor(general.npc)));
} else {
$item.css("color", "white");
}
$item.css("background-color", "black");
$optGroup.append($item);
}
$list.append($optGroup);
};
$.post({
url: "j_get_basic_general_list.php",
dataType: "json",
data: {
req: 2,
},
}).then(function (data: BasicGeneralListResponse | InvalidResponse) {
if (!data.result) {
alert(data.reason);
if (modalImport) {
modalImport.hide();
}
return false;
}
const defenderGenerals = $('.defender_form').map(function () {
return exportGeneralInfo($(this));
}).toArray();
const nations = data.nation;
const myNationID = data.nationID;
const defenderCity = {
def: parseInt($('#city_def').val() as string),
wall: parseInt($('#city_wall').val() as string),
level: parseInt($defenderNation.find('.form_city_level').val() as string),
};
$list.empty();
//자국 먼저
const defenderNation = {
type: unwrap_any<string>($defenderNation.find('.form_nation_type').val()),
tech: parseInt(unwrap_any<string>($defenderNation.find('.form_tech').val())) * 1000,
level: parseInt(unwrap_any<string>($defenderNation.find('.form_nation_level').val())),
capital: $defenderNation.find('.form_is_capital:checked').val() == '1' ? 3 : 4,
}
const year = parseInt(unwrap_any<string>($('#year').val()));
const month = parseInt(unwrap_any<string>($('#month').val()));
const repeatCnt = parseInt(unwrap_any<string>($('#repeat_cnt').val()));
return {
attackerGeneral: attackerGeneral,
attackerCity: attackerCity,
attackerNation: attackerNation,
defenderGenerals: defenderGenerals,
defenderCity: defenderCity,
defenderNation: defenderNation,
year: year,
month: month,
repeatCnt: repeatCnt,
};
}
const extendAllDataForDB = function (allData: BattleInfo): BattleInfo {
const defaultNation = {
if (0 in data.list) {
nations[0] = {
nation: 0,
name: '재야',
capital: 0,
level: 0,
gold: 0,
rice: 2000,
type: 'None',
tech: 0,
gennum: 200,
};
const defaultCity = {
nation: 0,
supply: 1,
name: '도시',
pop: 500000,
agri: 10000,
comm: 10000,
secu: 10000,
def: 1000,
wall: 1000,
trust: 100,
pop_max: 600000,
agri_max: 12000,
comm_max: 12000,
secu_max: 10000,
def_max: 12000,
wall_max: 12000,
dead: 0,
state: 0,
conflict: '{}',
};
const attackerNation = $.extend({}, defaultNation, allData.attackerNation);
attackerNation.nation = 1;
attackerNation.name = '출병국';
const attackerCity = $.extend({}, defaultCity, allData.attackerCity) as CityBasicInfo;
attackerCity.nation = 1;
attackerCity.city = 1;
const attackerGeneral = extendGeneralInfoForDB(allData.attackerGeneral);
if (2 <= attackerGeneral.officer_level && attackerGeneral.officer_level <= 4) {
attackerGeneral.officer_city = 1;
} else {
attackerGeneral.officer_city = 0;
name: "재야",
color: "#000000",
};
}
const defenderNation = $.extend({}, defaultNation, allData.defenderNation);
defenderNation.nation = 2;
defenderNation.name = '수비국';
addNation(
combineArray(data.list[myNationID], data.column) as GeneralInfo[],
nations[myNationID].name,
nations[myNationID].color
);
const defenderCity = $.extend({}, defaultCity, allData.defenderCity);
defenderCity.nation = 2;
defenderCity.city = 3;
defenderCity.wall_max = Math.floor(defenderCity.wall / 5 * 6);
defenderCity.def_max = Math.floor(defenderCity.def / 5 * 6);
const defenderGenerals: GeneralInfo[] = [];
$.each(allData.defenderGenerals, function () {
const defenderGeneral = extendGeneralInfoForDB(this);
if (2 <= defenderGeneral.officer_level && defenderGeneral.officer_level <= 4) {
defenderGeneral.officer_city = 3;
} else {
defenderGeneral.officer_city = 0;
}
defenderGenerals.push(defenderGeneral);
});
return $.extend({}, allData, {
attackerGeneral: attackerGeneral,
attackerCity: attackerCity,
attackerNation: attackerNation,
defenderGenerals: defenderGenerals,
defenderCity: defenderCity,
defenderNation: defenderNation,
});
for (const nationID of Object.keys(data.list)) {
if (parseInt(nationID) == myNationID) {
continue;
}
addNation(
combineArray(data.list[nationID], data.column) as GeneralInfo[],
nations[nationID].name,
nations[nationID].color
);
}
initGeneralList = true;
}, errUnknown);
}
});
const parseSkillCount = function (skills: Record<string, number>) {
const result: string[] = [];
for (const [key, value] of Object.entries(skills)) {
result.push(`${key}(${toPretty(value)}회)`);
}
initBasicEvent();
$attackerCard.append($generalForm.clone(true, true));
addDefender();
if (result.length == 0) {
return '-';
}
return result.join(', ');
}
$(".btn-begin_battle").on("click", function () {
beginBattle();
});
const toPretty = function (number: number) {
if (isInteger(number)) {
number = Math.floor(number);
} else {
number = parseFloat(number.toFixed(2));
}
return numberWithCommas(number);
}
const showBattleResult = function (result: BattleResult) {
$('#result_datetime').html(result.datetime);
$('#result_warcnt').html(toPretty(result.avgWar));
$('#result_phase').html(toPretty(result.phase));
$('#result_killed').html(toPretty(result.killed));
if (result.minKilled != result.maxKilled) {
$('#result_maxKilled').html(toPretty(result.maxKilled));
$('#result_minKilled').html(toPretty(result.minKilled));
$('#result_varKilled').show();
} else {
$('#result_varKilled').hide();
}
$('#result_dead').html(toPretty(result.dead));
if (result.minDead != result.maxDead) {
$('#result_maxDead').html(toPretty(result.maxDead));
$('#result_minDead').html(toPretty(result.minDead));
$('#result_varDead').show();
} else {
$('#result_varDead').hide();
}
$('#result_attackerRice').html(toPretty(result.attackerRice));
$('#result_defenderRice').html(toPretty(result.defenderRice));
$('#result_attackerSkills').html(parseSkillCount(result.attackerSkills));
$('.result_defenderSkills').detach();
const $summary = $('#battle_result_summary');
for (const [idx, defenderSkills] of Object.entries(result.defendersSkills)) {
console.log(defenderSkills);
const $result = $(`<tr class='result_defenderSkills'><th>수비자${parseInt(idx) + 1} 스킬</th><td></td></tr>`);
$result.find('td').html(parseSkillCount(defenderSkills));
$summary.append($result);
}
$('#generalBattleResultLog').html(result.lastWarLog.generalBattleResultLog);
$('#generalBattleDetailLog').html(result.lastWarLog.generalBattleDetailLog);
}
const beginBattle = function () {
const data = extendAllDataForDB(exportAllData());
console.log(data);
$.ajax({
type: 'post',
url: 'j_simulate_battle.php',
dataType: 'json',
data: {
action: 'battle',
query: JSON.stringify(data),
}
}).then(function (result) {
console.log(result);
if (!result.result) {
alert(result.reason);
return;
}
showBattleResult(result);
}, function () {
alert('전투 개시 실패!');
});
}
const reorderDefender = function (defenderOrder: number[]) {
for (const generalNo of defenderOrder) {
if (!(generalNo in defenderNoList)) {
//음..?
alert(`${generalNo}이 수비자 리스트에 없습니다. 버그인 듯 합니다.`);
return true;
}
const $defenderObj = defenderNoList[generalNo];
$defenderObj.detach();
$defenderColumn.append($defenderObj);
}
}
const requestReorderDefender = function () {
const data = extendAllDataForDB(exportAllData());
console.log(data);
$.ajax({
type: 'post',
url: 'j_simulate_battle.php',
dataType: 'json',
data: {
action: 'reorder',
query: JSON.stringify(data),
}
}).then(function (result) {
console.log(result);
if (!result.result) {
alert(result.reason);
return;
}
reorderDefender(result.order);
}, function () {
alert('재정렬 실패!');
});
}
let initGeneralList = false;
$('#importFromDB').on('click', function () {
const generalID = $('#modalSelector').val();
console.log(generalID);
$.post({
url: 'j_export_simulator_object.php',
dataType: 'json',
data: {
destGeneralID: generalID
}
}).then(function (data) {
if (!data.result) {
alert(data.reason);
return false;
}
const $modal = $('#importModal');
const $card = $modal.data('target');
importGeneralInfo($card, data.general);
if(modalImport){
modalImport.hide();
}
}, errUnknown);
});
unwrap(document.querySelector('#importModal')).addEventListener('show.bs.modal', function () {
if (!initGeneralList) {
const $list = $('#modalSelector');
const addNation = function (generalList: GeneralInfo[], nationName: string, color: string) {
generalList.sort(function (lhs, rhs) {
if (lhs.npc != rhs.npc) {
return (lhs.npc ?? 0) - (rhs.npc ?? 0);
}
if (lhs.name < rhs.name) {
return -1;
}
if (lhs.name > rhs.name) {
return 1;
}
return 0;
})
const $optGroup = $(`<optgroup label="${nationName}"></optgroup>`);
$optGroup.css('background-color', color);
$optGroup.css('color', isBrightColor(color) ? 'black' : 'white');
for (const general of generalList) {
const $item = $(`<option value="${general.no}">${general.name}</option>`);
if (general.npc) {
$item.css('color', unwrap(getNPCColor(general.npc)));
} else {
$item.css('color', 'white');
}
$item.css('background-color', 'black');
$optGroup.append($item);
}
$list.append($optGroup);
}
$.post({
url: 'j_get_basic_general_list.php',
dataType: 'json',
data: {
req: 2,
}
}).then(function (data: BasicGeneralListResponse | InvalidResponse) {
if (!data.result) {
alert(data.reason);
if(modalImport){
modalImport.hide();
}
return false;
}
const nations = data.nation;
const myNationID = data.nationID;
$list.empty();
//자국 먼저
if (0 in data.list) {
nations[0] = {
nation: 0,
name: '재야',
color: '#000000'
};
}
addNation(combineArray(data.list[myNationID], data.column) as GeneralInfo[], nations[myNationID].name, nations[myNationID].color);
for (const nationID of Object.keys(data.list)) {
if (parseInt(nationID) == myNationID) {
continue;
}
addNation(combineArray(data.list[nationID], data.column) as GeneralInfo[], nations[nationID].name, nations[nationID].color);
}
initGeneralList = true;
}, errUnknown);
}
});
initBasicEvent();
$attackerCard.append($generalForm.clone(true, true));
addDefender();
$('.btn-begin_battle').on('click', function () {
beginBattle();
});
$('.btn-reorder_defender').on('click', function () {
requestReorderDefender();
})
});
$(".btn-reorder_defender").on("click", function () {
requestReorderDefender();
});
});
-1
View File
@@ -1,6 +1,5 @@
{
"ingame": {
"troop": "troop.ts",
"map": "map.ts",
"install_db": "install_db.ts",
"install": "install.ts",
@@ -225,6 +225,8 @@
<i class="bi bi-clipboard-fill" />&nbsp;붙여넣기
</BDropdownItem>
<BDropdownDivider />
<BDropdownItem @click="clipboardTextCopy"> <i class="bi bi-files" />&nbsp;텍스트 복사 </BDropdownItem>
<BDropdownDivider />
<BDropdownItem @click="setStoredActions">
<i class="bi bi-bookmark-plus-fill" />&nbsp;보관하기
</BDropdownItem>
@@ -621,6 +623,37 @@ async function clipboardPaste(releaseSelect = true) {
return result;
}
const removeTagRegEx = /<[^>]*>?/g;
function clipboardTextCopy(releaseSelect = true) {
const rawActions = queryActionHelper.extractQueryActions();
if (rawActions.length === 0) {
return;
}
const actions = queryActionHelper.amplifyQueryActions(rawActions, queryActionHelper.getSelectedTurnList());
if (actions.length === 0) {
return;
}
const turnBriefs: [number, string][] = [];
for (const action of actions) {
const [turnIdxList, turnObj] = action;
for (const turnIdx of turnIdxList) {
turnBriefs.push([turnIdx, `${turnIdx + 1}${turnObj.brief.replace(removeTagRegEx, "")}`]);
}
}
turnBriefs.sort((a, b) => a[0] - b[0]);
const text = turnBriefs.map(([, brief]) => brief).join("\n");
void navigator.clipboard.writeText(text);
if (releaseSelect) {
queryActionHelper.releaseSelectedTurnList();
}
}
async function subRepeatCommand(releaseSelect = true): Promise<boolean> {
const reqTurnList = queryActionHelper.getSelectedTurnList();
const selectedMinTurnIdx = reqTurnList[0];
+67
View File
@@ -0,0 +1,67 @@
export type MsgType = "private" | "public" | "national" | "diplomacy";
export const minMsgSeq: Record<MsgType, number> = {
private: 0x7fffffff,
public: 0x7fffffff,
national: 0x7fffffff,
diplomacy: 0x7fffffff,
};
export type MsgTarget = {
id: number;
name: string;
nation_id: number; //XXX: 왜 이 값은 nationID가 아니고 nation_id인가?
nation: string;
color: string;
icon: string;
};
export type MsgItem = {
id: number;
msgType: MsgType;
src: MsgTarget;
dest?: MsgTarget;
text: string;
option: Record<string, string | number>;
time: string;
};
export type MsgPrintItem = MsgItem & {
generalName: string;
nationID: number;
nationType: "local" | "src" | "dest";
myGeneralID: number;
allowButton: boolean;
last5min: string;
now: string;
invalidType: "msg_invalid" | "msg_valid";
deletable: boolean;
src: MsgTarget & { colorType: "bright" | "dark" };
dest: MsgTarget & { colorType: "bright" | "dark" };
defaultIcon: string;
};
export type MsgResponse = {
[v in MsgType]: MsgItem[];
} & {
result: true;
keepRecent: boolean;
nationID: number;
generalName: string;
sequence: number;
};
export type MailboxItem = {
id: number,
mailbox: number,
color: string,
name: string,
nationID: number,
//nation: string,
general: [number, string, number][]
}
export type MabilboxListResponse = {
result: true,
nation: MailboxItem[]
}
+3 -3
View File
@@ -98,7 +98,7 @@ type ReservedGameInfo = {
scenarioName: string,
turnterm: number,
fictionMode: '가상' | '사실',
block_general_create: boolean,
block_general_create: number,
npcMode: '불가' | '가능' | '선택 생성',
openDatetime: string,
starttime: string,
@@ -119,7 +119,7 @@ type GameInfo = {
turntime: string,
join_mode: string,
fictionMode: '가상' | '사실',
block_general_create: boolean,
block_general_create: number,
autorun_user: string,
userCnt: number,
npcCnt: number,
@@ -271,7 +271,7 @@ async function Entrance_drawServerList(serverInfos: ServerResponseItem[]) {
$serverHtml.append(
TemplateEngine(serverCreateTemplate, {
serverPath: serverPath,
canCreate: !game.block_general_create,
canCreate: !(game.block_general_create & 1),
canSelectNPC: game.npcMode == '가능',
canSelectPool: game.npcMode == '선택 생성'
})
+68 -82
View File
@@ -6,7 +6,7 @@ import axios from 'axios';
import { convertFormData } from '@util/convertFormData';
import { isBrightColor } from "@util/isBrightColor";
import { unwrap } from '@util/unwrap';
import _ from 'lodash-es';
import _, { isError, isString } from 'lodash-es';
import { addMinutes } from 'date-fns';
import { parseTime } from '@util/parseTime';
import { formatTime } from '@util/formatTime';
@@ -15,6 +15,7 @@ import { isNotNull } from '@util/isNotNull';
import { unwrap_any } from '@util/unwrap_any';
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
import { exportWindow } from '@util/exportWindow';
import { SammoAPI } from './SammoAPI';
const messageTemplate = `<div
class="msg_plate msg_plate_<%msgType%> msg_plate_<%nationType%>"
@@ -164,37 +165,33 @@ type BasicInfoResponse = {
let generalList: Record<number, BasicGeneralTarget> = {};
async function responseMessage(msgID: number, responseAct: boolean): Promise<void> {
const response = await axios({
url: 'j_msg_decide_opt.php',
method: 'post',
responseType: 'json',
data: convertFormData({
data: JSON.stringify({
msgID: msgID,
response: responseAct
})
})
});
const result: InvalidResponse = response.data;
if (!result.result) {
alert(result.reason);
try{
await SammoAPI.Message.DecideMessageResponse({msgID, response: responseAct});
location.reload();
}
catch(e){
if(isString(e)){
alert(e);
}
if(isError(e)){
alert(e.message);
}
console.error(e);
}
location.reload();
}
async function deleteMessage(msgID: number): Promise<MsgResponse> {
const response = await axios({
url: 'j_msg_delete.php',
method: 'post',
responseType: 'json',
data: convertFormData({
msgID: msgID,
})
});
const result: InvalidResponse | MsgResponse = response.data;
if (!result.result) {
throw result.reason;
try{
await SammoAPI.Message.DeleteMessage({msgID});
}
catch(e){
if(isString(e)){
alert(e);
}
if(isError(e)){
alert(e.message);
}
console.error(e);
}
return await refreshMsg();
}
@@ -206,37 +203,34 @@ export async function refreshMsg(): Promise<MsgResponse> {
exportWindow(refreshMsg, 'refreshMsg');
async function fetchRecentMsg(): Promise<MsgResponse> {
const response = await axios({
url: 'j_msg_get_recent.php',
method: 'post',
responseType: 'json',
data: convertFormData({
sequence: lastSequence ?? 0
})
});
const result: InvalidResponse | MsgResponse = response.data;
if (!result.result) {
throw result.reason;
try {
console.log(lastSequence);
return await SammoAPI.Message.GetRecentMessage({sequence: lastSequence ?? -1});
}
catch (e) {
console.error(e);
throw e;
}
return result;
}
async function showOldMsg(msgType: MsgType): Promise<MsgResponse> {
const response = await axios({
url: 'j_msg_get_old.php',
responseType: 'json',
method: 'post',
data: convertFormData({
to: minMsgSeq[msgType],
try{
const response = await SammoAPI.Message.GetOldMessage({
type: msgType,
})
});
const result: InvalidResponse | MsgResponse = response.data;
if (!result.result) {
throw result.reason;
to: minMsgSeq[msgType],
});
return redrawMsg(response, false);
}
catch(e){
if(isString(e)){
alert(e);
}
if(isError(e)){
alert(e.message);
}
console.error(e);
throw e;
}
return redrawMsg(result, false);
}
function redrawMsg(msgResponse: MsgResponse, addFront: boolean): MsgResponse {
@@ -419,26 +413,26 @@ function redrawMsg(msgResponse: MsgResponse, addFront: boolean): MsgResponse {
return null;
}
$msg.find('.btn-delete-msg').click(function () {
$msg.find('.btn-delete-msg').on('click', async function () {
if (!confirm("삭제하시겠습니까?")) {
return false;
}
void deleteMessage(msg.id);
await deleteMessage(msg.id);
});
$msg.find('button.prompt_yes').click(function () {
$msg.find('button.prompt_yes').on('click', async function () {
if (!confirm("수락하시겠습니까?")) {
return false;
}
void responseMessage(msg.id, true);
await responseMessage(msg.id, true);
});
$msg.find('button.prompt_no').click(function () {
$msg.find('button.prompt_no').on('click', async function () {
if (!confirm("거절하시겠습니까?")) {
return false;
}
void responseMessage(msg.id, false);
await responseMessage(msg.id, false);
});
if ($existMsg.length) {
@@ -641,30 +635,26 @@ function activateMessageForm() {
}
});
$msgSubmit.click(async function () {
$msgSubmit.on('click', async function () {
const text = $.trim(unwrap_any<string>($msgInput.val()));
$msgInput.val('').focus();
const text = _.trim(unwrap_any<string>($msgInput.val()));
$msgInput.val('').trigger('focus');
const targetMailbox = unwrap_any<string>($mailboxList.val());
console.log(targetMailbox, text);
const response = await axios({
url: 'j_msg_submit.php',
method: 'post',
responseType: 'json',
data: convertFormData({
data: JSON.stringify({
mailbox: parseInt(targetMailbox),
text: text
})
})
})
const result: InvalidResponse = response.data;
if (!result.result) {
alert(result.reason);
try{
await SammoAPI.Message.SendMessage({
mailbox: parseInt(targetMailbox),
text,
});
await refreshMsg();
}
catch(e){
alert(e);
await refreshMsg();
return;
}
await refreshMsg();
});
}
@@ -679,11 +669,7 @@ $(async function ($) {
}).then((v) => registerGlobal(v.data));
//sender_list.json 은 서버측에선 캐시 가능한 데이터임.
const senderListP = axios({
url: 'j_msg_contact_list.php',
method: 'post',
responseType: 'json'
}).then(v => v.data);
const senderListP = SammoAPI.Message.GetContactList();
const messageListP = fetchRecentMsg();

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