diff --git a/@sammo/game_logic/src/ActionRequest/index.ts b/@sammo/game_logic/src/ActionRequest/index.ts new file mode 100644 index 0000000..8b016b4 --- /dev/null +++ b/@sammo/game_logic/src/ActionRequest/index.ts @@ -0,0 +1,62 @@ +import type { IResourceController } from "@/IResourceController.js"; +import type { CityID, GeneralID, NationID, TroopID } from "@/defs.js"; +import { NotYetImplemented } from "@sammo/util"; + + +export type QueueType = 'server' | 'api'; +//type QueueType = 'server' | 'npc' | 'api'; + +export function setDefenceTrained(invoker: QueueType, arg: { generalID: GeneralID, defenceTrained: number }, rsc: IResourceController): void{ + throw new NotYetImplemented(); +} + +export function kickTroopMember(invoker: QueueType, arg: { nationID: NationID, troopID: TroopID, memberID: GeneralID }, rsc: IResourceController): void { + throw new NotYetImplemented(); +} + + +export function blockAttackCommand(invoker: QueueType, arg: { nationID: NationID, block: boolean }, rsc: IResourceController): void { + throw new NotYetImplemented(); +} + +export function blockJoinNation(invoker: QueueType, arg: { nationID: NationID, block: boolean }, rsc: IResourceController): void { + throw new NotYetImplemented(); +} + +export function createGeneral(invoker: QueueType, arg: { + //유산 값등은 외부에서 처리하고 내부에는 순수 랜덤류만 입력 + name: string, + ownerID: number, + ownerName: string, + leadership: number, + strength: number, + intel: number, + pic: string + character: string + + turntimeZone?: number, + warSpecial?: string, + city?: CityID, + bonusStat?: [number, number, number], + + //penalty: Record, +}, rsc: IResourceController):void{ + throw new NotYetImplemented(); +} + +type ActionFunc, Res> = (invoker: QueueType, arg: Arg, rsc: IResourceController)=>Res; +export type ActionPackDef = { + [key: string]: ActionFunc +} + +export const ActionRequestList = { + setDefenceTrained, + kickTroopMember, + blockAttackCommand, + blockJoinNation, + createGeneral, +} as const satisfies ActionPackDef; + +export type ActionRequestKey = keyof typeof ActionRequestList; +export type ActionMainArg = Parameters[1]; +export type ActionRes = ReturnType; \ No newline at end of file diff --git a/@sammo/game_logic/src/index.ts b/@sammo/game_logic/src/index.ts index 60e10f8..1751e19 100644 --- a/@sammo/game_logic/src/index.ts +++ b/@sammo/game_logic/src/index.ts @@ -1,6 +1,6 @@ export * from "./LogicFunc/generalStats.js"; export * as defs from "./defs.js"; export * from "./IResourceController.js"; - +export * from "./ActionRequest/index.js"; // NOTE: game_logic은 client, server 모두에서 사용되는 라이브러리이다. // DB에 접근한다면 DI여야하나? \ No newline at end of file diff --git a/@sammo/server/src/GameEngine.ts b/@sammo/server/src/GameEngine.ts index 4bde7e5..958477a 100644 --- a/@sammo/server/src/GameEngine.ts +++ b/@sammo/server/src/GameEngine.ts @@ -4,9 +4,11 @@ import { ResourceController } from "./ResourceController.js"; import { entriesWithType } from "@sammo/util/converter"; import db from "./connectDB.js"; import type { Mongoose } from "mongoose"; -import type { GameEngineMsg } from "./GameEngineDefs.js"; +import type { ActionPack, GameEngineMsg } from "./GameEngineDefs.js"; +import { ActionRequestList, type QueueType } from "@sammo/game_logic"; export interface ActionRequest { - + name: keyof ActionPack; + args: Parameters[1]; } export interface ActionRequestContainer { @@ -16,7 +18,7 @@ export interface ActionRequestContainer { export type ActionSuccess = { success: true; - message?: string; + value: ReturnType; } export type ActionFailure = { @@ -25,8 +27,6 @@ export type ActionFailure = { } export type ActionResult = ActionSuccess | ActionFailure; -type QueueType = 'server' | 'api'; -//type QueueType = 'server' | 'npc' | 'api'; export class GameEngine { @@ -107,8 +107,7 @@ export class GameEngine { while (!queue.isEmpty()) { const action = must(queue.shift()); - //TODO: action 처리 - const errorReason: string | undefined = undefined; + const actionResult = this.processAction(invoker, action.action); const [ticketResolve, ticketRejected] = must_err( this.queueWaiters.get(action.waiterSeq), @@ -116,14 +115,14 @@ export class GameEngine { ); this.queueWaiters.delete(action.waiterSeq); - if (errorReason) { + if(actionResult.success){ responseQueue.push(() => { - ticketRejected(errorReason); + ticketResolve(actionResult); }); } - else { + else{ responseQueue.push(() => { - ticketResolve({ success: true }); + ticketRejected(actionResult); }); } @@ -236,6 +235,26 @@ export class GameEngine { console.log('GameEngine stopped'); } + private processAction(invoker: QueueType, action: ActionRequest): ActionResult { + const actionName = action.name; + const actionArgs = action.args; + + try{ + const result = ActionRequestList[actionName](invoker, actionArgs as any, this.rsc); + return { + success: true, + value: result + }; + } + catch(e){ + const msg = e instanceof Error ? e.message : e; + return { + success: false, + reason: String(msg), + }; + } + } + private async pushAction(queueName: QueueType, action: ActionRequest): Promise { const queue = this.actionQueue[queueName]; diff --git a/@sammo/server/src/GameEngineController.ts b/@sammo/server/src/GameEngineController.ts index eee33be..763f9e9 100644 --- a/@sammo/server/src/GameEngineController.ts +++ b/@sammo/server/src/GameEngineController.ts @@ -5,6 +5,7 @@ import * as nodePath from "node:path"; import { delay, must } from "@sammo/util"; import type { GameEngineMsg, GameEngineRPCDefs } from "./GameEngineDefs.js"; import { RPCClient } from "@sammo/server_util"; +import type { ActionMainArg, ActionRes, ActionRequestKey, ActionRequestList } from "@sammo/game_logic"; const __filename = fileURLToPath(new URL(import.meta.url)); @@ -90,11 +91,23 @@ class GameEngineController { await this.start(); } - async call(name: T, arg: Parameters[0]): Promise> { - return must(this.rpcClient).callFunction(name, arg); + async pushAPIAction(action: T, arg: ActionMainArg): Promise> { + const result = await must(this.rpcClient).callFunction('pushAPIAction', { name: action, args: arg }); + if (!result.success) { + throw new Error(result.reason); + } + return result.value as ReturnType; + } + + async pushServerAction(action: T, arg: ActionMainArg): Promise> { + const result = await must(this.rpcClient).callFunction('pushServerAction', { name: action, args: arg }); + if (!result.success) { + throw new Error(result.reason); + } + return result.value as ReturnType; } } export function getGameEngineController(): GameEngineController { return GameEngineController.instance; -} \ No newline at end of file +} diff --git a/@sammo/server/src/GameEngineDefs.ts b/@sammo/server/src/GameEngineDefs.ts index 35acf87..e7e9eb6 100644 --- a/@sammo/server/src/GameEngineDefs.ts +++ b/@sammo/server/src/GameEngineDefs.ts @@ -1,12 +1,20 @@ import type { RPCLists } from "@sammo/server_util"; import type { ActionRequest, ActionResult } from "./GameEngine.js"; import type { MessagePort } from "node:worker_threads"; +import type { CityID, GeneralID, NationID, TroopID } from "@sammo/game_logic/src/defs.js"; +import type { ActionPackDef } from "@sammo/game_logic"; +import type { ActionRequestList, QueueType } from "@sammo/game_logic/src/ActionRequest/index.js"; +//Remove Third argument. Use Infer +export type MapRscActionRequest = { + [P in keyof T]: (invoker: Parameters[1] & QueueType, arg: Parameters[1]) => ReturnType +} +export type ActionPack = MapRscActionRequest; export type GameEngineRPCDefs = { - stop: ()=>Promise, - pushAPIAction: (action: ActionRequest)=>Promise, - pushServerAction: (action: ActionRequest)=>Promise, + stop: () => Promise, + pushAPIAction: (action: ActionRequest) => Promise, + pushServerAction: (action: ActionRequest) => Promise, };