瀏覽代碼

[qsrc2tw] Introducing `setup.NPCs`.

This yields a major performance boost since the usage of NPC-variables in global space uses up massive serialization- and deserialization resources.

It might also introduce new bugs. That's to be tested.

Quirky syntax pain,
Debugging like endless rain—
QSP, such disdain.
Stephan Fuchs 3 月之前
父節點
當前提交
74ae52fafb

+ 1 - 1
locations/npcstatic1.qsrc

@@ -1,5 +1,5 @@
 # npcstatic1
-
+!! QSRC2TW_module npcInit
 !!2024/03/29
 
 !!boys body_builds

+ 1 - 1
locations/npcstatic2.qsrc

@@ -1,5 +1,5 @@
 # npcstatic2
-
+!! QSRC2TW_module npcInit
 !2021/05/05
 !2023/09/25 Preference update
 

+ 1 - 1
locations/npcstatic3.qsrc

@@ -1,5 +1,5 @@
 # npcstatic3
-
+!! QSRC2TW_module npcInit
 !2023/09/25 Preference update
 
 

+ 1 - 1
locations/npcstatic4.qsrc

@@ -1,5 +1,5 @@
 # npcstatic4
-
+!! QSRC2TW_module npcInit
 !2023/09/25 Preference update
 
 !2022/01/03

+ 1 - 0
locations/npcstatic5.qsrc

@@ -1,4 +1,5 @@
 # npcstatic5
+!! QSRC2TW_module npcInit
 !2023/09/25 Preference update
 
 

+ 2 - 2
locations/npcstatic6.qsrc

@@ -1,5 +1,5 @@
 # npcstatic6
-
+!! QSRC2TW_module npcInit
 !2023/09/25 Preference update
 
 npctemp = 250
@@ -688,4 +688,4 @@ gs 'npcstaticdefaults', 'defaults'
 
 !! {Keep this at the end of file of the npcstatics.}
 aarraynumber = npctemp
---- npcstatic6 ---------------------------------
+--- npcstatic6 ---------------------------------

+ 53 - 6
qsrc2tw/tools/QSRC2TW/resources/Overwrite/Overwrite.js

@@ -29,17 +29,60 @@ class Overwrite{
     }
 
     _var = {};
+    _varByPrefix = {};
     varGet(id,index){
-        if(this._var[id] === undefined || this._var[id].get === undefined)
+        let overwrite = null;
+
+        for(const [prefix, ow] of Object.entries(this._varByPrefix)){
+            if(id.startsWith(prefix)){
+
+                overwrite = ow;
+                if(typeof overwrite.get == "function")
+                    return overwrite.get(id.substring(prefix.length),index);
+                break;
+            }else if(id.startsWith("$"+prefix)){
+
+                overwrite = ow;
+                if(typeof overwrite.get == "function")
+                    return overwrite.get(id.substring(prefix.length+1),index);
+                break;
+            }
+        }
+
+        if(this._var[id] !== undefined && this._var[id].get !== undefined)
+            overwrite = this._var[id];
+
+        if(!overwrite)
             return undefined;
-        if(typeof this._var[id].get == "function")
-            return this._var[id].get(index);
-        if(Array.isArray(this._var[id].get) && typeof index == "number")
-            return this._var[id].get[index];
-        return this._var[id].get;
+
+        if(typeof overwrite.get == "function")
+            return overwrite.get(index);
+
+
+        if(Array.isArray(overwrite.get) && typeof index == "number")
+            return overwrite.get[index];
+        return overwrite.get;
     }
 
     varSet(id,index,val){
+        let overwrite = null;
+        for(const [prefix, ow] of Object.entries(this._varByPrefix)){
+            if(id.startsWith(prefix)){
+                overwrite = ow;
+                if(typeof overwrite.set == "function")
+                    return overwrite.set(id.substring(prefix.length),index,val);
+                break;
+            }
+
+            else if(id.startsWith("$"+prefix)){
+                overwrite = ow;
+                if(typeof overwrite.set == "function")
+                    return overwrite.set(id.substring(prefix.length+1),index,val);
+                break;
+            }
+        }
+
+
         if(this._var[id] === undefined || this._var[id].set === undefined)
             return undefined;
         if(typeof this._var[id].set == "function")
@@ -51,6 +94,10 @@ class Overwrite{
         this._var[id.toLowerCase()] = {get:get, set:set};
     }
 
+    varRegisterByPrefix(prefix,get,set){
+        this._varByPrefix[prefix.toLowerCase()] = {get:get, set:set};
+    }
+
 }
 
 setup.Overwrite = new Overwrite();

+ 82 - 0
qsrc2tw/twine-code/npcs/NPCAccessor.ts

@@ -0,0 +1,82 @@
+type NPC = NPCAccessor & {[key:string]:any};
+class NPCAccessor{
+    static _getHandler = function(target, prop){
+		if(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop)?.get){
+			return target[prop];
+		}
+		if(typeof target[prop] === 'function')
+			return new Proxy(target[prop], {
+				apply: (functionName, thisArg, argumentsList) =>  Reflect.apply(functionName, target, argumentsList)
+			});
+
+		return target.get(prop);
+	}
+
+    static handler = {
+		get(target, prop) {
+			if(prop.endsWith('_possessive')){
+				prop = prop.replace('_possessive','');
+				let value = NPCAccessor._getHandler(target,prop);
+				if(value.endsWith('s'))
+					return value+"'"; //https://dictionary.cambridge.org/grammar/british-grammar/possession-john-s-car-a-friend-of-mine
+				return value + "'s";
+			}
+			return NPCAccessor._getHandler(target,prop);
+
+		},
+		set(target, prop, value, receiver){
+			//console.warn(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop));
+			if(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop)?.set){
+				target[prop] = value;
+				return true;
+			}
+			target.set(prop,value);
+			return true;
+		}
+	};
+
+
+    constructor(npcId=undefined){
+		if(npcId)
+			this._npcID = npcId;
+	}
+
+	_npcID: string;
+    get id(){return this._npcID;}
+
+
+    get(key:string,def:any=undefined){
+        const NPCs = State.variables.npcs;
+		return NPCs.get(this._npcID,key,def);
+	}
+
+
+	set(key:string,v:any){
+        const NPCs = State.variables.npcs;
+		return NPCs.set(this._npcID,key,v);
+	}
+
+    _init(nPCsDict){
+        Object.keys(nPCsDict).forEach(function (pn) {
+            this[pn] = clone(nPCsDict[pn]);
+        }, this);
+
+        return this;
+    }
+
+    clone = function () {
+        return (new setup.NPCAccessor())._init(this);
+    };
+
+    toJSON = function () {
+        var ownData = {};
+        Object.keys(this).forEach(function (pn) {
+            if(typeof this[pn] !== "function")
+                ownData[pn] = clone(this[pn]);
+        }, this);
+        return JSON.reviveWrapper('(new setup.NPCAccessor())._init($ReviveData$)', ownData);
+    };
+}
+
+
+setup.NPCAccessor = NPCAccessor;

+ 91 - 0
qsrc2tw/twine-code/npcs/NPCs.ts

@@ -0,0 +1,91 @@
+class NPCs{
+    npc(npcId:string):NPC{
+		return new Proxy(new NPCAccessor(npcId),NPCAccessor.handler);
+	}
+
+    get(npcId:string,field:string,def:any=undefined):any{
+		let data = this.npcData(npcId);
+
+		if(field in data)
+			return data[field];
+		/*if("passage" in data){
+			let resultFromPassage = setup.func(data.passage,'vars',field);
+			if(resultFromPassage)
+				return resultFromPassage;
+		}*/
+
+
+
+        return def;
+    }
+
+    getDynamicData(npcId:string){
+        return State.variables.npcDynamic[npcId] ?? {};
+    }
+
+    getStaticData(npcId:string){
+        return setup.npcStatic[npcId] ?? {};
+    }
+
+    npcData(npcId:string){
+        let staticData = this.getStaticData(npcId);
+        let dynamicData = this.getDynamicData(npcId);
+
+        let data = Object.assign({},staticData,dynamicData);
+
+        /*if("gender" in data)
+            data = Object.assign({},setup.npcDefaults.gender[data.gender],data); //Apply the gender-defaults first.
+        if("defaults" in data){
+            for(let defaultId of data.defaults){
+                let def = setup.npcDefaults[defaultId];
+                data = Object.assign({},def,data);  //Apply the defaults first. They could be overwritten by static or dynamic data
+            }
+        }*/
+        return data;
+    }
+
+    set(npcId:string,field:string,v:any){
+        State.variables.npcDynamic[npcId] ??= {};
+
+        /*if(field in npcFieldBoundaries){
+            let fieldRules = npcFieldBoundaries[field];
+            if("min" in fieldRules)
+                v = Math.max(v, fieldRules.min);
+            if("max" in fieldRules)
+                v = Math.min(v, fieldRules.max);
+        }*/
+
+        State.variables.npcDynamic[npcId][field] = v;
+
+
+
+        console.log("NPC value set",npcId,field,v);
+    }
+
+    //#region System
+		constructor(){}
+
+		_init(nPCsDict){
+			Object.keys(nPCsDict).forEach(function (pn) {
+				this[pn] = clone(nPCsDict[pn]);
+			}, this);
+
+			return this;
+		}
+
+		clone = function () {
+			return (new setup.NPCs())._init(this);
+		};
+
+		toJSON = function () {
+			var ownData = {};
+			Object.keys(this).forEach(function (pn) {
+				if(typeof this[pn] !== "function")
+					ownData[pn] = clone(this[pn]);
+			}, this);
+			return JSON.reviveWrapper('(new setup.NPCs())._init($ReviveData$)', ownData);
+		};
+	//#endregion
+}
+
+setup.NPCs = NPCs;

+ 5 - 0
qsrc2tw/twine-code/npcs/overwrites.ts

@@ -0,0 +1,5 @@
+setup.Overwrite.varRegisterByPrefix(
+    "npc_",
+    ((varname,index)=>State.variables.npcs.get(index.toString(), varname)),
+    ((varname,index,v)=>State.variables.npcs.set(index.toString(), varname, v)),
+);

+ 2 - 0
qsrc2tw/twine-code/start/StoryInit.tw

@@ -1,2 +1,4 @@
 :: StoryInit
 <<set $PC = new setup.PlayerCharacter()>>
+<<set $npcs = new setup.NPCs()>>
+<<set $npcDynamic = {}>>

+ 27 - 2
qsrc2tw/twine-code/twine-code.d.ts

@@ -45,17 +45,37 @@ declare module "twine-sugarcube" {
              *
              */
             varRegister: (varname:string,
-                get:number|string|((index?:number)=>any)|undefined,
-                set:((index:number,val:number|string)=>any)|undefined,
+                get:number|string|((index?:number|string)=>any)|undefined,
+                set:((index:number|string,val:number|string)=>any)|undefined,
+            )=>void
+
+            varRegisterByPrefix: (varname:string,
+                get:number|string|((varname:string,index?:number|string)=>any)|undefined,
+                set:((varname:string,index:number|string,val:number|string)=>any)|undefined,
             )=>void
         };
 
+        NPCs: {new(): NPCs};
+        NPCAccessor: {new(): NPCAccessor};
+        npcsInit: (npcIds: string[]) => void;
+        npcInit: (npcId: string) => void;
+        npcStatic: {[npcID:string]: NPCStaticObject};
+
+        qsp_dyneval: (code:string, ...args : (string|number)[]) => string|number;
         qsp_func: (passage:string,...arguments:(string|number)[]) => string|number;
     }
 
     export interface SugarCubeStoryVariables{
         PC: PlayerCharacter;
 
+        /**
+         * The dynamic storage for NPC-data.
+         *
+         * @type {{[npcID:string]: {[field:string]:any}}}
+         */
+        npcDynamic: {[npcID:string]: {[field:string]:any}};
+
+        npcs: NPCs;
     }
 }
 
@@ -63,6 +83,11 @@ declare global {
     interface Window { QSP: {[key:(string|number)]: any}; }
 }
 
+export interface NPCStaticObject{
+    _defaults: string[];
+    [field: string]: string | number | string[];
+}
+
 export interface ProficiencyDefinition{
     calc: (attributs:{[skillId:string]:Skill}, skills: {[skillId:string]:Skill}) => number;
 }