Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e23be1585 | ||
|
|
ca0bf296a6 | ||
|
|
c713b858c1 | ||
|
|
7885c8c8d7 | ||
|
|
a8ddaac1ed | ||
|
|
21da4d14e6 | ||
|
|
be6e765d76 | ||
|
|
23070ee19a | ||
|
|
2fb1364282 | ||
|
|
9120943a1a | ||
|
|
bd6158c0bc | ||
|
|
3687d597c4 | ||
|
|
45f14f0b72 | ||
|
|
905b401aab | ||
|
|
4bc5407bf2 | ||
|
|
e084835249 | ||
|
|
cea888573c | ||
|
|
9495cff295 | ||
|
|
c3d3a05f6e | ||
|
|
a0c730299c | ||
|
|
778f189a21 | ||
|
|
5ec1173f15 | ||
|
|
409e6513e3 | ||
|
|
e59f9a9659 |
@@ -11,7 +11,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -21,7 +21,7 @@
|
||||
"@strpc/def": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
|
||||
@@ -2,24 +2,16 @@ import { GET, POST, type DefAPINamespace } from "@strpc/def";
|
||||
|
||||
//굳이 할 필요는 없지만, d.ts가 깔끔해짐
|
||||
import type {
|
||||
InvalidResponse, ValidResponse
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
InvalidResponse, ValidResponse
|
||||
} from "@strpc/def";
|
||||
import type {
|
||||
ArgDeleteAPI,
|
||||
ArgGetAPI,
|
||||
ArgHeadAPI,
|
||||
ArgPatchAPI,
|
||||
ArgPostAPI,
|
||||
ArgPutAPI,
|
||||
EmptyDeleteAPI,
|
||||
EmptyGetAPI,
|
||||
EmptyHeadAPI,
|
||||
EmptyPatchAPI,
|
||||
EmptyPostAPI,
|
||||
EmptyPutAPI,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
ArgDeleteAPI, ArgGetAPI, ArgHeadAPI, ArgPatchAPI, ArgPostAPI, ArgPutAPI,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
EmptyDeleteAPI, EmptyGetAPI, EmptyHeadAPI, EmptyPatchAPI, EmptyPostAPI, EmptyPutAPI,
|
||||
} from "@strpc/def/types";
|
||||
|
||||
|
||||
export type LoginResponse = {
|
||||
result: true,
|
||||
nextToken: [number, string] | undefined,
|
||||
@@ -49,7 +41,6 @@ export type AutoLoginFailed = {
|
||||
reason: string,
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const structure = {
|
||||
Login: {
|
||||
LoginByID: POST<{
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import type {
|
||||
DefAPINamespace,
|
||||
} from "@strpc/def";
|
||||
import { GET, POST } from '@strpc/def';
|
||||
|
||||
//굳이 할 필요는 없지만, d.ts가 깔끔해짐
|
||||
import type {
|
||||
InvalidResponse, ValidResponse
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
InvalidResponse, ValidResponse
|
||||
} from "@strpc/def";
|
||||
import type {
|
||||
ArgDeleteAPI,
|
||||
ArgGetAPI,
|
||||
ArgHeadAPI,
|
||||
ArgPatchAPI,
|
||||
ArgPostAPI,
|
||||
ArgPutAPI,
|
||||
EmptyDeleteAPI,
|
||||
EmptyGetAPI,
|
||||
EmptyHeadAPI,
|
||||
EmptyPatchAPI,
|
||||
EmptyPostAPI,
|
||||
EmptyPutAPI,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
ArgDeleteAPI, ArgGetAPI, ArgHeadAPI, ArgPatchAPI, ArgPostAPI, ArgPutAPI,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
EmptyDeleteAPI, EmptyGetAPI, EmptyHeadAPI, EmptyPatchAPI, EmptyPostAPI, EmptyPutAPI,
|
||||
} from "@strpc/def/types";
|
||||
import { GET, POST } from '@strpc/def';
|
||||
|
||||
|
||||
export type {
|
||||
InferResponse,
|
||||
|
||||
+24
-23
@@ -5,46 +5,47 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check build-only",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"type-check": "vue-tsc",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sammo/game_logic": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/client_ky": "workspace:^",
|
||||
"@strpc/def": "workspace:^",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-vue-next": "^0.9.26",
|
||||
"bson": "^5.4.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"ky": "^1.0.1",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-vue-next": "^0.16.6",
|
||||
"bson": "^6.4.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"ky": "^1.2.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "^2.1.4",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.2",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/node": "^20.6.3",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"eslint": "^8.50.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.24",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.22.0",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.2",
|
||||
"vite": "^4.4.6",
|
||||
"vue-tsc": "^1.8.6",
|
||||
"zod": "^3.22.2"
|
||||
"vite": "^5.1.5",
|
||||
"vue-tsc": "^2.0.5",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-24
@@ -1,28 +1,23 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
"extends": "../../tsconfig.vite.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
|
||||
"path": "../util"
|
||||
},
|
||||
{
|
||||
"path": "../secure_token"
|
||||
},
|
||||
{
|
||||
"path": "../../@strpc/def"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "../util"
|
||||
},
|
||||
{
|
||||
"path": "../secure_token"
|
||||
},
|
||||
{
|
||||
"path": "../../@strpc/def"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,10 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"],
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
}
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
||||
+11
-11
@@ -18,27 +18,27 @@
|
||||
"build": "tsc --build",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/util": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/lodash": "^4.14.198",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/node": "^20.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
"@typescript-eslint/parser": "^6.7.0",
|
||||
"bson": "^5.4.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.24",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"bson": "^6.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint": "^8.57.0",
|
||||
"jest": "^29.7.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-jest": "^29.1.2",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bson": "^5.4.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { InvalidArgument } from '@sammo/util';
|
||||
import type { RandUtil } from './RandUtil.js';
|
||||
|
||||
export class InvalidVType extends InvalidArgument {
|
||||
public override name = 'InvalidVType';
|
||||
@@ -99,6 +100,10 @@ export * as SHA2 from './SHA2.js';
|
||||
export * as ECDHe from './ECDHe.js';
|
||||
export * as ECDHe_AES from './ECDHe_AES.js';
|
||||
|
||||
export { RandUtil } from './RandUtil.js';
|
||||
export { LiteHashDRBG } from './LiteHashDRBG.js';
|
||||
export type { RNG } from './RNG.js';
|
||||
|
||||
export * as RawTypes from './RawTypes.js';
|
||||
|
||||
export * from './utils.js';
|
||||
|
||||
@@ -6,18 +6,19 @@
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/api_def": "workspace:^",
|
||||
"@sammo/crypto": "workspace:^",
|
||||
"@sammo/server_util": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/express": "workspace:^",
|
||||
"dotenv": "^16.3.1",
|
||||
"mongoose": "^7.4.3"
|
||||
"dotenv": "^16.4.5",
|
||||
"mongoose": "^8.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.6.3"
|
||||
"@types/node": "^20.11.24"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { CityID } from "./defs.js";
|
||||
|
||||
export class City{
|
||||
constructor(
|
||||
public readonly id: CityID
|
||||
){
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class GameEnv{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { InvalidArgument } from "@sammo/util";
|
||||
import { City } from "./City.js";
|
||||
import type { IGeneralEntity } from "./GeneralEntity.js";
|
||||
import type { IResourceController } from "./ResourceController.js";
|
||||
import { DiplomaticPermission, type CityID, type GeneralID, type GeneralName, type NationID, OfficerLevel, type TroopID } from "./defs.js";
|
||||
import { must } from "@sammo/util";
|
||||
import { Nation } from "./Nation.js";
|
||||
import { Troop } from "./Troop.js";
|
||||
|
||||
export class General {
|
||||
private _raw: IGeneralEntity;
|
||||
private _city: WeakRef<City>;
|
||||
private _troop: WeakRef<Troop> | undefined;
|
||||
private _nation: WeakRef<Nation>;
|
||||
|
||||
public get raw(): Readonly<IGeneralEntity> {
|
||||
return this._raw;
|
||||
}
|
||||
|
||||
constructor(
|
||||
raw: IGeneralEntity,
|
||||
private resourceController: IResourceController
|
||||
){
|
||||
this._raw = raw;
|
||||
this._city = new WeakRef(must(resourceController.getCity(raw.cityID)));
|
||||
this._nation = new WeakRef(must(resourceController.getNation(raw.nationID)));
|
||||
|
||||
if(raw.troopID){
|
||||
this._troop = new WeakRef(must(resourceController.getTroop(raw.troopID, raw.nationID)));
|
||||
}
|
||||
}
|
||||
|
||||
public get id(): GeneralID {
|
||||
return this._raw.id;
|
||||
}
|
||||
|
||||
public get name(): GeneralName {
|
||||
return this._raw.name;
|
||||
}
|
||||
|
||||
public update(callback: (oldRaw: IGeneralEntity) => void, force = false) {
|
||||
|
||||
const oldCityID = this._raw.cityID;
|
||||
const oldTroopID = this._raw.troopID;
|
||||
const oldNationID = this._raw.nationID;
|
||||
|
||||
callback(this._raw);
|
||||
|
||||
if(!force){
|
||||
if(oldCityID !== this._raw.cityID){
|
||||
throw new Error("City change by update() is not allowed");
|
||||
}
|
||||
if(oldTroopID !== this._raw.troopID){
|
||||
throw new Error("Troop change by update() is not allowed");
|
||||
}
|
||||
if(oldNationID !== this._raw.nationID){
|
||||
throw new Error("Nation change by update() is not allowed");
|
||||
}
|
||||
}
|
||||
this.resourceController.setDirtyGeneral(this._raw);
|
||||
}
|
||||
|
||||
public changeCity(city: CityID | City){
|
||||
const cityObj: City = (()=>{
|
||||
if(city instanceof City){
|
||||
return city;
|
||||
}
|
||||
const cityObj = this.resourceController.getCity(city);
|
||||
if(!cityObj){
|
||||
throw new InvalidArgument(`City not found: ${city}`);
|
||||
}
|
||||
return cityObj;
|
||||
})();
|
||||
this._city = new WeakRef(cityObj);
|
||||
|
||||
//TODO: ResourceController에게 알려야 하는가?
|
||||
this.update((raw) => {
|
||||
raw.cityID = cityObj.id;
|
||||
}, true);
|
||||
}
|
||||
|
||||
public quitTroop(){
|
||||
if(!this._troop){
|
||||
return;
|
||||
}
|
||||
|
||||
const troop = must(this._troop.deref());
|
||||
if(troop.leader.id === this._raw.id){
|
||||
troop.destructTroop(false);
|
||||
}
|
||||
else{
|
||||
troop.members.delete(this._raw.id);
|
||||
}
|
||||
|
||||
this._troop = undefined;
|
||||
this.update((raw) => {
|
||||
raw.troopID = undefined;
|
||||
}, true);
|
||||
}
|
||||
|
||||
public changeTroop(troop: TroopID | Troop){
|
||||
this.quitTroop();
|
||||
|
||||
const troopObj: Troop = (()=>{
|
||||
if(troop instanceof Troop){
|
||||
return troop;
|
||||
}
|
||||
const troopObj = this.resourceController.getTroop(troop, this._raw.nationID);
|
||||
if(!troopObj){
|
||||
throw new InvalidArgument(`Troop not found: ${troop}`);
|
||||
}
|
||||
return troopObj;
|
||||
})();
|
||||
|
||||
troopObj.members.set(this._raw.id, new WeakRef(this));
|
||||
this._troop = new WeakRef(troopObj);
|
||||
this.update((raw) => {
|
||||
raw.troopID = troopObj.id;
|
||||
}, true);
|
||||
}
|
||||
|
||||
public changeNation(nation: NationID | Nation){
|
||||
const nationObj: Nation = (()=>{
|
||||
if(nation instanceof Nation){
|
||||
return nation;
|
||||
}
|
||||
const nationObj = this.resourceController.getNation(nation);
|
||||
if(!nationObj){
|
||||
throw new InvalidArgument(`Nation not found: ${nation}`);
|
||||
}
|
||||
return nationObj;
|
||||
})();
|
||||
this._nation = new WeakRef(nationObj);
|
||||
this.quitTroop();
|
||||
|
||||
this.update((raw) => {
|
||||
raw.nationID = nationObj.id;
|
||||
raw.diplomaticPermission = DiplomaticPermission.none;
|
||||
if(nationObj.id == 0){
|
||||
raw.officerLevel = OfficerLevel.none;
|
||||
}
|
||||
else{
|
||||
raw.officerLevel = OfficerLevel.normal;
|
||||
}
|
||||
raw.nationBelong = 0;
|
||||
}, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { DiplomaticPermission, GeneralID, GeneralName, ItemID, ItemKeyType, NationID, NpcType, OfficerLevel, OwnerID, OwnerName, PersonalityType, SpecialityDomesticType, SpecialityWarType, TroopID } from "./defs.js";
|
||||
|
||||
export interface IGeneralEntity {
|
||||
id: GeneralID;
|
||||
owner?: OwnerID;
|
||||
npcType: NpcType;
|
||||
affinity?: number;
|
||||
picture?: string;
|
||||
name: GeneralName;
|
||||
ownerName?: OwnerName;
|
||||
nationID: NationID;
|
||||
cityID: number;
|
||||
troopID?: TroopID;
|
||||
|
||||
leadership: number;
|
||||
leadershipExp: number;
|
||||
strength: number;
|
||||
strengthExp: number;
|
||||
intel: number;
|
||||
intelExp: number;
|
||||
|
||||
injury: number;
|
||||
|
||||
experience: number;
|
||||
dedication: number;
|
||||
|
||||
experienceLevel: number;
|
||||
dedicationLevel: number;
|
||||
|
||||
dex: number[];
|
||||
|
||||
officerLevel: OfficerLevel;
|
||||
diplomaticPermission: DiplomaticPermission;
|
||||
|
||||
gold: number;
|
||||
rice: number;
|
||||
|
||||
item: Record<ItemKeyType, {
|
||||
id: ItemID,
|
||||
option: Record<string, unknown>,
|
||||
}>;
|
||||
|
||||
turntime: Date;
|
||||
|
||||
killturn: number;
|
||||
|
||||
age: number;
|
||||
startAge: number;
|
||||
|
||||
nationBelong: number;
|
||||
|
||||
betray: number;
|
||||
|
||||
personality: PersonalityType;
|
||||
specialityDomestic: SpecialityDomesticType;
|
||||
specialityWar: SpecialityWarType;
|
||||
|
||||
defenceTrain: number;
|
||||
|
||||
npcSpec?: {
|
||||
msg?: string;
|
||||
bornyear: number;
|
||||
deadyear: number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { IAction } from "../IAction.js";
|
||||
import type { SpecialReqType, WeightType } from "./defs.js";
|
||||
|
||||
export abstract class BaseSpecial implements IAction {
|
||||
public abstract readonly name: string;
|
||||
|
||||
static readonly selectWeight: WeightType = {
|
||||
type: "normal",
|
||||
weight: 1,
|
||||
}
|
||||
|
||||
static readonly type: SpecialReqType[] = [];
|
||||
|
||||
getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
abstract getInfo(): string[]|string;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { General } from "../../General.js";
|
||||
import type { BattlePhaseTrigger, CalcStat, CalcStatRange } from "../../IActionMethod.js";
|
||||
import { WarUnitTriggerCaller } from "../../TriggerCaller/WarUnitTriggerCaller.js";
|
||||
import type { WarUnit } from "../../WarUnit.js";
|
||||
import { che_필살강화_회피불가 } from "../../WarUnitTrigger/che_필살강화_회피불가.js";
|
||||
import type { CalcStatName, CalcStatRangeName } from "@/defs.js";
|
||||
import { BaseSpecial } from "../BaseSpecial.js";
|
||||
import type { SpecialReqType, WeightNormal } from "../defs.js";
|
||||
|
||||
export class che_필살 extends BaseSpecial implements CalcStat, CalcStatRange, BattlePhaseTrigger {
|
||||
public readonly name = "필살";
|
||||
|
||||
static override readonly selectWeight: WeightNormal = {
|
||||
type: "normal",
|
||||
weight: 1,
|
||||
}
|
||||
static override type: SpecialReqType[] = [
|
||||
'statLeadership',
|
||||
'statStrength',
|
||||
'statIntel',
|
||||
];
|
||||
|
||||
getInfo() {
|
||||
return "[전투] 필살 확률 +30%p, 필살 발동시 대상 회피 불가, 필살 계수 향상";
|
||||
}
|
||||
|
||||
onCalcStat(general: General, statName: CalcStatName, value: number, aux?: unknown[] | undefined): number {
|
||||
if (statName == 'warCriticalRatio') {
|
||||
return value + 0.3;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
onCalcStatRange(general: General, statName: CalcStatRangeName, value: [number, number], aux?: unknown[] | undefined): [number, number] {
|
||||
if (statName === 'criticalDamageRange'){
|
||||
const [rangeMin, rangeMax] = value;
|
||||
return [(rangeMin + rangeMax) / 2, rangeMax];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
getBattlePhaseSkillTriggerList(unit: WarUnit): WarUnitTriggerCaller | undefined {
|
||||
return new WarUnitTriggerCaller(
|
||||
new che_필살강화_회피불가(unit),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
class SpecialityHelper {
|
||||
}
|
||||
|
||||
export const specialityHelper = new SpecialityHelper();
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
export type SpecialReqType =
|
||||
"statLeadership" |
|
||||
"statStrength" |
|
||||
"statIntel" |
|
||||
|
||||
"armyFootMan" |
|
||||
"armyArcher" |
|
||||
"armyCavalry" |
|
||||
"armyWizard" |
|
||||
"armySiege" |
|
||||
|
||||
"reqDexterity" |
|
||||
|
||||
"statNotLeadership" |
|
||||
"statNotStrength" |
|
||||
"statNotIntel"
|
||||
;
|
||||
|
||||
export type WeightNormal = {
|
||||
type: "normal";
|
||||
weight: number; // 1을 기준으로 한 가중치 곱
|
||||
};
|
||||
|
||||
export type WeightPercent = {
|
||||
type: "percent";
|
||||
weight: number; // 1이면 100%
|
||||
};
|
||||
|
||||
export type WeightType = WeightNormal | WeightPercent;
|
||||
@@ -0,0 +1,8 @@
|
||||
export { BaseSpecial } from "./BaseSpecial.js";
|
||||
|
||||
import { che_필살 } from "./SpecialWar/che_필살.js";
|
||||
|
||||
|
||||
export const SpecialWar = {
|
||||
che_필살
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type * as IMethod from "./IActionMethod.js";
|
||||
export type * as IMethod from "./IActionMethod.js";
|
||||
|
||||
export interface IActionMethod extends
|
||||
IMethod.TurnExecuteTriggerList,
|
||||
IMethod.CalcDomestic,
|
||||
IMethod.CalcStat,
|
||||
IMethod.CalcStatRange,
|
||||
IMethod.CalcOpposeStat,
|
||||
IMethod.CalcStrategic,
|
||||
IMethod.CalcIncome,
|
||||
IMethod.WarPowerMultiplier,
|
||||
IMethod.BattleInitTrigger,
|
||||
IMethod.BattlePhaseTrigger,
|
||||
IMethod.ArbitraryAction {
|
||||
}
|
||||
|
||||
export interface IAction extends Partial<IActionMethod> {
|
||||
readonly name: string;
|
||||
|
||||
getName(): string;
|
||||
getInfo(): string[] | string;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { RandUtil } from "@sammo/crypto";
|
||||
import type { WarUnitTriggerCaller } from "./TriggerCaller/WarUnitTriggerCaller.js";
|
||||
import type { GeneralTriggerCaller } from "./TriggerCaller/GeneralTriggerCaller.js";
|
||||
import type { General } from "./General.js";
|
||||
import type { WarUnit } from "./WarUnit.js";
|
||||
import type { ArbitraryActionType, CalcIncomeType, CalcStatName, CalcStatRangeName, CalcStrategicVarType, StrategicTurnType } from "./defs.js";
|
||||
|
||||
//TODO: turnType, varType이 string이면 안됨. enum에 가까워야함!
|
||||
type onCalcDomesticFunc = (turnType: string, varType: string, value: number, aux?: unknown[]) => number;
|
||||
|
||||
type onCalcStatFunc = (general: General, statName: CalcStatName, value: number, aux?: unknown[]) => number;
|
||||
type onCalcStatRangeFunc = (general: General, statName: CalcStatRangeName, value: [number, number], aux?: unknown[]) => [number, number];
|
||||
type onCalcStrategicFunc = (turnType: StrategicTurnType, varType: CalcStrategicVarType, value: number) => number;
|
||||
type onCalcIncomeFunc = (type: CalcIncomeType, value: number) => number;
|
||||
|
||||
type getWarPowerMultiplierFunc = (unit: WarUnit) => [number, number];
|
||||
type getWarUnitTriggerListFunc = (unit: WarUnit) => WarUnitTriggerCaller | undefined;
|
||||
|
||||
type onArbitraryActionFunc = (general: General, rng: RandUtil, actionType: ArbitraryActionType, phase?: string, aux?: unknown[]) => null | string[];
|
||||
|
||||
export interface TurnExecuteTriggerList {
|
||||
getPreTurnExecuteTriggerList(general: General): GeneralTriggerCaller | undefined;
|
||||
}
|
||||
|
||||
export interface CalcDomestic {
|
||||
onCalcDomestic: onCalcDomesticFunc;
|
||||
}
|
||||
|
||||
export interface CalcStat {
|
||||
onCalcStat: onCalcStatFunc;
|
||||
}
|
||||
|
||||
export interface CalcStatRange {
|
||||
onCalcStatRange: onCalcStatRangeFunc;
|
||||
}
|
||||
|
||||
export interface CalcOpposeStat {
|
||||
onCalcOpposeStat: onCalcStatFunc;
|
||||
}
|
||||
|
||||
export interface CalcStrategic {
|
||||
onCalcStrategic: onCalcStrategicFunc;
|
||||
}
|
||||
|
||||
export interface CalcIncome {
|
||||
onCalcNationalIncome: onCalcIncomeFunc;
|
||||
}
|
||||
|
||||
export interface WarPowerMultiplier {
|
||||
getWarPowerMultiplier: getWarPowerMultiplierFunc;
|
||||
}
|
||||
|
||||
export interface BattleInitTrigger {
|
||||
getBatteInitSkillTriggerList: getWarUnitTriggerListFunc;
|
||||
}
|
||||
|
||||
export interface BattlePhaseTrigger {
|
||||
getBattlePhaseSkillTriggerList: getWarUnitTriggerListFunc;
|
||||
}
|
||||
|
||||
export interface ArbitraryAction {
|
||||
onArbitraryAction: onArbitraryActionFunc;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { NationID, NationName, NationType } from "./defs.js";
|
||||
|
||||
export class Nation {
|
||||
constructor(
|
||||
public readonly id: NationID,
|
||||
public readonly name: NationName,
|
||||
public readonly type: NationType,
|
||||
) { }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { General } from "./General.js";
|
||||
import type { City } from "./City.js";
|
||||
import type { Nation } from "./Nation.js";
|
||||
import type { CityID, GeneralID, NationID, TroopID } from "./defs.js";
|
||||
import type { GameEnv } from "./GameEnv.js";
|
||||
import type { IGeneralEntity } from "./GeneralEntity.js";
|
||||
import type { Troop } from "./Troop.js";
|
||||
import type { ITroopEntity } from "./TroopEntity.js";
|
||||
|
||||
export interface IResourceController{
|
||||
addCity(city: City): boolean;
|
||||
addNation(nation: Nation): boolean;
|
||||
addGeneral(general: General): boolean;
|
||||
|
||||
getCity(id: CityID): City | undefined;
|
||||
getNation(id: NationID): Nation | undefined;
|
||||
getGeneral(id: GeneralID): General | undefined;
|
||||
|
||||
getGameEnv(): Promise<GameEnv>;
|
||||
|
||||
findAllNations(): Map<NationID, Nation>;
|
||||
findAllCities(): Map<CityID, City>;
|
||||
findAllGenerals(): Map<GeneralID, General>;
|
||||
|
||||
findGeneralByTroop(id: TroopID): Map<GeneralID, General>;
|
||||
|
||||
findCityByNation(id: NationID): Map<CityID, City>;
|
||||
findGeneralByNation(id: NationID): Map<GeneralID, General>;
|
||||
findGeneralByCity(id: CityID, nationID?: NationID): Map<GeneralID, General>;
|
||||
|
||||
getTroop(id: TroopID, nation: NationID): Troop | undefined;
|
||||
|
||||
_getAllChanges(): Map<unknown, unknown>;
|
||||
|
||||
|
||||
setDirtyGeneral(rawGeneral: IGeneralEntity): void;
|
||||
setDirtyCity(city: City): void;
|
||||
setDirtyTroop(troop: Troop | TroopID): void;
|
||||
setDirtyNation(nation: Nation): void;
|
||||
|
||||
createGeneral(raw: IGeneralEntity): General;
|
||||
createTroop(raw: ITroopEntity): Troop;
|
||||
//createNation
|
||||
|
||||
reserveDeleteTroop(id: TroopID): void;
|
||||
reserveDeleteGeneral(id: GeneralID): void;
|
||||
reserveDeleteNation(id: NationID): void;
|
||||
|
||||
resetAndFill(): Promise<void>;
|
||||
saveAll(): Promise<void>;
|
||||
}
|
||||
|
||||
let _resourceController: IResourceController | undefined = undefined;
|
||||
|
||||
export const setResourceController = (resourceController: IResourceController) => {
|
||||
_resourceController = resourceController;
|
||||
}
|
||||
|
||||
export const getResourceController = () => {
|
||||
if (_resourceController === undefined) {
|
||||
throw new Error("ResourceController not set");
|
||||
}
|
||||
return _resourceController;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { General } from "../General.js";
|
||||
import { ObjectTrigger } from "./ObjectTrigger.js";
|
||||
|
||||
export abstract class BaseGeneralTrigger extends ObjectTrigger<General> {
|
||||
constructor(
|
||||
protected object: General,
|
||||
protected priority: number
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { RandUtil } from "@sammo/crypto";
|
||||
import type { WarUnit, WarUnitEnv } from "../WarUnit.js";
|
||||
import { ObjectTrigger, PRIORITY } from "./ObjectTrigger.js";
|
||||
import type { GameEnv } from "../GameEnv.js";
|
||||
import { NotYetImplemented } from "@sammo/util";
|
||||
|
||||
export const RaiseType = {
|
||||
None: 0,
|
||||
Item: 1,
|
||||
ConsumableItem: 3,
|
||||
DedupTypeBase: 1024,
|
||||
} as const;
|
||||
|
||||
export abstract class BaseWarUnitTrigger extends ObjectTrigger<WarUnit> {
|
||||
protected override priority: number = PRIORITY.BODY;
|
||||
|
||||
constructor(
|
||||
protected object: WarUnit,
|
||||
protected raiseType: number = RaiseType.None,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override getUniqueID(): string {
|
||||
const priority = this.getPriority();
|
||||
const name = this.constructor.name;
|
||||
const unitName = this.object?.constructor.name ?? "undefined";
|
||||
|
||||
return `${priority}:${name}:${unitName}`;
|
||||
}
|
||||
|
||||
action(rng: RandUtil, env: GameEnv, arg: unknown[]): void {
|
||||
throw new NotYetImplemented();
|
||||
console.log(rng, env, arg);
|
||||
}
|
||||
|
||||
abstract actionWar(self: WarUnit, oppose: WarUnit, selfEnv: WarUnitEnv, opposeEnv: WarUnitEnv, rng: RandUtil): boolean;
|
||||
|
||||
processConsumableItem(): boolean {
|
||||
throw new NotYetImplemented();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { RandUtil } from "@sammo/crypto";
|
||||
import type { GameEnv } from "../GameEnv.js";
|
||||
|
||||
export const PRIORITY = {
|
||||
MIN: 0,
|
||||
BEGIN: 10000,
|
||||
PRE: 20000,
|
||||
BODY: 30000,
|
||||
POST: 40000,
|
||||
FINAL: 50000,
|
||||
} as const;
|
||||
|
||||
export abstract class ObjectTrigger<T extends object> {
|
||||
protected abstract priority: number;
|
||||
protected abstract object?: T;
|
||||
|
||||
getPriority(): number {
|
||||
return this.priority;
|
||||
}
|
||||
|
||||
setPriority(priority: number): this {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
//FIXME: arg array. generic이어야하나?
|
||||
abstract action(rng: RandUtil, env: GameEnv, arg: unknown[]): void;
|
||||
|
||||
//FIXME: 재설계 필요.
|
||||
getUniqueID(): string{
|
||||
const priority = this.getPriority();
|
||||
//FIXME: PHP 버전에서 namespace 개념도 옮겨와야하는가?
|
||||
const objName = this.constructor.name;
|
||||
|
||||
return `${priority}:${objName}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { BaseGeneralTrigger } from "../Trigger/BaseGeneralTrigger.js";
|
||||
import type { ObjectTrigger } from "../Trigger/ObjectTrigger.js";
|
||||
import { TriggerCaller } from "./TriggerCaller.js";
|
||||
|
||||
export class GeneralTriggerCaller extends TriggerCaller<BaseGeneralTrigger> {
|
||||
checkValidTrigger(trigger: ObjectTrigger<object>): boolean {
|
||||
return trigger instanceof BaseGeneralTrigger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ObjectTrigger, PRIORITY } from "../Trigger/ObjectTrigger.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export abstract class TriggerCaller<T extends ObjectTrigger<any>> {
|
||||
protected triggerListByPriority: Map<number, Map<string, T>>;
|
||||
|
||||
|
||||
abstract checkValidTrigger(trigger: T): boolean;
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.triggerListByPriority.size === 0;
|
||||
}
|
||||
|
||||
constructor(...triggerList: T[]) {
|
||||
if(triggerList.length === 0){
|
||||
this.triggerListByPriority = new Map();
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerListByPriority = new Map();
|
||||
let sorted = true;
|
||||
let maxPriority: number = PRIORITY.MIN;
|
||||
|
||||
for (const trigger of triggerList) {
|
||||
const priority = trigger.getPriority();
|
||||
const uniqueID = trigger.getUniqueID();
|
||||
|
||||
if(sorted){
|
||||
if(maxPriority < priority){
|
||||
maxPriority = priority;
|
||||
}
|
||||
else if(maxPriority > priority){
|
||||
sorted = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.triggerListByPriority.has(priority)){
|
||||
const subTriggerList = this.triggerListByPriority.get(priority) as Map<string, T>;
|
||||
if(subTriggerList.has(uniqueID)){
|
||||
//TODO: 완성되기 전까진..
|
||||
throw `Duplicated trigger: ${uniqueID}`;
|
||||
}
|
||||
subTriggerList.set(uniqueID, trigger);
|
||||
}
|
||||
else {
|
||||
this.triggerListByPriority.set(priority, new Map([[uniqueID, trigger]]));
|
||||
}
|
||||
}
|
||||
|
||||
if(!sorted){
|
||||
this.triggerListByPriority = new Map([...this.triggerListByPriority.entries()].sort((a, b) => b[0] - a[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { BaseWarUnitTrigger } from "../Trigger/BaseWarUnitTrigger.js";
|
||||
import type { ObjectTrigger } from "../Trigger/ObjectTrigger.js";
|
||||
import { TriggerCaller } from "./TriggerCaller.js";
|
||||
|
||||
export class WarUnitTriggerCaller extends TriggerCaller<BaseWarUnitTrigger> {
|
||||
checkValidTrigger(trigger: ObjectTrigger<object>): boolean {
|
||||
return trigger instanceof BaseWarUnitTrigger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { must } from "@sammo/util";
|
||||
import type { General } from "./General.js";
|
||||
import type { IResourceController } from "./ResourceController.js";
|
||||
import type { ITroopEntity } from "./TroopEntity.js";
|
||||
import type { GeneralID } from "./defs.js";
|
||||
|
||||
export class Troop {
|
||||
private _raw: ITroopEntity
|
||||
private _leader: WeakRef<General>;
|
||||
private _members: Map<GeneralID, WeakRef<General>>;
|
||||
|
||||
public get raw(): Readonly<ITroopEntity> {
|
||||
return this._raw;
|
||||
}
|
||||
|
||||
constructor(
|
||||
raw: ITroopEntity,
|
||||
private resourceController: IResourceController
|
||||
){
|
||||
this._raw = raw;
|
||||
|
||||
const members = resourceController.findGeneralByTroop(raw.id);
|
||||
const leaderID: GeneralID = raw.id as GeneralID;
|
||||
this._leader = new WeakRef(must(members.get(leaderID)));
|
||||
members.delete(leaderID);
|
||||
this._members = new Map([...members].map(([k,v])=>[k, new WeakRef(v)]));
|
||||
}
|
||||
|
||||
public get id(){
|
||||
return this._raw.id;
|
||||
}
|
||||
|
||||
public get leader(){
|
||||
return must(this._leader.deref());
|
||||
}
|
||||
|
||||
public get members(): Map<GeneralID, WeakRef<General>> {
|
||||
return this._members;
|
||||
}
|
||||
|
||||
public destructTroop(withLeader = true){
|
||||
this._members.forEach((member)=>{
|
||||
const general = must(member.deref());
|
||||
general.quitTroop();
|
||||
});
|
||||
if(withLeader){
|
||||
this.leader.quitTroop();
|
||||
}
|
||||
|
||||
this.resourceController.reserveDeleteTroop(this._raw.id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { NationID, TroopID } from "./defs.js";
|
||||
|
||||
export interface ITroopEntity {
|
||||
id: TroopID;
|
||||
nationID: NationID;
|
||||
name: string;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { WarSkillName } from "./defs.js";
|
||||
|
||||
export class WarUnitEnv {
|
||||
|
||||
}
|
||||
|
||||
export abstract class WarUnit{
|
||||
hasActivatedSkill(skillName: WarSkillName): boolean {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
activateSkill(skillName: WarSkillName): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WarUnit } from "./WarUnit.js";
|
||||
|
||||
export class WarUnitCity extends WarUnit{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WarUnit } from "./WarUnit.js";
|
||||
|
||||
export class WarUnitGeneral extends WarUnit{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { RandUtil } from "@sammo/crypto";
|
||||
import { BaseWarUnitTrigger } from "../Trigger/BaseWarUnitTrigger.js";
|
||||
import type { WarUnit, WarUnitEnv } from "../WarUnit.js";
|
||||
import { PRIORITY } from "../Trigger/ObjectTrigger.js";
|
||||
|
||||
export class che_필살강화_회피불가 extends BaseWarUnitTrigger {
|
||||
protected override priority = PRIORITY.PRE + 150;
|
||||
|
||||
actionWar(self: WarUnit, oppose: WarUnit, selfEnv: WarUnitEnv, opposeEnv: WarUnitEnv, rng: RandUtil): boolean {
|
||||
if(!self.hasActivatedSkill("필살")){
|
||||
return true;
|
||||
}
|
||||
|
||||
oppose.activateSkill("회피불가");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
//Type 정의
|
||||
export type OwnerID = number & { zz_t?: "OwnerID" };
|
||||
export type OwnerName = string & { zz_t?: "OwnerName" };
|
||||
|
||||
export type GeneralID = number & { zz_t?: "GeneralID" };
|
||||
export type GeneralName = string & { zz_t?: "GeneralName" };
|
||||
|
||||
export type CityID = number & { zz_t?: "CityID" };
|
||||
export type CityName = string & { zz_t?: "CityName" };
|
||||
|
||||
export type TroopID = number & { zz_t?: "TroopID" };
|
||||
export type TroopName = string & { zz_t?: "TroopName" };
|
||||
|
||||
export type NationID = number & { zz_t?: "NationID" };
|
||||
export type NationName = string & { zz_t?: "NationName" };
|
||||
|
||||
//TODO: 무언가의 keyof 로 바꿔야함
|
||||
export type NationType = string & { zz_t?: "NationType" };
|
||||
export type StrategicTurnType = string & { zz_t?: "StrategicTurnType" };
|
||||
|
||||
export type ItemKeyType = 'horse' | 'weapon' | 'book' | 'item';
|
||||
export type ItemID = string & { zz_t?: "ItemID" };
|
||||
|
||||
export type PersonalityType = string & { zz_t?: "PersonalityType" };
|
||||
export type SpecialityDomesticType = string & { zz_t?: "SpecialityDomesticType" };
|
||||
export type SpecialityWarType = string & { zz_t?: "SpecialityWarType" };
|
||||
|
||||
export const enum NpcType {
|
||||
user = 0,
|
||||
user_borrowed_npc = 1,
|
||||
npc_scenario = 2,
|
||||
npc_generated = 3,
|
||||
npc_strategic = 4,
|
||||
npc_troop_leader = 5,
|
||||
npc_unselectable = 6,
|
||||
|
||||
npc_invader = 9,
|
||||
}
|
||||
|
||||
export const enum OfficerLevel {
|
||||
//재야
|
||||
none = 0,
|
||||
|
||||
//일반
|
||||
normal = 1,
|
||||
|
||||
//도시관직
|
||||
cityLeadership = 2,
|
||||
cityIntel = 3,
|
||||
cityStrength = 4,
|
||||
|
||||
//지력관직
|
||||
staffIntel3 = 5,
|
||||
staffIntel2 = 7,
|
||||
staffIntel1 = 9,
|
||||
|
||||
//무력관직
|
||||
staffStrength3 = 6,
|
||||
staffStrength2 = 8,
|
||||
staffStrength1 = 10,
|
||||
|
||||
//참모
|
||||
staffChief = 11,
|
||||
|
||||
//군주
|
||||
lord = 12,
|
||||
|
||||
}
|
||||
|
||||
export const enum DiplomaticPermission {
|
||||
none = 0,
|
||||
audit = 1,
|
||||
ambassador = 2,
|
||||
}
|
||||
|
||||
|
||||
/* 전용 stat이어서 확장하고자 하는경우 아래와 같이 확장 가능
|
||||
```ts
|
||||
declare module "@/defs.js" {
|
||||
export interface CalcStatNameBase {
|
||||
statName: unknown
|
||||
}
|
||||
|
||||
export interface CalcStatRangeNameBase {
|
||||
statName: unknown
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
|
||||
//필요할 때 확장
|
||||
export interface CalcStatNameBase {
|
||||
leadership: unknown,
|
||||
strength: unknown,
|
||||
intel: unknown,
|
||||
warCriticalRatio: unknown;
|
||||
}
|
||||
export type CalcStatName = keyof CalcStatNameBase;
|
||||
|
||||
//필요할 때 확장
|
||||
export interface CalcStatRangeNameBase {
|
||||
criticalDamageRange: unknown;
|
||||
}
|
||||
export type CalcStatRangeName = keyof CalcStatRangeNameBase;
|
||||
|
||||
export interface CalcStrategicVarTypeBase {
|
||||
globalDelay: unknown;
|
||||
}
|
||||
export type CalcStrategicVarType = keyof CalcStrategicVarTypeBase;
|
||||
|
||||
export interface CalcIncomeTypeBase {
|
||||
gold: unknown;
|
||||
rice: unknown;
|
||||
}
|
||||
export type CalcIncomeType = keyof CalcIncomeTypeBase;
|
||||
|
||||
export interface ArbitraryActionTypeBase {
|
||||
}
|
||||
export type ArbitraryActionType = keyof ArbitraryActionTypeBase;
|
||||
|
||||
export interface WarSkillNameBase {
|
||||
필살: unknown;
|
||||
회피불가: unknown;
|
||||
회피: unknown;
|
||||
}
|
||||
export type WarSkillName = keyof WarSkillNameBase;
|
||||
@@ -1 +1,5 @@
|
||||
export * from "./generalStats.js";
|
||||
export * from "./generalStats.js";
|
||||
export * as defs from "./defs.js";
|
||||
|
||||
// NOTE: game_logic은 client, server 모두에서 사용되는 라이브러리이다.
|
||||
// DB에 접근한다면 DI여야하나?
|
||||
@@ -3,6 +3,9 @@
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
|
||||
@@ -5,45 +5,46 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check build-only",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"type-check": "vue-tsc",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/client_ky": "workspace:^",
|
||||
"@strpc/def": "workspace:^",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-vue-next": "^0.9.26",
|
||||
"bson": "^5.4.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"ky": "^1.0.1",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-vue-next": "^0.16.6",
|
||||
"bson": "^6.4.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"ky": "^1.2.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "^2.1.4",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.2",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/node": "^20.6.3",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"eslint": "^8.50.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.24",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.22.0",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.2",
|
||||
"vite": "^4.4.6",
|
||||
"vue-tsc": "^1.8.6",
|
||||
"zod": "^3.22.2"
|
||||
"vite": "^5.1.5",
|
||||
"vue-tsc": "^2.0.5",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
"extends": "../../tsconfig.vite.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
@@ -20,9 +19,6 @@
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"],
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
}
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@util': fileURLToPath(new URL('./server/util', import.meta.url)),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
"build": "tsc --build",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./exports": "./dist/exports.js"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -19,19 +20,19 @@
|
||||
"@sammo/server_util": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/express": "workspace:^",
|
||||
"date-fns": "^2.30.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"date-fns": "^3.3.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.18.3",
|
||||
"express-session": "^1.18.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mongoose": "^7.4.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"mongoose": "^8.2.1",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"tslib": "^2.6.2",
|
||||
"zod": "^3.22.2"
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-session": "^1.17.7",
|
||||
"@types/node": "^20.6.3"
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/node": "^20.11.24"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -14,9 +14,9 @@
|
||||
"@sammo/util": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bson": "^5.4.0",
|
||||
"bson": "^6.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
"zod": "^3.22.2"
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bson": "^5.4.0",
|
||||
|
||||
+16
-13
@@ -4,9 +4,11 @@
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
"build": "tsc --build",
|
||||
"dev": "tsc --watch",
|
||||
"worker_test": "tsx src/worker_test.ts"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -16,22 +18,23 @@
|
||||
"@sammo/server_util": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/express": "workspace:^",
|
||||
"date-fns": "^2.30.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"date-fns": "^3.3.1",
|
||||
"denque": "^2.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"ky": "^1.0.1",
|
||||
"express": "^4.18.3",
|
||||
"express-session": "^1.18.0",
|
||||
"ky": "^1.2.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mongoose": "^7.4.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"mongoose": "^8.2.1",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"tslib": "^2.6.2",
|
||||
"zod": "^3.22.2"
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-session": "^1.17.7",
|
||||
"@sammo/gateway_server": "workspace:^",
|
||||
"@types/node": "^20.6.3"
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/node": "^20.11.24"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import Denque from "denque";
|
||||
|
||||
export class GameEngine {
|
||||
|
||||
private static instance: GameEngine | null = null;
|
||||
|
||||
private npcActionQueue = new Denque<unknown>();
|
||||
private apiActionQueue = new Denque<unknown>();
|
||||
|
||||
private constructor() {
|
||||
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!GameEngine.instance) {
|
||||
GameEngine.instance = new GameEngine();
|
||||
}
|
||||
return GameEngine.instance;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
|
||||
console.log('GameEngine started');
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
console.log('GameEngine stopped');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'dotenv/config';
|
||||
import './dotenv.js';
|
||||
import { connect, Mongoose } from "mongoose";
|
||||
import { unwrap } from '@sammo/util';
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { delay, must } from "@sammo/util";
|
||||
import { parentPort as _parentPort } from "node:worker_threads";
|
||||
import { type MessagePort } from "node:worker_threads";
|
||||
import { generateMessage, PortRPCClient, type InitPort, type RandomMsg } from "./worker_util.js";
|
||||
import type { RPCSampleList } from "./worker_common.js";
|
||||
|
||||
|
||||
if (!_parentPort) {
|
||||
throw new Error("parentPort is not defined");
|
||||
}
|
||||
|
||||
const parentPort = must(_parentPort);
|
||||
let run = true;
|
||||
|
||||
async function worker_a(port: MessagePort) {
|
||||
const rpcClient = new PortRPCClient<RPCSampleList>(port);
|
||||
|
||||
console.log("Hello from worker_a.ts!");
|
||||
|
||||
let cnt = 0;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
while (run && cnt < 10000) {
|
||||
const message = generateMessage();
|
||||
|
||||
await rpcClient.callFunction('do', message);
|
||||
cnt++;
|
||||
if(cnt % 100 === 0){
|
||||
console.log(`a_cnt: ${cnt}`);
|
||||
}
|
||||
}
|
||||
|
||||
const elapsed = Date.now() - now;
|
||||
console.log(`Elapsed: ${elapsed}, cnt: ${cnt}`);
|
||||
|
||||
parentPort.postMessage(elapsed);
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
parentPort.once('message', async (ports: InitPort) => {
|
||||
await worker_a(ports.oppose);
|
||||
resolve();
|
||||
});
|
||||
|
||||
parentPort.once('close', () => {
|
||||
run = false;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { delay, must } from "@sammo/util";
|
||||
import { parentPort as _parentPort } from "node:worker_threads";
|
||||
import { type MessagePort } from "node:worker_threads";
|
||||
import { PortRPCServer, type InitPort, type RandomMsg } from "./worker_util.js";
|
||||
import { unknown } from "zod";
|
||||
|
||||
if (!_parentPort) {
|
||||
throw new Error("parentPort is not defined");
|
||||
}
|
||||
|
||||
const parentPort = must(_parentPort);
|
||||
let run = true;
|
||||
|
||||
function doFunc(msg: RandomMsg): string {
|
||||
return msg.key;
|
||||
}
|
||||
|
||||
let rpcServer: PortRPCServer<{}>|null = null;
|
||||
|
||||
async function worker_b(port: MessagePort) {
|
||||
|
||||
console.log("Hello from worker_b.ts!");
|
||||
|
||||
|
||||
rpcServer = new PortRPCServer(port, {
|
||||
do: doFunc
|
||||
});
|
||||
parentPort.postMessage("ready");
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
parentPort.once('message', async (ports: InitPort) => {
|
||||
await worker_b(ports.oppose);
|
||||
resolve();
|
||||
});
|
||||
|
||||
parentPort.once('close', () => {
|
||||
run = false;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { RandomMsg } from "./worker_util.js";
|
||||
|
||||
export type RPCSampleList = {
|
||||
do: (msg: RandomMsg) => string;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Worker, MessageChannel } from "node:worker_threads";
|
||||
import * as nodePath from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
console.log("Hello from worker_test.ts!");
|
||||
|
||||
const __filename = fileURLToPath(new URL(import.meta.url));
|
||||
const __dirname = nodePath.dirname(__filename);
|
||||
const worker_b_ts = nodePath.join(__dirname, "worker_b");
|
||||
const worker_a_ts = nodePath.join(__dirname, "worker_a");
|
||||
|
||||
const worker_b = new Worker(worker_b_ts);
|
||||
const worker_a = new Worker(worker_a_ts);
|
||||
|
||||
{
|
||||
const betweenPort = new MessageChannel();
|
||||
|
||||
worker_b.postMessage({ oppose: betweenPort.port2 }, [betweenPort.port2]);
|
||||
worker_b.once('message', ()=>{
|
||||
worker_a.postMessage({ oppose: betweenPort.port1 }, [betweenPort.port1]);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
worker_a.once('message', async (cnt: number) => {
|
||||
await worker_b.terminate();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,143 @@
|
||||
|
||||
import { delay, must } from "@sammo/util";
|
||||
import { type MessagePort } from "node:worker_threads";
|
||||
|
||||
export function randomString(len: number): string {
|
||||
const charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const charSetLen = charSet.length;
|
||||
const str: string[] = [];
|
||||
for (let i = 0; i < len; i++) {
|
||||
str.push(charSet.charAt(Math.floor(Math.random() * charSetLen)));
|
||||
}
|
||||
return str.join("");
|
||||
}
|
||||
|
||||
export type RandomMsg = {
|
||||
key: string;
|
||||
cnt: number;
|
||||
arr: string[];
|
||||
};
|
||||
|
||||
export function ArrayBufferFromString(str: string): ArrayBuffer {
|
||||
const buf = Buffer.from(str);
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
}
|
||||
|
||||
export function generateMessage(): RandomMsg {
|
||||
const randomObjCnt = Math.floor(Math.random() * 10);
|
||||
const key = randomString(60);
|
||||
const obj = {
|
||||
key,
|
||||
cnt: randomObjCnt,
|
||||
arr: [] as string[],
|
||||
};
|
||||
|
||||
for (let i = 0; i < randomObjCnt; i++) {
|
||||
const randomStrLen = Math.floor(Math.random() * 20) + 4;
|
||||
obj.arr.push(randomString(randomStrLen));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
export type RawRPCCall = [name: string, id: number, arg: unknown];
|
||||
|
||||
export type RawRPCResponse = [id: number, success: 1 | 0, result: unknown];
|
||||
|
||||
export type RPCLists = {
|
||||
[name: string]: (arg: any) => any | Promise<any>;
|
||||
}
|
||||
|
||||
export class PortRPCServer<RPCL extends RPCLists>{
|
||||
private listeners: Map<string, (arg: unknown) => unknown | Promise<unknown>> = new Map();
|
||||
|
||||
constructor(private port: MessagePort, rpcLists: RPCL) {
|
||||
port.on('message', async (buffer: ArrayBuffer) => {
|
||||
if(!(buffer instanceof ArrayBuffer)){
|
||||
throw new Error("RPCServer: buffer is not ArrayBuffer");
|
||||
}
|
||||
const rawText = Buffer.from(buffer).toString();
|
||||
const data = JSON.parse(rawText) as RawRPCCall;
|
||||
const [name, id, arg] = data;
|
||||
const _func = this.listeners.get(name);
|
||||
|
||||
if (!_func) {
|
||||
const result: RawRPCResponse = [id, 0, `Function ${name} not found`];
|
||||
const resultBuffer = ArrayBufferFromString(JSON.stringify(result));
|
||||
port.postMessage(resultBuffer, [resultBuffer]);
|
||||
}
|
||||
const func = must(_func);
|
||||
|
||||
try {
|
||||
const result: RawRPCResponse = [id, 1, await func(arg)];
|
||||
const resultBuffer = ArrayBufferFromString(JSON.stringify(result));
|
||||
port.postMessage(resultBuffer, [resultBuffer]);
|
||||
}
|
||||
catch (e) {
|
||||
const errMsg = e instanceof Error ? e.message : e;
|
||||
const result: RawRPCResponse = [id, 0, String(errMsg)];
|
||||
const resultBuffer = ArrayBufferFromString(JSON.stringify(result));
|
||||
port.postMessage(resultBuffer, [resultBuffer]);
|
||||
}
|
||||
});
|
||||
|
||||
for (const [name, func] of Object.entries(rpcLists)) {
|
||||
this.listeners.set(name, func);
|
||||
}
|
||||
|
||||
console.info("RPC: PortRPCServer started");
|
||||
}
|
||||
}
|
||||
|
||||
export class PortRPCClient<RPCL extends RPCLists> {
|
||||
private prevID = 0;
|
||||
|
||||
private waiters = new Map<number, [resolve: (result: unknown)=>void, reject: (reason: unknown)=>void]>();
|
||||
|
||||
constructor(private port: MessagePort) {
|
||||
port.on('message', (buffer: ArrayBuffer) => {
|
||||
const data = JSON.parse(Buffer.from(buffer).toString()) as RawRPCResponse;
|
||||
const [id, success, result] = data;
|
||||
const waiter = this.waiters.get(id);
|
||||
if (!waiter) {
|
||||
console.error(`RPC: Response for unknown id: ${id}`);
|
||||
return;
|
||||
}
|
||||
this.waiters.delete(id);
|
||||
if (success) {
|
||||
waiter[0](result);
|
||||
}
|
||||
else {
|
||||
waiter[1](result);
|
||||
}
|
||||
});
|
||||
console.info("RPC: PortRPCClient started");
|
||||
}
|
||||
|
||||
public async callFunction<T extends keyof RPCL & string>(name: T, arg: Parameters<RPCL[T]>[0]): Promise<ReturnType<RPCL[T]>> {
|
||||
const id = this.prevID++;
|
||||
|
||||
let done = false;
|
||||
|
||||
const waiter = new Promise((resolve, reject) => {
|
||||
const call: RawRPCCall = [name, id, arg];
|
||||
const callBuffer = ArrayBufferFromString(JSON.stringify(call));
|
||||
|
||||
this.waiters.set(id, [resolve, reject]);
|
||||
done = true;
|
||||
this.port.postMessage(callBuffer, [callBuffer]);
|
||||
});
|
||||
|
||||
await delay(0);
|
||||
|
||||
if(!done){
|
||||
throw new Error("RPC: callFunction failed");
|
||||
}
|
||||
|
||||
return waiter as Promise<ReturnType<RPCL[T]>>;
|
||||
}
|
||||
}
|
||||
|
||||
export type InitPort = {
|
||||
oppose: MessagePort;
|
||||
};
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/api_def": "workspace:^",
|
||||
@@ -16,7 +16,7 @@
|
||||
"@strpc/express": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express-session": "^1.17.7",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
},
|
||||
"type": "module",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.6.3",
|
||||
"@types/node": "^20.11.24",
|
||||
"date-fns": "^3.3.1",
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"date-fns": "^2.30.0",
|
||||
"lodash-es": "^4.17.21"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from "./bsonify.js"
|
||||
export * from "./jsonify.js"
|
||||
export * from "./error.js"
|
||||
export * from "./unwrap.js"
|
||||
export * from "./must.js"
|
||||
export * from "./types.js"
|
||||
export * from "./web.js"
|
||||
|
||||
@@ -19,6 +20,9 @@ export function calcBase64Len(length: number) {
|
||||
export function delay(time: number): Promise<void>;
|
||||
export function delay<T>(time: number, result: T): Promise<T>;
|
||||
export function delay<T = undefined>(time: number, result?: T): Promise<T | void> {
|
||||
if(time <= 0){
|
||||
return new Promise(r => r(result));
|
||||
}
|
||||
return new Promise(resolve =>
|
||||
setTimeout(() => {
|
||||
resolve(result);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Nullable } from './types.js';
|
||||
import { NotNullExpected } from "./error.js";
|
||||
|
||||
export function must<T>(result: Nullable<T>): T {
|
||||
if (result === null || result === undefined) {
|
||||
throw new NotNullExpected();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function must_any<T>(result: Nullable<unknown>): T {
|
||||
if (result === null || result === undefined) {
|
||||
throw new NotNullExpected();
|
||||
}
|
||||
return result as T;
|
||||
}
|
||||
|
||||
type ErrType<T> = { new(msg?: string): T }
|
||||
|
||||
export function must_err<T, ErrT extends Error>(result: Nullable<T>, errType: ErrType<ErrT>, errMsg?: string): T {
|
||||
if (result === null || result === undefined) {
|
||||
throw new errType(errMsg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -11,14 +11,14 @@
|
||||
"@strpc/def": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ky": "^1.0.1",
|
||||
"ky": "^1.2.2",
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ky": "^1.0.1",
|
||||
|
||||
@@ -151,7 +151,7 @@ export async function callClientAPI<ResultType extends ValidResponse, ErrorType
|
||||
|
||||
return {
|
||||
result: false,
|
||||
reason: `failed to fetch(${path}): ${err}`,
|
||||
reason: `failed to fetch(${path}): ${infoText}`,
|
||||
detail: err,
|
||||
} as ErrorType;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
"./proc_decorator": "./dist/proc_decorator.js"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "",
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@strpc/def": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"@types/express": "^4.17.21",
|
||||
"express": "^4.18.3",
|
||||
"express-session": "^1.18.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"zod": "^3.22.2"
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": "^4.18.2",
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
마지막수정일: 2024-03-01
|
||||
---
|
||||
|
||||
# Component 구성
|
||||
|
||||
컴포넌트 구성과 역할을 설명합니다.
|
||||
|
||||
## 객체
|
||||
|
||||
서버 내에 객체는 다음 그림과 같습니다.
|
||||
|
||||

|
||||
|
||||
|
||||
### MongoDB
|
||||
|
||||
메인 DB는 MongoDB입니다.
|
||||
|
||||
Collection은 다양하나, 게임 엔진에 필요한 값과 게임 엔진에 필요하지 않은 값이 분리됩니다.
|
||||
|
||||
분리되는 예시는 다음과 같습니다.
|
||||
|
||||
- 게임 내부 Collection
|
||||
- 장수의 능력치
|
||||
- 장수의 금/쌀
|
||||
- 장수의 수비 켬/끔 여부
|
||||
- 도시의 현재 스탯
|
||||
- 국가의 금/쌀, 전략 수치
|
||||
- 국가의 게임 내 정책(세율, 지급률)
|
||||
|
||||
- 게임 외부 Collection
|
||||
- 장수의 메시지함
|
||||
- 국가의 방침 게시물
|
||||
- 국가의 회의록
|
||||
- 국가간 외교 메시지
|
||||
- 로그인 정보, 벌점
|
||||
- 유니크 경매장 진행상태
|
||||
|
||||
|
||||
### 게임 엔진
|
||||
|
||||
턴 실행과 관련된 게임 핵심 로직을 처리하는 엔진 컴포넌트이며, 게임 내부 Collection에 대한 쓰기 명령은 게임 엔진에서만 가능합니다.
|
||||
|
||||
게임 엔진은 예외적인 경우에 게임 외부 Collection의 쓰기 명령을 할 수 있지만, 게임 내부에 영향을 주지 않는 경우여야만 합니다. 만약 예전에는 게임 외부 Collection에 기록되는 데이터였다 할지라도, 게임 엔진에서 다루기 시작한다면 게임 내부 Collection으로 이관됩니다.
|
||||
|
||||
#### 명령 처리 순서
|
||||
|
||||

|
||||
|
||||
게임 엔진은 내부적으로 세개의 처리 큐를 가집니다.
|
||||
|
||||
- 장수 처리 Queue: 턴마다 처리되는 장수 명령 처리 Queue입니다.
|
||||
- NPC Action Queue: NPC의 명령을 처리하는 Queue입니다.
|
||||
- NPC 명령 선택기의 입력 요청이 이곳에 쌓입니다.
|
||||
- 유저 Action Queue: 유저의 명령을 처리합니다.
|
||||
- API 처리기를 통해 전달됩니다.
|
||||
|
||||
세개의 처리 큐는 장수 처리 Queue > NPC Action Queue > 유저 Action Queue 순의 우선순위를 가집니다.
|
||||
|
||||
#### DB 반영 시점
|
||||
|
||||
MongoDB에 데이터가 기록되는 시점은 Queue의 아이템 하나를 해소했을 때 이루어집니다.
|
||||
즉, 장수의 턴 하나가 완전히 실행되었을 때에만 DB에 반영된다고 할 수 있습니다.
|
||||
|
||||
게임 엔진 내에서 변경되는 내용은 메모리 내의 저장소에만 담아두고, 처리를 마치는 시점에 변경된 내용을 정리하여 한번에 쓰기 작업을 수행하여 Transaction을 모사합니다.
|
||||
|
||||
|
||||
### NPC 명령 선택기
|
||||
|
||||
NPC 명령 선택기는 NPC가 어떤 턴을 실행해야할지만을 연산하는 컴포넌트입니다.
|
||||
|
||||
NPC의 실행턴보다 일정 시간 이전에 적절한 턴이 무엇인지 계산하고, 게임 엔진으로 결과값을 전송합니다.
|
||||
|
||||
이때 명령의 규격은 일반 유저가 내리는 명령 규격과 유사한 형태를 갖습니다.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 기존 삼모전 PHP에서는 명령 선택기가 게임 엔진과 동기적으로 구성되어있었습니다.
|
||||
|
||||
|
||||
|
||||
### API 처리기
|
||||
|
||||
API 처리기는 유저의 명령을 받아 1차적으로 처리합니다.
|
||||
|
||||
게임 외부 데이터에 대한 처리라면 읽기/쓰기를 모두 수행하며, 게임 내부 데이터이면 읽기 작업은 직접할 수 있습니다.
|
||||
|
||||
턴 입력 등 쓰기 작업이 요구되는 경우라면 게임 엔진에 해당 작업을 요청하고, 작업이 끝날때까지 기다린 다음, 엔진으로부터 결과값을 받아 반환합니다.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> **어떠한 경우에도** 게임 내부 DB에 직접 쓰기를 해서는 안됩니다.
|
||||
>
|
||||
> 게임 엔진은 메모리 cache에 강력히 의존하므로, 내부 DB에 직접 쓰는 작업은 게임 엔진의 처리 결과를 훼손할 것입니다.
|
||||
|
||||
|
||||
## 기존 삼국지 모의전투 PHP와 비교하여 달라진 부분
|
||||
|
||||
### 게임 엔진
|
||||
|
||||
PHP 버전에서 장수 처리 Queue는 `sammo::TurnExecutionHelper`에 해당합니다.
|
||||
|
||||
턴 실행 도중에 장수의 행동 또는 국가 정보가 바뀔 수 있으므로, 처리 시점에 DBMS로부터 최신 정보를 가져와 처리하고, 장수 한명의 실행이 마무리되면 모든 cache를 invalidate합니다.
|
||||
|
||||
이러한 처리 방법은 장수 처리 Queue 하나만으로 운영되는 형태로 생각할 수 있습니다.
|
||||
|
||||
### NPC 명령 선택기
|
||||
|
||||
NPC의 실행 턴이 무엇이 될지는 엔진내에 NPC 턴 실행 직전에 결정되므로, 게임 엔진 에서 처리되어있다고 볼 수 있습니다.
|
||||
|
||||
`sammo::GeneralAI`가 처리를 전담하고 있으며, `sammo::TurnExecutionHelper`가 `sammo::GeneralAI::chooseNationTurn()`, `sammo::GeneralAI::chooseGeneralTurn()`를 호출하는 방식입니다.
|
||||
|
||||
게임 엔진과 분리되어 있지 않으므로, 턴 입력, 세율 조정 등의 작업이 직접 이루어졌습니다.
|
||||
|
||||
### API 처리기
|
||||
|
||||
유기체 서버 시절에서는 `c_*.php` 계열 함수들이 API를 처리하는 형태였고, 이후 HiDCHe 에서 json web api 처리를 위해 `j_*.php` 계열 함수들이 추가되었습니다.
|
||||
|
||||
0.23 버전부터는 `api.php`를 통해 처리하는 (제대로 된)API 처리기가 도입되었습니다.
|
||||
|
||||
`api.php?path=**/*`의 경로는 `sammo::API::**::*.php` 이하의 클래스를 생성하고 호출합니다.
|
||||
|
||||
이때의 API 처리기는 게임 엔진에서 다루어야 할 데이터 변경도 같이 하고 있어 게임 엔진 측에서 조심해서 DB를 조회하도록 하여야 했습니다.
|
||||
|
||||
### DBMS
|
||||
|
||||
PHP 버전에서는 MariaDB를 사용하였습니다.
|
||||
|
||||
다만, 게임 내의 모든 데이터를 column으로 처리하는 데에는 무리가 있어 크게 두가지 방식으로 나누어 확장 데이터를 처리하였습니다.
|
||||
|
||||
- `general`, `city`, `nation` 테이블에 `aux` 필드를 만들어 json으로 처리
|
||||
- `storage`, `nation_env`와 같은 Key/Value(json) 테이블을 만들어 처리
|
||||
|
||||
게임 엔진의 턴 실행과 연계된 부분인가, 유저 API에 연계된 부분인가에 따라서 다르게 저장되고 있어 일관성이 떨어지는 문제가 있었습니다.
|
||||
|
||||
"어차피 많은 데이터가 json으로 따로 관리되니, 굳이 RDBMS일 이유가 있는가?"가 DBMS 전환의 가장 큰 이유입니다.
|
||||
@@ -0,0 +1,302 @@
|
||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="441px" height="271px" viewBox="-0.5 -0.5 441 271" content="<mxfile><diagram id="ovDIAAWsHlNK0DibyGEH" name="페이지-1">zZjBjpswEIafxlJ7iQAbYh9twraXVq320LMTvAGJhIiQJrtP37ExCQSiUikFLsjM2GB/8zNjg3C4u3wp5CH5lscqQ54TXxBeIc8LAgxXbXivDJQElWFbpHFlcm+G1/RDWaNjrac0VsdWxzLPszI9tI2bfL9Xm7Jlk0WRn9vd3vKs/daD3KqO4XUjs671VxqXiV2Ft7zZv6p0m9RvdgNWeXay7mxXckxknJ8bJhwhHBZ5Xlat3SVUmWZXc6nGvTzwXidWqH05ZIBXDfgts5NdG4pCxCLEfd2gFDEqoYPjIsw9G7Zj+V6TKPLTPlb6YQ7C4pykpXo9yI32niH0YEvKXQZ3MF7Yd6miVJeH83WvFEA9Kt+psniHLnaARy04qxzi2PvzLQ5uDTdpxABbm7Sh314ffaMDDQuoHxYeAAv8ui083Y4E4gHi8GDn50nBoMf43EnwYTwQH3kCPjIA31prjYDWfDY/rQ2F9Qyt+R1Yi8VifkiCEZEEA/TzAR1AOvPUDxsR1rIfVoi4yU9w1bNz+KZM8/1s8lPgTJif6MD8hKIVogQJAmsJIamFwDrIYCJiXUBrq1sm9XPETQ0QGDHXFgYqTAQ4EksdgYZ36dSLn1Cz9wEYXF+fEQDWCcD3H+H8RErIhCKtt8BtSLJWF8iS1h+63ovoafiTq+qe2Ki7Ntd9TOz2IXc/YNjEBZpj6wMWejcniEVMyfzQjirG7vEB0G7+ivaa/VaIMbNpnhvF5ZgUh5wr3gxUrg8WYmV0uESc1JbKhZEI+0sRdBCuHUWpjQ/jiJpHMZD3avIYdEoPGTMG/YcTqNY6A0CDG9ogal9betOF3lO5pk9ganyTdhXOKkzUGOdGm45Ju3u6MWR8xFemoXensp9yp8ThWZS4e56+NybPIUejy6cu4/XnB5A9Xecis1cVrJnMHZtOWMM9O/ZDT6VPYd89aUENjHtgSw1b7vT69+vjwaz9n+HDzYtxQz+s883U8O9Lp+/+P/hwe/s1anyN/8s4+gM=</diagram></mxfile>">
|
||||
<defs/>
|
||||
<g>
|
||||
<rect x="0" y="70" width="120" height="30" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 85px; margin-left: 1px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
장수a 01:23
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="60" y="89" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
장수a 01:23
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="0" width="120" height="40" rx="6" ry="6" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 20px; margin-left: 1px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
장수 처리 Queue
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="60" y="24" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
장수 처리 Queue
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="100" width="120" height="30" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 115px; margin-left: 1px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
장수b 04:59
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="60" y="119" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
장수b 04:59
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="130" width="120" height="30" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 145px; margin-left: 1px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="60" y="149" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="160" width="120" height="30" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 175px; margin-left: 1px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
장수z 59:59
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="60" y="179" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
장수z 59:59
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="320" y="0" width="120" height="40" rx="6" ry="6" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 20px; margin-left: 321px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
유저 Action Queue
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="380" y="24" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
유저 Action Queue
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="320" y="70" width="120" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 90px; margin-left: 321px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
장수b 턴1,4,7
|
||||
<br/>
|
||||
모병 신귀병 7000
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="380" y="94" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
장수b 턴1,4,7...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="160" y="0" width="120" height="40" rx="6" ry="6" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 20px; margin-left: 161px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
NPC Action Queue
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="220" y="24" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
NPC Action Queue
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="160" y="70" width="120" height="30" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 85px; margin-left: 161px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
NPCa 세율 20%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="220" y="89" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
NPCa 세율 20%
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="160" y="100" width="120" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 120px; margin-left: 161px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
NPCa 턴1
|
||||
<br/>
|
||||
출병 무위
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="220" y="124" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
NPCa 턴1...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="160" y="140" width="120" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 160px; margin-left: 161px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
NPCc 턴1
|
||||
<br/>
|
||||
귀환
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="220" y="164" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
NPCc 턴1...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="320" y="110" width="120" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 130px; margin-left: 321px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
장수f 경매결과
|
||||
<br/>
|
||||
백마 획득
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="380" y="134" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
장수f 경매결과...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="320" y="150" width="120" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 170px; margin-left: 321px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
시스템
|
||||
<br/>
|
||||
적토마 잠금
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="380" y="174" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
시스템
|
||||
적토마 잠금
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="320" y="190" width="120" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 210px; margin-left: 321px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
국가a
|
||||
<br/>
|
||||
세율 30%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="380" y="214" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
국가a...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="320" y="230" width="120" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 250px; margin-left: 321px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
장수x(국가b)
|
||||
<br/>
|
||||
사령턴1 발령
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="380" y="254" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
장수x(국가b)...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="160" y="180" width="120" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 200px; margin-left: 161px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
NPCd(국가a)
|
||||
<br/>
|
||||
사령턴1 포상
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="220" y="204" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
NPCd(국가a)...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Text is not SVG - cannot display
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 23 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 26 KiB |
+15
-19
@@ -5,43 +5,39 @@
|
||||
"scripts": {
|
||||
"dev": "run-p dev:client dev:server dev:gateway_server dev:gateway_client",
|
||||
"build": "run-s build:ts build:vite",
|
||||
"build:ts": "run-p build:server build:gateway_server",
|
||||
"build:ts": "run-s build:server build:gateway_server",
|
||||
"build:vite": "run-s build:client build:gateway_client",
|
||||
"type-check": "run-p type-check:server type-check:client type-check:gateway_server type-check:gateway_client",
|
||||
|
||||
"dev:server": "pnpm --filter @sammo/server dev",
|
||||
"dev:client": "pnpm --filter @sammo/client dev",
|
||||
"dev:gateway_server": "pnpm --filter @sammo/gateway_server dev",
|
||||
"dev:gateway_client": "pnpm --filter @sammo/gateway_client dev",
|
||||
|
||||
"build:def": "pnpm --filter @sammo/api_def build --force",
|
||||
"build:server": "pnpm --filter @sammo/server build",
|
||||
"build:client": "pnpm --filter @sammo/client build",
|
||||
"build:gateway_server": "pnpm --filter @sammo/gateway_server build",
|
||||
"build:gateway_client": "pnpm --filter @sammo/gateway_client build",
|
||||
|
||||
"type-check:server": "pnpm --filter @sammo/server type-check",
|
||||
"type-check:client": "pnpm --filter @sammo/client type-check",
|
||||
"type-check:gateway_server": "pnpm --filter @sammo/gateway_server type-check",
|
||||
"type-check:gateway_client": "pnpm --filter @sammo/gateway_client type-check",
|
||||
|
||||
"lint": "lint",
|
||||
"test": "jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.2",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/node": "^18.17.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"@rushstack/eslint-patch": "^1.7.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.24",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.22.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"typescript": "~5.1.6"
|
||||
}
|
||||
"prettier": "^3.2.5",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
"packageManager": "pnpm@8.7.6"
|
||||
}
|
||||
|
||||
Generated
+1096
-1443
File diff suppressed because it is too large
Load Diff
+1
-7
@@ -1,17 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"references": [
|
||||
{
|
||||
"path": "./@sammo/gateway_client"
|
||||
},
|
||||
{
|
||||
"path": "./@sammo/gateway_server"
|
||||
},
|
||||
{
|
||||
"path": "./@sammo/server"
|
||||
},
|
||||
{
|
||||
"path": "./@sammo/client"
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
"verbatimModuleSyntax": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user