wip: 개체 작업 진행

This commit is contained in:
2024-03-06 16:33:23 +00:00
parent be6e765d76
commit 21da4d14e6
9 changed files with 394 additions and 23 deletions
View File
+144 -5
View File
@@ -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<City>;
private _troop: WeakRef<Troop> | undefined;
private _nation: WeakRef<Nation>;
public get raw(): Readonly<IGeneralEntity> {
return this._raw;
}
constructor(
raw: IGeneralEntity,
private resourceController: IResourceController
){
this._raw = raw;
this._city = new WeakRef(must(resourceController.getCity(raw.cityID)));
this._nation = new WeakRef(must(resourceController.getNation(raw.nationID)));
if(raw.troopID){
this._troop = new WeakRef(must(resourceController.getTroop(raw.troopID, raw.nationID)));
}
}
public get id(): GeneralID {
return this._raw.id;
}
public get name(): GeneralName {
return this._raw.name;
}
public update(callback: (oldRaw: IGeneralEntity) => void, force = false) {
const oldCityID = this._raw.cityID;
const oldTroopID = this._raw.troopID;
const oldNationID = this._raw.nationID;
callback(this._raw);
if(!force){
if(oldCityID !== this._raw.cityID){
throw new Error("City change by update() is not allowed");
}
if(oldTroopID !== this._raw.troopID){
throw new Error("Troop change by update() is not allowed");
}
if(oldNationID !== this._raw.nationID){
throw new Error("Nation change by update() is not allowed");
}
}
this.resourceController.setDirtyGeneral(this._raw);
}
public changeCity(city: CityID | City){
const cityObj: City = (()=>{
if(city instanceof City){
return city;
}
const cityObj = this.resourceController.getCity(city);
if(!cityObj){
throw new InvalidArgument(`City not found: ${city}`);
}
return cityObj;
})();
this._city = new WeakRef(cityObj);
//TODO: ResourceController에게 알려야 하는가?
this.update((raw) => {
raw.cityID = cityObj.id;
}, true);
}
public quitTroop(){
if(!this._troop){
return;
}
const troop = must(this._troop.deref());
if(troop.leader.id === this._raw.id){
troop.destructTroop(false);
}
else{
troop.members.delete(this._raw.id);
}
this._troop = undefined;
this.update((raw) => {
raw.troopID = undefined;
}, true);
}
public changeTroop(troop: TroopID | Troop){
this.quitTroop();
const troopObj: Troop = (()=>{
if(troop instanceof Troop){
return troop;
}
const troopObj = this.resourceController.getTroop(troop, this._raw.nationID);
if(!troopObj){
throw new InvalidArgument(`Troop not found: ${troop}`);
}
return troopObj;
})();
troopObj.members.set(this._raw.id, new WeakRef(this));
this._troop = new WeakRef(troopObj);
this.update((raw) => {
raw.troopID = troopObj.id;
}, true);
}
public changeNation(nation: NationID | Nation){
const nationObj: Nation = (()=>{
if(nation instanceof Nation){
return nation;
}
const nationObj = this.resourceController.getNation(nation);
if(!nationObj){
throw new InvalidArgument(`Nation not found: ${nation}`);
}
return nationObj;
})();
this._nation = new WeakRef(nationObj);
this.quitTroop();
this.update((raw) => {
raw.nationID = nationObj.id;
raw.diplomaticPermission = DiplomaticPermission.none;
if(nationObj.id == 0){
raw.officerLevel = OfficerLevel.none;
}
else{
raw.officerLevel = OfficerLevel.normal;
}
raw.nationBelong = 0;
}, true);
}
}
+65
View File
@@ -0,0 +1,65 @@
import type { DiplomaticPermission, GeneralID, GeneralName, ItemID, ItemKeyType, NationID, NpcType, OfficerLevel, OwnerID, OwnerName, PersonalityType, SpecialityDomesticType, SpecialityWarType, TroopID } from "./defs.js";
export interface IGeneralEntity {
id: GeneralID;
owner?: OwnerID;
npcType: NpcType;
affinity?: number;
picture?: string;
name: GeneralName;
ownerName?: OwnerName;
nationID: NationID;
cityID: number;
troopID?: TroopID;
leadership: number;
leadershipExp: number;
strength: number;
strengthExp: number;
intel: number;
intelExp: number;
injury: number;
experience: number;
dedication: number;
experienceLevel: number;
dedicationLevel: number;
dex: number[];
officerLevel: OfficerLevel;
diplomaticPermission: DiplomaticPermission;
gold: number;
rice: number;
item: Record<ItemKeyType, {
id: ItemID,
option: Record<string, unknown>,
}>;
turntime: Date;
killturn: number;
age: number;
startAge: number;
nationBelong: number;
betray: number;
personality: PersonalityType;
specialityDomestic: SpecialityDomesticType;
specialityWar: SpecialityWarType;
defenceTrain: number;
npcSpec?: {
msg?: string;
bornyear: number;
deadyear: number;
}
}
+37 -18
View File
@@ -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<boolean>;
addNation(nation: Nation): Promise<boolean>;
addGeneral(general: General): Promise<boolean>;
addCity(city: City): boolean;
addNation(nation: Nation): boolean;
addGeneral(general: General): boolean;
getCity(id: CityID, allowFromDB?: boolean): Promise<City | undefined>;
getNation(id: NationID, allowFromDB?: boolean): Promise<Nation | undefined>;
getGeneral(id: GeneralID, allowFromDB?: boolean): Promise<General | undefined>;
getCity(id: CityID): City | undefined;
getNation(id: NationID): Nation | undefined;
getGeneral(id: GeneralID): General | undefined;
getGameEnv(): Promise<GameEnv>;
findAllNations(): Promise<Map<NationID, Nation>>;
findAllCities(): Promise<Map<CityID, City>>;
findAllGenerals(): Promise<Map<GeneralID, General>>;
findAllNations(): Map<NationID, Nation>;
findAllCities(): Map<CityID, City>;
findAllGenerals(): Map<GeneralID, General>;
findCityByNation(id: NationID): Promise<Map<CityID, City>>;
findGeneralByNation(id: NationID): Promise<Map<GeneralID, General>>;
findGeneralByCity(id: CityID, nationID?: NationID): Promise<Map<GeneralID, General>>;
findGeneralByTroop(id: TroopID): Map<GeneralID, General>;
findGeneralByQuery(query: unknown): Promise<Map<GeneralID, General>>;
findCityByQuery(query: unknown): Promise<Map<CityID, City>>;
findNationByQuery(query: unknown): Promise<Map<NationID, Nation>>;
findCityByNation(id: NationID): Map<CityID, City>;
findGeneralByNation(id: NationID): Map<GeneralID, General>;
findGeneralByCity(id: CityID, nationID?: NationID): Map<GeneralID, General>;
reset(): void;
save(): Promise<void>;
getTroop(id: TroopID, nation: NationID): Troop | undefined;
_getAllChanges(): Map<unknown, unknown>;
setDirtyGeneral(rawGeneral: IGeneralEntity): void;
setDirtyCity(city: City): void;
setDirtyTroop(troop: Troop | TroopID): void;
setDirtyNation(nation: Nation): void;
createGeneral(raw: IGeneralEntity): General;
createTroop(raw: ITroopEntity): Troop;
//createNation
reserveDeleteTroop(id: TroopID): void;
reserveDeleteGeneral(id: GeneralID): void;
reserveDeleteNation(id: NationID): void;
resetAndFill(): Promise<void>;
saveAll(): Promise<void>;
}
let _resourceController: IResourceController | undefined = undefined;
+53
View File
@@ -0,0 +1,53 @@
import { must } from "@sammo/util";
import type { General } from "./General.js";
import type { IResourceController } from "./ResourceController.js";
import type { ITroopEntity } from "./TroopEntity.js";
import type { GeneralID } from "./defs.js";
export class Troop {
private _raw: ITroopEntity
private _leader: WeakRef<General>;
private _members: Map<GeneralID, WeakRef<General>>;
public get raw(): Readonly<ITroopEntity> {
return this._raw;
}
constructor(
raw: ITroopEntity,
private resourceController: IResourceController
){
this._raw = raw;
const members = resourceController.findGeneralByTroop(raw.id);
const leaderID: GeneralID = raw.id as GeneralID;
this._leader = new WeakRef(must(members.get(leaderID)));
members.delete(leaderID);
this._members = new Map([...members].map(([k,v])=>[k, new WeakRef(v)]));
}
public get id(){
return this._raw.id;
}
public get leader(){
return must(this._leader.deref());
}
public get members(): Map<GeneralID, WeakRef<General>> {
return this._members;
}
public destructTroop(withLeader = true){
this._members.forEach((member)=>{
const general = must(member.deref());
general.quitTroop();
});
if(withLeader){
this.leader.quitTroop();
}
this.resourceController.reserveDeleteTroop(this._raw.id);
}
}
+7
View File
@@ -0,0 +1,7 @@
import type { NationID, TroopID } from "./defs.js";
export interface ITroopEntity {
id: TroopID;
nationID: NationID;
name: string;
}
+62
View File
@@ -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" {
+1
View File
@@ -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"
+25
View File
@@ -0,0 +1,25 @@
import type { Nullable } from './types.js';
import { NotNullExpected } from "./error.js";
export function must<T>(result: Nullable<T>): T {
if (result === null || result === undefined) {
throw new NotNullExpected();
}
return result;
}
export function must_any<T>(result: Nullable<unknown>): T {
if (result === null || result === undefined) {
throw new NotNullExpected();
}
return result as T;
}
type ErrType<T> = { new(msg?: string): T }
export function must_err<T, ErrT extends Error>(result: Nullable<T>, errType: ErrType<ErrT>, errMsg?: string): T {
if (result === null || result === undefined) {
throw new errType(errMsg);
}
return result;
}