Compare commits
95
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2997d5dc21 | ||
|
|
bfbc0134de | ||
|
|
ffd3ab681e | ||
|
|
4e629e0b1e | ||
|
|
438df6cd3c | ||
|
|
9c9d9e9545 | ||
|
|
93686c4912 | ||
|
|
438ee85ed2 | ||
|
|
295d39327b | ||
|
|
b8d57a014a | ||
|
|
0f9d5c2dc9 | ||
|
|
3013554cef
|
||
|
|
32b41dd07a
|
||
|
|
f4640abe17
|
||
|
|
56a9c5bc45
|
||
|
|
65bf02a3aa
|
||
|
|
08890309c2
|
||
|
|
194b213f61
|
||
|
|
f8b8ea6cbe
|
||
|
|
0ee7066679
|
||
|
|
b850eb285b
|
||
|
|
6cbbc3222a
|
||
|
|
ae7701ee18
|
||
|
|
72c6f5ae64
|
||
|
|
ad4ac9dfb2
|
||
|
|
635d7f167d
|
||
|
|
87c239610a
|
||
|
|
199938f2b2
|
||
|
|
60848987eb
|
||
|
|
a37baa64fe
|
||
|
|
ae22fc7c28
|
||
|
|
17977b4d96
|
||
|
|
0fda257ca0
|
||
|
|
aec88333c2
|
||
|
|
df8b5e854b
|
||
|
|
c012a81377
|
||
|
|
b6cdd49449
|
||
|
|
837cff225b
|
||
|
|
de0b851217
|
||
|
|
bfe0b99d44
|
||
|
|
7716c6936b
|
||
|
|
8e96f6082d
|
||
|
|
302bab518e
|
||
|
|
0ba0b83433
|
||
|
|
3a06e44e85
|
||
|
|
81d16aaba2
|
||
|
|
0c511d27c5 | ||
|
|
1214036091
|
||
|
|
13d9662439
|
||
|
|
e381dd5a7e
|
||
|
|
c7d0702e66
|
||
|
|
bf35eb4c09
|
||
|
|
deb989e45b
|
||
|
|
96b18ad5ff
|
||
|
|
0d57ff9783
|
||
|
|
ff423189e9
|
||
|
|
507cba34eb
|
||
|
|
b30dde442b
|
||
|
|
3f7e645e40
|
||
|
|
d928241e00
|
||
|
|
35f5726659
|
||
|
|
ce1749e090
|
||
|
|
4b6d48850e
|
||
|
|
31fa264160
|
||
|
|
69de10642e
|
||
|
|
691f5e79ce
|
||
|
|
3256620276
|
||
|
|
a1502ebc16
|
||
|
|
1d751ea045
|
||
|
|
cca7214446
|
||
|
|
257644aff7
|
||
|
|
5e216e5f67
|
||
|
|
19a19d8a2f
|
||
|
|
3432009213
|
||
|
|
826b4212e5
|
||
|
|
7352cdbf23
|
||
|
|
2b27b8b427
|
||
|
|
5b6751f420
|
||
|
|
2fb22ed6a9
|
||
|
|
743ab3e1aa
|
||
|
|
92a67315f3
|
||
|
|
8130c731e0
|
||
|
|
5ba073c125
|
||
|
|
45d05f900f
|
||
|
|
580dd4aeee
|
||
|
|
b88fdf85e1
|
||
|
|
421d75f665
|
||
|
|
a261b45e5d
|
||
|
|
a39543e718
|
||
|
|
f2ad6ad897
|
||
|
|
bb28815f93
|
||
|
|
9557e9b099
|
||
|
|
0622c534bc
|
||
|
|
58562e2c99
|
||
|
|
1287486d74
|
@@ -11,7 +11,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -21,7 +21,7 @@
|
||||
"@strpc/def": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"zod": "^4.1.12"
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
|
||||
@@ -2,16 +2,24 @@ import { GET, POST, type DefAPINamespace } from "@strpc/def";
|
||||
|
||||
//굳이 할 필요는 없지만, d.ts가 깔끔해짐
|
||||
import type {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
InvalidResponse, ValidResponse
|
||||
InvalidResponse, ValidResponse
|
||||
} from "@strpc/def";
|
||||
import type {
|
||||
// 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,
|
||||
ArgDeleteAPI,
|
||||
ArgGetAPI,
|
||||
ArgHeadAPI,
|
||||
ArgPatchAPI,
|
||||
ArgPostAPI,
|
||||
ArgPutAPI,
|
||||
EmptyDeleteAPI,
|
||||
EmptyGetAPI,
|
||||
EmptyHeadAPI,
|
||||
EmptyPatchAPI,
|
||||
EmptyPostAPI,
|
||||
EmptyPutAPI,
|
||||
} from "@strpc/def/types";
|
||||
|
||||
|
||||
export type LoginResponse = {
|
||||
result: true,
|
||||
nextToken: [number, string] | undefined,
|
||||
@@ -41,6 +49,7 @@ export type AutoLoginFailed = {
|
||||
reason: string,
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const structure = {
|
||||
Login: {
|
||||
LoginByID: POST<{
|
||||
|
||||
+18
-18
@@ -1,33 +1,33 @@
|
||||
import type {
|
||||
DefAPINamespace,
|
||||
DefAPINamespace,
|
||||
} from "@strpc/def";
|
||||
import { GET, POST } from '@strpc/def';
|
||||
|
||||
//굳이 할 필요는 없지만, d.ts가 깔끔해짐
|
||||
import type {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
InvalidResponse, ValidResponse
|
||||
InvalidResponse, ValidResponse
|
||||
} from "@strpc/def";
|
||||
import type {
|
||||
// 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,
|
||||
ArgDeleteAPI,
|
||||
ArgGetAPI,
|
||||
ArgHeadAPI,
|
||||
ArgPatchAPI,
|
||||
ArgPostAPI,
|
||||
ArgPutAPI,
|
||||
EmptyDeleteAPI,
|
||||
EmptyGetAPI,
|
||||
EmptyHeadAPI,
|
||||
EmptyPatchAPI,
|
||||
EmptyPostAPI,
|
||||
EmptyPutAPI,
|
||||
} from "@strpc/def/types";
|
||||
|
||||
import { GET, POST } from '@strpc/def';
|
||||
|
||||
export type {
|
||||
InferResponse,
|
||||
InferError,
|
||||
InferQuery,
|
||||
InferResponse,
|
||||
InferError,
|
||||
InferQuery,
|
||||
} from "@strpc/def";
|
||||
export type * as def from './def/index.js';
|
||||
|
||||
export const structure = {
|
||||
daemon: {
|
||||
status: GET<ValidResponse & { isRunning: boolean, lastExecuted?: string }>(),
|
||||
start: POST<ValidResponse>(),
|
||||
stop: POST<ValidResponse>(),
|
||||
restart: POST<ValidResponse>(),
|
||||
}
|
||||
} satisfies DefAPINamespace;
|
||||
+25
-26
@@ -5,47 +5,46 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"build": "run-p type-check build-only",
|
||||
"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": "Hide_D <hided62@gmail.com>",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sammo/game_logic": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/client_ky": "workspace:^",
|
||||
"@strpc/def": "workspace:^",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-vue-next": "^0.40.6",
|
||||
"bson": "^6.10.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"ky": "^1.11.0",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-vue-next": "^0.9.26",
|
||||
"bson": "^5.4.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"ky": "^1.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.5.1"
|
||||
"pinia": "^2.1.4",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.7.0",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.6.0",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint-plugin-vue": "^10.5.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tslib": "^2.8.1",
|
||||
"vite": "^7.1.9",
|
||||
"vue-tsc": "^3.1.1",
|
||||
"zod": "^4.1.12"
|
||||
"@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",
|
||||
"tslib": "^2.6.2",
|
||||
"vite": "^4.4.6",
|
||||
"vue-tsc": "^1.8.6",
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-19
@@ -1,23 +1,28 @@
|
||||
{
|
||||
"extends": "../../tsconfig.vite.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../util"
|
||||
},
|
||||
{
|
||||
"path": "../secure_token"
|
||||
},
|
||||
{
|
||||
"path": "../../@strpc/def"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
{
|
||||
|
||||
"path": "../util"
|
||||
},
|
||||
{
|
||||
"path": "../secure_token"
|
||||
},
|
||||
{
|
||||
"path": "../../@strpc/def"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
{
|
||||
"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",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"],
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
const jestConfig = {
|
||||
preset: 'ts-jest/presets/default-esm',
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
testEnvironment: 'node',
|
||||
rootDir: '.',
|
||||
transform: {
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
useESM: true,
|
||||
tsconfig: 'tsconfig.jest.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
'mjs',
|
||||
'ts',
|
||||
'mts',
|
||||
'tsx',
|
||||
'jsx',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
"^lodash-es$": "lodash",
|
||||
"^lodash-es/(.*)$": "<rootDir>/node_modules/lodash/$1",
|
||||
},
|
||||
modulePathIgnorePatterns: [
|
||||
'<rootDir>/dist/',
|
||||
],
|
||||
moduleDirectories: [
|
||||
"node_modules",
|
||||
".*/@sammo/util"
|
||||
],
|
||||
};
|
||||
|
||||
export default jestConfig
|
||||
+15
-12
@@ -16,26 +16,29 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build",
|
||||
"test": "vitest"
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/util": "workspace:^",
|
||||
"vitest": "^3.2.4"
|
||||
"@sammo/util": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
||||
"@typescript-eslint/parser": "^8.46.0",
|
||||
"bson": "^6.10.4",
|
||||
"@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",
|
||||
"buffer": "^6.0.3",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint": "^8.49.0",
|
||||
"jest": "^29.7.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"tslib": "^2.8.1"
|
||||
"ts-jest": "^29.1.1",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bson": "^5.4.0",
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { AES_GCM_Decrypt, AES_GCM_Encrypt } from "./AES.js";
|
||||
|
||||
type TestType = {
|
||||
key: Buffer<ArrayBuffer>,
|
||||
IV: Buffer<ArrayBuffer>,
|
||||
PT: Buffer<ArrayBuffer>,
|
||||
AAD?: Buffer<ArrayBuffer>,
|
||||
CT: Buffer<ArrayBuffer>,
|
||||
key: Buffer,
|
||||
IV: Buffer,
|
||||
PT: Buffer,
|
||||
AAD?: Buffer,
|
||||
CT: Buffer,
|
||||
failed?: boolean,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { BufferSource } from "@sammo/util";
|
||||
|
||||
const subtle = globalThis.crypto.subtle;
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { ECDSA_sign_bson, ECDSA_verify_bson } from "./ECDSA.js";
|
||||
import { genKey } from "./ECKey.js";
|
||||
import { randomBytes } from "./utils.js";
|
||||
|
||||
+16
-39
@@ -3,6 +3,7 @@ import { type Bsonifiable, bsonify, type Jsonifiable, jsonify } from "@sammo/uti
|
||||
import { importSignKey, importVerifyKey } from "./ECKey.js";
|
||||
import type { ECDSASignature, ECDSA_PKCS8_P384_SignKey, ECDSA_P384_VerifyKey, ECDSASignatureBSON, ECDSASignatureJSON, ECDSASignatureRaw } from "./RawTypes.js";
|
||||
import { isBufferSource } from './types.js';
|
||||
import { isArray, isDate, isObject } from "lodash-es";
|
||||
|
||||
// 간편하게 저장하기 위해서 그냥! 매번 복잡한 일을 하도록 하자
|
||||
const subtle = globalThis.crypto.subtle;
|
||||
@@ -12,53 +13,35 @@ const hashName: EcdsaParams = {
|
||||
hash: 'SHA-512'
|
||||
}
|
||||
|
||||
export async function ECDSA_sign(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource | Uint8Array): Promise<ECDSASignatureRaw> {
|
||||
export async function ECDSA_sign(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource): Promise<ECDSASignatureRaw> {
|
||||
const subtleSignKeyP = importSignKey(sign_key);
|
||||
if (msg instanceof Uint8Array){
|
||||
if(!(msg.buffer instanceof ArrayBuffer)){
|
||||
const nmsg = new Uint8Array(new ArrayBuffer(msg.length));
|
||||
nmsg.set(msg);
|
||||
return Buffer.from(await subtle.sign(hashName, await subtleSignKeyP, nmsg));
|
||||
}
|
||||
//XXX: 멍청한 TS 5.9 버그. 신 버전 나오면 Uint8Array 해결할 것
|
||||
return Buffer.from(await subtle.sign(hashName, await subtleSignKeyP, msg as Uint8Array<ArrayBuffer>));
|
||||
}
|
||||
return Buffer.from(await subtle.sign(hashName, await subtleSignKeyP, msg));
|
||||
}
|
||||
|
||||
|
||||
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable): Promise<ECDSASignatureBSON>
|
||||
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource | Uint8Array): Promise<ECDSASignatureRaw>
|
||||
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable | BufferSource | Uint8Array): Promise<ECDSASignatureRaw | ECDSASignatureBSON> {
|
||||
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource): Promise<ECDSASignatureRaw>
|
||||
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable | BufferSource): Promise<ECDSASignatureRaw | ECDSASignatureBSON> {
|
||||
if (!isBufferSource(msg)) {
|
||||
const nmsg = BSON.serialize(bsonify(msg as Record<string, string>));
|
||||
return await ECDSA_sign(sign_key, nmsg) as ECDSASignatureBSON;
|
||||
msg = BSON.serialize(bsonify(msg as Record<string, string>));
|
||||
return await ECDSA_sign(sign_key, msg) as ECDSASignatureBSON;
|
||||
}
|
||||
return ECDSA_sign(sign_key, msg);
|
||||
}
|
||||
|
||||
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable): Promise<ECDSASignatureJSON>
|
||||
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource | Uint8Array): Promise<ECDSASignatureRaw>
|
||||
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Jsonifiable | BufferSource | Uint8Array): Promise<ECDSASignatureRaw | ECDSASignatureJSON> {
|
||||
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource): Promise<ECDSASignatureRaw>
|
||||
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Jsonifiable | BufferSource): Promise<ECDSASignatureRaw | ECDSASignatureJSON> {
|
||||
if (!isBufferSource(msg)) {
|
||||
const nmsg = Buffer.from(JSON.stringify(jsonify(msg as Record<string, string>)), 'utf-8') as Uint8Array<ArrayBuffer>;
|
||||
return await ECDSA_sign(sign_key, nmsg) as ECDSASignatureJSON;
|
||||
msg = Buffer.from(JSON.stringify(jsonify(msg as Record<string, string>)), 'utf-8');
|
||||
return await ECDSA_sign(sign_key, msg) as ECDSASignatureJSON;
|
||||
}
|
||||
return ECDSA_sign(sign_key, msg);
|
||||
}
|
||||
|
||||
export async function ECDSA_verify(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource | Uint8Array, sign: ECDSASignature): Promise<boolean> {
|
||||
export async function ECDSA_verify(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource, sign: ECDSASignature): Promise<boolean> {
|
||||
try {
|
||||
//XXX: 멍청한 TS 5.9 버그. 신 버전 나오면 Uint8Array 해결할 것
|
||||
const subtleVerifyKeyP = importVerifyKey(verify_key);
|
||||
if (msg instanceof Uint8Array){
|
||||
if(!(msg.buffer instanceof ArrayBuffer)){
|
||||
const nmsg = new Uint8Array(new ArrayBuffer(msg.length));
|
||||
nmsg.set(msg);
|
||||
return await subtle.verify(hashName, await subtleVerifyKeyP, sign, nmsg);
|
||||
}
|
||||
return await subtle.verify(hashName, await subtleVerifyKeyP, sign, msg as Uint8Array<ArrayBuffer>);
|
||||
}
|
||||
return await subtle.verify(hashName, await subtleVerifyKeyP, sign, msg);
|
||||
}
|
||||
catch (e) {
|
||||
@@ -67,13 +50,10 @@ export async function ECDSA_verify(verify_key: ECDSA_P384_VerifyKey, msg: Buffer
|
||||
}
|
||||
|
||||
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable, sign: ECDSASignatureBSON): Promise<boolean>;
|
||||
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource | Uint8Array, sign: ECDSASignatureRaw): Promise<boolean>;
|
||||
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable | BufferSource | Uint8Array, sign: ECDSASignatureBSON | ECDSASignatureRaw): Promise<boolean> {
|
||||
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource, sign: ECDSASignatureRaw): Promise<boolean>;
|
||||
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable | BufferSource, sign: ECDSASignatureBSON | ECDSASignatureRaw): Promise<boolean> {
|
||||
|
||||
try {
|
||||
if (msg instanceof Uint8Array){
|
||||
return ECDSA_verify(verify_key, msg, sign);
|
||||
}
|
||||
if (!isBufferSource(msg)) {
|
||||
msg = BSON.serialize(bsonify(msg as Record<string, string>));
|
||||
}
|
||||
@@ -85,16 +65,13 @@ export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: B
|
||||
}
|
||||
|
||||
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable, sign: ECDSASignatureJSON): Promise<boolean>;
|
||||
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource | Uint8Array, sign: ECDSASignatureRaw): Promise<boolean>;
|
||||
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: Jsonifiable | BufferSource | Uint8Array, sign: ECDSASignatureJSON | ECDSASignatureRaw): Promise<boolean> {
|
||||
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource, sign: ECDSASignatureRaw): Promise<boolean>;
|
||||
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: Jsonifiable | BufferSource, sign: ECDSASignatureJSON | ECDSASignatureRaw): Promise<boolean> {
|
||||
|
||||
try {
|
||||
if (msg instanceof Uint8Array){
|
||||
return ECDSA_verify(verify_key, msg, sign);
|
||||
}
|
||||
if (!isBufferSource(msg)) {
|
||||
const x = JSON.stringify(jsonify(msg as Record<string, string>));
|
||||
return ECDSA_verify(verify_key, Buffer.from(x, 'utf-8'), sign);
|
||||
msg = Buffer.from(x, 'utf-8');
|
||||
}
|
||||
return ECDSA_verify(verify_key, msg, sign);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { genKey, verifyKeyFromSignKey, pemFromVerifyKey, importVerifyKey, importSignKey, pemFromSignKey } from "./ECKey.js";
|
||||
import { TypeLength } from "./TypeLength.js";
|
||||
import { decodePEM } from "./utils.js";
|
||||
|
||||
@@ -80,7 +80,7 @@ export class LiteHashDRBG implements RNG {
|
||||
|
||||
protected buffer!: ArrayBuffer;
|
||||
protected bufferIdx!: number;
|
||||
protected hq: DataView<ArrayBuffer>;
|
||||
protected hq: DataView;
|
||||
protected hqIdxPos: number;
|
||||
|
||||
protected ready: Promise<void>;
|
||||
@@ -120,7 +120,7 @@ export class LiteHashDRBG implements RNG {
|
||||
return maxInt;
|
||||
}
|
||||
|
||||
public async nextBytes(bytes: number, baseBytes?: number): Promise<Uint8Array<ArrayBuffer>> {
|
||||
public async nextBytes(bytes: number, baseBytes?: number): Promise<Uint8Array> {
|
||||
bytes |= 0;
|
||||
if (bytes <= 0) {
|
||||
throw new Error(`${bytes} <= 0`);
|
||||
@@ -128,7 +128,7 @@ export class LiteHashDRBG implements RNG {
|
||||
|
||||
const ticket = this.ready;
|
||||
|
||||
let waiter: Promise<Uint8Array<ArrayBuffer> | undefined> = Promise.resolve(undefined);
|
||||
let waiter: Promise<Uint8Array | undefined> = Promise.resolve(undefined);
|
||||
|
||||
let nextBlockWait: (() => void) | null = (() => { throw 'something wrong'; });
|
||||
|
||||
@@ -194,11 +194,10 @@ export class LiteHashDRBG implements RNG {
|
||||
if (nextBlockWait) {
|
||||
nextBlockWait();
|
||||
}
|
||||
if(!nextBlock){ throw new Error("Internal error: nextBlock is undefined"); }
|
||||
return nextBlock;
|
||||
return nextBlock as Uint8Array;
|
||||
}
|
||||
|
||||
public async nextBits(bits: number, baseBytes?: number): Promise<Uint8Array<ArrayBuffer>> {
|
||||
public async nextBits(bits: number, baseBytes?: number): Promise<Uint8Array> {
|
||||
await this.ready;
|
||||
|
||||
bits |= 0;
|
||||
|
||||
@@ -5,8 +5,8 @@ export interface RNG {
|
||||
*/
|
||||
getMaxInt(): number;
|
||||
|
||||
nextBytes(bytes: number): Promise<Uint8Array<ArrayBuffer>>;
|
||||
nextBits(bits: number): Promise<Uint8Array<ArrayBuffer>>;
|
||||
nextBytes(bytes: number): Promise<Uint8Array>;
|
||||
nextBits(bits: number): Promise<Uint8Array>;
|
||||
|
||||
nextInt(max?: number): Promise<number>;
|
||||
nextFloat1(): Promise<number>;
|
||||
|
||||
@@ -24,8 +24,8 @@ export class RandUtil {
|
||||
}
|
||||
|
||||
public async nextBit(): Promise<boolean> {
|
||||
const view = await this.rng.nextBits(1);
|
||||
return view[0] != 0;
|
||||
const view = new DataView(await this.rng.nextBits(1) as ArrayBufferLike);
|
||||
return view.getUint8(0) != 0;
|
||||
}
|
||||
|
||||
public async nextBool(prob = 0.5): Promise<boolean> {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { sha256, sha512 } from "./SHA2.js";
|
||||
|
||||
test('sha2', async () => {
|
||||
|
||||
@@ -1,44 +1,19 @@
|
||||
import { isBufferSource } from "./types.js";
|
||||
import { BSON } from "bson";
|
||||
import { type Bsonifiable, bsonify } from "@sammo/util";
|
||||
import { isArray, isDate, isObject } from "lodash-es";
|
||||
const subtle = globalThis.crypto.subtle;
|
||||
|
||||
export async function sha256(msg: Bsonifiable | BufferSource | Uint8Array): Promise<ArrayBuffer> {
|
||||
if (msg instanceof Uint8Array){
|
||||
if(!(msg.buffer instanceof ArrayBuffer)){
|
||||
const nmsg = new Uint8Array(new ArrayBuffer(msg.length));
|
||||
nmsg.set(msg);
|
||||
return await subtle.digest('SHA-256', nmsg);
|
||||
}
|
||||
//XXX: 멍청한 TS 5.9 버그. 신 버전 나오면 Uint8Array 해결할 것
|
||||
const nmsg = msg as Uint8Array<ArrayBuffer>;
|
||||
return await subtle.digest('SHA-256', nmsg);
|
||||
}
|
||||
export async function sha256(msg: Bsonifiable | BufferSource): Promise<ArrayBuffer> {
|
||||
if (!isBufferSource(msg)) {
|
||||
//XXX: 멍청한 TS 5.9 버그. 신 버전 나오면 Uint8Array 해결할 것
|
||||
const nmsg = BSON.serialize(bsonify(msg)) as Uint8Array<ArrayBuffer>;
|
||||
if (!(nmsg.buffer instanceof ArrayBuffer)){ throw new Error("Internal error: BSON.serialize did not return ArrayBuffer"); }
|
||||
return await subtle.digest('SHA-256', nmsg);
|
||||
msg = BSON.serialize(bsonify(msg));
|
||||
}
|
||||
return await subtle.digest('SHA-256', msg);
|
||||
}
|
||||
|
||||
export async function sha512(msg: Bsonifiable | BufferSource | Uint8Array): Promise<ArrayBuffer> {
|
||||
if (msg instanceof Uint8Array){
|
||||
if(!(msg.buffer instanceof ArrayBuffer)){
|
||||
const nmsg = new Uint8Array(new ArrayBuffer(msg.length));
|
||||
nmsg.set(msg);
|
||||
return await subtle.digest('SHA-512', nmsg);
|
||||
}
|
||||
//XXX: 멍청한 TS 5.9 버그. 신 버전 나오면 Uint8Array 해결할 것
|
||||
const nmsg = msg as Uint8Array<ArrayBuffer>;
|
||||
return await subtle.digest('SHA-512', nmsg);
|
||||
}
|
||||
export async function sha512(msg: Bsonifiable | BufferSource): Promise<ArrayBuffer> {
|
||||
if (!isBufferSource(msg)) {
|
||||
//XXX: 멍청한 TS 5.9 버그. 신 버전 나오면 Uint8Array 해결할 것
|
||||
const nmsg = BSON.serialize(bsonify(msg)) as Uint8Array<ArrayBuffer>;
|
||||
if (!(nmsg.buffer instanceof ArrayBuffer)){ throw new Error("Internal error: BSON.serialize did not return ArrayBuffer"); }
|
||||
return await subtle.digest('SHA-512', nmsg);
|
||||
msg = BSON.serialize(bsonify(msg));
|
||||
}
|
||||
return await subtle.digest('SHA-512', msg);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { InvalidArgument } from '@sammo/util';
|
||||
import type { RandUtil } from './RandUtil.js';
|
||||
|
||||
export class InvalidVType extends InvalidArgument {
|
||||
public override name = 'InvalidVType';
|
||||
@@ -35,7 +34,7 @@ export class PrivilegedGenViolation extends RuntimeError {
|
||||
export type MergeableBuffer = MergeableBuffer[] | Buffer;
|
||||
|
||||
export function calcMergeableBuffer(item: MergeableBuffer): number {
|
||||
if (item instanceof Uint8Array) {
|
||||
if (item instanceof Buffer) {
|
||||
return item.byteLength;
|
||||
}
|
||||
let bufferSize = 0;
|
||||
@@ -57,7 +56,7 @@ export function mergeBuffer(...buffers: MergeableBuffer[]): Buffer {
|
||||
}
|
||||
|
||||
const fillBuffer = function (item: MergeableBuffer, bufferIdx: number): number {
|
||||
if (item instanceof Uint8Array) {
|
||||
if (item instanceof Buffer) {
|
||||
result.set(item, bufferIdx);
|
||||
bufferIdx += item.byteLength;
|
||||
return bufferIdx;
|
||||
@@ -100,10 +99,6 @@ 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';
|
||||
|
||||
@@ -3,6 +3,9 @@ export function isBufferSource(obj: unknown): obj is BufferSource {
|
||||
if (obj instanceof ArrayBuffer){
|
||||
return true;
|
||||
}
|
||||
if('SharedArrayBuffer' in globalThis && obj instanceof SharedArrayBuffer){
|
||||
return true;
|
||||
}
|
||||
if (ArrayBuffer.isView(obj)){
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ type InferPEMBuffer<T extends PEMString<any, any>> = Exclude<undefined, T['_pem_
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function decodePEM<T extends PEMString<any, any>>(pem: T, pemType: InferPEMType<T>): InferPEMBuffer<T>[];
|
||||
export function decodePEM(pem: string, pemType?: ValidPEMType): Buffer<ArrayBuffer>[];
|
||||
export function decodePEM(pem: string, pemType?: ValidPEMType): Buffer<ArrayBuffer>[] {
|
||||
export function decodePEM(pem: string, pemType?: ValidPEMType): Buffer[];
|
||||
export function decodePEM(pem: string, pemType?: ValidPEMType): Buffer[] {
|
||||
const tag = pemType ?? "[A-Z0-9 ]+";
|
||||
const pattern = new RegExp(`-{5}BEGIN ${tag}-{5}([a-zA-Z0-9=+\\/\\n\\r]+)-{5}END ${tag}-{5}`, "g");
|
||||
|
||||
const res: Buffer<ArrayBuffer>[] = [];
|
||||
const res: Buffer[] = [];
|
||||
let matches: RegExpExecArray | null = null;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (matches = pattern.exec(pem)) {
|
||||
@@ -63,11 +63,11 @@ export function decodePEM(pem: string, pemType?: ValidPEMType): Buffer<ArrayBuff
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function decodeSinglePEM<T extends PEMString<any, any>>(pem: T, pemType: InferPEMType<T>): InferPEMBuffer<T>;
|
||||
export function decodeSinglePEM(pem: string, pemType?: ValidPEMType): Buffer<ArrayBuffer>;
|
||||
export function decodeSinglePEM(pem: string, pemType?: ValidPEMType): Buffer<ArrayBuffer> {
|
||||
export function decodeSinglePEM(pem: string, pemType?: ValidPEMType): Buffer;
|
||||
export function decodeSinglePEM(pem: string, pemType?: ValidPEMType): Buffer {
|
||||
const res = decodePEM(pem, pemType);
|
||||
if (res.length != 1) {
|
||||
throw new Error("invalid pem");
|
||||
}
|
||||
return res[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"verbatimModuleSyntax": false,
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,18 @@
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/api_def": "workspace:^",
|
||||
"@sammo/crypto": "workspace:^",
|
||||
"@sammo/server_util": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/express": "workspace:^",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"mongoose": "^8.19.1",
|
||||
"zod": "^4.1.12"
|
||||
"dotenv": "^16.3.1",
|
||||
"mongoose": "^7.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.0"
|
||||
"@types/node": "^20.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import type { IResourceController } from "@/IResourceController.js";
|
||||
import type { CityID, GeneralID, NationID, SquadID } 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 }, rc: IResourceController): void{
|
||||
throw new NotYetImplemented();
|
||||
}
|
||||
|
||||
export function kickSquadMember(invoker: QueueType, arg: { nationID: NationID, squadID: SquadID, memberID: GeneralID }, rc: IResourceController): void {
|
||||
throw new NotYetImplemented();
|
||||
}
|
||||
|
||||
|
||||
export function blockAttackCommand(invoker: QueueType, arg: { nationID: NationID, block: boolean }, rc: IResourceController): void {
|
||||
throw new NotYetImplemented();
|
||||
}
|
||||
|
||||
export function blockJoinNation(invoker: QueueType, arg: { nationID: NationID, block: boolean }, rc: 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<GeneralPenalty, number>,
|
||||
}, rc: IResourceController):void{
|
||||
throw new NotYetImplemented();
|
||||
}
|
||||
|
||||
type ActionFunc<Arg extends Record<string, any>, Res> = (invoker: QueueType, arg: Arg, rc: IResourceController)=>Res;
|
||||
export type ActionPackDef = {
|
||||
[key: string]: ActionFunc<any, any>
|
||||
}
|
||||
|
||||
export const ActionRequestList = {
|
||||
setDefenceTrained,
|
||||
kickSquadMember,
|
||||
blockAttackCommand,
|
||||
blockJoinNation,
|
||||
createGeneral,
|
||||
} as const satisfies ActionPackDef;
|
||||
|
||||
export type ActionRequestKey = keyof typeof ActionRequestList;
|
||||
export type ActionMainArg<T extends ActionRequestKey> = Parameters<typeof ActionRequestList[T]>[1];
|
||||
export type ActionRes<T extends ActionRequestKey> = ReturnType<typeof ActionRequestList[T]>;
|
||||
@@ -1,72 +0,0 @@
|
||||
import type { ICityEntity } from "./Entity/CityEntity.js";
|
||||
import { LazyEntityUpdater } from "./LazyEntityUpdater.js";
|
||||
import type { IResourceController } from "./IResourceController.js";
|
||||
import type { CityID, GeneralID, NationID } from "./defs.js";
|
||||
import type { General } from "./General.js";
|
||||
import { must } from "@sammo/util";
|
||||
|
||||
export class City extends LazyEntityUpdater<ICityEntity>{
|
||||
protected readonly _indirectNames: (keyof ICityEntity)[] = ["id", "nationID"];
|
||||
protected readonly _entityType = "City";
|
||||
protected readonly _raw: ICityEntity;
|
||||
|
||||
protected _members = new Map<GeneralID, WeakRef<General>>;
|
||||
protected _otherMembers = new Map<NationID, Map<GeneralID, WeakRef<General>>>;
|
||||
|
||||
constructor(
|
||||
raw: ICityEntity,
|
||||
protected readonly rc: IResourceController,
|
||||
bootstrapInfo: {
|
||||
general: GeneralID[];
|
||||
}
|
||||
){
|
||||
super();
|
||||
this._raw = raw;
|
||||
this._bootstrapInfo = bootstrapInfo;
|
||||
}
|
||||
|
||||
private _bootstrapInfo?: ConstructorParameters<typeof City>[2];
|
||||
public bootstrap(){
|
||||
const rc = this.rc;
|
||||
|
||||
const bootstrapInfo = must(this._bootstrapInfo);
|
||||
delete this._bootstrapInfo;
|
||||
|
||||
const nationID = this._raw.nationID;
|
||||
|
||||
for(const id of bootstrapInfo.general){
|
||||
const general = must(rc.general(id));
|
||||
this.notifyJoinGeneral(general);
|
||||
}
|
||||
}
|
||||
|
||||
get id(): CityID {
|
||||
return this._raw.id;
|
||||
}
|
||||
|
||||
notifyJoinGeneral(general: General){
|
||||
const nationID = general.raw.nationID;
|
||||
if(nationID === this._raw.nationID){
|
||||
this._members.set(general.id, new WeakRef(general));
|
||||
return;
|
||||
}
|
||||
|
||||
let otherMembers = this._otherMembers.get(nationID);
|
||||
if(!otherMembers){
|
||||
otherMembers = new Map();
|
||||
this._otherMembers.set(nationID, otherMembers);
|
||||
}
|
||||
otherMembers.set(general.id, new WeakRef(general));
|
||||
}
|
||||
|
||||
notifyQuitGeneral(general: General){
|
||||
const nationID = general.raw.nationID;
|
||||
if(nationID === this._raw.nationID){
|
||||
this._members.delete(general.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const otherMembers = must(this._otherMembers.get(nationID));
|
||||
otherMembers.delete(general.id);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { GeneralID } from "@/defs.js"
|
||||
|
||||
//TODO: 작성!!
|
||||
|
||||
export type DestGeneralArgType = {
|
||||
destGeneralID: GeneralID;
|
||||
}
|
||||
|
||||
export type DestCityArgType = {
|
||||
destCityID: string;
|
||||
}
|
||||
|
||||
export type DestNationArgType = {
|
||||
destNationID: string;
|
||||
}
|
||||
|
||||
export type ColorArgType = {
|
||||
color: string;
|
||||
}
|
||||
|
||||
export type TextArgType = {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export type ArmyTypeArgType = {
|
||||
armyType: number;
|
||||
}
|
||||
|
||||
export type ArgType =
|
||||
| DestGeneralArgType
|
||||
| DestCityArgType
|
||||
| DestNationArgType
|
||||
;
|
||||
@@ -1,290 +0,0 @@
|
||||
import type { City } from "@/City.js";
|
||||
import { testContraints, type IConstraint } from "@/Constraint/index.js";
|
||||
import type { GameEnv } from "@/GameEnv.js";
|
||||
import type { General } from "@/General.js";
|
||||
import type { Nation } from "@/Nation.js";
|
||||
import type { IntYearMonth } from "@/defs.js";
|
||||
import type { RandUtil } from "@sammo/crypto";
|
||||
import { InvalidArgument, must } from "@sammo/util";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const enum CommandType {
|
||||
General = 0,
|
||||
Nation = 1,
|
||||
};
|
||||
|
||||
export const enum CompensateType {
|
||||
Neutral = 0,
|
||||
Positive = 1,
|
||||
Negative = -1,
|
||||
}
|
||||
|
||||
export abstract class BaseCommand<ArgType extends Record<string, any>>{
|
||||
abstract readonly actionName: string;
|
||||
abstract readonly className: string;
|
||||
abstract readonly type: CommandType;
|
||||
|
||||
public abstract readonly compensateType: CompensateType;
|
||||
|
||||
readonly isLazyCalcReqTurn: boolean = false;
|
||||
|
||||
protected cachedPermissionToReserve = false;
|
||||
protected cachedMinConditionMet = false;
|
||||
protected cachedFullConditionMet = false;
|
||||
|
||||
protected reasonNotFullConditionMet: string | undefined = undefined;
|
||||
protected reasonNotMinConditionMet: string | undefined = undefined;
|
||||
protected reasonNotPermissionToReserve: string | undefined = undefined;
|
||||
|
||||
protected _destNation?: Nation;
|
||||
protected _destCity?: City;
|
||||
protected _destGeneral?: General;
|
||||
|
||||
public get destNation(): Readonly<Nation> | undefined {
|
||||
return this._destNation;
|
||||
}
|
||||
public get destCity(): Readonly<City> | undefined {
|
||||
return this._destCity;
|
||||
}
|
||||
public get destGeneral(): Readonly<General> | undefined {
|
||||
return this._destGeneral;
|
||||
}
|
||||
|
||||
protected abstract alternative: BaseCommand<any> | undefined;
|
||||
|
||||
protected abstract permissionConstraints: IConstraint<any>[] | undefined;
|
||||
protected abstract minConditionConstraints: IConstraint<any>[] | undefined;
|
||||
protected abstract fullConditionConstraints: IConstraint<any>[] | undefined;
|
||||
protected abstract errorInvoker: string | undefined;
|
||||
|
||||
protected abstract readonly argSchema: {} extends ArgType ? undefined : z.ZodObject<ArgType>;
|
||||
protected _arg: ArgType | undefined;
|
||||
protected readonly reasonArgTestFailed?: string;
|
||||
public get arg(): Readonly<ArgType> {
|
||||
return must(this._arg);
|
||||
}
|
||||
|
||||
|
||||
public constructor(
|
||||
public readonly general: General,
|
||||
public readonly env: GameEnv,
|
||||
arg: ArgType
|
||||
) {
|
||||
this.init();
|
||||
|
||||
this.reasonArgTestFailed = this.testArg(arg);
|
||||
if(this.reasonArgTestFailed === undefined){
|
||||
this.initWithArg();
|
||||
}
|
||||
}
|
||||
|
||||
private testArg(rawArg: ArgType): string | undefined {
|
||||
if(this.argSchema === undefined){
|
||||
if(Object.keys(rawArg).length === 0){
|
||||
return undefined;
|
||||
}
|
||||
return "이 커맨드는 인자를 받지 않습니다";
|
||||
}
|
||||
const result = must(this.argSchema).safeParse(this.arg);
|
||||
if(!result.success){
|
||||
return result.error.errors[0].message;
|
||||
}
|
||||
this._arg = result.data as unknown as ArgType;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected abstract init(): void;
|
||||
protected initWithArg(): void {
|
||||
if (Object.keys(this.arg).length == 0) {
|
||||
return;
|
||||
}
|
||||
throw new InvalidArgument("initWithArg must be overridden if arg is not undefined");
|
||||
}
|
||||
|
||||
public get commandBrief(): string {
|
||||
return this.actionName;
|
||||
}
|
||||
|
||||
|
||||
//TODO: ExecuteKey의 타입
|
||||
abstract getNextExecuteKey(): string;
|
||||
abstract getNextAvailableTurn(): IntYearMonth | undefined;
|
||||
abstract setNextAvailableTurn(turn?: IntYearMonth): void;
|
||||
|
||||
abstract getCost(): [gold: number, rice: number];
|
||||
abstract getPreReqTurn(): number;
|
||||
abstract getPostReqTurn(): number;
|
||||
|
||||
protected testPostReqTurn(): ["testPostReqTurn", string] | undefined {
|
||||
if (this.getPostReqTurn() === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const nextAvailableTurn = this.getNextAvailableTurn();
|
||||
if (nextAvailableTurn === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const yearMonth = this.env.raw.yearMonth;
|
||||
const remainTurn = nextAvailableTurn - yearMonth;
|
||||
if (remainTurn <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return ["testPostReqTurn", `${remainTurn}턴 더 기다려야 합니다`];
|
||||
}
|
||||
|
||||
testPermissionToReserve(): string | undefined {
|
||||
if (this.cachedPermissionToReserve) {
|
||||
return this.reasonNotPermissionToReserve;
|
||||
}
|
||||
|
||||
if (this.reasonNotPermissionToReserve) {
|
||||
return this.reasonNotPermissionToReserve;
|
||||
}
|
||||
|
||||
const testResult = testContraints(this.permissionConstraints ?? [], this);
|
||||
if (testResult !== undefined) {
|
||||
[this.errorInvoker, this.reasonNotPermissionToReserve] = testResult;
|
||||
}
|
||||
this.cachedPermissionToReserve = true;
|
||||
return this.reasonNotPermissionToReserve;
|
||||
}
|
||||
|
||||
public canDisplay(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public testMinConditionMet(): string | undefined {
|
||||
if (this.argSchema === undefined && this.minConditionConstraints === undefined) {
|
||||
return this.testFullConditionMet();
|
||||
}
|
||||
|
||||
if(this.minConditionConstraints === undefined){
|
||||
throw new InvalidArgument("minConditionConstraints가 제대로 설정되지 않았습니다");
|
||||
}
|
||||
|
||||
if (this.cachedMinConditionMet) {
|
||||
return this.reasonNotMinConditionMet;
|
||||
}
|
||||
|
||||
const testResult = testContraints(this.minConditionConstraints, this);
|
||||
if (testResult !== undefined) {
|
||||
[this.errorInvoker, this.reasonNotMinConditionMet] = testResult;
|
||||
}
|
||||
|
||||
if (this.reasonNotMinConditionMet === undefined && !this.isLazyCalcReqTurn) {
|
||||
const result = this.testPostReqTurn();
|
||||
if (result !== undefined) {
|
||||
[this.errorInvoker, this.reasonNotMinConditionMet] = result;
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedMinConditionMet = true;
|
||||
return this.reasonNotMinConditionMet;
|
||||
}
|
||||
|
||||
public testFullConditionMet(): string | undefined {
|
||||
if(this.reasonArgTestFailed !== undefined){
|
||||
this.reasonNotFullConditionMet = this.reasonArgTestFailed;
|
||||
this.cachedFullConditionMet = true;
|
||||
return this.reasonNotFullConditionMet;
|
||||
}
|
||||
|
||||
if (this.fullConditionConstraints === undefined) {
|
||||
throw new InvalidArgument('fullConditionConstraints가 제대로 설정되지 않았습니다');
|
||||
}
|
||||
|
||||
if (this.cachedFullConditionMet) {
|
||||
return this.reasonNotFullConditionMet;
|
||||
}
|
||||
|
||||
const testResult = testContraints(this.fullConditionConstraints, this);
|
||||
if (testResult !== undefined) {
|
||||
[this.errorInvoker, this.reasonNotFullConditionMet] = testResult;
|
||||
}
|
||||
|
||||
if (this.reasonNotFullConditionMet === undefined) {
|
||||
const result = this.testPostReqTurn();
|
||||
if (result !== undefined) {
|
||||
[this.errorInvoker, this.reasonNotFullConditionMet] = result;
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedFullConditionMet = true;
|
||||
return this.reasonNotFullConditionMet;
|
||||
}
|
||||
|
||||
public get termString(): string {
|
||||
const commandName = this.commandBrief;
|
||||
const term = this.getNextAvailableTurn();
|
||||
const termMax = this.getPreReqTurn() + 1;
|
||||
return `${commandName} 수행중... (${term}/${termMax})`;
|
||||
}
|
||||
|
||||
public addTermStack(): boolean {
|
||||
if (this.getPreReqTurn() === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const lastTurn = this.general.raw.aux.lastTurn;
|
||||
|
||||
const nextTurn: Exclude<typeof lastTurn, undefined> = {
|
||||
action: this.className,
|
||||
arg: this.arg,
|
||||
brief: this.commandBrief,
|
||||
term: 1
|
||||
};
|
||||
|
||||
if (lastTurn === undefined) {
|
||||
this.general.update((raw) => {
|
||||
raw.aux.lastTurn = nextTurn;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lastTurn.action !== this.className || lastTurn.arg !== this.arg) {
|
||||
this.general.update((raw) => {
|
||||
raw.aux.lastTurn = nextTurn;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lastTurn.term < this.getPreReqTurn()) {
|
||||
nextTurn.term = lastTurn.term + 1;
|
||||
this.general.update((raw) => {
|
||||
raw.aux.lastTurn = nextTurn;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public get hasPermissionToReserve(): boolean {
|
||||
return this.testPermissionToReserve() === undefined;
|
||||
}
|
||||
|
||||
public get hasMinConditionMet(): boolean {
|
||||
return this.testMinConditionMet() === undefined;
|
||||
}
|
||||
|
||||
public get hasFullConditionMet(): boolean {
|
||||
return this.testFullConditionMet() === undefined;
|
||||
}
|
||||
|
||||
public get failString(): string {
|
||||
const commandName = this.commandBrief;
|
||||
const failReason = this.testFullConditionMet();
|
||||
if (failReason === undefined) {
|
||||
throw new Error('실행 가능한 커맨드에 대해 실패 이유를 수집');
|
||||
}
|
||||
return `${failReason} ${commandName} 실패.`;
|
||||
}
|
||||
|
||||
public get alternativeCommand(): BaseCommand<any> {
|
||||
return must(this.alternative);
|
||||
}
|
||||
|
||||
abstract run(rng: RandUtil): boolean;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import type { City } from "@/City.js"
|
||||
import type { GameEnv } from "@/GameEnv.js"
|
||||
import type { General } from "@/General.js"
|
||||
import type { Nation } from "@/Nation.js"
|
||||
|
||||
export const enum RequiredDestLevel {
|
||||
None = 0,
|
||||
Nation = 1,
|
||||
City = 2,
|
||||
General = 3,
|
||||
}
|
||||
|
||||
export interface IConstraint<ArgType extends Record<string, unknown> = {}> {
|
||||
readonly reason?: string;
|
||||
readonly requiredDestLevel: RequiredDestLevel;
|
||||
readonly arg: ArgType;
|
||||
test(input: TestInput): undefined | [testName: string, err: string];
|
||||
}
|
||||
|
||||
export const constraints: {
|
||||
[key: string]: IConstraint
|
||||
} = {
|
||||
}
|
||||
|
||||
export interface TestInput {
|
||||
general: Readonly<General>,
|
||||
destNation?: Readonly<Nation>,
|
||||
destCity?: Readonly<City>,
|
||||
destGeneral?: Readonly<General>,
|
||||
env: Readonly<GameEnv>,
|
||||
}
|
||||
|
||||
export function testContraints(testList: IConstraint[], input: TestInput): undefined | [testName: string, err: string]{
|
||||
for(const test of testList){
|
||||
if(test.requiredDestLevel >= RequiredDestLevel.Nation && input.destNation === undefined){
|
||||
return [test.constructor.name, "require destNation"];
|
||||
}
|
||||
else if(test.requiredDestLevel >= RequiredDestLevel.City && input.destCity === undefined){
|
||||
return [test.constructor.name, "require destCity"];
|
||||
}
|
||||
else if(test.requiredDestLevel >= RequiredDestLevel.General && input.destGeneral === undefined){
|
||||
return [test.constructor.name, "require destGeneral"];
|
||||
}
|
||||
const result = test.test(input);
|
||||
if(result !== undefined){
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import type { CityID, CityLevel, CityName, CityOfficerLevel, CityState, FrontStatus, NationID, FractionalRange, RegionID, IntMonth } from "../defs.js";
|
||||
|
||||
|
||||
export interface ICityEntity{
|
||||
id: CityID;
|
||||
name: CityName;
|
||||
nationID: NationID;
|
||||
|
||||
level: CityLevel;
|
||||
|
||||
//보급품 있음
|
||||
isSupplied: boolean;
|
||||
|
||||
//도시 접경 상태
|
||||
frontStatus: FrontStatus;
|
||||
|
||||
//인구
|
||||
population: {
|
||||
current: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
//농업
|
||||
agriculture: {
|
||||
current: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
//상업
|
||||
commerce: {
|
||||
current: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
//치안
|
||||
security: {
|
||||
current: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
//민도
|
||||
loyalty: {
|
||||
current: number;
|
||||
max: 100;
|
||||
}
|
||||
|
||||
//수비병
|
||||
defence: {
|
||||
current: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
//성벽
|
||||
wall: {
|
||||
current: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
//전투 사망자 수(혹은 패잔병)
|
||||
battleDeath: number;
|
||||
|
||||
//상인 존재 및 거래율, base 1
|
||||
traderStatus: {
|
||||
exists: false;
|
||||
} | {
|
||||
exists: true;
|
||||
rate: FractionalRange;
|
||||
}
|
||||
|
||||
officerAssignability: Record<CityOfficerLevel, boolean>;
|
||||
|
||||
regionID: RegionID;
|
||||
|
||||
state: CityState;
|
||||
//전투 잔여 개월
|
||||
remainWarMonth: IntMonth;
|
||||
|
||||
//국가별 수비병 사살 수
|
||||
cityDamageRatio: Record<NationID, number>;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { DiplomacyState, NationID } from "@/defs.js";
|
||||
|
||||
export interface Diplomacy {
|
||||
_id: string;
|
||||
|
||||
lowerNationID: NationID;
|
||||
higherNationID: NationID;
|
||||
|
||||
state: DiplomacyState;
|
||||
|
||||
lowerNationDeadCrew?: number;
|
||||
higherNationDeadCrew?: number;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { GeneralID, GeneralName, NationID } from "@/defs.js";
|
||||
|
||||
export interface IDiplomaticLetter {
|
||||
_id: string;
|
||||
|
||||
srcNationID: NationID;
|
||||
destNationID: NationID;
|
||||
|
||||
prevID?: string;
|
||||
|
||||
state: 'proposed' | 'accepted' | 'rejected' | 'canceled' | 'replaced';
|
||||
|
||||
srcSigner: {
|
||||
generalID: GeneralID;
|
||||
generalName: GeneralName;
|
||||
icon: string;
|
||||
};
|
||||
destSigner: {
|
||||
generalID: GeneralID;
|
||||
generalName: GeneralName;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
publicText: string;
|
||||
privateText: string;
|
||||
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { GeneralID } from "@/defs.js";
|
||||
|
||||
//게임 외부적인 General Rank 정보
|
||||
export interface IGeneralExternalRankEntity {
|
||||
generalID: GeneralID;
|
||||
|
||||
//토너먼트 결과가 앞으로도 게임 내부 데이터로 반영되지 않을것인가?
|
||||
|
||||
//토너먼트 결과
|
||||
tournamentResult: Record<'all'|'leadership'|'strength'|'intel', {
|
||||
win: number,
|
||||
lose: number,
|
||||
draw: number,
|
||||
|
||||
//토너먼트 참가 횟수
|
||||
cnt: number,
|
||||
//포인트
|
||||
point: number,
|
||||
}>;
|
||||
|
||||
tournamentBet: {
|
||||
//베팅 성공 횟수
|
||||
betWin: number;
|
||||
//베팅 금액
|
||||
betGold: number;
|
||||
//베팅 수익금
|
||||
betProfit: number;
|
||||
}
|
||||
|
||||
//유상 포인트 획득(지연)
|
||||
inheritPointEarned: number;
|
||||
//유상 포인트 소모(지연)
|
||||
inheritPointSpent: number;
|
||||
|
||||
//유산 포인트 획득량(동적)
|
||||
inheritPointEarnedDyn: number;
|
||||
//유산 포인트 획득량(베팅 등 별도 명령)
|
||||
inheritPointEarnedAction: number;
|
||||
//유산 포인트 소모량(동적)
|
||||
inheritPointSpentDyn: number;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { LogType } from "@/defs.js";
|
||||
|
||||
export interface IGeneralRecord {
|
||||
_id: string;
|
||||
|
||||
generalID: string;
|
||||
|
||||
logType: LogType;
|
||||
|
||||
year: number;
|
||||
month: number;
|
||||
|
||||
text: string;
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { GeneralID, ReservedTurn } from "@/defs.js";
|
||||
|
||||
export interface IGeneralReservedTurn {
|
||||
generalID: GeneralID;
|
||||
turn: ReservedTurn[];
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export interface IGlobalRecord {
|
||||
_id: string;
|
||||
type: 'history' | 'action';
|
||||
year: number;
|
||||
month: number;
|
||||
text: string;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { GeneralID, GeneralName, MessageType, NationID, NationName } from "@/defs.js";
|
||||
|
||||
export interface MessageTarget {
|
||||
mailbox: number;
|
||||
generalID: GeneralID;
|
||||
generalName: GeneralName;
|
||||
nationID: NationID;
|
||||
nationName: NationName;
|
||||
icon: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface IMessage{
|
||||
_id: string;
|
||||
|
||||
mailbox: number;
|
||||
|
||||
state: 'unread' | 'read' | 'deleted';
|
||||
|
||||
type: MessageType;
|
||||
|
||||
src: MessageTarget;
|
||||
dest: MessageTarget;
|
||||
|
||||
createdAt: Date;
|
||||
|
||||
text: string;
|
||||
|
||||
action?: {
|
||||
validUntil?: Date;
|
||||
used: boolean;
|
||||
action: string;
|
||||
arg: Record<string, unknown>;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { NationID } from "@/defs.js";
|
||||
|
||||
export interface INationRecord {
|
||||
_id: string;
|
||||
nationID: NationID;
|
||||
year: number;
|
||||
month: number;
|
||||
text: string;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ReservedTurn, StaffLevel } from "@/defs.js";
|
||||
|
||||
export interface INationReservedTurn {
|
||||
_id: string;
|
||||
|
||||
nationID: string;
|
||||
officerLevel: StaffLevel,
|
||||
|
||||
turn: ReservedTurn[];
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
export interface ISurvey {
|
||||
_id: string;
|
||||
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
//투표 시작 시간
|
||||
startTime: Date;
|
||||
//투표 종료 시간
|
||||
endTime?: Date;
|
||||
|
||||
multipleOptions: number;
|
||||
|
||||
options: string[];
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { GeneralID, GeneralName, NationID, NationName } from "@/defs.js";
|
||||
|
||||
export interface ISurveyComment {
|
||||
_id: string;
|
||||
surveyID: string;
|
||||
|
||||
generalID: GeneralID;
|
||||
nationID: NationID;
|
||||
|
||||
generalName: GeneralName;
|
||||
nationName: NationName;
|
||||
|
||||
comment: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { GeneralID, NationID } from "@/defs.js";
|
||||
|
||||
export interface ISurveyResponse {
|
||||
_id: string;
|
||||
surveyID: string;
|
||||
|
||||
//투표자
|
||||
generalID: GeneralID;
|
||||
|
||||
nationID: NationID;
|
||||
|
||||
//투표한 선택지
|
||||
selectedOption: number[];
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { NationID } from "@/defs.js";
|
||||
|
||||
//국제 정세
|
||||
export interface IWorldHistory {
|
||||
//MongoDB ID
|
||||
_id: string;
|
||||
|
||||
//nationID -> year -> month
|
||||
year: number;
|
||||
month: number;
|
||||
nationID?: NationID;
|
||||
|
||||
text: string;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { IntYear, IntYearMonth } from "@/defs.js";
|
||||
|
||||
export type UnitedGameState = 'onGame' | 'united' | 'onEvent' | 'endEvent';
|
||||
|
||||
export interface IGameEnvEntity {
|
||||
startTime: Date;
|
||||
lastExecuted: Date;
|
||||
|
||||
initYear: number;
|
||||
initMonth: number;
|
||||
|
||||
gameStartYear: number;
|
||||
gameStartMonth: number;
|
||||
|
||||
year: IntYear;
|
||||
month: IntYearMonth;
|
||||
yearMonth: IntYearMonth;
|
||||
|
||||
secondsPerTurn: number;
|
||||
|
||||
numberOfIdleTurnTillDeath: number;
|
||||
|
||||
isFictionMode: boolean;
|
||||
unitedGameState: UnitedGameState;
|
||||
blockGeneralCreate: boolean;
|
||||
|
||||
mapTheme: string;
|
||||
prevWinnerName?: string;
|
||||
|
||||
scenarioID: number;
|
||||
scenarioName: string;
|
||||
gameSeason: number;
|
||||
|
||||
maxGeneralCnt: number;
|
||||
maxNationCnt: number;
|
||||
|
||||
remainGeniusCnt: number;
|
||||
autoRunUserPolicy: Record<string, unknown>;
|
||||
|
||||
//게임 외부는 다른 엔티티로 분리
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import type { CityID, DiplomaticPermission, FractionalRange, GeneralID, GeneralName, IntMonth, IntYear, IntYearMonth, ItemID, ItemKeyType, NationID, NpcType, OfficerLevel, PersonalityType, ReservedTurn, SpecialityDomesticType, SpecialityWarType, SquadID, URILike, UserID, UserName } from "../defs.js";
|
||||
|
||||
export interface IGeneralEntity {
|
||||
id: GeneralID;
|
||||
ownerID?: UserID;
|
||||
npcType: NpcType;
|
||||
affinity?: number;
|
||||
icon?: URILike;
|
||||
name: GeneralName;
|
||||
ownerName?: UserName;
|
||||
nationID: NationID;
|
||||
cityID: number;
|
||||
squadID?: SquadID;
|
||||
|
||||
leadership: number;
|
||||
leadershipExp: number;
|
||||
strength: number;
|
||||
strengthExp: number;
|
||||
intel: number;
|
||||
intelExp: number;
|
||||
|
||||
injury: FractionalRange;
|
||||
|
||||
experience: number;
|
||||
dedication: number;
|
||||
|
||||
experienceLevel: number;
|
||||
dedicationLevel: number;
|
||||
|
||||
armyClassExperience: number[];
|
||||
|
||||
officerLevel: OfficerLevel;
|
||||
officerCityID?: CityID;
|
||||
|
||||
diplomaticPermission: DiplomaticPermission;
|
||||
|
||||
gold: number;
|
||||
rice: number;
|
||||
|
||||
item: Record<ItemKeyType, {
|
||||
id: ItemID,
|
||||
option: Record<string, unknown>,
|
||||
}>;
|
||||
|
||||
turnTime: Date;
|
||||
|
||||
idleTurn: IntMonth;
|
||||
|
||||
age: IntYear;
|
||||
startAge: IntYear;
|
||||
|
||||
nationJoinedYearMonth: IntYearMonth;
|
||||
|
||||
betray: number;
|
||||
|
||||
personality: PersonalityType;
|
||||
specialityDomestic: SpecialityDomesticType;
|
||||
specialityWar: SpecialityWarType;
|
||||
|
||||
defenceTrained: number;
|
||||
|
||||
armyType: number;
|
||||
armyCount: number;
|
||||
trained: number;
|
||||
morale: number;
|
||||
|
||||
npcSpec?: {
|
||||
msg?: string;
|
||||
bornyear: number;
|
||||
deadyear: number;
|
||||
}
|
||||
|
||||
aux: {
|
||||
lastBattleDate?: Date;
|
||||
lastBattleYearMonth?: IntYearMonth;
|
||||
lastTurn?: ReservedTurn & { term: number };
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { GeneralID } from "@/defs.js";
|
||||
|
||||
export interface IGeneralRankEntity {
|
||||
generalID: GeneralID;
|
||||
|
||||
//계략 성공 횟수
|
||||
sabotageCnt: number;
|
||||
|
||||
//전투 횟수
|
||||
battleCnt: number;
|
||||
//전투 승리 횟수
|
||||
battleWinCnt: number;
|
||||
//전투 패배 횟수
|
||||
battleLoseCnt: number;
|
||||
|
||||
//누적 병력 사망 수
|
||||
allDeathCnt: number;
|
||||
//누적 병력 사살 수
|
||||
allKillCnt: number;
|
||||
|
||||
//누적 대인 병력 사망 수
|
||||
armyDeathCnt: number;
|
||||
//누적 대인 병력 사살 수
|
||||
armyKillCnt: number;
|
||||
|
||||
//점령 수
|
||||
occupyCnt: number;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import type { CityID, ColorType, IntYearMonth, NationCmdType, NationID, NationLevel, NationName, NationType, FractionalRange, IntMonth, ReservedTurn, StaffLevel, StrategicCmdType } from "../defs.js";
|
||||
|
||||
export interface INationEntity{
|
||||
id: NationID;
|
||||
name: NationName;
|
||||
type: NationType;
|
||||
|
||||
capitalID: CityID;
|
||||
color: ColorType;
|
||||
|
||||
level: NationLevel;
|
||||
|
||||
lastExecutedYearMonth: Record<
|
||||
'capitalSet'
|
||||
,number>;
|
||||
|
||||
//TODO: 이게 뭐더라
|
||||
capset: IntYearMonth;
|
||||
|
||||
strategicCmdLimit: Record<StrategicCmdType, IntYearMonth>;
|
||||
|
||||
//세율
|
||||
taxRate: FractionalRange;
|
||||
//지급율
|
||||
payRate: FractionalRange;
|
||||
|
||||
gold: number;
|
||||
rice: number;
|
||||
|
||||
blockJoin: boolean;
|
||||
blockWar: boolean;
|
||||
|
||||
//특수외교커맨드 제한
|
||||
specialDiplomacyCmdLimit: IntYearMonth;
|
||||
|
||||
//기술력
|
||||
technology: number;
|
||||
|
||||
//환산 국력
|
||||
power: number;
|
||||
|
||||
//첩보 제한
|
||||
spyLimit: Map<CityID, IntMonth>;
|
||||
|
||||
//커맨드별 옵션
|
||||
commandOption: Map<NationCmdType, Map<string, unknown>>;
|
||||
|
||||
lastExecutedTurn: Map<StaffLevel, ReservedTurn>;
|
||||
|
||||
aux: {
|
||||
availableWarSettingCnt?: number;
|
||||
prevIncomeGold: number;
|
||||
prevIncomeRice: number;
|
||||
|
||||
lastBattleDate?: Date;
|
||||
lastBattleYearMonth?: IntYearMonth;
|
||||
}
|
||||
|
||||
//TODO: 타입 정의
|
||||
nationPolicy: Record<string, unknown>;
|
||||
//TODO: 타입 정의
|
||||
npcPolicy: Record<string, unknown>;
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import type { GeneralID, GeneralName, NationName, ScenarioID, ServerID, UserID, UserName } from "@/defs.js"
|
||||
import type { IGeneralRankEntity } from "../GeneralRankEntity.js";
|
||||
import type { IGeneralExternalRankEntity } from "../External/GeneralExternalRankEntity.js";
|
||||
|
||||
|
||||
|
||||
export interface IHallOfFame {
|
||||
_id: string
|
||||
|
||||
serverID: ServerID;
|
||||
serverIdx: number;
|
||||
season: number;
|
||||
|
||||
scenarioID: ScenarioID;
|
||||
scenarioName: string;
|
||||
|
||||
generalID: GeneralID;
|
||||
generalName: GeneralName;
|
||||
ownerID: UserID;
|
||||
ownerName: UserName;
|
||||
icon: string;
|
||||
|
||||
nationName: NationName;
|
||||
nationColor: string;
|
||||
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
|
||||
ingame: IGeneralRankEntity;
|
||||
external: IGeneralExternalRankEntity;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { CityID, CityLevel, CityState, NationID, NpcType, OfficerLevel, RegionID, ServerID } from "@/defs.js";
|
||||
|
||||
export interface IHistory {
|
||||
_id: string;
|
||||
|
||||
serverID: ServerID;
|
||||
year: number;
|
||||
month: number;
|
||||
mapBase: string;
|
||||
nations: Map<NationID, {
|
||||
name: string;
|
||||
color: string;
|
||||
cities: Set<CityID>;
|
||||
generals: {
|
||||
name: string;
|
||||
npcType: NpcType;
|
||||
}[];
|
||||
}>;
|
||||
cities: Map<CityID, {
|
||||
nationID: NationID;
|
||||
name: string;
|
||||
isSupplied: boolean;
|
||||
|
||||
level: CityLevel;
|
||||
state: CityState;
|
||||
regionID: RegionID;
|
||||
|
||||
remainWarMonth: number;
|
||||
}>
|
||||
globalHistory: string[];
|
||||
globalAction: string[];
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { CityLevel, GeneralID, ServerID, UserID } from "@/defs.js";
|
||||
import type { IGeneralEntity } from "../GeneralEntity.js";
|
||||
import type { IGeneralRankEntity } from "../GeneralRankEntity.js";
|
||||
|
||||
export interface OldGeneral {
|
||||
_id: string;
|
||||
|
||||
serverID: ServerID;
|
||||
generalID: GeneralID;
|
||||
ownerID?: UserID;
|
||||
|
||||
recordVersion: 1 | 2;
|
||||
|
||||
lastYearMonth: number;
|
||||
|
||||
general: IGeneralEntity;
|
||||
rank: IGeneralRankEntity;
|
||||
|
||||
squadName?: string;
|
||||
cityBasic: {
|
||||
name: string;
|
||||
level: CityLevel;
|
||||
}
|
||||
nationBasic: {
|
||||
name: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { GeneralID, GeneralName, ItemID, ItemKeyType, NationID, NpcType, OfficerLevel, PersonalityType, ServerID, SpecialityDomesticType, SpecialityWarType, URILike, UserID } from "@/defs.js";
|
||||
import type { INationEntity } from "../NationEntity.js";
|
||||
|
||||
export interface IOldNation {
|
||||
_id: string;
|
||||
|
||||
serverID: ServerID;
|
||||
nationID: NationID;
|
||||
|
||||
recordVersion: 1 | 2;
|
||||
|
||||
nation: INationEntity;
|
||||
|
||||
lastYearMonth: number;
|
||||
|
||||
members: Record<number, {
|
||||
generalID: GeneralID;
|
||||
generalName: GeneralName;
|
||||
ownerID?: UserID;
|
||||
ownerName?: string;
|
||||
|
||||
npcType?: NpcType;
|
||||
icon?: URILike;
|
||||
|
||||
officerLevel: OfficerLevel;
|
||||
officerCityID?: number;
|
||||
}>
|
||||
|
||||
history: {
|
||||
year: number;
|
||||
month: number;
|
||||
text: string[];
|
||||
};
|
||||
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { ServerID, UserID } from "@/defs.js";
|
||||
|
||||
export interface IUserRecord {
|
||||
_id: string;
|
||||
userID: UserID;
|
||||
serverID: ServerID;
|
||||
|
||||
logType: 'inheritPoint';
|
||||
|
||||
//게임 내 연도
|
||||
year: number;
|
||||
month: number;
|
||||
|
||||
text: string;
|
||||
|
||||
createdAt: Date;
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { NationID, SquadID } from "../defs.js";
|
||||
|
||||
export interface ISquadEntity {
|
||||
id: SquadID;
|
||||
nationID: NationID;
|
||||
name: string;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { ValuesOf } from '@sammo/util';
|
||||
import type { ICityEntity } from './CityEntity.js';
|
||||
import type { IGeneralEntity } from './GeneralEntity.js';
|
||||
import type { INationEntity } from './NationEntity.js';
|
||||
import type { ISquadEntity } from './SquadEntity.js';
|
||||
import type { IGameEnvEntity } from './GameEnvEntity.js';
|
||||
|
||||
export type { IGeneralEntity } from './GeneralEntity.js';
|
||||
export type { ICityEntity } from './CityEntity.js';
|
||||
export type { ISquadEntity } from './SquadEntity.js';
|
||||
export type { INationEntity } from './NationEntity.js';
|
||||
|
||||
|
||||
export type ValidEntityList = {
|
||||
'Nation': INationEntity;
|
||||
'City': ICityEntity;
|
||||
'Squad': ISquadEntity;
|
||||
'General': IGeneralEntity;
|
||||
|
||||
'GameEnv': IGameEnvEntity;
|
||||
};
|
||||
|
||||
export type ValidEntity = ValuesOf<ValidEntityList>;
|
||||
export type ValidEntityType = keyof ValidEntityList;
|
||||
export type EntityType<Entity> = Entity extends ValidEntityList[infer T extends keyof ValidEntityList] ? T : never;
|
||||
@@ -1,6 +0,0 @@
|
||||
export class LogicError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
this.name = 'LogicError';
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { IGameEnvEntity } from "./Entity/GameEnvEntity.js";
|
||||
import { LazyEntityUpdater } from "./LazyEntityUpdater.js";
|
||||
import type { IResourceController } from "./IResourceController.js";
|
||||
|
||||
export class GameEnv extends LazyEntityUpdater<IGameEnvEntity>{
|
||||
protected readonly _indirectNames: readonly (keyof IGameEnvEntity)[] = [
|
||||
//사실상 모두.
|
||||
];
|
||||
protected readonly _entityType = "GameEnv";
|
||||
protected readonly _raw: IGameEnvEntity;
|
||||
|
||||
constructor(
|
||||
raw: IGameEnvEntity,
|
||||
protected readonly rc: IResourceController
|
||||
){
|
||||
super();
|
||||
this._raw = raw;
|
||||
}
|
||||
|
||||
public bootstrap(){
|
||||
//do nothing;
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
import type { IResourceController } from "./IResourceController.js";
|
||||
import type { GeneralID, IntMonth, IntYear, LogType, NationID } from "./defs.js";
|
||||
|
||||
type LoggerStack = {
|
||||
symbol: Symbol;
|
||||
year: IntYear;
|
||||
month: IntMonth;
|
||||
|
||||
global: {
|
||||
action: string[];
|
||||
history: string[];
|
||||
}
|
||||
nation: {
|
||||
nationID: NationID;
|
||||
history: string[];
|
||||
}
|
||||
general: {
|
||||
[key in LogType]: string[];
|
||||
} & {
|
||||
generalID: GeneralID;
|
||||
}
|
||||
}
|
||||
|
||||
export class GameLoggerEngine {
|
||||
private static instance: GameLoggerEngine;
|
||||
|
||||
private constructor(
|
||||
private rc: IResourceController
|
||||
) {
|
||||
}
|
||||
|
||||
public static initInstance(rc: IResourceController): GameLoggerEngine{
|
||||
if(GameLoggerEngine.instance){
|
||||
throw new Error("GameLogger is already initialized");
|
||||
}
|
||||
GameLoggerEngine.instance = new GameLoggerEngine(rc);
|
||||
return GameLoggerEngine.instance;
|
||||
}
|
||||
|
||||
public static getInstance(): GameLoggerEngine {
|
||||
if (!GameLoggerEngine.instance) {
|
||||
throw new Error("GameLogger is not initialized");
|
||||
}
|
||||
|
||||
return GameLoggerEngine.instance;
|
||||
}
|
||||
|
||||
private logStack = new Map<Symbol, LoggerStack>();
|
||||
|
||||
public addStack(stack: LoggerStack) {
|
||||
this.logStack.set(stack.symbol, stack);
|
||||
return stack;
|
||||
}
|
||||
|
||||
public getGameLogger(year: IntYear, month: IntMonth): GameLogger {
|
||||
return new GameLogger(year, month, this);
|
||||
}
|
||||
|
||||
public getNationLogger(year: IntYear, month: IntMonth, nationID: NationID): NationLogger {
|
||||
return new NationLogger(year, month, nationID, this);
|
||||
}
|
||||
|
||||
public getGeneralLogger(year: IntYear, month: IntMonth, nationID: NationID, generalID: GeneralID): GeneralLogger {
|
||||
return new GeneralLogger(year, month, nationID, generalID, this);
|
||||
}
|
||||
|
||||
flushAll() {
|
||||
if(this.logStack.size === 0){
|
||||
return;
|
||||
}
|
||||
//rc?
|
||||
throw new Error("Not yet implemented");
|
||||
this.logStack.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export class GameLogger {
|
||||
protected stack: LoggerStack;
|
||||
|
||||
public constructor(
|
||||
year: IntYear,
|
||||
month: IntMonth,
|
||||
protected readonly engine: GameLoggerEngine,
|
||||
) {
|
||||
this.stack = {
|
||||
symbol: Symbol(),
|
||||
year,
|
||||
month,
|
||||
global: {
|
||||
action: [],
|
||||
history: []
|
||||
},
|
||||
nation: {
|
||||
nationID: 0,
|
||||
history: []
|
||||
},
|
||||
general: {
|
||||
action: [],
|
||||
battle: [],
|
||||
battleBrief: [],
|
||||
history: [],
|
||||
generalID: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pushGlobalActionLog(text: string) {
|
||||
this.stack.global.action.push(text);
|
||||
}
|
||||
|
||||
pushGlobalHistoryLog(text: string) {
|
||||
this.stack.global.history.push(text);
|
||||
}
|
||||
|
||||
reset() {
|
||||
const { year, month } = this.stack;
|
||||
const nationID = this.stack.nation.nationID;
|
||||
const generalID = this.stack.general.generalID;
|
||||
this.stack = {
|
||||
symbol: Symbol(),
|
||||
year,
|
||||
month,
|
||||
global: {
|
||||
action: [],
|
||||
history: []
|
||||
},
|
||||
nation: {
|
||||
nationID,
|
||||
history: []
|
||||
},
|
||||
general: {
|
||||
action: [],
|
||||
battle: [],
|
||||
battleBrief: [],
|
||||
history: [],
|
||||
generalID
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
commit() {
|
||||
this.engine.addStack(this.stack);
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
export class NationLogger extends GameLogger {
|
||||
public constructor(
|
||||
year: IntYear,
|
||||
month: IntMonth,
|
||||
private readonly nationID: NationID,
|
||||
engine: GameLoggerEngine
|
||||
) {
|
||||
super(year, month, engine);
|
||||
this.stack.nation.nationID = nationID;
|
||||
}
|
||||
|
||||
pushNationLog(text: string) {
|
||||
this.stack.nation.history.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
export class GeneralLogger extends NationLogger {
|
||||
public constructor(
|
||||
year: IntYear,
|
||||
month: IntMonth,
|
||||
nationID: NationID,
|
||||
private readonly generalID: GeneralID,
|
||||
engine: GameLoggerEngine
|
||||
) {
|
||||
super(year, month, nationID, engine);
|
||||
this.stack.general.generalID = generalID;
|
||||
}
|
||||
|
||||
pushGeneralActionLog(text: string) {
|
||||
this.stack.general.action.push(text);
|
||||
}
|
||||
|
||||
pushGeneralBattleLog(text: string) {
|
||||
this.stack.general.battle.push(text);
|
||||
}
|
||||
|
||||
pushGeneralBattleBriefLog(text: string) {
|
||||
this.stack.general.battleBrief.push(text);
|
||||
}
|
||||
|
||||
pushGeneralHistoryLog(text: string) {
|
||||
this.stack.general.history.push(text);
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
import { InvalidArgument, NotYetImplemented } from "@sammo/util";
|
||||
import { City } from "./City.js";
|
||||
import type { IGeneralEntity } from "./Entity/GeneralEntity.js";
|
||||
import type { IResourceController } from "./IResourceController.js";
|
||||
import { DiplomaticPermission, type CityID, type GeneralID, type GeneralName, type NationID, OfficerLevel, type SquadID, NpcType, StaffLevel, CityOfficerLevel } from "./defs.js";
|
||||
import { must } from "@sammo/util";
|
||||
import { Nation } from "./Nation.js";
|
||||
import { Squad } from "./Squad.js";
|
||||
import { LazyEntityUpdater } from "./LazyEntityUpdater.js";
|
||||
import type { IAction, IActionKey } from "./IAction.js";
|
||||
import { addSeconds } from "date-fns";
|
||||
|
||||
export class General extends LazyEntityUpdater<IGeneralEntity> {
|
||||
protected readonly _indirectNames: (keyof IGeneralEntity)[] = [
|
||||
"id", "nationID", "squadID", "nationID", "turnTime"
|
||||
];
|
||||
protected readonly _entityType = "General";
|
||||
protected readonly _raw: IGeneralEntity;
|
||||
|
||||
protected _city!: WeakRef<City>;
|
||||
protected _squad!: WeakRef<Squad> | undefined;
|
||||
protected _nation!: WeakRef<Nation>;
|
||||
|
||||
protected readonly iActionHandlers = new Map<IActionKey, IAction>;
|
||||
|
||||
constructor(
|
||||
raw: IGeneralEntity,
|
||||
protected readonly rc: IResourceController
|
||||
){
|
||||
super();
|
||||
this._raw = raw;
|
||||
}
|
||||
|
||||
public bootstrap(){
|
||||
const rc = this.rc;
|
||||
const raw = this._raw;
|
||||
this._city = new WeakRef(must(rc.city(raw.cityID)));
|
||||
this._nation = new WeakRef(must(rc.nation(raw.nationID)));
|
||||
|
||||
if(raw.squadID){
|
||||
this._squad = new WeakRef(must(rc.squad(raw.squadID, raw.nationID)));
|
||||
}
|
||||
|
||||
this.reconfigureIActionHandlers();
|
||||
}
|
||||
|
||||
public get id(): GeneralID {
|
||||
return this._raw.id;
|
||||
}
|
||||
|
||||
public get name(): GeneralName {
|
||||
return this._raw.name;
|
||||
}
|
||||
|
||||
public get city(): City {
|
||||
return must(this._city.deref());
|
||||
}
|
||||
|
||||
public get squad(): Squad | undefined {
|
||||
return this._squad?.deref();
|
||||
}
|
||||
|
||||
public get nation(): Nation {
|
||||
return must(this._nation.deref());
|
||||
}
|
||||
|
||||
public get isUser(): boolean {
|
||||
return this._raw.npcType <= NpcType.user_borrowed_npc;
|
||||
}
|
||||
|
||||
public get staffLevel(): StaffLevel | undefined {
|
||||
if(this._raw.officerLevel < OfficerLevel.staffIntel3){
|
||||
return undefined;
|
||||
}
|
||||
return this._raw.officerLevel as number as StaffLevel;
|
||||
}
|
||||
|
||||
public get cityOfficerLevel(): CityOfficerLevel | undefined {
|
||||
if(this._raw.officerLevel >= OfficerLevel.staffIntel3){
|
||||
return undefined;
|
||||
}
|
||||
if(this._raw.officerLevel < OfficerLevel.cityLeadership){
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._raw.officerLevel as number as CityOfficerLevel;
|
||||
}
|
||||
|
||||
public changeCity(city: City){
|
||||
this.city.notifyQuitGeneral(this);
|
||||
|
||||
this._city = new WeakRef(city);
|
||||
this.forceUpdate((raw) => {
|
||||
raw.cityID = city.id;
|
||||
});
|
||||
city.notifyJoinGeneral(this);
|
||||
|
||||
this.reconfigureIActionHandlers();
|
||||
}
|
||||
|
||||
public quitSquad(){
|
||||
if(!this._squad){
|
||||
return;
|
||||
}
|
||||
|
||||
const squad = must(this._squad.deref());
|
||||
if(squad.leader.id === this._raw.id){
|
||||
squad.destroySquad(false);
|
||||
}
|
||||
else{
|
||||
squad.notifyQuitGeneral(this);
|
||||
}
|
||||
|
||||
this._squad = undefined;
|
||||
this.forceUpdate((raw) => {
|
||||
raw.squadID = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
public changeSquad(squad: Squad){
|
||||
this.quitSquad();
|
||||
|
||||
this._squad = new WeakRef(squad);
|
||||
this.forceUpdate((raw) => {
|
||||
raw.squadID = squad.id;
|
||||
});
|
||||
squad.notifyJoinGeneral(this);
|
||||
}
|
||||
|
||||
public changeNation(nation: Nation){
|
||||
this.quitSquad();
|
||||
this.nation.notifyQuitGeneral(this);
|
||||
|
||||
this._nation = new WeakRef(nation);
|
||||
this.forceUpdate((raw) => {
|
||||
raw.nationID = nation.id;
|
||||
raw.diplomaticPermission = DiplomaticPermission.none;
|
||||
if(nation.id == 0){
|
||||
raw.officerLevel = OfficerLevel.none;
|
||||
}
|
||||
else{
|
||||
raw.officerLevel = OfficerLevel.normal;
|
||||
}
|
||||
raw.nationJoinedYearMonth = this.rc.gameEnv.raw.yearMonth;
|
||||
});
|
||||
nation.notifyJoinGeneral(this);
|
||||
|
||||
this.reconfigureIActionHandlers();
|
||||
}
|
||||
|
||||
private reconfigureIActionHandlers(){
|
||||
throw new NotYetImplemented();
|
||||
}
|
||||
|
||||
public updateTurnTime(){
|
||||
const secondsPerTurn = this.rc.gameEnv.raw.secondsPerTurn;
|
||||
|
||||
this.forceUpdate((raw) => {
|
||||
raw.turnTime = addSeconds(raw.turnTime, secondsPerTurn);
|
||||
});
|
||||
}
|
||||
|
||||
public compareTurnTime(other: General): number {
|
||||
if(this._raw.turnTime < other._raw.turnTime){
|
||||
return -1;
|
||||
}
|
||||
if(this._raw.turnTime > other._raw.turnTime){
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(this._raw.id < other._raw.id){
|
||||
return -1;
|
||||
}
|
||||
if(this._raw.id > other._raw.id){
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
|
||||
class SpecialityHelper {
|
||||
}
|
||||
|
||||
export const specialityHelper = new SpecialityHelper();
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
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;
|
||||
@@ -1,8 +0,0 @@
|
||||
export { BaseSpecial } from "./BaseSpecial.js";
|
||||
|
||||
import { che_필살 } from "./SpecialWar/che_필살.js";
|
||||
|
||||
|
||||
export const SpecialWar = {
|
||||
che_필살
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import type { ValuesOf } from "@sammo/util";
|
||||
import type * as IMethod from "./IActionMethod.js";
|
||||
import type { ItemKeyType } from "./defs.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;
|
||||
}
|
||||
|
||||
export const IActionOrder = [
|
||||
'nationType',
|
||||
'officerLevel',
|
||||
'specialityDomestic',
|
||||
'specialityWar',
|
||||
'personlity',
|
||||
'armyType',
|
||||
'inheritBuff',
|
||||
'scenarioEffect',
|
||||
'horse',
|
||||
'weapon',
|
||||
'book',
|
||||
'item',
|
||||
];
|
||||
|
||||
export type IActionKey = ValuesOf<IAction>;
|
||||
@@ -1,63 +0,0 @@
|
||||
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, StrategicCmdType } 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: StrategicCmdType, 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;
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import type { General } from "./General.js";
|
||||
import type { City } from "./City.js";
|
||||
import type { Nation } from "./Nation.js";
|
||||
import type { CityID, GeneralID, NationID, ReservedTurn, SquadID } from "./defs.js";
|
||||
import type { GameEnv } from "./GameEnv.js";
|
||||
import type { IGeneralEntity } from "./Entity/GeneralEntity.js";
|
||||
import type { Squad } from "./Squad.js";
|
||||
import type { ISquadEntity } from "./Entity/SquadEntity.js";
|
||||
import type { LazyEntityUpdater } from "./LazyEntityUpdater.js";
|
||||
import type { ICityEntity } from "./Entity/CityEntity.js";
|
||||
import type { INationEntity } from "./Entity/NationEntity.js";
|
||||
import type { EntityType, ValidEntityType } from "./Entity/index.js";
|
||||
|
||||
export interface IResourceController {
|
||||
|
||||
nation(id: NationID): Nation | undefined;
|
||||
city(id: CityID): City | undefined;
|
||||
squad(id: SquadID, nation: NationID): Squad | undefined;
|
||||
general(id: GeneralID): General | undefined;
|
||||
|
||||
peekGeneralTurnTimeQueue(): General | undefined;
|
||||
popGeneralTurnTimeQueue(): General | undefined;
|
||||
insertGeneralTurnTimeQueue(general: General, maybeTail: boolean): void;
|
||||
|
||||
readonly gameEnv: GameEnv;
|
||||
|
||||
readonly nationList: Map<NationID, Nation>;
|
||||
readonly cityList: Map<CityID, City>;
|
||||
readonly squadList: Map<SquadID, Squad>;
|
||||
readonly generalList: Map<GeneralID, General>;
|
||||
|
||||
//----Insert, Update, Delete
|
||||
|
||||
reserveUpdate(entity: LazyEntityUpdater<any>): void;
|
||||
reserveDelete(entity: LazyEntityUpdater<any>): void;
|
||||
|
||||
//TODO: create* 방식과 구현 방식 비교
|
||||
reserveInsert(entity: LazyEntityUpdater<any>): void;
|
||||
|
||||
//TODO: reserveInsert와 구현 방식 비교
|
||||
createGeneral(raw: IGeneralEntity): General;
|
||||
createNation(raw: INationEntity): Nation;
|
||||
createSquad(raw: ISquadEntity): Squad;
|
||||
|
||||
|
||||
_getAllChanges(): Record<
|
||||
ValidEntityType,
|
||||
Map<number, [op: 'insert' | 'update' | 'delete', value: LazyEntityUpdater<any>]>
|
||||
>;
|
||||
|
||||
//---- DB 접근 ---
|
||||
|
||||
//예턴은 호출마다 DB에서 조회
|
||||
getReservedTurn(general: General): Promise<ReservedTurn>;
|
||||
nationReservedTurn(general: General): Promise<ReservedTurn | undefined>;
|
||||
|
||||
resetAndFill(): Promise<void>;
|
||||
saveAll(): Promise<void>;
|
||||
}
|
||||
|
||||
let _resourceController: IResourceController | undefined = undefined;
|
||||
|
||||
export const setResourceController = (rc: IResourceController) => {
|
||||
_resourceController = rc;
|
||||
}
|
||||
|
||||
export const getResourceController = () => {
|
||||
if (_resourceController === undefined) {
|
||||
throw new Error("ResourceController not set");
|
||||
}
|
||||
return _resourceController;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { ValidEntity, EntityType } from "./Entity/index.js";
|
||||
import type { IResourceController } from "./IResourceController.js";
|
||||
|
||||
export abstract class LazyEntityUpdater<Entity extends ValidEntity>{
|
||||
protected abstract readonly _indirectNames: ReadonlyArray<keyof Entity>;
|
||||
protected abstract readonly _entityType: EntityType<Entity>;
|
||||
|
||||
protected abstract readonly _raw: Entity;
|
||||
|
||||
protected abstract readonly rc: IResourceController;
|
||||
|
||||
public abstract bootstrap(): void;
|
||||
|
||||
public get raw(): Readonly<Entity> {
|
||||
return this._raw;
|
||||
}
|
||||
|
||||
public get entityType(): EntityType<Entity> {
|
||||
return this._entityType;
|
||||
}
|
||||
|
||||
protected forceUpdate(callback: (raw: Entity) => void) {
|
||||
callback(this._raw);
|
||||
this.rc.reserveUpdate(this);
|
||||
}
|
||||
|
||||
public update(callback: (raw: Entity) => void) {
|
||||
const oldRaw = { ...this._raw };
|
||||
callback(this._raw);
|
||||
|
||||
//for구문이 빠른가, Proxy setter로 제어하는게 빠른가
|
||||
for (const indirectName of this._indirectNames) {
|
||||
if (oldRaw[indirectName] !== this._raw[indirectName]) {
|
||||
throw new Error(`${String(indirectName)} change by update() is not allowed`);
|
||||
}
|
||||
}
|
||||
this.rc.reserveUpdate(this);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { General } from "@/General.js";
|
||||
import { NotYetImplemented } from "@sammo/util";
|
||||
|
||||
const battleMonthThreshold = 12;
|
||||
|
||||
export function categorizeGeneral(general: General): 'war' | 'domestic' {
|
||||
|
||||
const nationLastBattle = general.nation.raw.aux.lastBattleYearMonth;
|
||||
const lastBattle = general.raw.aux.lastBattleYearMonth;
|
||||
|
||||
if(lastBattle !== undefined && nationLastBattle !== undefined){
|
||||
if(lastBattle >= nationLastBattle - battleMonthThreshold){
|
||||
return 'war';
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotYetImplemented();
|
||||
|
||||
return 'domestic';
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
import { LazyEntityUpdater } from "./LazyEntityUpdater.js";
|
||||
import type { INationEntity } from "./Entity/NationEntity.js";
|
||||
import type { IResourceController } from "./IResourceController.js";
|
||||
import { StaffLevel, type CityID, type GeneralID, type NationID, type NationName, type SquadID } from "./defs.js";
|
||||
import type { City } from "./City.js";
|
||||
import { General } from "./General.js";
|
||||
import { must } from "@sammo/util";
|
||||
import type { Squad } from "./Squad.js";
|
||||
import { LogicError } from "./Errors.js";
|
||||
import { categorizeGeneral } from "./LogicFunc/categorizeGeneral.js";
|
||||
|
||||
export class Nation extends LazyEntityUpdater<INationEntity> {
|
||||
protected readonly _indirectNames: (keyof INationEntity)[] = ["id"];
|
||||
protected readonly _entityType = "Nation";
|
||||
protected readonly _raw: INationEntity;
|
||||
|
||||
|
||||
protected readonly categorizedNPCGeneralList = {
|
||||
war: new Map<GeneralID, WeakRef<General>>(),
|
||||
domestic: new Map<GeneralID, WeakRef<General>>()
|
||||
};
|
||||
protected readonly categorizedUserGeneralList = {
|
||||
war: new Map<GeneralID, WeakRef<General>>(),
|
||||
domestic: new Map<GeneralID, WeakRef<General>>()
|
||||
};
|
||||
|
||||
|
||||
protected readonly cityList = new Map<CityID, WeakRef<City>>;
|
||||
protected readonly squadList = new Map<SquadID, WeakRef<Squad>>;
|
||||
protected readonly generalList = new Map<GeneralID, WeakRef<General>>;
|
||||
|
||||
protected readonly npcGeneralList = new Map<GeneralID, WeakRef<General>>();
|
||||
protected readonly userGeneralList = new Map<GeneralID, WeakRef<General>>();
|
||||
|
||||
protected readonly warNPCGeneralList = new Map<GeneralID, WeakRef<General>>();
|
||||
protected readonly warUserGeneralList = new Map<GeneralID, WeakRef<General>>();
|
||||
protected readonly domesticNPCGeneralList = new Map<GeneralID, WeakRef<General>>();
|
||||
protected readonly domesticUserGeneralList = new Map<GeneralID, WeakRef<General>>();
|
||||
|
||||
protected readonly staffList = new Map<StaffLevel, WeakRef<General>>();
|
||||
|
||||
constructor(
|
||||
raw: INationEntity,
|
||||
protected readonly rc: IResourceController,
|
||||
bootstrapInfo: {
|
||||
general: GeneralID[];
|
||||
squad: SquadID[];
|
||||
city: CityID[];
|
||||
},
|
||||
) {
|
||||
super();
|
||||
this._raw = raw;
|
||||
this._bootstrapInfo = bootstrapInfo;
|
||||
}
|
||||
|
||||
private _bootstrapInfo?: ConstructorParameters<typeof Nation>[2];
|
||||
bootstrap() {
|
||||
const rc = this.rc;
|
||||
|
||||
const bootstrapInfo = must(this._bootstrapInfo);
|
||||
delete this._bootstrapInfo;
|
||||
|
||||
const bootstrapCityID = bootstrapInfo.city;
|
||||
for (const city of bootstrapCityID.map((id) => must(rc.cityList.get(id)))) {
|
||||
this.notifyJoinCity(city);
|
||||
}
|
||||
|
||||
const bootstrapSquadID = bootstrapInfo.squad;
|
||||
for(const squad of bootstrapSquadID.map((id) => must(rc.squadList.get(id)))){
|
||||
this.notifyAddSquad(squad);
|
||||
}
|
||||
|
||||
const bootstrapGeneralID = bootstrapInfo.general;
|
||||
for (const general of bootstrapGeneralID.map((id) => must(rc.generalList.get(id)))) {
|
||||
this.notifyJoinGeneral(general);
|
||||
}
|
||||
}
|
||||
|
||||
public get id(): NationID {
|
||||
return this._raw.id;
|
||||
}
|
||||
|
||||
public get name(): NationName {
|
||||
return this._raw.name;
|
||||
}
|
||||
|
||||
public notifyAddSquad(squad: Squad) {
|
||||
this.squadList.set(squad.id, new WeakRef(squad));
|
||||
}
|
||||
|
||||
public notifyDestroySquad(squad: Squad) {
|
||||
this.squadList.delete(squad.id);
|
||||
}
|
||||
|
||||
public notifyJoinCity(city: City) {
|
||||
this.cityList.set(city.id, new WeakRef(city));
|
||||
}
|
||||
|
||||
public notifyQuitCity(city: City) {
|
||||
this.cityList.delete(city.id);
|
||||
}
|
||||
|
||||
public notifyQuitGeneral(general: General) {
|
||||
const generalID = general.id;
|
||||
|
||||
this.generalList.delete(generalID);
|
||||
|
||||
if (general.isUser) {
|
||||
this.userGeneralList.delete(generalID);
|
||||
this.warUserGeneralList.delete(generalID);
|
||||
this.domesticUserGeneralList.delete(generalID);
|
||||
} else {
|
||||
this.npcGeneralList.delete(generalID);
|
||||
this.warNPCGeneralList.delete(generalID);
|
||||
this.domesticNPCGeneralList.delete(generalID);
|
||||
}
|
||||
|
||||
const staffLevel = general.staffLevel;
|
||||
if (staffLevel !== undefined) {
|
||||
this.staffList.delete(staffLevel);
|
||||
|
||||
if (staffLevel === StaffLevel.lord) {
|
||||
throw new LogicError(`Lord is kicked: nationID=${this.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (general.squad) {
|
||||
general.quitSquad();
|
||||
}
|
||||
}
|
||||
|
||||
public notifyJoinGeneral(general: General) {
|
||||
//국가에 장수가 들어오는 경우, class내 변수에 대한 처리.
|
||||
const generalID = general.id;
|
||||
|
||||
this.generalList.set(generalID, new WeakRef(general));
|
||||
|
||||
if (general.isUser) {
|
||||
this.userGeneralList.set(generalID, new WeakRef(general));
|
||||
if (categorizeGeneral(general) === "war") {
|
||||
this.warUserGeneralList.set(generalID, new WeakRef(general));
|
||||
}
|
||||
else {
|
||||
this.domesticUserGeneralList.set(generalID, new WeakRef(general));
|
||||
}
|
||||
} else {
|
||||
this.npcGeneralList.set(generalID, new WeakRef(general));
|
||||
if (categorizeGeneral(general) === "war") {
|
||||
this.warNPCGeneralList.set(generalID, new WeakRef(general));
|
||||
}
|
||||
else {
|
||||
this.domesticNPCGeneralList.set(generalID, new WeakRef(general));
|
||||
}
|
||||
}
|
||||
|
||||
const staffLevel = general.staffLevel;
|
||||
if (staffLevel !== undefined) {
|
||||
this.staffList.set(staffLevel, new WeakRef(general));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { must } from "@sammo/util";
|
||||
import type { General } from "./General.js";
|
||||
import type { IResourceController } from "./IResourceController.js";
|
||||
import type { ISquadEntity } from "./Entity/SquadEntity.js";
|
||||
import type { GeneralID } from "./defs.js";
|
||||
import { LazyEntityUpdater } from "./LazyEntityUpdater.js";
|
||||
|
||||
export class Squad extends LazyEntityUpdater<ISquadEntity> {
|
||||
protected readonly _indirectNames: (keyof ISquadEntity)[] = ["id"];
|
||||
protected readonly _entityType = "Squad";
|
||||
protected readonly _raw: ISquadEntity;
|
||||
private _leader!: WeakRef<General>;
|
||||
private _members!: Map<GeneralID, WeakRef<General>>;
|
||||
|
||||
constructor(
|
||||
raw: ISquadEntity,
|
||||
protected rc: IResourceController,
|
||||
bootstrapInfo: {
|
||||
general: GeneralID[]
|
||||
}
|
||||
) {
|
||||
super();
|
||||
this._raw = raw;
|
||||
this._bootstrapInfo = bootstrapInfo;
|
||||
}
|
||||
|
||||
private _bootstrapInfo?: ConstructorParameters<typeof Squad>[2];
|
||||
public bootstrap() {
|
||||
const rc = this.rc;
|
||||
|
||||
const bootstrapInfo = must(this._bootstrapInfo);
|
||||
delete this._bootstrapInfo;
|
||||
|
||||
this._members = new Map(bootstrapInfo.general.map((id) => {
|
||||
return [id, new WeakRef(must(rc.general(id)))];
|
||||
}));
|
||||
|
||||
const leaderID = this._raw.id as GeneralID;
|
||||
|
||||
this._leader = must(this._members.get(leaderID));
|
||||
this._members.delete(leaderID);
|
||||
}
|
||||
|
||||
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 destroySquad(withLeader = true) {
|
||||
this._members.forEach((member) => {
|
||||
const general = must(member.deref());
|
||||
general.quitSquad();
|
||||
});
|
||||
if (withLeader) {
|
||||
this.leader.quitSquad();
|
||||
}
|
||||
|
||||
this.leader.nation.notifyDestroySquad(this);
|
||||
|
||||
this.rc.reserveDelete(this);
|
||||
}
|
||||
|
||||
public changeName(name: string) {
|
||||
this.update((raw) => {
|
||||
raw.name = name;
|
||||
});
|
||||
}
|
||||
|
||||
public notifyQuitGeneral(general: General) {
|
||||
if(general.id === this._leader.deref()?.id){
|
||||
throw new Error("Leader cannot quit");
|
||||
}
|
||||
this._members.delete(general.id);
|
||||
}
|
||||
|
||||
public notifyJoinGeneral(general: General) {
|
||||
this._members.set(general.id, new WeakRef(general));
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { WarUnit } from "./WarUnit.js";
|
||||
|
||||
export class WarUnitCity extends WarUnit{
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { WarUnit } from "./WarUnit.js";
|
||||
|
||||
export class WarUnitGeneral extends WarUnit{
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
//Type 정의
|
||||
export type UserID = number & { zz_t?: "UserID" };
|
||||
export type UserName = string & { zz_t?: "UserName" };
|
||||
|
||||
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 SquadID = number & { zz_t?: "SquadID" };
|
||||
export type SquadName = string & { zz_t?: "SquadName" };
|
||||
|
||||
export type NationID = number & { zz_t?: "NationID" };
|
||||
export type NationName = string & { zz_t?: "NationName" };
|
||||
|
||||
//지역 ID
|
||||
export type RegionID = number & { zz_t?: "RegionID" };
|
||||
|
||||
//서버 ID
|
||||
export type ServerID = number & { zz_t?: "ServerID" };
|
||||
export type ScenarioID = number & { zz_t?: "ScenarioID" };
|
||||
|
||||
|
||||
|
||||
//TODO: 무언가의 keyof 로 바꿔야함
|
||||
export type NationType = string & { zz_t?: "NationType" };
|
||||
|
||||
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_squad_leader = 5,
|
||||
npc_unselectable = 6,
|
||||
|
||||
npc_invader = 9,
|
||||
}
|
||||
|
||||
export const enum StaffLevel {
|
||||
//지력관직
|
||||
staffIntel3 = 5,
|
||||
staffIntel2 = 7,
|
||||
staffIntel1 = 9,
|
||||
|
||||
//무력관직
|
||||
staffStrength3 = 6,
|
||||
staffStrength2 = 8,
|
||||
staffStrength1 = 10,
|
||||
|
||||
//참모
|
||||
staffChief = 11,
|
||||
|
||||
//군주
|
||||
lord = 12,
|
||||
}
|
||||
|
||||
|
||||
export const enum CityOfficerLevel {
|
||||
cityLeadership = 2,
|
||||
cityIntel = 3,
|
||||
cityStrength = 4,
|
||||
}
|
||||
|
||||
|
||||
//XXX: const enum의 merge가 없어?
|
||||
|
||||
export const enum OfficerLevel {
|
||||
//재야
|
||||
none = 0,
|
||||
|
||||
//일반
|
||||
normal = 1,
|
||||
|
||||
//도시관직
|
||||
cityLeadership = CityOfficerLevel.cityLeadership,
|
||||
cityIntel = CityOfficerLevel.cityIntel,
|
||||
cityStrength = CityOfficerLevel.cityStrength,
|
||||
|
||||
//지력관직
|
||||
staffIntel3 = StaffLevel.staffIntel3,
|
||||
staffIntel2 = StaffLevel.staffIntel2,
|
||||
staffIntel1 = StaffLevel.staffIntel1,
|
||||
|
||||
//무력관직
|
||||
staffStrength3 = StaffLevel.staffStrength3,
|
||||
staffStrength2 = StaffLevel.staffStrength2,
|
||||
staffStrength1 = StaffLevel.staffStrength1,
|
||||
|
||||
//참모
|
||||
staffChief = StaffLevel.staffChief,
|
||||
|
||||
//군주
|
||||
lord = StaffLevel.lord,
|
||||
}
|
||||
|
||||
export const enum DiplomaticPermission {
|
||||
none = 0,
|
||||
audit = 1,
|
||||
ambassador = 2,
|
||||
}
|
||||
|
||||
export const enum NationLevel {
|
||||
방랑군 = 0,
|
||||
호족 = 1,
|
||||
군벌 = 2,
|
||||
주자사 = 3,
|
||||
주목 = 4,
|
||||
공 = 5,
|
||||
왕 = 6,
|
||||
황제 = 7,
|
||||
}
|
||||
|
||||
export const enum CityLevel {
|
||||
진 = 1,
|
||||
관 = 2,
|
||||
수 = 3,
|
||||
|
||||
이 = 4,
|
||||
소 = 5,
|
||||
중 = 6,
|
||||
대 = 7,
|
||||
특 = 8,
|
||||
}
|
||||
|
||||
export const enum FrontStatus {
|
||||
//출병가능한 도시 없음. 중립.
|
||||
Neutral = 0,
|
||||
//전쟁 중인 국가가 인접함
|
||||
NearWar = 1,
|
||||
//선포 중인 국가가 인접함
|
||||
NearDeclare = 2,
|
||||
//공백지 있음
|
||||
NearEmpty = 3,
|
||||
}
|
||||
|
||||
export type NationCmdType = string & { zz_t?: "NationCmdType" };
|
||||
|
||||
export type StrategicCmdType = string & { zz_t?: "StrategicCmdType" };
|
||||
|
||||
//string? const or? Enum?
|
||||
export type ColorType = string & { zz_t?: "ColorType" };
|
||||
|
||||
//XXX: 기존처럼 값 하나만 유지할 것인가?
|
||||
export const enum CityState {
|
||||
//일반
|
||||
normal = 0,
|
||||
|
||||
//풍년
|
||||
goodHarvest = 1,
|
||||
//호황
|
||||
commercialBoom = 2,
|
||||
|
||||
//혹한
|
||||
coldWave = 3,
|
||||
//역병
|
||||
plague = 4,
|
||||
//지진
|
||||
earthquake = 5,
|
||||
//태풍
|
||||
typhoon = 6,
|
||||
//홍수
|
||||
flood = 7,
|
||||
//메뚜기 떼
|
||||
locust = 8,
|
||||
//도적
|
||||
thief = 9,
|
||||
}
|
||||
|
||||
//NOTE: PHP버전과 수치가 다름
|
||||
export const enum DiplomacyState{
|
||||
//중립
|
||||
neutral = 0,
|
||||
|
||||
//선포중
|
||||
declare = 1,
|
||||
|
||||
//전쟁중
|
||||
war = 2,
|
||||
|
||||
//불가침
|
||||
inviolable = 7,
|
||||
}
|
||||
|
||||
/* 전용 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;
|
||||
|
||||
export interface ReservedTurn {
|
||||
action: string;
|
||||
arg: Record<string, unknown>;
|
||||
brief: string;
|
||||
}
|
||||
|
||||
export type LogType = 'action' | 'battleBrief' | 'battle' | 'history';
|
||||
export type MessageType = 'private' | 'national' | 'public' | 'diplomacy' | 'system';
|
||||
|
||||
|
||||
export const enum ArmyClass {
|
||||
//기병
|
||||
cavalry = 0,
|
||||
//보병
|
||||
infantry = 1,
|
||||
//궁병
|
||||
archer = 2,
|
||||
|
||||
|
||||
}
|
||||
|
||||
//특수 숫자 정의
|
||||
//연 * 12 + 월
|
||||
export type IntYearMonth = number & { zz_t?: "YearMonth" };
|
||||
export type IntMonth = number & { zz_t?: "IntMonth" };
|
||||
export type IntYear = number & { zz_t?: "IntYear" };
|
||||
//1.0을 기준으로한 number 타입
|
||||
export type FractionalRange = number & { zz_t?: "FractionalRange" };
|
||||
|
||||
//특수 문자열 정의
|
||||
export type URILike = string & { zz_t?: "URILike" };
|
||||
@@ -1,7 +1 @@
|
||||
export * from "./LogicFunc/generalStats.js";
|
||||
export * as defs from "./defs.js";
|
||||
export * from "./IResourceController.js";
|
||||
export * from "./ActionRequest/index.js";
|
||||
export * from "./GameLogger.js";
|
||||
// NOTE: game_logic은 client, server 모두에서 사용되는 라이브러리이다.
|
||||
// DB에 접근한다면 DI여야하나?
|
||||
export * from "./generalStats.js";
|
||||
@@ -3,9 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
|
||||
@@ -5,45 +5,47 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"build": "run-p type-check build-only",
|
||||
"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": "Hide_D <hided62@gmail.com>",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/client_ky": "workspace:^",
|
||||
"@strpc/def": "workspace:^",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-vue-next": "^0.40.6",
|
||||
"bson": "^6.10.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"ky": "^1.11.0",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-vue-next": "^0.9.26",
|
||||
"bson": "^5.4.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"ky": "^1.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.5.1"
|
||||
"pinia": "^2.1.4",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.6",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.7.0",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.6.0",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint-plugin-vue": "^10.5.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tslib": "^2.8.1",
|
||||
"vite": "^7.1.9",
|
||||
"vue-tsc": "^3.1.1",
|
||||
"zod": "^4.1.12"
|
||||
"@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",
|
||||
"tslib": "^2.6.2",
|
||||
"vite": "^4.4.6",
|
||||
"vue-tsc": "^1.8.6",
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.vite.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
@@ -19,6 +20,9 @@
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
{
|
||||
"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",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"],
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@util': fileURLToPath(new URL('./server/util', import.meta.url)),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc --build",
|
||||
"dev": "tsc --watch"
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./exports": "./dist/exports.js"
|
||||
},
|
||||
"author": "Hide_D <hided62@gmail.com>",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -20,21 +19,19 @@
|
||||
"@sammo/server_util": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/express": "workspace:^",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"@trpc/server": "^11.6.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.1.0",
|
||||
"express-session": "^1.18.2",
|
||||
"date-fns": "^2.30.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mongoose": "^8.19.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"tslib": "^2.8.1",
|
||||
"zod": "^4.1.12"
|
||||
"mongoose": "^7.4.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"tslib": "^2.6.2",
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/express-session": "^1.18.2",
|
||||
"@types/node": "^24.7.0"
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-session": "^1.17.7",
|
||||
"@types/node": "^20.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { StartSession } from "@sammo/server_util";
|
||||
import { POST, type APIReturnType } from "@strpc/express";
|
||||
import { declProcDecorators } from "@strpc/express/proc_decorator";
|
||||
import { z } from "zod";
|
||||
import { gatewayLoginCtxSessionKey, type GatewayLoginCtx } from "@/procDecorator/ReqGatewayLogin.js";
|
||||
import { loginCtxSessionKey, type GatewayLoginCtx } from "@/procDecorator/ReqGatewayLogin.js";
|
||||
import { delay } from "@sammo/util";
|
||||
|
||||
type BaseAPI = typeof structure.Login.LoginByID;
|
||||
@@ -52,7 +52,7 @@ export const LoginByID = POST<BaseAPI>(LoginByIDReq)(declProcDecorators(
|
||||
loginDate: new Date(),
|
||||
}
|
||||
|
||||
ctx.session.setItem(gatewayLoginCtxSessionKey, loginCtx);
|
||||
ctx.session.setItem(loginCtxSessionKey, loginCtx);
|
||||
|
||||
|
||||
return {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { StartSession } from "@sammo/server_util";
|
||||
import { POST, type APIReturnType } from "@strpc/express";
|
||||
import { declProcDecorators } from "@strpc/express/proc_decorator";
|
||||
import { z } from "zod";
|
||||
import { gatewayLoginCtxSessionKey, type GatewayLoginCtx } from "@/procDecorator/ReqGatewayLogin.js";
|
||||
import { loginCtxSessionKey, type GatewayLoginCtx } from "@/procDecorator/ReqGatewayLogin.js";
|
||||
import { delay } from "@sammo/util";
|
||||
|
||||
type BaseAPI = typeof structure.Login.LoginByToken;
|
||||
@@ -38,7 +38,7 @@ export const LoginByToken = POST<BaseAPI>(LoginByTokenReq)(declProcDecorators(
|
||||
loginDate: new Date(),
|
||||
}
|
||||
|
||||
ctx.session.setItem(gatewayLoginCtxSessionKey, loginCtx);
|
||||
ctx.session.setItem(loginCtxSessionKey, loginCtx);
|
||||
|
||||
|
||||
//throw new Error("Method not implemented.");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import './dotenv.js';
|
||||
import { connect,type Mongoose } from "mongoose";
|
||||
import { connect, Mongoose } from "mongoose";
|
||||
import { unwrap } from '@sammo/util';
|
||||
|
||||
const dbConfig = {
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { SessionCtx } from "@sammo/server_util";
|
||||
import type { ProcDecorator } from "@strpc/express/proc_decorator";
|
||||
import type { GatewayLoginCtx } from "./ReqGatewayLogin.js";
|
||||
import type { GatewayActionType } from "@/schema/User.js";
|
||||
|
||||
export function ReqGatewayACL<Q extends GatewayLoginCtx & SessionCtx>(...actions: GatewayActionType[]): ()=>ProcDecorator<Q, Q> {
|
||||
return () => {
|
||||
return (ctx) => {
|
||||
if (actions.length === 0) {
|
||||
return [{
|
||||
result: true,
|
||||
}, ctx];
|
||||
}
|
||||
if(!ctx.allowGatewayAction){
|
||||
return [{
|
||||
result: false,
|
||||
type: 'Required ACL',
|
||||
info: 'No Gateway ACL in session context.'
|
||||
}, ctx];
|
||||
}
|
||||
|
||||
for (const action of actions) {
|
||||
if (!ctx.allowGatewayAction.has(action)) {
|
||||
return [{
|
||||
result: false,
|
||||
type: 'Required ACL',
|
||||
info: `NotEnoughPermission: ${action}`
|
||||
}, ctx];
|
||||
}
|
||||
}
|
||||
|
||||
return [{
|
||||
result: true,
|
||||
}, ctx];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,11 @@ export type GatewayLoginCtx = {
|
||||
allowGatewayAction: Set<GatewayActionType>;
|
||||
loginDate: Date;
|
||||
}
|
||||
export const gatewayLoginCtxSessionKey = 'gatewayLoginCtx';
|
||||
export const loginCtxSessionKey = 'loginCtx';
|
||||
|
||||
export function ReqGatewayLogin<Q extends SessionCtx>(): ProcDecorator<GatewayLoginCtx & Q, Q> {
|
||||
return (ctx) => {
|
||||
const loginCtx = ctx.session.getItem<GatewayLoginCtx>(gatewayLoginCtxSessionKey);
|
||||
const loginCtx = ctx.session.getItem<GatewayLoginCtx>(loginCtxSessionKey);
|
||||
if(!loginCtx){
|
||||
return [{
|
||||
result: false,
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { SessionCtx } from "@sammo/server_util";
|
||||
import type { ProcDecorator } from "@strpc/express/proc_decorator";
|
||||
import type { ServerActionType } from "@sammo/gateway_server/exports"
|
||||
import type { GatewayLoginCtx } from "./ReqGatewayLogin.js";
|
||||
|
||||
export function ReqServerACL<Q extends GatewayLoginCtx & SessionCtx>(serverName: string, ...actions: ServerActionType[]): ()=>ProcDecorator<Q, Q> {
|
||||
return () => {
|
||||
return (ctx) => {
|
||||
if (actions.length === 0) {
|
||||
return [{
|
||||
result: true,
|
||||
}, ctx];
|
||||
}
|
||||
const allowServerAction = ctx.allowServerAction.get(serverName);
|
||||
if(!allowServerAction){
|
||||
return [{
|
||||
result: false,
|
||||
type: 'Required ACL',
|
||||
info: 'No Server ACL in session context.'
|
||||
}, ctx];
|
||||
}
|
||||
|
||||
for (const action of actions) {
|
||||
if (!allowServerAction.has(action)) {
|
||||
return [{
|
||||
result: false,
|
||||
type: 'Required ACL',
|
||||
info: `NotEnoughPermission: ${action}`
|
||||
}, ctx];
|
||||
}
|
||||
}
|
||||
|
||||
return [{
|
||||
result: true,
|
||||
}, ctx];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type UserIDType } from "./User.js";
|
||||
import { type Mongoose, Schema, model } from "mongoose";
|
||||
import { Mongoose, Schema, model } from "mongoose";
|
||||
|
||||
export interface ILoginToken {
|
||||
userID: UserIDType;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user