From c379ecf317dc8fd8c13063c4b2ebf5dd8b6a1b91 Mon Sep 17 00:00:00 2001 From: Hide_D Date: Mon, 18 Apr 2022 03:05:08 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=97=B0=EA=B0=90=EC=9D=84=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8A=94=20API=20-=20Global/GetHistory=20-?= =?UTF-8?q?=20=EA=B3=BC=EA=B1=B0=20=EA=B8=B0=EB=A1=9D.=20=EC=BA=90?= =?UTF-8?q?=EC=8B=B1=20=EA=B0=80=EB=8A=A5=20-=20GetCurrentHistory=20-=20?= =?UTF-8?q?=ED=98=84=EC=9E=AC=20=EA=B8=B0=EB=A1=9D.=20=EC=BA=90=EC=8B=B1?= =?UTF-8?q?=20=EB=B6=88=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hwe/func_template.php | 4 + hwe/sammo/API/Global/GetCurrentHistory.php | 31 ++++ hwe/sammo/API/Global/GetHistory.php | 191 +++++++++++++++++++++ hwe/ts/SammoAPI.ts | 9 +- hwe/ts/defs/API/Global.ts | 34 ++++ hwe/ts/defs/index.ts | 19 ++ hwe/ts/map.ts | 24 +-- 7 files changed, 289 insertions(+), 23 deletions(-) create mode 100644 hwe/sammo/API/Global/GetCurrentHistory.php create mode 100644 hwe/sammo/API/Global/GetHistory.php diff --git a/hwe/func_template.php b/hwe/func_template.php index 8407257e..784decae 100644 --- a/hwe/func_template.php +++ b/hwe/func_template.php @@ -60,6 +60,10 @@ function chiefTurnTable() "; } +function templateLimitMsg(string $turntime): string{ + return "이미 너무 많은 접속을 하셨습니다. 다음 턴에 다시 시도해주세요. (턴시간: {$turntime})"; +} + function displayiActionObjInfo(?iAction $action) { if ($action === null) { diff --git a/hwe/sammo/API/Global/GetCurrentHistory.php b/hwe/sammo/API/Global/GetCurrentHistory.php new file mode 100644 index 00000000..02377d48 --- /dev/null +++ b/hwe/sammo/API/Global/GetCurrentHistory.php @@ -0,0 +1,31 @@ + true, + 'data' => $history, + ]; + } +} diff --git a/hwe/sammo/API/Global/GetHistory.php b/hwe/sammo/API/Global/GetHistory.php new file mode 100644 index 00000000..e97acc40 --- /dev/null +++ b/hwe/sammo/API/Global/GetHistory.php @@ -0,0 +1,191 @@ +args); + $v->rule('required', ['year', 'month']) + ->rule('lengthMin', 'serverID', 1) + ->rule('integer', 'year') + ->rule('integer', 'month'); + if (!$v->validate()) { + return $v->errorStr(); + } + if (!$this->args['serverID']) { + $this->args['serverID'] = UniqueConst::$serverID; + } + return null; + } + + public function getRequiredSessionMode(): int + { + return static::REQ_LOGIN | static::REQ_READ_ONLY; + } + + public function tryCache(): ?APICacheResult + { + if (!$this->cacheHash) { + return null; + } + return new APICacheResult( + null, + $this->getCacheKey($this->args['serverID'], $this->args['year'], $this->args['month'], $this->cacheHash) + ); + } + + private function getHistory(string $serverID, int $year, int $month): array + { + $db = DB::db(); + $history = $db->queryFirstRow( + 'SELECT * FROM ng_history WHERE server_id = %s AND year = %i AND month = %i', + $serverID, + $year, + $month + ); + $hash = hash( + 'sha256', + "[" . join(',', [ + $history['global_history'], + $history['global_action'], + $history['nations'], + $history['map'], + ]) . "]" + ); + $history['global_history'] = Json::decode($history['global_history']); + $history['global_action'] = Json::decode($history['global_action']); + $history['nations'] = Json::decode($history['nations']); + $history['map'] = Json::decode($history['map']); + $history['hash'] = $hash; + return $history; + } + + public function parseEtag(?string $etag): ?array + { + if (!$etag) { + return null; + } + $tags = explode('-', $etag); + if (count($tags) != 4) { + return null; + } + if (!is_numeric($tags[1])) { + return null; + } + if (!is_numeric($tags[2])) { + return null; + } + return [$tags[0], Util::toInt($tags[1]), Util::toInt($tags[2]), $tags[3]]; + } + + public function getCacheKey(string $serverID, int $year, int $month, string $cacheHash): string + { + return "{$serverID}-{$year}-{$month}-{$cacheHash}"; + } + + public function checkCached(?string $reqEtag): ?string + { + if (!$reqEtag) { + return null; + } + $parsedTags = $this->parseEtag($reqEtag); + + if (!$parsedTags) { + return null; + } + + $serverID = $this->args['serverID']; + $year = Util::toInt($this->args['year']); + $month = Util::toInt($this->args['month']); + + [$cachedServerID, $cachedYear, $cachedMonth, $cachedHash] = $parsedTags; + if ($cachedServerID !== $serverID || $cachedYear !== $year || $cachedMonth !== $month) { + return null; + } + + //tag로 hash를 굳이 붙이고 왔다면, 이미 데이터를 가지고 있다고 가정한다. + return $cachedHash; + } + + public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) + { + $checkCache = $this->checkCached($reqEtag); + if ($checkCache) { + $this->cacheHash = $checkCache; + return null; + } + + $serverID = $this->args['serverID']; + $year = $this->args['year']; + $month = $this->args['month']; + + $db = DB::db(); + + if ($serverID === UniqueConst::$serverID) { + if (!$session->isGameLoggedIn()) { + return '진행중인 서버의 연감은 게임에 로그인해야 볼 수 있습니다.'; + } + increaseRefresh("연감", 1); + + $me = $db->queryFirstRow('SELECT con, turntime FROM general WHERE owner = %i', $session->userID); + if (!$me) { + return '장수가 사망했습니다.'; + } + + $con = checkLimit($me['con']); + if ($con >= 2) { + return templateLimitMsg($me['turntime']); + } + } + + [$f_year, $f_month] = $db->queryFirstList( + 'SELECT year, month FROM ng_history WHERE server_id = %s ORDER BY year ASC, month ASC LIMIT 1', + $serverID + ); + if ($f_year === null || $f_month === null) { + return '올바르지 않은 서버 아이디입니다.'; + } + $firstYearMonth = Util::joinYearMonth($f_year, $f_month); + + + [$l_year, $l_month] = $db->queryFirstList( + 'SELECT year, month FROM ng_history WHERE server_id = %s ORDER BY year DESC, month DESC LIMIT 1', + $serverID + ); + $lastYearMonth = Util::joinYearMonth($l_year, $l_month); + + $queryYearMonth = Util::joinYearMonth($year, $month); + if ($queryYearMonth < $firstYearMonth || $queryYearMonth > $lastYearMonth) { + return '올바르지 않은 범위입니다.'; + } + + $history = $this->getHistory($serverID, $year, $month); + $this->cacheHash = $history['hash']; + + return [ + 'result' => true, + 'data' => $history, + ]; + } +} diff --git a/hwe/ts/SammoAPI.ts b/hwe/ts/SammoAPI.ts index 1b87fd83..5a44559c 100644 --- a/hwe/ts/SammoAPI.ts +++ b/hwe/ts/SammoAPI.ts @@ -4,7 +4,7 @@ import { type APITail, type APICallT, type RawArgType, type ValidResponse, type InvalidResponse } from "./util/callSammoAPI"; export type { ValidResponse, InvalidResponse }; -import { APIPathGen, NumVar } from "./util/APIPathGen.js"; +import { APIPathGen, NumVar, StrVar } from "./util/APIPathGen.js"; import type { BettingDetailResponse, BettingListResponse } from "./defs/API/Betting"; import type { ReserveBulkCommandResponse, ReserveCommandResponse, ReservedCommandResponse } from "./defs/API/Command"; import type { ChiefResponse } from "./defs/API/NationCommand"; @@ -12,7 +12,7 @@ import type { inheritBuffType } from "./defs/API/InheritAction"; import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse } from "./defs/API/Nation"; import type { UploadImageResponse } from "./defs/API/Misc"; import type { JoinArgs } from "./defs/API/General"; -import type { GetConstResponse } from "./defs/API/Global"; +import type { GetConstResponse, GetCurrentHistoryResponse, GetHistoryResponse } from "./defs/API/Global"; import type { GeneralListResponse } from "./defs"; const apiRealPath = { @@ -54,6 +54,11 @@ const apiRealPath = { with_token?: boolean }, GeneralListResponse>, GetConst: GET as APICallT, + GetHistory: StrVar('serverID')( + NumVar('year', + NumVar('month', GET as APICallT + ))), + GetCurrentHistory: GET as APICallT }, InheritAction: { BuyHiddenBuff: PUT as APICallT<{ diff --git a/hwe/ts/defs/API/Global.ts b/hwe/ts/defs/API/Global.ts index d2477d0f..b2f332fd 100644 --- a/hwe/ts/defs/API/Global.ts +++ b/hwe/ts/defs/API/Global.ts @@ -1,4 +1,5 @@ import type { CityID, CrewTypeID, GameCityDefault, GameConstType, GameUnitType, GameIActionKey, GameIActionCategory, GameIActionInfo } from "@/defs/GameObj"; +import type { MapResult } from "@/defs"; export interface GetConstResponse { result: true; @@ -33,4 +34,37 @@ export interface GetConstResponse { allItems: "item"; }; }; +} + +export type HistoryObj = { + server_id: string, + year: number; + month: number; + map: MapResult, + global_history: string[], + global_action: string[], + nations: { + capital: number, + cities: string[], + color: string, + gennum: number, + level: number, + name: string, + nation: number, + power: number, + type: GameIActionKey + }[], +} + +export type GetHistoryResponse = { + result: true; + data: HistoryObj & { + hash: string; + //no: number, + }; +} + +export type GetCurrentHistoryResponse = { + result: true; + data: HistoryObj; } \ No newline at end of file diff --git a/hwe/ts/defs/index.ts b/hwe/ts/defs/index.ts index 5140102e..fce1617c 100644 --- a/hwe/ts/defs/index.ts +++ b/hwe/ts/defs/index.ts @@ -228,4 +228,23 @@ export const diplomacyStateInfo: Record = { 1: { name: '선포중', color: 'magenta' }, 2: { name: '통상' }, 7: { name: '불가침', color: 'green' }, +} + +//Map +type MapCityCompact = [number, number, number, number, number, number]; +type MapNationCompact = [number, string, string, number]; +export type MapResult = { + result: true, + startYear: number, + year: number, + month: number, + cityList: MapCityCompact[], + nationList: MapNationCompact[], + spyList: Record, + shownByGeneralList: number[], + myCity?: number, + myNation?: number, + + theme?: string, + history?: string[], } \ No newline at end of file diff --git a/hwe/ts/map.ts b/hwe/ts/map.ts index 405fd698..3d04fdcc 100644 --- a/hwe/ts/map.ts +++ b/hwe/ts/map.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import $ from 'jquery'; import { isNumber, merge } from 'lodash'; import { convColorValue, convertDictById, stringFormat } from '@/common_legacy'; -import type { InvalidResponse } from '@/defs'; +import type { InvalidResponse, MapResult } from '@/defs'; import { unwrap } from "@util/unwrap"; import { convertFormData } from '@util/convertFormData'; import { exportWindow } from '@util/exportWindow'; @@ -28,9 +28,6 @@ declare global { } } -type MapCityCompact = [number, number, number, number, number, number]; -type MapNationCompact = [number, string, string, number]; - type MapCityParsedRaw = { id: number, level: number, @@ -77,21 +74,6 @@ type MapNationParsed = { capital: number } -type MapResult = { - result: true, - startYear: number, - year: number, - month: number, - cityList: MapCityCompact[], - nationList: MapNationCompact[], - spyList: Record, - shownByGeneralList: number[], - myCity?: number, - myNation?: number, - - theme?: string, - history?: string[], -} type MapRawResult = InvalidResponse | MapResult; function is_touch_device(): boolean { @@ -285,7 +267,7 @@ export async function reloadWorldMap(option: loadMapOption, drawTarget = '.world async function convertCityObjs(obj: MapResult): Promise { //원본 Obj는 굉장히 간소하게 온다, Object 형태로 변환해서 사용한다. - function toCityObj([id, level, state, nationID, region, supply]: MapCityCompact): MapCityParsedRaw { + function toCityObj([id, level, state, nationID, region, supply]: MapResult['cityList'][0]): MapCityParsedRaw { return { id: id, level: level, @@ -296,7 +278,7 @@ export async function reloadWorldMap(option: loadMapOption, drawTarget = '.world }; } - function toNationObj([id, name, color, capital]: MapNationCompact): MapNationParsed { + function toNationObj([id, name, color, capital]: MapResult['nationList'][0]): MapNationParsed { return { id, name,