Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/webgal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@emotion/css": "^11.11.2",
"@icon-park/react": "^1.4.2",
"@reduxjs/toolkit": "^1.8.1",
"angular-expressions": "^1.4.3",
"angular-expressions": "^1.5.1",
"axios": "^0.30.2",
"cloudlogjs": "^1.0.9",
"gifuct-js": "^2.1.2",
Expand Down
23 changes: 4 additions & 19 deletions packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,18 @@ import { logger } from '../../util/logger';
import { IStageState } from '@/store/stageInterface';
import { restoreScene } from '../scene/restoreScene';
import { webgalStore } from '@/store/store';
import { getValueFromStateElseKey } from '@/Core/gameScripts/setVar';
import { strIf } from '@/Core/controller/gamePlay/strIf';
import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
import cloneDeep from 'lodash/cloneDeep';
import { ISceneEntry } from '@/Core/Modules/scene';
import { IBacklogItem } from '@/Core/Modules/backlog';
import { SYSTEM_CONFIG } from '@/config';
import { WebGAL } from '@/Core/WebGAL';
import { getBooleanArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg';
import { EvaluateExpression } from '@/Core/util/evalSentenceFn';

export const whenChecker = (whenValue: string | undefined): boolean => {
if (whenValue === undefined) {
return true;
}
// 先把变量解析出来
const valExpArr = whenValue.split(/([+\-*\/()><!]|>=|<=|==|&&|\|\||!=)/g);
const valExp = valExpArr
.map((_e) => {
const e = _e.trim();
if (e.match(/[a-zA-Z]/)) {
if (e.match(/^(true|false)$/)) {
return e;
}
return getValueFromStateElseKey(e, true, true);
} else return e;
})
.reduce((pre, curr) => pre + curr, '');
return !!strIf(valExp);
return !!EvaluateExpression(whenValue, { ErrorReturnsBoolean: true });
};

/**
Expand Down Expand Up @@ -62,7 +46,8 @@ export const scriptExecutor = () => {

if (contentExp !== null) {
contentExp.forEach((e) => {
const contentVarValue = getValueFromStateElseKey(e.replace(/(?<!\\)\{(.*)\}/, '$1'));
const likeExpr = e.replace(/(?<!\\)\{(.*)\}/, '$1');
const contentVarValue = EvaluateExpression(likeExpr, { InvalidValueReturns: 'block' });
retContent = retContent.replace(e, contentVarValue);
});
}
Expand Down
10 changes: 0 additions & 10 deletions packages/webgal/src/Core/controller/gamePlay/strIf.ts

This file was deleted.

13 changes: 12 additions & 1 deletion packages/webgal/src/Core/controller/scene/callScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import uniqWith from 'lodash/uniqWith';
import { scenePrefetcher } from '@/Core/util/prefetcher/scenePrefetcher';

import { WebGAL } from '@/Core/WebGAL';
import { arg } from './sceneInterface';
import { webgalStore } from '@/store/store';
import { stageActions } from '@/store/stageReducer';

/**
* 调用场景
* @param sceneUrl 场景路径
* @param sceneName 场景名称
* @param args 参数
*/
export const callScene = (sceneUrl: string, sceneName: string) => {
export const callScene = (sceneUrl: string, sceneName: string, args: Array<arg>) => {
if (WebGAL.sceneManager.lockSceneWrite) {
return;
}
Expand All @@ -35,6 +39,13 @@ export const callScene = (sceneUrl: string, sceneName: string) => {
scenePrefetcher(subSceneListUniq);
logger.debug('现在调用场景,调用结果:', WebGAL.sceneManager.sceneData);
WebGAL.sceneManager.lockSceneWrite = false;
// 写入场景调用参数
webgalStore.dispatch(
stageActions.addSceneArgument({
url: sceneUrl,
value: args,
}),
);
nextSentence();
})
.catch((e) => {
Expand Down
13 changes: 12 additions & 1 deletion packages/webgal/src/Core/controller/scene/changeScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import uniqWith from 'lodash/uniqWith';
import { scenePrefetcher } from '@/Core/util/prefetcher/scenePrefetcher';

import { WebGAL } from '@/Core/WebGAL';
import { arg } from './sceneInterface';
import { stageActions } from '@/store/stageReducer';
import { webgalStore } from '@/store/store';

/**
* 切换场景
* @param sceneUrl 场景路径
* @param sceneName 场景名称
* @param args 场景参数
*/
export const changeScene = (sceneUrl: string, sceneName: string) => {
export const changeScene = (sceneUrl: string, sceneName: string, args: Array<arg>) => {
if (WebGAL.sceneManager.lockSceneWrite) {
return;
}
Expand All @@ -29,6 +33,13 @@ export const changeScene = (sceneUrl: string, sceneName: string) => {
scenePrefetcher(subSceneListUniq);
logger.debug('现在切换场景,切换后的结果:', WebGAL.sceneManager.sceneData);
WebGAL.sceneManager.lockSceneWrite = false;
// 写入场景调用参数
webgalStore.dispatch(
stageActions.addSceneArgument({
url: sceneUrl,
value: args,
}),
);
nextSentence();
})
.catch((e) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/webgal/src/Core/gameScripts/callSceneScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { callScene } from '../controller/scene/callScene';
export const callSceneScript = (sentence: ISentence): IPerform => {
const sceneNameArray: Array<string> = sentence.content.split('/');
const sceneName = sceneNameArray[sceneNameArray.length - 1];
callScene(sentence.content, sceneName);
callScene(sentence.content, sceneName, sentence.args);
return {
performName: 'none',
duration: 0,
Expand Down
2 changes: 1 addition & 1 deletion packages/webgal/src/Core/gameScripts/changeSceneScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { changeScene } from '../controller/scene/changeScene';
export const changeSceneScript = (sentence: ISentence): IPerform => {
const sceneNameArray: Array<string> = sentence.content.split('/');
const sceneName = sceneNameArray[sceneNameArray.length - 1];
changeScene(sentence.content, sceneName);
changeScene(sentence.content, sceneName, sentence.args);
return {
performName: 'none',
duration: 0,
Expand Down
8 changes: 4 additions & 4 deletions packages/webgal/src/Core/gameScripts/choose/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ISentence } from '@/Core/controller/scene/sceneInterface';
import { arg, ISentence } from '@/Core/controller/scene/sceneInterface';
import { IPerform } from '@/Core/Modules/perform/performInterface';
import { changeScene } from '@/Core/controller/scene/changeScene';
import { jmp } from '@/Core/gameScripts/label/jmp';
Expand Down Expand Up @@ -62,7 +62,7 @@ export const choose = (sentence: ISentence): IPerform => {
// eslint-disable-next-line react/no-deprecated
ReactDOM.render(
<Provider store={webgalStore}>
<Choose chooseOptions={chooseOptions} />
<Choose chooseOptions={chooseOptions} args={sentence.args} />
</Provider>,
document.getElementById('chooseContainer'),
);
Expand All @@ -80,7 +80,7 @@ export const choose = (sentence: ISentence): IPerform => {
};
};

function Choose(props: { chooseOptions: ChooseOption[] }) {
function Choose(props: { chooseOptions: ChooseOption[]; args: Array<arg> }) {
const font = useFontFamily();
const { playSeEnter, playSeClick } = useSEByWebgalStore();
const applyStyle = useApplyStyle('choose');
Expand All @@ -97,7 +97,7 @@ function Choose(props: { chooseOptions: ChooseOption[] }) {
? () => {
playSeClick();
if (e.jumpToScene) {
changeScene(e.jump, e.text);
changeScene(e.jump, e.text, props.args);
} else {
jmp(e.jump);
}
Expand Down
102 changes: 7 additions & 95 deletions packages/webgal/src/Core/gameScripts/setVar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ import { IPerform } from '@/Core/Modules/perform/performInterface';
import { webgalStore } from '@/store/store';
import { setStageVar } from '@/store/stageReducer';
import { logger } from '@/Core/util/logger';
import { compile } from 'angular-expressions';
import { setScriptManagedGlobalVar } from '@/store/userDataReducer';
import { ActionCreatorWithPayload } from '@reduxjs/toolkit';
import { ISetGameVar } from '@/store/stageInterface';
import { dumpToStorageFast } from '@/Core/controller/storage/storageController';
import expression from 'angular-expressions';
import get from 'lodash/get';
import random from 'lodash/random';
import { getBooleanArgByKey } from '../util/getSentenceArg';
import { EvaluateExpression } from '../util/evalSentenceFn';

/**
* 设置变量
Expand All @@ -29,47 +26,14 @@ export const setVar = (sentence: ISentence): IPerform => {
if (sentence.content.match(/\s*=\s*/)) {
const key = sentence.content.split(/\s*=\s*/)[0];
const valExp = sentence.content.split(/\s*=\s*/)[1];
if (/^\s*[a-zA-Z_$][\w$]*\s*\(.*\)\s*$/.test(valExp)) {
webgalStore.dispatch(targetReducerFunction({ key, value: EvaluateExpression(valExp) }));
} else if (valExp.match(/[+\-*\/()]/)) {
// 如果包含加减乘除号,则运算
// 先取出运算表达式中的变量
const valExpArr = valExp.split(/([+\-*\/()])/g);
// 将变量替换为变量的值,然后合成表达式字符串
const valExp2 = valExpArr
.map((e) => {
if (!e.trim().match(/^[a-zA-Z_$][a-zA-Z0-9_.]*$/)) {
// 检查是否是变量名,不是就返回本身
return e;
}
const _r = getValueFromStateElseKey(e.trim(), true);
return typeof _r === 'string' ? `'${_r}'` : _r;
})
.reduce((pre, curr) => pre + curr, '');
let result = '';
try {
const exp = compile(valExp2);
result = exp();
} catch (e) {
logger.error('expression compile error', e);
}
webgalStore.dispatch(targetReducerFunction({ key, value: result }));
} else if (valExp.match(/true|false/)) {
if (valExp.match(/true/)) {
webgalStore.dispatch(targetReducerFunction({ key, value: true }));
}
if (valExp.match(/false/)) {
webgalStore.dispatch(targetReducerFunction({ key, value: false }));
}
} else if (valExp.length === 0) {
if (valExp.length === 0) {
webgalStore.dispatch(targetReducerFunction({ key, value: '' }));
} else if (!isNaN(Number(valExp))) {
webgalStore.dispatch(targetReducerFunction({ key, value: Number(valExp) }));
} else {
if (!isNaN(Number(valExp))) {
webgalStore.dispatch(targetReducerFunction({ key, value: Number(valExp) }));
} else {
// 字符串
webgalStore.dispatch(targetReducerFunction({ key, value: getValueFromStateElseKey(valExp, true) }));
}
webgalStore.dispatch(
targetReducerFunction({ key, value: EvaluateExpression(valExp, { InvalidValueReturns: 'origin' }) }),
);
}
if (setGlobal) {
logger.debug('设置全局变量:', { key, value: webgalStore.getState().userData.globalGameVar[key] });
Expand All @@ -88,55 +52,3 @@ export const setVar = (sentence: ISentence): IPerform => {
stopTimeout: undefined, // 暂时不用,后面会交给自动清除
};
};

type BaseVal = string | number | boolean | undefined;

/**
* 执行函数
*/
function EvaluateExpression(val: string) {
const instance = expression.compile(val);
return instance({
random: (...args: any[]) => {
return args.length ? random(...args) : Math.random();
},
});
}

/**
* 取不到时返回 undefined
*/
export function getValueFromState(key: string) {
let ret: any;
const stage = webgalStore.getState().stage;
const userData = webgalStore.getState().userData;
const _Merge = { stage, userData }; // 不要直接合并到一起,防止可能的键冲突
if (stage.GameVar.hasOwnProperty(key)) {
ret = stage.GameVar[key];
} else if (userData.globalGameVar.hasOwnProperty(key)) {
ret = userData.globalGameVar[key];
} else if (key.startsWith('$')) {
const propertyKey = key.replace('$', '');
ret = get(_Merge, propertyKey, undefined) as BaseVal;
}
return ret;
}

/**
* 取不到时返回 {key}
*/
export function getValueFromStateElseKey(key: string, useKeyNameAsReturn = false, quoteString = false) {
const valueFromState = getValueFromState(key);
if (valueFromState === null || valueFromState === undefined) {
logger.warn('valueFromState result null, key = ' + key);
if (useKeyNameAsReturn) {
return key;
}
return `{${key}}`;
}
// 用 "" 包裹字符串,用于使用 compile 条件判断,处理字符串类型的变量
if (quoteString && typeof valueFromState === 'string') {
return `"${valueFromState.replaceAll('"', '\\"')}"`;
}
return valueFromState;
}
82 changes: 82 additions & 0 deletions packages/webgal/src/Core/util/evalSentenceFn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { webgalStore } from '@/store/store';
import { random } from 'lodash';
import { WebGAL } from '../WebGAL';
import expression from 'angular-expressions';
import { logger } from '@/Core/util/logger';

// 是否是函数调用
export const isFunctionCall = (valExp: string) => {
return /^\s*[a-zA-Z_$][\w$]*\s*\(.*\)\s*$/.test(valExp);
};

export interface EvaluateExpressionOptions {
/**
* 当是无效值 `null | undefined | 报错` 时返回原值还是返回{...}包裹
* @default block {...}包裹
*/
InvalidValueReturns?: 'origin' | 'block';
/**
* 当是表达式报错时,是否返回布尔值
*/
ErrorReturnsBoolean?: boolean;
}

/**
* 执行运行时表达式
*/
export const EvaluateExpression = (val: string, options: EvaluateExpressionOptions = {}) => {
const sceneUrl = WebGAL.sceneManager.sceneData.currentScene.sceneUrl;
const sceneArguments = webgalStore.getState().stage.sceneArguments;
const stage = webgalStore.getState().stage;
const userData = webgalStore.getState().userData;
const globalVars = userData.globalGameVar;
const localVars = stage.GameVar;
const _Merge = { $stage: stage, $userData: userData }; // 不要直接合并到一起,防止可能的键冲突
try {
const instance = expression.compile(val);
const evalResult = instance({
// 注入变量
...globalVars,
...localVars,
..._Merge,
// 随机函数
random(...args: any[]) {
return args.length ? random(...args) : Math.random();
},
// 获取场景调用参数
getArg(key: string) {
const target = sceneArguments[sceneUrl];
if (target) {
return target.find((item) => item.key === key)?.value ?? null;
}
return null;
},
});

if ((evalResult === null || evalResult === undefined) && options) {
switch (options.InvalidValueReturns) {
case 'block':
return `{${val}}`;
case 'origin':
return val;
default:
return evalResult;
}
}

return evalResult;
} catch {
logger.warn('EvaluateExpression throw error, expr = ' + val);
if (options.ErrorReturnsBoolean) {
return false;
}
switch (options.InvalidValueReturns) {
case 'block':
return `{${val}}`;
case 'origin':
return val;
default:
return `{${val}}`;
}
}
};
Loading