script-engine-lib 1.0.0-rc4 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,3 +1,6 @@
1
+ import * as actions_lib from 'actions-lib';
2
+ import { SingleEvent } from 'actions-lib';
3
+
1
4
  declare function ActionBeforeTesting(): (_: unknown, propertyKey: string, descriptor: PropertyDescriptor) => void;
2
5
 
3
6
  type ScriptActionType = 'command' | 'dialog' | 'jumpTo' | 'branchByCondition' | 'branchByPlayerChoice' | 'branchByChance';
@@ -81,9 +84,12 @@ declare abstract class ScriptEngineFunctions {
81
84
  declare enum ScriptEngineState {
82
85
  Idle = 1,
83
86
  Running = 2,
84
- WaitingForPlayerChoice = 3
87
+ WaitingForPlayerChoice = 3,
88
+ WaitingForManualChoice = 4,
89
+ WaitingForDialog = 5
85
90
  }
86
91
  declare class ScriptEngine<ScriptFunctions extends ScriptEngineFunctions> {
92
+ readonly onManualBranching: actions_lib.Notifier<readonly string[]>;
87
93
  get variables(): {
88
94
  [key: string]: any;
89
95
  };
@@ -91,9 +97,10 @@ declare class ScriptEngine<ScriptFunctions extends ScriptEngineFunctions> {
91
97
  constructor(scriptDefinitions: Readonly<Record<string, ScriptDefinition>>, functions: ScriptFunctions, variables: {
92
98
  [key: string]: any;
93
99
  }, options?: ScriptEngineOptions);
94
- start(scriptId: string): void;
95
- next(): void;
100
+ run(scriptId: string): SingleEvent<void>;
101
+ continue(): void;
96
102
  playerChoice(choice: number): void;
103
+ manualBranchingChoice(choice: number): void;
97
104
  }
98
105
 
99
106
  declare abstract class ScriptEngineSimulatorFunctions {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import * as actions_lib from 'actions-lib';
2
+ import { SingleEvent } from 'actions-lib';
3
+
1
4
  declare function ActionBeforeTesting(): (_: unknown, propertyKey: string, descriptor: PropertyDescriptor) => void;
2
5
 
3
6
  type ScriptActionType = 'command' | 'dialog' | 'jumpTo' | 'branchByCondition' | 'branchByPlayerChoice' | 'branchByChance';
@@ -81,9 +84,12 @@ declare abstract class ScriptEngineFunctions {
81
84
  declare enum ScriptEngineState {
82
85
  Idle = 1,
83
86
  Running = 2,
84
- WaitingForPlayerChoice = 3
87
+ WaitingForPlayerChoice = 3,
88
+ WaitingForManualChoice = 4,
89
+ WaitingForDialog = 5
85
90
  }
86
91
  declare class ScriptEngine<ScriptFunctions extends ScriptEngineFunctions> {
92
+ readonly onManualBranching: actions_lib.Notifier<readonly string[]>;
87
93
  get variables(): {
88
94
  [key: string]: any;
89
95
  };
@@ -91,9 +97,10 @@ declare class ScriptEngine<ScriptFunctions extends ScriptEngineFunctions> {
91
97
  constructor(scriptDefinitions: Readonly<Record<string, ScriptDefinition>>, functions: ScriptFunctions, variables: {
92
98
  [key: string]: any;
93
99
  }, options?: ScriptEngineOptions);
94
- start(scriptId: string): void;
95
- next(): void;
100
+ run(scriptId: string): SingleEvent<void>;
101
+ continue(): void;
96
102
  playerChoice(choice: number): void;
103
+ manualBranchingChoice(choice: number): void;
97
104
  }
98
105
 
99
106
  declare abstract class ScriptEngineSimulatorFunctions {
package/dist/index.js CHANGED
@@ -1,21 +1,21 @@
1
- 'use strict';var helpersLib=require('helpers-lib');require('reflect-metadata');var b=new Set(["Boolean","Number","String"]),d="jsEngineExecutionFlag",$="JSEngineFunctionFlag";function P(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(d,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be usable by JSEngine.`)},helpersLib.MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var p=class h{constructor(e,t,i){this.variables={};this.šap=new Proxy({},{get:(e,t)=>{if(typeof t!="symbol"){if(t==="Boolean")return Boolean;if(t==="Number")return Number;if(t==="String")return String;if(typeof this.functions[t]=="function"){if(!h.isJSEngineFunction(this.functions[t]))throw new Error(`"${t}(...)" is not a JSEngine function, it cannot be called during the executions.`);return (...i)=>this.functions[t](...i)}else if(Object.hasOwn(this.variables,t))return this.variables[t]}},set:(e,t,i)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be assigned.`);if(Object.hasOwn(this.functions,t))throw new Error(`JSEngine: Cannot set a value to the property "${t}". It is already in use as a function.`);if(this.globalNameSpace&&this.globalNameSpace.has(t)&&typeof i!==this.globalNameSpace.get(t))throw new Error(`JSEngine: Type mismatch during variable set. The type of "${t}" is "${typeof i}", and it is tried to set to "${this.globalNameSpace.get(t)}".`);return this.variables[t]=i,this.globalNameSpace&&this.globalNameSpace.set(t,typeof i),true},has(){return true},deleteProperty:(e,t)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be deleted.`);return delete this.variables[t],true}});this.šan(e,t),this.variables=t,this.functions=e,this.globalNameSpace=i;}static isJSEngineFunction(e){return helpersLib.Comparator.isFunction(e)?Reflect.getOwnMetadata($,e)===true:false}static setAsJSEngineFunction(e){Reflect.defineMetadata($,true,e);}execute(e){Reflect.defineMetadata(d,true,this.functions);let t=new Function("vars",`
1
+ 'use strict';var helpersLib=require('helpers-lib');require('reflect-metadata');var actionsLib=require('actions-lib');var b=new Set(["Boolean","Number","String"]),d="jsEngineExecutionFlag",$="JSEngineFunctionFlag";function O(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(d,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be usable by JSEngine.`)},helpersLib.MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var p=class h{constructor(e,t,i){this.variables={};this.šau=new Proxy({},{get:(e,t)=>{if(typeof t!="symbol"){if(t==="Boolean")return Boolean;if(t==="Number")return Number;if(t==="String")return String;if(typeof this.functions[t]=="function"){if(!h.isJSEngineFunction(this.functions[t]))throw new Error(`"${t}(...)" is not a JSEngine function, it cannot be called during the executions.`);return (...i)=>this.functions[t](...i)}else if(Object.hasOwn(this.variables,t))return this.variables[t]}},set:(e,t,i)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be assigned.`);if(Object.hasOwn(this.functions,t))throw new Error(`JSEngine: Cannot set a value to the property "${t}". It is already in use as a function.`);if(this.globalNameSpace&&this.globalNameSpace.has(t)&&typeof i!==this.globalNameSpace.get(t))throw new Error(`JSEngine: Type mismatch during variable set. The type of "${t}" is "${typeof i}", and it is tried to set to "${this.globalNameSpace.get(t)}".`);return this.variables[t]=i,this.globalNameSpace&&this.globalNameSpace.set(t,typeof i),true},has(){return true},deleteProperty:(e,t)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be deleted.`);return delete this.variables[t],true}});this.šas(e,t),this.variables=t,this.functions=e,this.globalNameSpace=i;}static isJSEngineFunction(e){return helpersLib.Comparator.isFunction(e)?Reflect.getOwnMetadata($,e)===true:false}static setAsJSEngineFunction(e){Reflect.defineMetadata($,true,e);}execute(e){Reflect.defineMetadata(d,true,this.functions);let t=new Function("vars",`
2
2
  with (vars) {
3
3
  ${e};
4
4
  }
5
- `);try{t(this.šap);}catch(i){this.šaf(i);}Reflect.defineMetadata(d,false,this.functions);}boolean(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
5
+ `);try{t(this.šau);}catch(i){this.šak(i);}Reflect.defineMetadata(d,false,this.functions);}boolean(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
6
6
  with (vars) {
7
7
  return Boolean(${e});
8
8
  }
9
- `)(this.šap);}catch(i){return this.šaf(i),false}return Reflect.defineMetadata(d,false,this.functions),t}number(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
9
+ `)(this.šau);}catch(i){return this.šak(i),false}return Reflect.defineMetadata(d,false,this.functions),t}number(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
10
10
  with (vars) {
11
11
  return Number(${e});
12
12
  }
13
- `)(this.šap);}catch(i){return this.šaf(i),0}return Reflect.defineMetadata(d,false,this.functions),t}string(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
13
+ `)(this.šau);}catch(i){return this.šak(i),0}return Reflect.defineMetadata(d,false,this.functions),t}string(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
14
14
  with (vars) {
15
15
  return String(\`${e}\`);
16
16
  }
17
- `)(this.šap);}catch(i){return this.šaf(i),""}return Reflect.defineMetadata(d,false,this.functions),t}duplicate(){let e=helpersLib.JsonHelper.deepCopy(this.variables),t=helpersLib.JsonHelper.deepCopy(this.functions);return new h(t,e,this.globalNameSpace)}šan(e,t){b.forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`);if(Object.hasOwn(e,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a function.`)}),Object.getOwnPropertyNames(e).forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`)});}šaf(e){throw e instanceof Error?(e.message=e.message.replace(/^.*Error: /,""),e):new Error(`${e}`.replace(/^.*Error: /,""))}};var E="actionsBeforeTesting";function J(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(E,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be called at "actions before testing".`)},helpersLib.MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var l=class extends Error{constructor(e,t){super(e),this.location=t;}},S=class extends Error{constructor(e,t){super(e),this.actionIndex=t;}},x=class extends Error{constructor(e,t,i){super(e),this.actionIndex=t,this.location=i;}};var v=class{},C=(i=>(i[i.Idle=1]="Idle",i[i.Running=2]="Running",i[i.WaitingForPlayerChoice=3]="WaitingForPlayerChoice",i))(C||{}),B=class{constructor(e,t,i,n){this.šal=new helpersLib.Stack;this.šz=false;this.šao(t),this.šz=!!n?.manualTestingMode,this.šak=e,this.šx=new p(t,i);}get variables(){return this.šx.variables}get state(){return this.šab?3:this.šai?2:1}get šai(){return !this.šal.isEmpty}start(e){if(!this.šal.isEmpty)throw new Error("ScriptEngine: Existing script haven't been concluded yet.");this.šy(e);}next(){if(this.šal.isEmpty)throw new Error("ScriptEngine: There is no active script to iterate.");if(this.state!==2)throw new Error(`ScriptEngine: The engine is not in running state. Next action cannot be executed. State: ${C[this.state]}`);let e=this.šal.pop();this.ši(e);}playerChoice(e){if(!this.šab)throw new Error("ScriptEngine: No player branching choices available.");let t=this.šab[e];if(!t)throw new Error("ScriptEngine: Invalid player choice.");this.šv(t),this.šab=void 0,this.šai&&this.next();}šy(e){let t=this.šak[e];if(!t)throw new Error(`ScriptEngine: Script definition not found: ${e}`);this.šv(t.actions);}ši(e){switch(e.type){case "command":this.šx.execute(e.value);break;case "dialog":{let t=e.value;this.šx.functions.onDialog(this.šx.string(t.text),t.speaker);break}case "jumpTo":{this.šy(e.value),this.šai&&this.next();break}case "branchByCondition":{let i=e.value.find(n=>n.condition?this.šx.boolean(n.condition):true);if(i)this.šv(i.actions);else throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByCondition action.");this.šai&&this.next();break}case "branchByChance":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByChance action.");if(this.šz)this.šab=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map((i,n)=>i.label??`Anonymous ${n}`));else {let i=helpersLib.Random.pickRandomElementWithWeight(t.map(n=>({value:n.actions,weight:helpersLib.Comparator.isString(n.weight)?this.šx.number(n.weight):n.weight})));this.šv(i),this.šai&&this.next();}break}case "branchByPlayerChoice":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByPlayerChoice action.");this.šab=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map(i=>this.šx.string(i.text)));break}default:throw new Error(`ScriptEngine: Unknown action type: ${e.type}`)}}šv(e){this.šal.add(...e);}šao(e){if(p.isJSEngineFunction(e.onPlayerChoice))throw new Error("ScriptEngine: onPlayerChoice function shall not be decorated as a JSEngineFunction.")}};var y=class{static isValid(e,t){try{return this.validate(e,t),!0}catch{return false}}static validate(e,t){let i=[],n=0;for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1)break;let a=e[r+1]==="/",o=r+(a?2:1),s=e.indexOf(">",r);if(s===-1)throw new Error(`Malformed tag at position ${r}: missing closing bracket '>'`);let c=e.substring(o,s);if(c.trim()==="")throw new Error(`Empty tag name found at position ${r}`);if(/\s/.test(c))throw new Error(`Invalid tag name '${c}' at position ${r}: contains whitespace`);if(t&&!a&&!t.has(c))throw new Error(`Unknown tag '${c}' at position ${r}. Only these tags are allowed: ${[...t].join(", ")}`);if(a){if(i.length===0)throw new Error(`Closing tag '${c}' at position ${r} has no matching opening tag`);let u=i.pop();if(u!==c)throw new Error(`Mismatched tags: expected closing tag for '${u}', but found '${c}' at position ${r}`)}else i.push(c);n=s+1;}if(i.length>0)throw new Error(`Unclosed tag${i.length>1?"s":""}: ${i.join(", ")}`)}};var D=class{static process(e,t){y.validate(e,t);let i=this.šae(e,[]);return this.šf(i)}static šf(e){if(e.length<=1)return e;let t=[],i=e[0];for(let n=1;n<e.length;n++){let r=e[n];i.tags.length===r.tags.length&&i.tags.every((o,s)=>o===r.tags[s])?i={text:i.text+r.text,tags:i.tags}:(t.push(i),i=r);}return t.push(i),t}static šae(e,t){let i=[],n=0;if(e==="")return [{text:"",tags:[]}];for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1){if(n<e.length){let g=e.substring(n);g.length>0&&i.push({text:g,tags:[...t]});}break}if(r>n){let g=e.substring(n,r);g.length>0&&i.push({text:g,tags:[...t]});}let a=e.indexOf(">",r),o=e.substring(r+1,a),s=this.šs(e,o,a+1),c=e.substring(a+1,s);if(c.length===0){n=s+o.length+3;continue}let u=[...t,o],f=this.šae(c,u);i.push(...f),n=s+o.length+3;}return i}static šs(e,t,i){let n=i,r=1;for(;n<e.length&&r>0;){let a=e.indexOf("<"+t,n),o=e.indexOf("</"+t+">",n);if(a!==-1&&a<o)r++,n=a+1;else {if(r--,r===0)return o;n=o+1;}}return e.length}};var m=class h{constructor(){this.šh=[];}addEntry(e){return this.šh.push(e),this}toString(){return this.šh.join(`
18
- `)}duplicate(){let e=new h;return e.šh=[...this.šh],e}};var w=class{constructor(e){this.šu=new Map;this.šam=new Set;this.šc=new Map;this.ša=new Map;e.forEach(t=>{this.šu.set(t.id,t),this.šag(t,1,t.id,void 0,[]);});}getScript(e){let t=this.šu.get(e);if(!t)throw new Error(`Script with id ${e} not found.`);return t}getRootBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:[]}}getBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}getActionLocation(e){let t=this.ša.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}setVisited(e){this.šam.delete(e);}getUnvisitedBranchLocations(){return Array.from(this.šam).map(e=>this.šc.get(e)).filter(e=>!e.parentBranchInfo||!this.šam.has(e.parentBranchInfo.branch)).map(e=>({scriptId:e.rootScriptId,path:e.path}))}šag(e,t,i,n,r){let a={branch:e,type:t,parentBranchInfo:n,rootScriptId:i,path:r};this.šam.add(e),this.šc.set(e,a),this.še(e.actions,i,a,r);}še(e,t,i,n){e.forEach((r,a)=>{let o=[...n,"actions",`${a}`],s={action:r,rootScriptId:t,path:o};this.ša.set(r,s),r.type==="branchByCondition"?r.value.forEach((u,f)=>{let g=[...o,"branchByCondition",`${f}`];this.šag(u,2,t,i,g);}):r.type==="branchByPlayerChoice"?r.value.forEach((u,f)=>{let g=[...o,"branchByPlayerChoice",`${f}`];this.šag(u,3,t,i,g);}):r.type==="branchByChance"&&r.value.forEach((u,f)=>{let g=[...o,"branchByChance",`${f}`];this.šag(u,4,t,i,g);});});}};var T=class{},M=100,A=class{constructor(e,t={prscriptTypeChanges:true,richTextTags:void 0}){this.šak=e;this.šaa=t;this.šaq=new Map;this.šaj=new w(this.šak),this.šaa.prscriptTypeChanges&&(this.št=new Map);}simulateScript(e,t,i){this.šaa.prscriptTypeChanges&&(t.globalNameSpace=this.št);let r=[{executionHistory:new m,stack:new helpersLib.Stack,jsEngine:t,depth:0,lastExecutedAction:void 0}];this.šad(i,r),this.šaq.clear(),this.šr(e,r),this.šaq.clear();}getUnvisitedBranchLocations(){return this.šaj.getUnvisitedBranchLocations()}šad(e,t){e.forEach((i,n)=>{switch(i.type){case "command":this.šj(i,t,n);break;case "runScript":this.šb(i,t,n);break;default:throw new Error(`Unknown action before testing type: ${i.type}`)}});}šj(e,t,i){try{t.forEach(n=>{Reflect.defineMetadata(E,!0,n.jsEngine.functions),n.jsEngine.execute(e.value);});}catch(n){let r=this.šac(`${n}`,t[0].executionHistory);throw new S(r,i)}}šb(e,t,i){let n=[],r=this.šaj.getScript(e.value);if(!r)throw new Error(`Target script is not found: ${e.value}`);try{this.šaq.clear(),t.forEach(o=>{o.executionHistory.addEntry(`Branching out from script: ${e.value}`),o.depth=0,Reflect.defineMetadata(E,!1,o.jsEngine.functions);let s=this.šp(r,o,!1);if(!s.exitFound){let c=this.šac(`All possible paths are creating infinite loops, no exit found while executing: ${e.value}`,o.executionHistory);throw new l(c,this.šaj.getRootBranchLocation(r))}s.allEndings.forEach(c=>{n.push(c);});});let a=this.šah(n);t.length=0,t.push(...a),t.forEach(o=>{if(o.executionHistory.addEntry(`${a.length} unique ending${a.length>1?"s":""} generated.`).addEntry(""),o.lastExecutedAction)try{o.jsEngine.functions.onScriptBranchingEnd();}catch(s){let c=this.šaj.getActionLocation(o.lastExecutedAction),u=this.šac(`${s}`,o.executionHistory);throw new l(u,c)}else throw new Error("Script execution is ended without executing any command!")});}catch(a){throw a instanceof l?new x(`${a.message}`,i,a.location):a}}šr(e,t){t.forEach(i=>{i.executionHistory.addEntry(`Running script: ${e.id}`),i.depth=0,Reflect.defineMetadata(E,false,i.jsEngine.functions);let n=this.šp(e,i,true);if(!n.exitFound){let r=this.šac(`All possible paths are creating infinite loops, no exit found while executing: ${e.id}`,i.executionHistory);throw new l(r,this.šaj.getRootBranchLocation(e))}n.allEndings.forEach(r=>{if(r.lastExecutedAction)try{r.jsEngine.functions.onEnd();}catch(a){let o=this.šaj.getActionLocation(r.lastExecutedAction),s=this.šac(`${a}`,r.executionHistory);throw new l(s,o)}else throw new Error("Script execution is ended without executing any command!")});});}šp(e,t,i){if(!this.šw(e,t))return {allEndings:[],exitFound:false};let n=[],r=true;if(i&&this.šaj.setVisited(e),t.depth++,t.depth>M){let a=this.šac(`Maximum depth "${M}" is reached. Try to reduce the script branching tree or check infinite loops with trivial state changes.`,t.executionHistory);throw new l(a,this.šaj.getRootBranchLocation(e))}for(t.stack.add(...e.actions);!t.stack.isEmpty;){let a=t.stack.pop(),o=this.ši(a,t,i);t.stack.isEmpty&&(n.push(...o.allEndings),r=o.exitFound&&r);}return n=n.length>0?this.šah(n):[],{allEndings:n,exitFound:r}}ši(e,t,i){switch(t.lastExecutedAction=e,e.type){case "command":return this.šn(e,t);case "dialog":return this.šq(e,t);case "jumpTo":return this.šo(e,t,i);case "branchByCondition":return this.šl(e,t,i);case "branchByChance":return this.šk(e,t,i);case "branchByPlayerChoice":return this.šm(e,t,i);default:throw new Error(`Unknown action type: ${e.type}`)}}šn(e,t){try{t.jsEngine.execute(e.value);}catch(i){let n=this.šaj.getActionLocation(e),r=this.šac(`${i}`,t.executionHistory);throw new l(r,n)}return {allEndings:[t],exitFound:true}}šq(e,t){let i=e.value,n;try{n=t.jsEngine.string(i.text),this.šaa.richTextTags&&y.validate(n,this.šaa.richTextTags),t.jsEngine.functions.onDialog(n,i.speaker);}catch(r){let a=this.šaj.getActionLocation(e),o=this.šac(`${r}`,t.executionHistory);throw new l(o,a)}return {allEndings:[t],exitFound:true}}šo(e,t,i){let n=this.šaj.getScript(e.value);return t.executionHistory.addEntry(`Jumping to script: ${e.value}`),this.šp(n,t,i)}šl(e,t,i){let r=e.value.find(a=>this.šd(a,t));if(!r){let a=this.šaj.getActionLocation(e),o=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(o,a)}return t.executionHistory.addEntry(`Branching by condition: ${r.condition?r.condition:"default"}`),this.šp(r,t,i)}šk(e,t,i){let r=e.value.map((o,s)=>({index:s,subBranch:o})).filter(o=>this.šd(o.subBranch,t));if(r.length===0){let o=this.šaj.getActionLocation(e),s=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}let a=r.map(o=>{try{let c=helpersLib.Comparator.isString(o.subBranch.weight)?t.jsEngine.number(o.subBranch.weight):o.subBranch.weight;if(!helpersLib.Comparator.isNumber(c)||c<0)throw new Error(`Weight of branch ${o.index} is not a valid number: "${o.subBranch.weight}" -> ${c}.`)}catch(c){let u=this.šaj.getActionLocation(e),f=this.šac(`${c}`,t.executionHistory);throw new l(f,u)}let s=this.šg(t);return s.executionHistory.addEntry(`Branching by chance: ${o.index}`),this.šp(o.subBranch,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šm(e,t,i){let r=e.value.filter(o=>this.šd(o,t));if(r.length===0){let o=this.šaj.getActionLocation(e),s=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}try{t.jsEngine.functions.onPlayerChoice(r.map(o=>t.jsEngine.string(o.text)));}catch(o){let s=this.šaj.getActionLocation(e),c=this.šac(`${o}`,t.executionHistory);throw new l(c,s)}let a=r.map(o=>{let s=this.šg(t);return s.executionHistory.addEntry(`Player choice: ${o.text}`),this.šp(o,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šd(e,t){if(e.condition)try{return t.jsEngine.boolean(e.condition)}catch(i){let n=this.šaj.getBranchLocation(e),r=this.šac(`${i}`,t.executionHistory);throw new l(r,n)}else return true}šah(e){let t=[];return e.map(n=>({ending:n,variablesString:JSON.stringify(n.jsEngine.variables),functionsString:JSON.stringify(n.jsEngine.functions)})).forEach(n=>{t.every(a=>n.functionsString!==a.functionsString||n.variablesString!==a.variablesString)&&t.push({ending:n.ending,variablesString:n.variablesString,functionsString:n.functionsString});}),t.map(n=>n.ending)}šw(e,t){let i=JSON.stringify(t.jsEngine.variables)+JSON.stringify(t.jsEngine.functions),n=this.šaq.get(e);return n||(n=new Set,this.šaq.set(e,n)),n.has(i)?false:(n.add(i),true)}šg(e){return {executionHistory:e.executionHistory.duplicate(),stack:e.stack.duplicate(),jsEngine:e.jsEngine.duplicate(),depth:e.depth,lastExecutedAction:e.lastExecutedAction}}šac(e,t){return `${e}
17
+ `)(this.šau);}catch(i){return this.šak(i),""}return Reflect.defineMetadata(d,false,this.functions),t}duplicate(){let e=helpersLib.JsonHelper.deepCopy(this.variables),t=helpersLib.JsonHelper.deepCopy(this.functions);return new h(t,e,this.globalNameSpace)}šas(e,t){b.forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`);if(Object.hasOwn(e,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a function.`)}),Object.getOwnPropertyNames(e).forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`)});}šak(e){throw e instanceof Error?(e.message=e.message.replace(/^.*Error: /,""),e):new Error(`${e}`.replace(/^.*Error: /,""))}};var E="actionsBeforeTesting";function L(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(E,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be called at "actions before testing".`)},helpersLib.MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var l=class extends Error{constructor(e,t){super(e),this.location=t;}},S=class extends Error{constructor(e,t){super(e),this.actionIndex=t;}},x=class extends Error{constructor(e,t,i){super(e),this.actionIndex=t,this.location=i;}};var v=class{},D=(r=>(r[r.Idle=1]="Idle",r[r.Running=2]="Running",r[r.WaitingForPlayerChoice=3]="WaitingForPlayerChoice",r[r.WaitingForManualChoice=4]="WaitingForManualChoice",r[r.WaitingForDialog=5]="WaitingForDialog",r))(D||{}),B=class{constructor(e,t,i,n){this.šaq=new helpersLib.Stack;this.šaw=false;this.šaa=false;this.šac=new actionsLib.Action;this.onManualBranching=this.šac.notifier;this.šad=new actionsLib.Action;this.šat(t),this.šaa=!!n?.manualTestingMode,this.šap=e,this.šx=new p(t,i);}get variables(){return this.šx.variables}get state(){return this.šaw?5:this.šz?4:this.šaf?3:this.šan?2:1}get šan(){return !this.šaq.isEmpty}run(e){if(this.state!==1)throw new Error("ScriptEngine: Existing script haven't been concluded yet.");this.šy(e);let t=this.šad.toSingleEvent().destroyIfNotAttached();return this.šaj(),t}continue(){let e=this.state;if(e!==5){if(this.šaq.isEmpty)throw new Error("ScriptEngine: There is no active script to iterate.");if(e!==2)throw new Error(`ScriptEngine: The engine is not in running state. Next action cannot be executed. State: ${D[this.state]}`)}this.šaw=false,this.šaj();}playerChoice(e){if(!this.šaf)throw new Error("ScriptEngine: No player branching choices available.");let t=this.šaf[e];if(!t)throw new Error("ScriptEngine: Invalid player choice.");this.šv(t),this.šaf=void 0,this.šaj();}manualBranchingChoice(e){if(!this.šz)throw new Error("ScriptEngine: No manual branching choices available.");let t=this.šz[e];if(!t)throw new Error("ScriptEngine: Invalid manual choice.");this.šv(t),this.šz=void 0,this.šaj();}šaj(){for(;this.state===2;)this.šab();this.state===1&&this.šad.trigger();}šab(){let e=this.šaq.pop();this.ši(e);}šy(e){let t=this.šap[e];if(!t)throw new Error(`ScriptEngine: Script definition not found: ${e}`);this.šv(t.actions);}ši(e){switch(e.type){case "command":this.šx.execute(e.value);break;case "dialog":{let t=e.value;this.šaw=true,this.šx.functions.onDialog(this.šx.string(t.text),t.speaker);break}case "jumpTo":{this.šy(e.value),this.šan&&this.šab();break}case "branchByCondition":{let i=e.value.find(n=>n.condition?this.šx.boolean(n.condition):true);if(i)this.šv(i.actions);else throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByCondition action.");this.šan&&this.šab();break}case "branchByChance":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByChance action.");if(this.šaa)this.šz=t.map(i=>i.actions),this.šac.trigger(t.map((i,n)=>i.label??`Anonymous ${n}`));else {let i=helpersLib.Random.pickRandomElementWithWeight(t.map(n=>({value:n.actions,weight:helpersLib.Comparator.isString(n.weight)?this.šx.number(n.weight):n.weight})));this.šv(i),this.šan&&this.šab();}break}case "branchByPlayerChoice":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByPlayerChoice action.");this.šaf=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map(i=>this.šx.string(i.text)));break}default:throw new Error(`ScriptEngine: Unknown action type: ${e.type}`)}}šv(e){this.šaq.add(...e);}šat(e){if(p.isJSEngineFunction(e.onPlayerChoice))throw new Error("ScriptEngine: onPlayerChoice function shall not be decorated as a JSEngineFunction.")}};var y=class{static isValid(e,t){try{return this.validate(e,t),!0}catch{return false}}static validate(e,t){let i=[],n=0;for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1)break;let a=e[r+1]==="/",o=r+(a?2:1),s=e.indexOf(">",r);if(s===-1)throw new Error(`Malformed tag at position ${r}: missing closing bracket '>'`);let c=e.substring(o,s);if(c.trim()==="")throw new Error(`Empty tag name found at position ${r}`);if(/\s/.test(c))throw new Error(`Invalid tag name '${c}' at position ${r}: contains whitespace`);if(t&&!a&&!t.has(c))throw new Error(`Unknown tag '${c}' at position ${r}. Only these tags are allowed: ${[...t].join(", ")}`);if(a){if(i.length===0)throw new Error(`Closing tag '${c}' at position ${r} has no matching opening tag`);let u=i.pop();if(u!==c)throw new Error(`Mismatched tags: expected closing tag for '${u}', but found '${c}' at position ${r}`)}else i.push(c);n=s+1;}if(i.length>0)throw new Error(`Unclosed tag${i.length>1?"s":""}: ${i.join(", ")}`)}};var C=class{static process(e,t){y.validate(e,t);let i=this.šai(e,[]);return this.šf(i)}static šf(e){if(e.length<=1)return e;let t=[],i=e[0];for(let n=1;n<e.length;n++){let r=e[n];i.tags.length===r.tags.length&&i.tags.every((o,s)=>o===r.tags[s])?i={text:i.text+r.text,tags:i.tags}:(t.push(i),i=r);}return t.push(i),t}static šai(e,t){let i=[],n=0;if(e==="")return [{text:"",tags:[]}];for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1){if(n<e.length){let g=e.substring(n);g.length>0&&i.push({text:g,tags:[...t]});}break}if(r>n){let g=e.substring(n,r);g.length>0&&i.push({text:g,tags:[...t]});}let a=e.indexOf(">",r),o=e.substring(r+1,a),s=this.šs(e,o,a+1),c=e.substring(a+1,s);if(c.length===0){n=s+o.length+3;continue}let u=[...t,o],f=this.šai(c,u);i.push(...f),n=s+o.length+3;}return i}static šs(e,t,i){let n=i,r=1;for(;n<e.length&&r>0;){let a=e.indexOf("<"+t,n),o=e.indexOf("</"+t+">",n);if(a!==-1&&a<o)r++,n=a+1;else {if(r--,r===0)return o;n=o+1;}}return e.length}};var w=class h{constructor(){this.šh=[];}addEntry(e){return this.šh.push(e),this}toString(){return this.šh.join(`
18
+ `)}duplicate(){let e=new h;return e.šh=[...this.šh],e}};var m=class{constructor(e){this.šu=new Map;this.šar=new Set;this.šc=new Map;this.ša=new Map;e.forEach(t=>{this.šu.set(t.id,t),this.šal(t,1,t.id,void 0,[]);});}getScript(e){let t=this.šu.get(e);if(!t)throw new Error(`Script with id ${e} not found.`);return t}getRootBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:[]}}getBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}getActionLocation(e){let t=this.ša.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}setVisited(e){this.šar.delete(e);}getUnvisitedBranchLocations(){return Array.from(this.šar).map(e=>this.šc.get(e)).filter(e=>!e.parentBranchInfo||!this.šar.has(e.parentBranchInfo.branch)).map(e=>({scriptId:e.rootScriptId,path:e.path}))}šal(e,t,i,n,r){let a={branch:e,type:t,parentBranchInfo:n,rootScriptId:i,path:r};this.šar.add(e),this.šc.set(e,a),this.še(e.actions,i,a,r);}še(e,t,i,n){e.forEach((r,a)=>{let o=[...n,"actions",`${a}`],s={action:r,rootScriptId:t,path:o};this.ša.set(r,s),r.type==="branchByCondition"?r.value.forEach((u,f)=>{let g=[...o,"branchByCondition",`${f}`];this.šal(u,2,t,i,g);}):r.type==="branchByPlayerChoice"?r.value.forEach((u,f)=>{let g=[...o,"branchByPlayerChoice",`${f}`];this.šal(u,3,t,i,g);}):r.type==="branchByChance"&&r.value.forEach((u,f)=>{let g=[...o,"branchByChance",`${f}`];this.šal(u,4,t,i,g);});});}};var T=class{},R=100,A=class{constructor(e,t={prscriptTypeChanges:true,richTextTags:void 0}){this.šap=e;this.šae=t;this.šav=new Map;this.šao=new m(this.šap),this.šae.prscriptTypeChanges&&(this.št=new Map);}simulateScript(e,t,i){this.šae.prscriptTypeChanges&&(t.globalNameSpace=this.št);let r=[{executionHistory:new w,stack:new helpersLib.Stack,jsEngine:t,depth:0,lastExecutedAction:void 0}];this.šah(i,r),this.šav.clear(),this.šr(e,r),this.šav.clear();}getUnvisitedBranchLocations(){return this.šao.getUnvisitedBranchLocations()}šah(e,t){e.forEach((i,n)=>{switch(i.type){case "command":this.šj(i,t,n);break;case "runScript":this.šb(i,t,n);break;default:throw new Error(`Unknown action before testing type: ${i.type}`)}});}šj(e,t,i){try{t.forEach(n=>{Reflect.defineMetadata(E,!0,n.jsEngine.functions),n.jsEngine.execute(e.value);});}catch(n){let r=this.šag(`${n}`,t[0].executionHistory);throw new S(r,i)}}šb(e,t,i){let n=[],r=this.šao.getScript(e.value);if(!r)throw new Error(`Target script is not found: ${e.value}`);try{this.šav.clear(),t.forEach(o=>{o.executionHistory.addEntry(`Branching out from script: ${e.value}`),o.depth=0,Reflect.defineMetadata(E,!1,o.jsEngine.functions);let s=this.šp(r,o,!1);if(!s.exitFound){let c=this.šag(`All possible paths are creating infinite loops, no exit found while executing: ${e.value}`,o.executionHistory);throw new l(c,this.šao.getRootBranchLocation(r))}s.allEndings.forEach(c=>{n.push(c);});});let a=this.šam(n);t.length=0,t.push(...a),t.forEach(o=>{if(o.executionHistory.addEntry(`${a.length} unique ending${a.length>1?"s":""} generated.`).addEntry(""),o.lastExecutedAction)try{o.jsEngine.functions.onScriptBranchingEnd();}catch(s){let c=this.šao.getActionLocation(o.lastExecutedAction),u=this.šag(`${s}`,o.executionHistory);throw new l(u,c)}else throw new Error("Script execution is ended without executing any command!")});}catch(a){throw a instanceof l?new x(`${a.message}`,i,a.location):a}}šr(e,t){t.forEach(i=>{i.executionHistory.addEntry(`Running script: ${e.id}`),i.depth=0,Reflect.defineMetadata(E,false,i.jsEngine.functions);let n=this.šp(e,i,true);if(!n.exitFound){let r=this.šag(`All possible paths are creating infinite loops, no exit found while executing: ${e.id}`,i.executionHistory);throw new l(r,this.šao.getRootBranchLocation(e))}n.allEndings.forEach(r=>{if(r.lastExecutedAction)try{r.jsEngine.functions.onEnd();}catch(a){let o=this.šao.getActionLocation(r.lastExecutedAction),s=this.šag(`${a}`,r.executionHistory);throw new l(s,o)}else throw new Error("Script execution is ended without executing any command!")});});}šp(e,t,i){if(!this.šw(e,t))return {allEndings:[],exitFound:false};let n=[],r=true;if(i&&this.šao.setVisited(e),t.depth++,t.depth>R){let a=this.šag(`Maximum depth "${R}" is reached. Try to reduce the script branching tree or check infinite loops with trivial state changes.`,t.executionHistory);throw new l(a,this.šao.getRootBranchLocation(e))}for(t.stack.add(...e.actions);!t.stack.isEmpty;){let a=t.stack.pop(),o=this.ši(a,t,i);t.stack.isEmpty&&(n.push(...o.allEndings),r=o.exitFound&&r);}return n=n.length>0?this.šam(n):[],{allEndings:n,exitFound:r}}ši(e,t,i){switch(t.lastExecutedAction=e,e.type){case "command":return this.šn(e,t);case "dialog":return this.šq(e,t);case "jumpTo":return this.šo(e,t,i);case "branchByCondition":return this.šl(e,t,i);case "branchByChance":return this.šk(e,t,i);case "branchByPlayerChoice":return this.šm(e,t,i);default:throw new Error(`Unknown action type: ${e.type}`)}}šn(e,t){try{t.jsEngine.execute(e.value);}catch(i){let n=this.šao.getActionLocation(e),r=this.šag(`${i}`,t.executionHistory);throw new l(r,n)}return {allEndings:[t],exitFound:true}}šq(e,t){let i=e.value,n;try{n=t.jsEngine.string(i.text),this.šae.richTextTags&&y.validate(n,this.šae.richTextTags),t.jsEngine.functions.onDialog(n,i.speaker);}catch(r){let a=this.šao.getActionLocation(e),o=this.šag(`${r}`,t.executionHistory);throw new l(o,a)}return {allEndings:[t],exitFound:true}}šo(e,t,i){let n=this.šao.getScript(e.value);return t.executionHistory.addEntry(`Jumping to script: ${e.value}`),this.šp(n,t,i)}šl(e,t,i){let r=e.value.find(a=>this.šd(a,t));if(!r){let a=this.šao.getActionLocation(e),o=this.šag("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(o,a)}return t.executionHistory.addEntry(`Branching by condition: ${r.condition?r.condition:"default"}`),this.šp(r,t,i)}šk(e,t,i){let r=e.value.map((o,s)=>({index:s,subBranch:o})).filter(o=>this.šd(o.subBranch,t));if(r.length===0){let o=this.šao.getActionLocation(e),s=this.šag("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}let a=r.map(o=>{try{let c=helpersLib.Comparator.isString(o.subBranch.weight)?t.jsEngine.number(o.subBranch.weight):o.subBranch.weight;if(!helpersLib.Comparator.isNumber(c)||c<0)throw new Error(`Weight of branch ${o.index} is not a valid number: "${o.subBranch.weight}" -> ${c}.`)}catch(c){let u=this.šao.getActionLocation(e),f=this.šag(`${c}`,t.executionHistory);throw new l(f,u)}let s=this.šg(t);return s.executionHistory.addEntry(`Branching by chance: ${o.index}`),this.šp(o.subBranch,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šm(e,t,i){let r=e.value.filter(o=>this.šd(o,t));if(r.length===0){let o=this.šao.getActionLocation(e),s=this.šag("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}try{t.jsEngine.functions.onPlayerChoice(r.map(o=>t.jsEngine.string(o.text)));}catch(o){let s=this.šao.getActionLocation(e),c=this.šag(`${o}`,t.executionHistory);throw new l(c,s)}let a=r.map(o=>{let s=this.šg(t);return s.executionHistory.addEntry(`Player choice: ${o.text}`),this.šp(o,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šd(e,t){if(e.condition)try{return t.jsEngine.boolean(e.condition)}catch(i){let n=this.šao.getBranchLocation(e),r=this.šag(`${i}`,t.executionHistory);throw new l(r,n)}else return true}šam(e){let t=[];return e.map(n=>({ending:n,variablesString:JSON.stringify(n.jsEngine.variables),functionsString:JSON.stringify(n.jsEngine.functions)})).forEach(n=>{t.every(a=>n.functionsString!==a.functionsString||n.variablesString!==a.variablesString)&&t.push({ending:n.ending,variablesString:n.variablesString,functionsString:n.functionsString});}),t.map(n=>n.ending)}šw(e,t){let i=JSON.stringify(t.jsEngine.variables)+JSON.stringify(t.jsEngine.functions),n=this.šav.get(e);return n||(n=new Set,this.šav.set(e,n)),n.has(i)?false:(n.add(i),true)}šg(e){return {executionHistory:e.executionHistory.duplicate(),stack:e.stack.duplicate(),jsEngine:e.jsEngine.duplicate(),depth:e.depth,lastExecutedAction:e.lastExecutedAction}}šag(e,t){return `${e}
19
19
 
20
20
  ----Execution history----
21
- ${t.toString()}`}};exports.ActionBeforeTesting=J;exports.ActionsBeforeTestingError=S;exports.BranchingBeforeTestingError=x;exports.JSEngine=p;exports.JSEngineFunction=P;exports.RichTextSeparator=D;exports.RichTextValidator=y;exports.ScriptEngine=B;exports.ScriptEngineFunctions=v;exports.ScriptEngineSimulatorFunctions=T;exports.ScriptEngineState=C;exports.ScriptTestSimulator=A;exports.SimulationError=l;
21
+ ${t.toString()}`}};exports.ActionBeforeTesting=L;exports.ActionsBeforeTestingError=S;exports.BranchingBeforeTestingError=x;exports.JSEngine=p;exports.JSEngineFunction=O;exports.RichTextSeparator=C;exports.RichTextValidator=y;exports.ScriptEngine=B;exports.ScriptEngineFunctions=v;exports.ScriptEngineSimulatorFunctions=T;exports.ScriptEngineState=D;exports.ScriptTestSimulator=A;exports.SimulationError=l;
package/dist/index.mjs CHANGED
@@ -1,21 +1,21 @@
1
- import {MetaDataHelper,Comparator,JsonHelper,Stack,Random}from'helpers-lib';import'reflect-metadata';var b=new Set(["Boolean","Number","String"]),d="jsEngineExecutionFlag",$="JSEngineFunctionFlag";function P(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(d,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be usable by JSEngine.`)},MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var p=class h{constructor(e,t,i){this.variables={};this.šap=new Proxy({},{get:(e,t)=>{if(typeof t!="symbol"){if(t==="Boolean")return Boolean;if(t==="Number")return Number;if(t==="String")return String;if(typeof this.functions[t]=="function"){if(!h.isJSEngineFunction(this.functions[t]))throw new Error(`"${t}(...)" is not a JSEngine function, it cannot be called during the executions.`);return (...i)=>this.functions[t](...i)}else if(Object.hasOwn(this.variables,t))return this.variables[t]}},set:(e,t,i)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be assigned.`);if(Object.hasOwn(this.functions,t))throw new Error(`JSEngine: Cannot set a value to the property "${t}". It is already in use as a function.`);if(this.globalNameSpace&&this.globalNameSpace.has(t)&&typeof i!==this.globalNameSpace.get(t))throw new Error(`JSEngine: Type mismatch during variable set. The type of "${t}" is "${typeof i}", and it is tried to set to "${this.globalNameSpace.get(t)}".`);return this.variables[t]=i,this.globalNameSpace&&this.globalNameSpace.set(t,typeof i),true},has(){return true},deleteProperty:(e,t)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be deleted.`);return delete this.variables[t],true}});this.šan(e,t),this.variables=t,this.functions=e,this.globalNameSpace=i;}static isJSEngineFunction(e){return Comparator.isFunction(e)?Reflect.getOwnMetadata($,e)===true:false}static setAsJSEngineFunction(e){Reflect.defineMetadata($,true,e);}execute(e){Reflect.defineMetadata(d,true,this.functions);let t=new Function("vars",`
1
+ import {MetaDataHelper,Comparator,JsonHelper,Stack,Random}from'helpers-lib';import'reflect-metadata';import {Action}from'actions-lib';var b=new Set(["Boolean","Number","String"]),d="jsEngineExecutionFlag",$="JSEngineFunctionFlag";function O(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(d,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be usable by JSEngine.`)},MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var p=class h{constructor(e,t,i){this.variables={};this.šau=new Proxy({},{get:(e,t)=>{if(typeof t!="symbol"){if(t==="Boolean")return Boolean;if(t==="Number")return Number;if(t==="String")return String;if(typeof this.functions[t]=="function"){if(!h.isJSEngineFunction(this.functions[t]))throw new Error(`"${t}(...)" is not a JSEngine function, it cannot be called during the executions.`);return (...i)=>this.functions[t](...i)}else if(Object.hasOwn(this.variables,t))return this.variables[t]}},set:(e,t,i)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be assigned.`);if(Object.hasOwn(this.functions,t))throw new Error(`JSEngine: Cannot set a value to the property "${t}". It is already in use as a function.`);if(this.globalNameSpace&&this.globalNameSpace.has(t)&&typeof i!==this.globalNameSpace.get(t))throw new Error(`JSEngine: Type mismatch during variable set. The type of "${t}" is "${typeof i}", and it is tried to set to "${this.globalNameSpace.get(t)}".`);return this.variables[t]=i,this.globalNameSpace&&this.globalNameSpace.set(t,typeof i),true},has(){return true},deleteProperty:(e,t)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be deleted.`);return delete this.variables[t],true}});this.šas(e,t),this.variables=t,this.functions=e,this.globalNameSpace=i;}static isJSEngineFunction(e){return Comparator.isFunction(e)?Reflect.getOwnMetadata($,e)===true:false}static setAsJSEngineFunction(e){Reflect.defineMetadata($,true,e);}execute(e){Reflect.defineMetadata(d,true,this.functions);let t=new Function("vars",`
2
2
  with (vars) {
3
3
  ${e};
4
4
  }
5
- `);try{t(this.šap);}catch(i){this.šaf(i);}Reflect.defineMetadata(d,false,this.functions);}boolean(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
5
+ `);try{t(this.šau);}catch(i){this.šak(i);}Reflect.defineMetadata(d,false,this.functions);}boolean(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
6
6
  with (vars) {
7
7
  return Boolean(${e});
8
8
  }
9
- `)(this.šap);}catch(i){return this.šaf(i),false}return Reflect.defineMetadata(d,false,this.functions),t}number(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
9
+ `)(this.šau);}catch(i){return this.šak(i),false}return Reflect.defineMetadata(d,false,this.functions),t}number(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
10
10
  with (vars) {
11
11
  return Number(${e});
12
12
  }
13
- `)(this.šap);}catch(i){return this.šaf(i),0}return Reflect.defineMetadata(d,false,this.functions),t}string(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
13
+ `)(this.šau);}catch(i){return this.šak(i),0}return Reflect.defineMetadata(d,false,this.functions),t}string(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
14
14
  with (vars) {
15
15
  return String(\`${e}\`);
16
16
  }
17
- `)(this.šap);}catch(i){return this.šaf(i),""}return Reflect.defineMetadata(d,false,this.functions),t}duplicate(){let e=JsonHelper.deepCopy(this.variables),t=JsonHelper.deepCopy(this.functions);return new h(t,e,this.globalNameSpace)}šan(e,t){b.forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`);if(Object.hasOwn(e,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a function.`)}),Object.getOwnPropertyNames(e).forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`)});}šaf(e){throw e instanceof Error?(e.message=e.message.replace(/^.*Error: /,""),e):new Error(`${e}`.replace(/^.*Error: /,""))}};var E="actionsBeforeTesting";function J(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(E,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be called at "actions before testing".`)},MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var l=class extends Error{constructor(e,t){super(e),this.location=t;}},S=class extends Error{constructor(e,t){super(e),this.actionIndex=t;}},x=class extends Error{constructor(e,t,i){super(e),this.actionIndex=t,this.location=i;}};var v=class{},C=(i=>(i[i.Idle=1]="Idle",i[i.Running=2]="Running",i[i.WaitingForPlayerChoice=3]="WaitingForPlayerChoice",i))(C||{}),B=class{constructor(e,t,i,n){this.šal=new Stack;this.šz=false;this.šao(t),this.šz=!!n?.manualTestingMode,this.šak=e,this.šx=new p(t,i);}get variables(){return this.šx.variables}get state(){return this.šab?3:this.šai?2:1}get šai(){return !this.šal.isEmpty}start(e){if(!this.šal.isEmpty)throw new Error("ScriptEngine: Existing script haven't been concluded yet.");this.šy(e);}next(){if(this.šal.isEmpty)throw new Error("ScriptEngine: There is no active script to iterate.");if(this.state!==2)throw new Error(`ScriptEngine: The engine is not in running state. Next action cannot be executed. State: ${C[this.state]}`);let e=this.šal.pop();this.ši(e);}playerChoice(e){if(!this.šab)throw new Error("ScriptEngine: No player branching choices available.");let t=this.šab[e];if(!t)throw new Error("ScriptEngine: Invalid player choice.");this.šv(t),this.šab=void 0,this.šai&&this.next();}šy(e){let t=this.šak[e];if(!t)throw new Error(`ScriptEngine: Script definition not found: ${e}`);this.šv(t.actions);}ši(e){switch(e.type){case "command":this.šx.execute(e.value);break;case "dialog":{let t=e.value;this.šx.functions.onDialog(this.šx.string(t.text),t.speaker);break}case "jumpTo":{this.šy(e.value),this.šai&&this.next();break}case "branchByCondition":{let i=e.value.find(n=>n.condition?this.šx.boolean(n.condition):true);if(i)this.šv(i.actions);else throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByCondition action.");this.šai&&this.next();break}case "branchByChance":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByChance action.");if(this.šz)this.šab=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map((i,n)=>i.label??`Anonymous ${n}`));else {let i=Random.pickRandomElementWithWeight(t.map(n=>({value:n.actions,weight:Comparator.isString(n.weight)?this.šx.number(n.weight):n.weight})));this.šv(i),this.šai&&this.next();}break}case "branchByPlayerChoice":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByPlayerChoice action.");this.šab=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map(i=>this.šx.string(i.text)));break}default:throw new Error(`ScriptEngine: Unknown action type: ${e.type}`)}}šv(e){this.šal.add(...e);}šao(e){if(p.isJSEngineFunction(e.onPlayerChoice))throw new Error("ScriptEngine: onPlayerChoice function shall not be decorated as a JSEngineFunction.")}};var y=class{static isValid(e,t){try{return this.validate(e,t),!0}catch{return false}}static validate(e,t){let i=[],n=0;for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1)break;let a=e[r+1]==="/",o=r+(a?2:1),s=e.indexOf(">",r);if(s===-1)throw new Error(`Malformed tag at position ${r}: missing closing bracket '>'`);let c=e.substring(o,s);if(c.trim()==="")throw new Error(`Empty tag name found at position ${r}`);if(/\s/.test(c))throw new Error(`Invalid tag name '${c}' at position ${r}: contains whitespace`);if(t&&!a&&!t.has(c))throw new Error(`Unknown tag '${c}' at position ${r}. Only these tags are allowed: ${[...t].join(", ")}`);if(a){if(i.length===0)throw new Error(`Closing tag '${c}' at position ${r} has no matching opening tag`);let u=i.pop();if(u!==c)throw new Error(`Mismatched tags: expected closing tag for '${u}', but found '${c}' at position ${r}`)}else i.push(c);n=s+1;}if(i.length>0)throw new Error(`Unclosed tag${i.length>1?"s":""}: ${i.join(", ")}`)}};var D=class{static process(e,t){y.validate(e,t);let i=this.šae(e,[]);return this.šf(i)}static šf(e){if(e.length<=1)return e;let t=[],i=e[0];for(let n=1;n<e.length;n++){let r=e[n];i.tags.length===r.tags.length&&i.tags.every((o,s)=>o===r.tags[s])?i={text:i.text+r.text,tags:i.tags}:(t.push(i),i=r);}return t.push(i),t}static šae(e,t){let i=[],n=0;if(e==="")return [{text:"",tags:[]}];for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1){if(n<e.length){let g=e.substring(n);g.length>0&&i.push({text:g,tags:[...t]});}break}if(r>n){let g=e.substring(n,r);g.length>0&&i.push({text:g,tags:[...t]});}let a=e.indexOf(">",r),o=e.substring(r+1,a),s=this.šs(e,o,a+1),c=e.substring(a+1,s);if(c.length===0){n=s+o.length+3;continue}let u=[...t,o],f=this.šae(c,u);i.push(...f),n=s+o.length+3;}return i}static šs(e,t,i){let n=i,r=1;for(;n<e.length&&r>0;){let a=e.indexOf("<"+t,n),o=e.indexOf("</"+t+">",n);if(a!==-1&&a<o)r++,n=a+1;else {if(r--,r===0)return o;n=o+1;}}return e.length}};var m=class h{constructor(){this.šh=[];}addEntry(e){return this.šh.push(e),this}toString(){return this.šh.join(`
18
- `)}duplicate(){let e=new h;return e.šh=[...this.šh],e}};var w=class{constructor(e){this.šu=new Map;this.šam=new Set;this.šc=new Map;this.ša=new Map;e.forEach(t=>{this.šu.set(t.id,t),this.šag(t,1,t.id,void 0,[]);});}getScript(e){let t=this.šu.get(e);if(!t)throw new Error(`Script with id ${e} not found.`);return t}getRootBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:[]}}getBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}getActionLocation(e){let t=this.ša.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}setVisited(e){this.šam.delete(e);}getUnvisitedBranchLocations(){return Array.from(this.šam).map(e=>this.šc.get(e)).filter(e=>!e.parentBranchInfo||!this.šam.has(e.parentBranchInfo.branch)).map(e=>({scriptId:e.rootScriptId,path:e.path}))}šag(e,t,i,n,r){let a={branch:e,type:t,parentBranchInfo:n,rootScriptId:i,path:r};this.šam.add(e),this.šc.set(e,a),this.še(e.actions,i,a,r);}še(e,t,i,n){e.forEach((r,a)=>{let o=[...n,"actions",`${a}`],s={action:r,rootScriptId:t,path:o};this.ša.set(r,s),r.type==="branchByCondition"?r.value.forEach((u,f)=>{let g=[...o,"branchByCondition",`${f}`];this.šag(u,2,t,i,g);}):r.type==="branchByPlayerChoice"?r.value.forEach((u,f)=>{let g=[...o,"branchByPlayerChoice",`${f}`];this.šag(u,3,t,i,g);}):r.type==="branchByChance"&&r.value.forEach((u,f)=>{let g=[...o,"branchByChance",`${f}`];this.šag(u,4,t,i,g);});});}};var T=class{},M=100,A=class{constructor(e,t={prscriptTypeChanges:true,richTextTags:void 0}){this.šak=e;this.šaa=t;this.šaq=new Map;this.šaj=new w(this.šak),this.šaa.prscriptTypeChanges&&(this.št=new Map);}simulateScript(e,t,i){this.šaa.prscriptTypeChanges&&(t.globalNameSpace=this.št);let r=[{executionHistory:new m,stack:new Stack,jsEngine:t,depth:0,lastExecutedAction:void 0}];this.šad(i,r),this.šaq.clear(),this.šr(e,r),this.šaq.clear();}getUnvisitedBranchLocations(){return this.šaj.getUnvisitedBranchLocations()}šad(e,t){e.forEach((i,n)=>{switch(i.type){case "command":this.šj(i,t,n);break;case "runScript":this.šb(i,t,n);break;default:throw new Error(`Unknown action before testing type: ${i.type}`)}});}šj(e,t,i){try{t.forEach(n=>{Reflect.defineMetadata(E,!0,n.jsEngine.functions),n.jsEngine.execute(e.value);});}catch(n){let r=this.šac(`${n}`,t[0].executionHistory);throw new S(r,i)}}šb(e,t,i){let n=[],r=this.šaj.getScript(e.value);if(!r)throw new Error(`Target script is not found: ${e.value}`);try{this.šaq.clear(),t.forEach(o=>{o.executionHistory.addEntry(`Branching out from script: ${e.value}`),o.depth=0,Reflect.defineMetadata(E,!1,o.jsEngine.functions);let s=this.šp(r,o,!1);if(!s.exitFound){let c=this.šac(`All possible paths are creating infinite loops, no exit found while executing: ${e.value}`,o.executionHistory);throw new l(c,this.šaj.getRootBranchLocation(r))}s.allEndings.forEach(c=>{n.push(c);});});let a=this.šah(n);t.length=0,t.push(...a),t.forEach(o=>{if(o.executionHistory.addEntry(`${a.length} unique ending${a.length>1?"s":""} generated.`).addEntry(""),o.lastExecutedAction)try{o.jsEngine.functions.onScriptBranchingEnd();}catch(s){let c=this.šaj.getActionLocation(o.lastExecutedAction),u=this.šac(`${s}`,o.executionHistory);throw new l(u,c)}else throw new Error("Script execution is ended without executing any command!")});}catch(a){throw a instanceof l?new x(`${a.message}`,i,a.location):a}}šr(e,t){t.forEach(i=>{i.executionHistory.addEntry(`Running script: ${e.id}`),i.depth=0,Reflect.defineMetadata(E,false,i.jsEngine.functions);let n=this.šp(e,i,true);if(!n.exitFound){let r=this.šac(`All possible paths are creating infinite loops, no exit found while executing: ${e.id}`,i.executionHistory);throw new l(r,this.šaj.getRootBranchLocation(e))}n.allEndings.forEach(r=>{if(r.lastExecutedAction)try{r.jsEngine.functions.onEnd();}catch(a){let o=this.šaj.getActionLocation(r.lastExecutedAction),s=this.šac(`${a}`,r.executionHistory);throw new l(s,o)}else throw new Error("Script execution is ended without executing any command!")});});}šp(e,t,i){if(!this.šw(e,t))return {allEndings:[],exitFound:false};let n=[],r=true;if(i&&this.šaj.setVisited(e),t.depth++,t.depth>M){let a=this.šac(`Maximum depth "${M}" is reached. Try to reduce the script branching tree or check infinite loops with trivial state changes.`,t.executionHistory);throw new l(a,this.šaj.getRootBranchLocation(e))}for(t.stack.add(...e.actions);!t.stack.isEmpty;){let a=t.stack.pop(),o=this.ši(a,t,i);t.stack.isEmpty&&(n.push(...o.allEndings),r=o.exitFound&&r);}return n=n.length>0?this.šah(n):[],{allEndings:n,exitFound:r}}ši(e,t,i){switch(t.lastExecutedAction=e,e.type){case "command":return this.šn(e,t);case "dialog":return this.šq(e,t);case "jumpTo":return this.šo(e,t,i);case "branchByCondition":return this.šl(e,t,i);case "branchByChance":return this.šk(e,t,i);case "branchByPlayerChoice":return this.šm(e,t,i);default:throw new Error(`Unknown action type: ${e.type}`)}}šn(e,t){try{t.jsEngine.execute(e.value);}catch(i){let n=this.šaj.getActionLocation(e),r=this.šac(`${i}`,t.executionHistory);throw new l(r,n)}return {allEndings:[t],exitFound:true}}šq(e,t){let i=e.value,n;try{n=t.jsEngine.string(i.text),this.šaa.richTextTags&&y.validate(n,this.šaa.richTextTags),t.jsEngine.functions.onDialog(n,i.speaker);}catch(r){let a=this.šaj.getActionLocation(e),o=this.šac(`${r}`,t.executionHistory);throw new l(o,a)}return {allEndings:[t],exitFound:true}}šo(e,t,i){let n=this.šaj.getScript(e.value);return t.executionHistory.addEntry(`Jumping to script: ${e.value}`),this.šp(n,t,i)}šl(e,t,i){let r=e.value.find(a=>this.šd(a,t));if(!r){let a=this.šaj.getActionLocation(e),o=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(o,a)}return t.executionHistory.addEntry(`Branching by condition: ${r.condition?r.condition:"default"}`),this.šp(r,t,i)}šk(e,t,i){let r=e.value.map((o,s)=>({index:s,subBranch:o})).filter(o=>this.šd(o.subBranch,t));if(r.length===0){let o=this.šaj.getActionLocation(e),s=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}let a=r.map(o=>{try{let c=Comparator.isString(o.subBranch.weight)?t.jsEngine.number(o.subBranch.weight):o.subBranch.weight;if(!Comparator.isNumber(c)||c<0)throw new Error(`Weight of branch ${o.index} is not a valid number: "${o.subBranch.weight}" -> ${c}.`)}catch(c){let u=this.šaj.getActionLocation(e),f=this.šac(`${c}`,t.executionHistory);throw new l(f,u)}let s=this.šg(t);return s.executionHistory.addEntry(`Branching by chance: ${o.index}`),this.šp(o.subBranch,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šm(e,t,i){let r=e.value.filter(o=>this.šd(o,t));if(r.length===0){let o=this.šaj.getActionLocation(e),s=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}try{t.jsEngine.functions.onPlayerChoice(r.map(o=>t.jsEngine.string(o.text)));}catch(o){let s=this.šaj.getActionLocation(e),c=this.šac(`${o}`,t.executionHistory);throw new l(c,s)}let a=r.map(o=>{let s=this.šg(t);return s.executionHistory.addEntry(`Player choice: ${o.text}`),this.šp(o,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šd(e,t){if(e.condition)try{return t.jsEngine.boolean(e.condition)}catch(i){let n=this.šaj.getBranchLocation(e),r=this.šac(`${i}`,t.executionHistory);throw new l(r,n)}else return true}šah(e){let t=[];return e.map(n=>({ending:n,variablesString:JSON.stringify(n.jsEngine.variables),functionsString:JSON.stringify(n.jsEngine.functions)})).forEach(n=>{t.every(a=>n.functionsString!==a.functionsString||n.variablesString!==a.variablesString)&&t.push({ending:n.ending,variablesString:n.variablesString,functionsString:n.functionsString});}),t.map(n=>n.ending)}šw(e,t){let i=JSON.stringify(t.jsEngine.variables)+JSON.stringify(t.jsEngine.functions),n=this.šaq.get(e);return n||(n=new Set,this.šaq.set(e,n)),n.has(i)?false:(n.add(i),true)}šg(e){return {executionHistory:e.executionHistory.duplicate(),stack:e.stack.duplicate(),jsEngine:e.jsEngine.duplicate(),depth:e.depth,lastExecutedAction:e.lastExecutedAction}}šac(e,t){return `${e}
17
+ `)(this.šau);}catch(i){return this.šak(i),""}return Reflect.defineMetadata(d,false,this.functions),t}duplicate(){let e=JsonHelper.deepCopy(this.variables),t=JsonHelper.deepCopy(this.functions);return new h(t,e,this.globalNameSpace)}šas(e,t){b.forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`);if(Object.hasOwn(e,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a function.`)}),Object.getOwnPropertyNames(e).forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`)});}šak(e){throw e instanceof Error?(e.message=e.message.replace(/^.*Error: /,""),e):new Error(`${e}`.replace(/^.*Error: /,""))}};var E="actionsBeforeTesting";function L(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(E,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be called at "actions before testing".`)},MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var l=class extends Error{constructor(e,t){super(e),this.location=t;}},S=class extends Error{constructor(e,t){super(e),this.actionIndex=t;}},x=class extends Error{constructor(e,t,i){super(e),this.actionIndex=t,this.location=i;}};var v=class{},D=(r=>(r[r.Idle=1]="Idle",r[r.Running=2]="Running",r[r.WaitingForPlayerChoice=3]="WaitingForPlayerChoice",r[r.WaitingForManualChoice=4]="WaitingForManualChoice",r[r.WaitingForDialog=5]="WaitingForDialog",r))(D||{}),B=class{constructor(e,t,i,n){this.šaq=new Stack;this.šaw=false;this.šaa=false;this.šac=new Action;this.onManualBranching=this.šac.notifier;this.šad=new Action;this.šat(t),this.šaa=!!n?.manualTestingMode,this.šap=e,this.šx=new p(t,i);}get variables(){return this.šx.variables}get state(){return this.šaw?5:this.šz?4:this.šaf?3:this.šan?2:1}get šan(){return !this.šaq.isEmpty}run(e){if(this.state!==1)throw new Error("ScriptEngine: Existing script haven't been concluded yet.");this.šy(e);let t=this.šad.toSingleEvent().destroyIfNotAttached();return this.šaj(),t}continue(){let e=this.state;if(e!==5){if(this.šaq.isEmpty)throw new Error("ScriptEngine: There is no active script to iterate.");if(e!==2)throw new Error(`ScriptEngine: The engine is not in running state. Next action cannot be executed. State: ${D[this.state]}`)}this.šaw=false,this.šaj();}playerChoice(e){if(!this.šaf)throw new Error("ScriptEngine: No player branching choices available.");let t=this.šaf[e];if(!t)throw new Error("ScriptEngine: Invalid player choice.");this.šv(t),this.šaf=void 0,this.šaj();}manualBranchingChoice(e){if(!this.šz)throw new Error("ScriptEngine: No manual branching choices available.");let t=this.šz[e];if(!t)throw new Error("ScriptEngine: Invalid manual choice.");this.šv(t),this.šz=void 0,this.šaj();}šaj(){for(;this.state===2;)this.šab();this.state===1&&this.šad.trigger();}šab(){let e=this.šaq.pop();this.ši(e);}šy(e){let t=this.šap[e];if(!t)throw new Error(`ScriptEngine: Script definition not found: ${e}`);this.šv(t.actions);}ši(e){switch(e.type){case "command":this.šx.execute(e.value);break;case "dialog":{let t=e.value;this.šaw=true,this.šx.functions.onDialog(this.šx.string(t.text),t.speaker);break}case "jumpTo":{this.šy(e.value),this.šan&&this.šab();break}case "branchByCondition":{let i=e.value.find(n=>n.condition?this.šx.boolean(n.condition):true);if(i)this.šv(i.actions);else throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByCondition action.");this.šan&&this.šab();break}case "branchByChance":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByChance action.");if(this.šaa)this.šz=t.map(i=>i.actions),this.šac.trigger(t.map((i,n)=>i.label??`Anonymous ${n}`));else {let i=Random.pickRandomElementWithWeight(t.map(n=>({value:n.actions,weight:Comparator.isString(n.weight)?this.šx.number(n.weight):n.weight})));this.šv(i),this.šan&&this.šab();}break}case "branchByPlayerChoice":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByPlayerChoice action.");this.šaf=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map(i=>this.šx.string(i.text)));break}default:throw new Error(`ScriptEngine: Unknown action type: ${e.type}`)}}šv(e){this.šaq.add(...e);}šat(e){if(p.isJSEngineFunction(e.onPlayerChoice))throw new Error("ScriptEngine: onPlayerChoice function shall not be decorated as a JSEngineFunction.")}};var y=class{static isValid(e,t){try{return this.validate(e,t),!0}catch{return false}}static validate(e,t){let i=[],n=0;for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1)break;let a=e[r+1]==="/",o=r+(a?2:1),s=e.indexOf(">",r);if(s===-1)throw new Error(`Malformed tag at position ${r}: missing closing bracket '>'`);let c=e.substring(o,s);if(c.trim()==="")throw new Error(`Empty tag name found at position ${r}`);if(/\s/.test(c))throw new Error(`Invalid tag name '${c}' at position ${r}: contains whitespace`);if(t&&!a&&!t.has(c))throw new Error(`Unknown tag '${c}' at position ${r}. Only these tags are allowed: ${[...t].join(", ")}`);if(a){if(i.length===0)throw new Error(`Closing tag '${c}' at position ${r} has no matching opening tag`);let u=i.pop();if(u!==c)throw new Error(`Mismatched tags: expected closing tag for '${u}', but found '${c}' at position ${r}`)}else i.push(c);n=s+1;}if(i.length>0)throw new Error(`Unclosed tag${i.length>1?"s":""}: ${i.join(", ")}`)}};var C=class{static process(e,t){y.validate(e,t);let i=this.šai(e,[]);return this.šf(i)}static šf(e){if(e.length<=1)return e;let t=[],i=e[0];for(let n=1;n<e.length;n++){let r=e[n];i.tags.length===r.tags.length&&i.tags.every((o,s)=>o===r.tags[s])?i={text:i.text+r.text,tags:i.tags}:(t.push(i),i=r);}return t.push(i),t}static šai(e,t){let i=[],n=0;if(e==="")return [{text:"",tags:[]}];for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1){if(n<e.length){let g=e.substring(n);g.length>0&&i.push({text:g,tags:[...t]});}break}if(r>n){let g=e.substring(n,r);g.length>0&&i.push({text:g,tags:[...t]});}let a=e.indexOf(">",r),o=e.substring(r+1,a),s=this.šs(e,o,a+1),c=e.substring(a+1,s);if(c.length===0){n=s+o.length+3;continue}let u=[...t,o],f=this.šai(c,u);i.push(...f),n=s+o.length+3;}return i}static šs(e,t,i){let n=i,r=1;for(;n<e.length&&r>0;){let a=e.indexOf("<"+t,n),o=e.indexOf("</"+t+">",n);if(a!==-1&&a<o)r++,n=a+1;else {if(r--,r===0)return o;n=o+1;}}return e.length}};var w=class h{constructor(){this.šh=[];}addEntry(e){return this.šh.push(e),this}toString(){return this.šh.join(`
18
+ `)}duplicate(){let e=new h;return e.šh=[...this.šh],e}};var m=class{constructor(e){this.šu=new Map;this.šar=new Set;this.šc=new Map;this.ša=new Map;e.forEach(t=>{this.šu.set(t.id,t),this.šal(t,1,t.id,void 0,[]);});}getScript(e){let t=this.šu.get(e);if(!t)throw new Error(`Script with id ${e} not found.`);return t}getRootBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:[]}}getBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}getActionLocation(e){let t=this.ša.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}setVisited(e){this.šar.delete(e);}getUnvisitedBranchLocations(){return Array.from(this.šar).map(e=>this.šc.get(e)).filter(e=>!e.parentBranchInfo||!this.šar.has(e.parentBranchInfo.branch)).map(e=>({scriptId:e.rootScriptId,path:e.path}))}šal(e,t,i,n,r){let a={branch:e,type:t,parentBranchInfo:n,rootScriptId:i,path:r};this.šar.add(e),this.šc.set(e,a),this.še(e.actions,i,a,r);}še(e,t,i,n){e.forEach((r,a)=>{let o=[...n,"actions",`${a}`],s={action:r,rootScriptId:t,path:o};this.ša.set(r,s),r.type==="branchByCondition"?r.value.forEach((u,f)=>{let g=[...o,"branchByCondition",`${f}`];this.šal(u,2,t,i,g);}):r.type==="branchByPlayerChoice"?r.value.forEach((u,f)=>{let g=[...o,"branchByPlayerChoice",`${f}`];this.šal(u,3,t,i,g);}):r.type==="branchByChance"&&r.value.forEach((u,f)=>{let g=[...o,"branchByChance",`${f}`];this.šal(u,4,t,i,g);});});}};var T=class{},R=100,A=class{constructor(e,t={prscriptTypeChanges:true,richTextTags:void 0}){this.šap=e;this.šae=t;this.šav=new Map;this.šao=new m(this.šap),this.šae.prscriptTypeChanges&&(this.št=new Map);}simulateScript(e,t,i){this.šae.prscriptTypeChanges&&(t.globalNameSpace=this.št);let r=[{executionHistory:new w,stack:new Stack,jsEngine:t,depth:0,lastExecutedAction:void 0}];this.šah(i,r),this.šav.clear(),this.šr(e,r),this.šav.clear();}getUnvisitedBranchLocations(){return this.šao.getUnvisitedBranchLocations()}šah(e,t){e.forEach((i,n)=>{switch(i.type){case "command":this.šj(i,t,n);break;case "runScript":this.šb(i,t,n);break;default:throw new Error(`Unknown action before testing type: ${i.type}`)}});}šj(e,t,i){try{t.forEach(n=>{Reflect.defineMetadata(E,!0,n.jsEngine.functions),n.jsEngine.execute(e.value);});}catch(n){let r=this.šag(`${n}`,t[0].executionHistory);throw new S(r,i)}}šb(e,t,i){let n=[],r=this.šao.getScript(e.value);if(!r)throw new Error(`Target script is not found: ${e.value}`);try{this.šav.clear(),t.forEach(o=>{o.executionHistory.addEntry(`Branching out from script: ${e.value}`),o.depth=0,Reflect.defineMetadata(E,!1,o.jsEngine.functions);let s=this.šp(r,o,!1);if(!s.exitFound){let c=this.šag(`All possible paths are creating infinite loops, no exit found while executing: ${e.value}`,o.executionHistory);throw new l(c,this.šao.getRootBranchLocation(r))}s.allEndings.forEach(c=>{n.push(c);});});let a=this.šam(n);t.length=0,t.push(...a),t.forEach(o=>{if(o.executionHistory.addEntry(`${a.length} unique ending${a.length>1?"s":""} generated.`).addEntry(""),o.lastExecutedAction)try{o.jsEngine.functions.onScriptBranchingEnd();}catch(s){let c=this.šao.getActionLocation(o.lastExecutedAction),u=this.šag(`${s}`,o.executionHistory);throw new l(u,c)}else throw new Error("Script execution is ended without executing any command!")});}catch(a){throw a instanceof l?new x(`${a.message}`,i,a.location):a}}šr(e,t){t.forEach(i=>{i.executionHistory.addEntry(`Running script: ${e.id}`),i.depth=0,Reflect.defineMetadata(E,false,i.jsEngine.functions);let n=this.šp(e,i,true);if(!n.exitFound){let r=this.šag(`All possible paths are creating infinite loops, no exit found while executing: ${e.id}`,i.executionHistory);throw new l(r,this.šao.getRootBranchLocation(e))}n.allEndings.forEach(r=>{if(r.lastExecutedAction)try{r.jsEngine.functions.onEnd();}catch(a){let o=this.šao.getActionLocation(r.lastExecutedAction),s=this.šag(`${a}`,r.executionHistory);throw new l(s,o)}else throw new Error("Script execution is ended without executing any command!")});});}šp(e,t,i){if(!this.šw(e,t))return {allEndings:[],exitFound:false};let n=[],r=true;if(i&&this.šao.setVisited(e),t.depth++,t.depth>R){let a=this.šag(`Maximum depth "${R}" is reached. Try to reduce the script branching tree or check infinite loops with trivial state changes.`,t.executionHistory);throw new l(a,this.šao.getRootBranchLocation(e))}for(t.stack.add(...e.actions);!t.stack.isEmpty;){let a=t.stack.pop(),o=this.ši(a,t,i);t.stack.isEmpty&&(n.push(...o.allEndings),r=o.exitFound&&r);}return n=n.length>0?this.šam(n):[],{allEndings:n,exitFound:r}}ši(e,t,i){switch(t.lastExecutedAction=e,e.type){case "command":return this.šn(e,t);case "dialog":return this.šq(e,t);case "jumpTo":return this.šo(e,t,i);case "branchByCondition":return this.šl(e,t,i);case "branchByChance":return this.šk(e,t,i);case "branchByPlayerChoice":return this.šm(e,t,i);default:throw new Error(`Unknown action type: ${e.type}`)}}šn(e,t){try{t.jsEngine.execute(e.value);}catch(i){let n=this.šao.getActionLocation(e),r=this.šag(`${i}`,t.executionHistory);throw new l(r,n)}return {allEndings:[t],exitFound:true}}šq(e,t){let i=e.value,n;try{n=t.jsEngine.string(i.text),this.šae.richTextTags&&y.validate(n,this.šae.richTextTags),t.jsEngine.functions.onDialog(n,i.speaker);}catch(r){let a=this.šao.getActionLocation(e),o=this.šag(`${r}`,t.executionHistory);throw new l(o,a)}return {allEndings:[t],exitFound:true}}šo(e,t,i){let n=this.šao.getScript(e.value);return t.executionHistory.addEntry(`Jumping to script: ${e.value}`),this.šp(n,t,i)}šl(e,t,i){let r=e.value.find(a=>this.šd(a,t));if(!r){let a=this.šao.getActionLocation(e),o=this.šag("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(o,a)}return t.executionHistory.addEntry(`Branching by condition: ${r.condition?r.condition:"default"}`),this.šp(r,t,i)}šk(e,t,i){let r=e.value.map((o,s)=>({index:s,subBranch:o})).filter(o=>this.šd(o.subBranch,t));if(r.length===0){let o=this.šao.getActionLocation(e),s=this.šag("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}let a=r.map(o=>{try{let c=Comparator.isString(o.subBranch.weight)?t.jsEngine.number(o.subBranch.weight):o.subBranch.weight;if(!Comparator.isNumber(c)||c<0)throw new Error(`Weight of branch ${o.index} is not a valid number: "${o.subBranch.weight}" -> ${c}.`)}catch(c){let u=this.šao.getActionLocation(e),f=this.šag(`${c}`,t.executionHistory);throw new l(f,u)}let s=this.šg(t);return s.executionHistory.addEntry(`Branching by chance: ${o.index}`),this.šp(o.subBranch,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šm(e,t,i){let r=e.value.filter(o=>this.šd(o,t));if(r.length===0){let o=this.šao.getActionLocation(e),s=this.šag("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}try{t.jsEngine.functions.onPlayerChoice(r.map(o=>t.jsEngine.string(o.text)));}catch(o){let s=this.šao.getActionLocation(e),c=this.šag(`${o}`,t.executionHistory);throw new l(c,s)}let a=r.map(o=>{let s=this.šg(t);return s.executionHistory.addEntry(`Player choice: ${o.text}`),this.šp(o,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šd(e,t){if(e.condition)try{return t.jsEngine.boolean(e.condition)}catch(i){let n=this.šao.getBranchLocation(e),r=this.šag(`${i}`,t.executionHistory);throw new l(r,n)}else return true}šam(e){let t=[];return e.map(n=>({ending:n,variablesString:JSON.stringify(n.jsEngine.variables),functionsString:JSON.stringify(n.jsEngine.functions)})).forEach(n=>{t.every(a=>n.functionsString!==a.functionsString||n.variablesString!==a.variablesString)&&t.push({ending:n.ending,variablesString:n.variablesString,functionsString:n.functionsString});}),t.map(n=>n.ending)}šw(e,t){let i=JSON.stringify(t.jsEngine.variables)+JSON.stringify(t.jsEngine.functions),n=this.šav.get(e);return n||(n=new Set,this.šav.set(e,n)),n.has(i)?false:(n.add(i),true)}šg(e){return {executionHistory:e.executionHistory.duplicate(),stack:e.stack.duplicate(),jsEngine:e.jsEngine.duplicate(),depth:e.depth,lastExecutedAction:e.lastExecutedAction}}šag(e,t){return `${e}
19
19
 
20
20
  ----Execution history----
21
- ${t.toString()}`}};export{J as ActionBeforeTesting,S as ActionsBeforeTestingError,x as BranchingBeforeTestingError,p as JSEngine,P as JSEngineFunction,D as RichTextSeparator,y as RichTextValidator,B as ScriptEngine,v as ScriptEngineFunctions,T as ScriptEngineSimulatorFunctions,C as ScriptEngineState,A as ScriptTestSimulator,l as SimulationError};
21
+ ${t.toString()}`}};export{L as ActionBeforeTesting,S as ActionsBeforeTestingError,x as BranchingBeforeTestingError,p as JSEngine,O as JSEngineFunction,C as RichTextSeparator,y as RichTextValidator,B as ScriptEngine,v as ScriptEngineFunctions,T as ScriptEngineSimulatorFunctions,D as ScriptEngineState,A as ScriptTestSimulator,l as SimulationError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "script-engine-lib",
3
- "version": "1.0.0-rc4",
3
+ "version": "1.0.1",
4
4
  "description": "Script Engine",
5
5
  "main": "dist/index.js",
6
6
  "publishConfig": {
@@ -42,7 +42,8 @@
42
42
  "postpack": "mv package.json.bak package.json"
43
43
  },
44
44
  "peerDependencies": {
45
- "helpers-lib": "^2.0.0-rc3",
45
+ "helpers-lib": "^2.0.0",
46
+ "actions-lib": "^3.0.0",
46
47
  "reflect-metadata": "^0.2.2"
47
48
  },
48
49
  "devDependencies": {