From 21da4d14e628dbda292887a0523e5444923c1320 Mon Sep 17 00:00:00 2001 From: Hide_D Date: Wed, 6 Mar 2024 16:33:23 +0000 Subject: [PATCH] =?UTF-8?q?wip:=20=EA=B0=9C=EC=B2=B4=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- @sammo/game_logic/src/Errors.ts | 0 @sammo/game_logic/src/General.ts | 149 +++++++++++++++++++- @sammo/game_logic/src/GeneralEntity.ts | 65 +++++++++ @sammo/game_logic/src/ResourceController.ts | 55 +++++--- @sammo/game_logic/src/Troop.ts | 53 +++++++ @sammo/game_logic/src/TroopEntity.ts | 7 + @sammo/game_logic/src/defs.ts | 62 ++++++++ @sammo/util/src/index.ts | 1 + @sammo/util/src/must.ts | 25 ++++ 9 files changed, 394 insertions(+), 23 deletions(-) create mode 100644 @sammo/game_logic/src/Errors.ts create mode 100644 @sammo/game_logic/src/GeneralEntity.ts create mode 100644 @sammo/game_logic/src/Troop.ts create mode 100644 @sammo/game_logic/src/TroopEntity.ts create mode 100644 @sammo/util/src/must.ts diff --git a/@sammo/game_logic/src/Errors.ts b/@sammo/game_logic/src/Errors.ts new file mode 100644 index 0000000..e69de29 diff --git a/@sammo/game_logic/src/General.ts b/@sammo/game_logic/src/General.ts index a3f1bff..4b33c0b 100644 --- a/@sammo/game_logic/src/General.ts +++ b/@sammo/game_logic/src/General.ts @@ -1,10 +1,149 @@ -import type { GeneralID, GeneralName } from "./defs.js"; +import { InvalidArgument } from "@sammo/util"; +import { City } from "./City.js"; +import type { IGeneralEntity } from "./GeneralEntity.js"; +import type { IResourceController } from "./ResourceController.js"; +import { DiplomaticPermission, type CityID, type GeneralID, type GeneralName, type NationID, OfficerLevel, type TroopID } from "./defs.js"; +import { must } from "@sammo/util"; +import { Nation } from "./Nation.js"; +import { Troop } from "./Troop.js"; export class General { - constructor( - public readonly id: GeneralID, - public name: GeneralName, - ){ + private _raw: IGeneralEntity; + private _city: WeakRef; + private _troop: WeakRef | undefined; + private _nation: WeakRef; + public get raw(): Readonly { + return this._raw; } + + constructor( + raw: IGeneralEntity, + private resourceController: IResourceController + ){ + this._raw = raw; + this._city = new WeakRef(must(resourceController.getCity(raw.cityID))); + this._nation = new WeakRef(must(resourceController.getNation(raw.nationID))); + + if(raw.troopID){ + this._troop = new WeakRef(must(resourceController.getTroop(raw.troopID, raw.nationID))); + } + } + + public get id(): GeneralID { + return this._raw.id; + } + + public get name(): GeneralName { + return this._raw.name; + } + + public update(callback: (oldRaw: IGeneralEntity) => void, force = false) { + + const oldCityID = this._raw.cityID; + const oldTroopID = this._raw.troopID; + const oldNationID = this._raw.nationID; + + callback(this._raw); + + if(!force){ + if(oldCityID !== this._raw.cityID){ + throw new Error("City change by update() is not allowed"); + } + if(oldTroopID !== this._raw.troopID){ + throw new Error("Troop change by update() is not allowed"); + } + if(oldNationID !== this._raw.nationID){ + throw new Error("Nation change by update() is not allowed"); + } + } + this.resourceController.setDirtyGeneral(this._raw); + } + + public changeCity(city: CityID | City){ + const cityObj: City = (()=>{ + if(city instanceof City){ + return city; + } + const cityObj = this.resourceController.getCity(city); + if(!cityObj){ + throw new InvalidArgument(`City not found: ${city}`); + } + return cityObj; + })(); + this._city = new WeakRef(cityObj); + + //TODO: ResourceController에게 알려야 하는가? + this.update((raw) => { + raw.cityID = cityObj.id; + }, true); + } + + public quitTroop(){ + if(!this._troop){ + return; + } + + const troop = must(this._troop.deref()); + if(troop.leader.id === this._raw.id){ + troop.destructTroop(false); + } + else{ + troop.members.delete(this._raw.id); + } + + this._troop = undefined; + this.update((raw) => { + raw.troopID = undefined; + }, true); + } + + public changeTroop(troop: TroopID | Troop){ + this.quitTroop(); + + const troopObj: Troop = (()=>{ + if(troop instanceof Troop){ + return troop; + } + const troopObj = this.resourceController.getTroop(troop, this._raw.nationID); + if(!troopObj){ + throw new InvalidArgument(`Troop not found: ${troop}`); + } + return troopObj; + })(); + + troopObj.members.set(this._raw.id, new WeakRef(this)); + this._troop = new WeakRef(troopObj); + this.update((raw) => { + raw.troopID = troopObj.id; + }, true); + } + + public changeNation(nation: NationID | Nation){ + const nationObj: Nation = (()=>{ + if(nation instanceof Nation){ + return nation; + } + const nationObj = this.resourceController.getNation(nation); + if(!nationObj){ + throw new InvalidArgument(`Nation not found: ${nation}`); + } + return nationObj; + })(); + this._nation = new WeakRef(nationObj); + this.quitTroop(); + + this.update((raw) => { + raw.nationID = nationObj.id; + raw.diplomaticPermission = DiplomaticPermission.none; + if(nationObj.id == 0){ + raw.officerLevel = OfficerLevel.none; + } + else{ + raw.officerLevel = OfficerLevel.normal; + } + raw.nationBelong = 0; + }, true); + } + } \ No newline at end of file diff --git a/@sammo/game_logic/src/GeneralEntity.ts b/@sammo/game_logic/src/GeneralEntity.ts new file mode 100644 index 0000000..7c618e8 --- /dev/null +++ b/@sammo/game_logic/src/GeneralEntity.ts @@ -0,0 +1,65 @@ +import type { DiplomaticPermission, GeneralID, GeneralName, ItemID, ItemKeyType, NationID, NpcType, OfficerLevel, OwnerID, OwnerName, PersonalityType, SpecialityDomesticType, SpecialityWarType, TroopID } from "./defs.js"; + +export interface IGeneralEntity { + id: GeneralID; + owner?: OwnerID; + npcType: NpcType; + affinity?: number; + picture?: string; + name: GeneralName; + ownerName?: OwnerName; + nationID: NationID; + cityID: number; + troopID?: TroopID; + + leadership: number; + leadershipExp: number; + strength: number; + strengthExp: number; + intel: number; + intelExp: number; + + injury: number; + + experience: number; + dedication: number; + + experienceLevel: number; + dedicationLevel: number; + + dex: number[]; + + officerLevel: OfficerLevel; + diplomaticPermission: DiplomaticPermission; + + gold: number; + rice: number; + + item: Record, + }>; + + turntime: Date; + + killturn: number; + + age: number; + startAge: number; + + nationBelong: number; + + betray: number; + + personality: PersonalityType; + specialityDomestic: SpecialityDomesticType; + specialityWar: SpecialityWarType; + + defenceTrain: number; + + npcSpec?: { + msg?: string; + bornyear: number; + deadyear: number; + } +} \ No newline at end of file diff --git a/@sammo/game_logic/src/ResourceController.ts b/@sammo/game_logic/src/ResourceController.ts index a07ac5f..38ff14d 100644 --- a/@sammo/game_logic/src/ResourceController.ts +++ b/@sammo/game_logic/src/ResourceController.ts @@ -1,34 +1,53 @@ import type { General } from "./General.js"; import type { City } from "./City.js"; import type { Nation } from "./Nation.js"; -import type { CityID, GeneralID, NationID } from "./defs.js"; +import type { CityID, GeneralID, NationID, TroopID } from "./defs.js"; import type { GameEnv } from "./GameEnv.js"; +import type { IGeneralEntity } from "./GeneralEntity.js"; +import type { Troop } from "./Troop.js"; +import type { ITroopEntity } from "./TroopEntity.js"; export interface IResourceController{ - addCity(city: City): Promise; - addNation(nation: Nation): Promise; - addGeneral(general: General): Promise; + addCity(city: City): boolean; + addNation(nation: Nation): boolean; + addGeneral(general: General): boolean; - getCity(id: CityID, allowFromDB?: boolean): Promise; - getNation(id: NationID, allowFromDB?: boolean): Promise; - getGeneral(id: GeneralID, allowFromDB?: boolean): Promise; + getCity(id: CityID): City | undefined; + getNation(id: NationID): Nation | undefined; + getGeneral(id: GeneralID): General | undefined; getGameEnv(): Promise; - findAllNations(): Promise>; - findAllCities(): Promise>; - findAllGenerals(): Promise>; + findAllNations(): Map; + findAllCities(): Map; + findAllGenerals(): Map; - findCityByNation(id: NationID): Promise>; - findGeneralByNation(id: NationID): Promise>; - findGeneralByCity(id: CityID, nationID?: NationID): Promise>; + findGeneralByTroop(id: TroopID): Map; - findGeneralByQuery(query: unknown): Promise>; - findCityByQuery(query: unknown): Promise>; - findNationByQuery(query: unknown): Promise>; + findCityByNation(id: NationID): Map; + findGeneralByNation(id: NationID): Map; + findGeneralByCity(id: CityID, nationID?: NationID): Map; - reset(): void; - save(): Promise; + getTroop(id: TroopID, nation: NationID): Troop | undefined; + + _getAllChanges(): Map; + + + setDirtyGeneral(rawGeneral: IGeneralEntity): void; + setDirtyCity(city: City): void; + setDirtyTroop(troop: Troop | TroopID): void; + setDirtyNation(nation: Nation): void; + + createGeneral(raw: IGeneralEntity): General; + createTroop(raw: ITroopEntity): Troop; + //createNation + + reserveDeleteTroop(id: TroopID): void; + reserveDeleteGeneral(id: GeneralID): void; + reserveDeleteNation(id: NationID): void; + + resetAndFill(): Promise; + saveAll(): Promise; } let _resourceController: IResourceController | undefined = undefined; diff --git a/@sammo/game_logic/src/Troop.ts b/@sammo/game_logic/src/Troop.ts new file mode 100644 index 0000000..667e0df --- /dev/null +++ b/@sammo/game_logic/src/Troop.ts @@ -0,0 +1,53 @@ +import { must } from "@sammo/util"; +import type { General } from "./General.js"; +import type { IResourceController } from "./ResourceController.js"; +import type { ITroopEntity } from "./TroopEntity.js"; +import type { GeneralID } from "./defs.js"; + +export class Troop { + private _raw: ITroopEntity + private _leader: WeakRef; + private _members: Map>; + + public get raw(): Readonly { + return this._raw; + } + + constructor( + raw: ITroopEntity, + private resourceController: IResourceController + ){ + this._raw = raw; + + const members = resourceController.findGeneralByTroop(raw.id); + const leaderID: GeneralID = raw.id as GeneralID; + this._leader = new WeakRef(must(members.get(leaderID))); + members.delete(leaderID); + this._members = new Map([...members].map(([k,v])=>[k, new WeakRef(v)])); + } + + public get id(){ + return this._raw.id; + } + + public get leader(){ + return must(this._leader.deref()); + } + + public get members(): Map> { + return this._members; + } + + public destructTroop(withLeader = true){ + this._members.forEach((member)=>{ + const general = must(member.deref()); + general.quitTroop(); + }); + if(withLeader){ + this.leader.quitTroop(); + } + + this.resourceController.reserveDeleteTroop(this._raw.id); + } + +} \ No newline at end of file diff --git a/@sammo/game_logic/src/TroopEntity.ts b/@sammo/game_logic/src/TroopEntity.ts new file mode 100644 index 0000000..fa9abd8 --- /dev/null +++ b/@sammo/game_logic/src/TroopEntity.ts @@ -0,0 +1,7 @@ +import type { NationID, TroopID } from "./defs.js"; + +export interface ITroopEntity { + id: TroopID; + nationID: NationID; + name: string; +} \ No newline at end of file diff --git a/@sammo/game_logic/src/defs.ts b/@sammo/game_logic/src/defs.ts index e9bd3c1..602f6ef 100644 --- a/@sammo/game_logic/src/defs.ts +++ b/@sammo/game_logic/src/defs.ts @@ -1,10 +1,16 @@ //Type 정의 +export type OwnerID = number & { zz_t?: "OwnerID" }; +export type OwnerName = string & { zz_t?: "OwnerName" }; + export type GeneralID = number & { zz_t?: "GeneralID" }; export type GeneralName = string & { zz_t?: "GeneralName" }; export type CityID = number & { zz_t?: "CityID" }; export type CityName = string & { zz_t?: "CityName" }; +export type TroopID = number & { zz_t?: "TroopID" }; +export type TroopName = string & { zz_t?: "TroopName" }; + export type NationID = number & { zz_t?: "NationID" }; export type NationName = string & { zz_t?: "NationName" }; @@ -12,6 +18,62 @@ export type NationName = string & { zz_t?: "NationName" }; export type NationType = string & { zz_t?: "NationType" }; export type StrategicTurnType = string & { zz_t?: "StrategicTurnType" }; +export type ItemKeyType = 'horse' | 'weapon' | 'book' | 'item'; +export type ItemID = string & { zz_t?: "ItemID" }; + +export type PersonalityType = string & { zz_t?: "PersonalityType" }; +export type SpecialityDomesticType = string & { zz_t?: "SpecialityDomesticType" }; +export type SpecialityWarType = string & { zz_t?: "SpecialityWarType" }; + +export const enum NpcType { + user = 0, + user_borrowed_npc = 1, + npc_scenario = 2, + npc_generated = 3, + npc_strategic = 4, + npc_troop_leader = 5, + npc_unselectable = 6, + + npc_invader = 9, +} + +export const enum OfficerLevel { + //재야 + none = 0, + + //일반 + normal = 1, + + //도시관직 + cityLeadership = 2, + cityIntel = 3, + cityStrength = 4, + + //지력관직 + staffIntel3 = 5, + staffIntel2 = 7, + staffIntel1 = 9, + + //무력관직 + staffStrength3 = 6, + staffStrength2 = 8, + staffStrength1 = 10, + + //참모 + staffChief = 11, + + //군주 + lord = 12, + +} + +export const enum DiplomaticPermission { + none = 0, + audit = 1, + ambassador = 2, +} + + /* 전용 stat이어서 확장하고자 하는경우 아래와 같이 확장 가능 ```ts declare module "@/defs.js" { diff --git a/@sammo/util/src/index.ts b/@sammo/util/src/index.ts index 207a2e4..d1109c0 100644 --- a/@sammo/util/src/index.ts +++ b/@sammo/util/src/index.ts @@ -2,6 +2,7 @@ export * from "./bsonify.js" export * from "./jsonify.js" export * from "./error.js" export * from "./unwrap.js" +export * from "./must.js" export * from "./types.js" export * from "./web.js" diff --git a/@sammo/util/src/must.ts b/@sammo/util/src/must.ts new file mode 100644 index 0000000..9aea37c --- /dev/null +++ b/@sammo/util/src/must.ts @@ -0,0 +1,25 @@ +import type { Nullable } from './types.js'; +import { NotNullExpected } from "./error.js"; + +export function must(result: Nullable): T { + if (result === null || result === undefined) { + throw new NotNullExpected(); + } + return result; +} + +export function must_any(result: Nullable): T { + if (result === null || result === undefined) { + throw new NotNullExpected(); + } + return result as T; +} + +type ErrType = { new(msg?: string): T } + +export function must_err(result: Nullable, errType: ErrType, errMsg?: string): T { + if (result === null || result === undefined) { + throw new errType(errMsg); + } + return result; +} \ No newline at end of file