argument with options treat as enum

This commit is contained in:
codetypes committed 2025-03-25 22:40:40 +08:00
1 parent 49bfc76fc6
commit 767d8aba95
14 files changed
+105 -133

No files matched your search

+4 -8
View File
@@ -45,14 +45,10 @@ export interface NodeDef {
| "json?" | "json?"
| "json[]" | "json[]"
| "json[]?" | "json[]?"
| "enum" | "expr"
| "enum?" | "expr?"
| "enum[]" | "expr[]"
| "enum[]?" | "expr[]?";
| "code"
| "code?"
| "code[]"
| "code[]?";
desc: string; desc: string;
default?: unknown; default?: unknown;
/** Input `value`, only one is allowed between `value` and this arg.*/ /** Input `value`, only one is allowed between `value` and this arg.*/
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "behavior3editor", "name": "behavior3editor",
"version": "1.8.0", "version": "1.8.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "behavior3editor", "name": "behavior3editor",
"version": "1.8.0", "version": "1.8.1",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "behavior3editor", "name": "behavior3editor",
"version": "1.8.0", "version": "1.8.1",
"main": "dist-electron/main/index.js", "main": "dist-electron/main/index.js",
"description": "行为树编辑器", "description": "行为树编辑器",
"author": "zhandouxiaojiji & codetypes", "author": "zhandouxiaojiji & codetypes",
+1
View File
@@ -86,6 +86,7 @@
"tree.export": "Export", "tree.export": "Export",
"tree.firstid": "First id", "tree.firstid": "First id",
"tree.group": "Enable Node Grouping", "tree.group": "Enable Node Grouping",
"tree.group.placeholder": "Select node groups",
"tree.vars": "Define Variables", "tree.vars": "Define Variables",
"tree.vars.name": "name", "tree.vars.name": "name",
"tree.vars.invalidName": "Invalid variable name", "tree.vars.invalidName": "Invalid variable name",
+1
View File
@@ -86,6 +86,7 @@
"tree.export": "导出", "tree.export": "导出",
"tree.firstid": "起始标识", "tree.firstid": "起始标识",
"tree.group": "启用节点分组", "tree.group": "启用节点分组",
"tree.group.placeholder": "选择节点分组",
"tree.vars": "定义变量", "tree.vars": "定义变量",
"tree.vars.name": "变量名", "tree.vars.name": "变量名",
"tree.vars.invalidName": "变量名不合法", "tree.vars.invalidName": "变量名不合法",
+16 -5
View File
@@ -66,7 +66,7 @@
"args": [ "args": [
{ {
"name": "builtin", "name": "builtin",
"type": "enum", "type": "string",
"desc": "事件", "desc": "事件",
"options": [ "options": [
{ {
@@ -332,7 +332,10 @@
}, },
{ {
"name": "MoveToPos", "name": "MoveToPos",
"type": "Action (Client)", "type": "Action",
"group": [
"Client"
],
"args": [ "args": [
{ {
"name": "x", "name": "x",
@@ -349,7 +352,10 @@
}, },
{ {
"name": "MoveToTarget", "name": "MoveToTarget",
"type": "Action (Server)", "type": "Action",
"group": [
"Server"
],
"input": [ "input": [
"{目标}" "{目标}"
], ],
@@ -357,7 +363,12 @@
}, },
{ {
"name": "TestB3", "name": "TestB3",
"type": "Action (Client|Server|Test)", "type": "Action",
"group": [
"Client",
"Server",
"Test"
],
"args": [ "args": [
{ {
"name": "time", "name": "time",
@@ -387,7 +398,7 @@
{ {
"name": "status", "name": "status",
"desc": "状态", "desc": "状态",
"type": "enum", "type": "string",
"options": [ "options": [
{ {
"name": "成功", "name": "成功",
+8 -1
View File
@@ -50,7 +50,7 @@
"id": 6, "id": 6,
"name": "Check", "name": "Check",
"args": { "args": {
"value": "target[0].x > 0 |" "value": "target[0].x > 0 "
} }
}, },
{ {
@@ -95,6 +95,13 @@
"output": [ "output": [
"context" "context"
] ]
},
{
"id": 12,
"name": "Listen",
"args": {
"builtin": "afterRunFailure"
}
} }
] ]
} }
+11 -14
View File
@@ -1,16 +1,24 @@
{ {
"version": "1.8.0",
"name": "hero", "name": "hero",
"desc": "英雄测试AI",
"firstid": 1,
"export": true,
"group": [
"Client",
"Server"
],
"import": [],
"declvar": [],
"root": { "root": {
"id": 1, "id": 1,
"name": "Selector", "name": "Selector",
"desc": "英雄测试AI", "desc": "英雄测试AI",
"args": {},
"children": [ "children": [
{ {
"id": 2, "id": 2,
"name": "Sequence", "name": "Sequence",
"desc": "攻击", "desc": "攻击",
"args": {},
"children": [ "children": [
{ {
"id": 3, "id": 3,
@@ -28,7 +36,6 @@
{ {
"id": 4, "id": 4,
"name": "Attack", "name": "Attack",
"args": {},
"input": [ "input": [
"enemy" "enemy"
] ]
@@ -46,7 +53,6 @@
"id": 6, "id": 6,
"name": "Sequence", "name": "Sequence",
"desc": "移动", "desc": "移动",
"args": {},
"children": [ "children": [
{ {
"id": 7, "id": 7,
@@ -64,7 +70,6 @@
{ {
"id": 8, "id": 8,
"name": "MoveToTarget", "name": "MoveToTarget",
"args": {},
"input": [ "input": [
"enemy" "enemy"
] ]
@@ -75,12 +80,10 @@
"id": 9, "id": 9,
"name": "Sequence", "name": "Sequence",
"desc": "逃跑", "desc": "逃跑",
"args": {},
"children": [ "children": [
{ {
"id": 10, "id": 10,
"name": "GetHp", "name": "GetHp",
"args": {},
"output": [ "output": [
"hp" "hp"
] ]
@@ -110,11 +113,5 @@
"name": "Idle" "name": "Idle"
} }
] ]
}, }
"desc": "英雄测试AI",
"version": "1.8.0",
"firstid": 1,
"group": [],
"import": [],
"declvar": []
} }
+22 -14
View File
@@ -1,5 +1,14 @@
{ {
"version": "1.8.0",
"name": "monster", "name": "monster",
"desc": "怪物测试AI",
"firstid": 1,
"export": true,
"group": [
"Test"
],
"import": [],
"declvar": [],
"root": { "root": {
"id": 1, "id": 1,
"name": "Sequence", "name": "Sequence",
@@ -26,26 +35,30 @@
] ]
}, },
{ {
"id": 7, "id": 6,
"name": "Cmp", "name": "Cmp",
"args": { "args": {
"gt": 50, "value": "hp",
"value": "hp" "gt": 50
}, },
"input": [ "input": [
"" "hp"
] ]
}, },
{ {
"id": 8, "id": 7,
"name": "TestB3", "name": "TestB3",
"desc": "攻击", "desc": "攻击",
"args": { "args": {
"str": "Attack!" "time": 1,
"open": true,
"check": "1",
"name": "1",
"status": "FAILURE"
} }
}, },
{ {
"id": 9, "id": 8,
"name": "Sequence", "name": "Sequence",
"desc": "新建行为树", "desc": "新建行为树",
"path": "workdir/subtree1.json" "path": "workdir/subtree1.json"
@@ -53,7 +66,7 @@
] ]
}, },
{ {
"id": 12, "id": 11,
"name": "Log", "name": "Log",
"desc": "逃跑", "desc": "逃跑",
"args": { "args": {
@@ -61,10 +74,5 @@
} }
} }
] ]
}, }
"desc": "怪物测试AI",
"firstid": 1,
"group": [],
"import": [],
"declvar": []
} }
+21 -60
View File
@@ -8,7 +8,6 @@ import {
import { import {
AutoComplete, AutoComplete,
Button, Button,
Checkbox,
Divider, Divider,
Flex, Flex,
Form, Form,
@@ -29,9 +28,9 @@ import { useShallow } from "zustand/react/shallow";
import { ExpressionEvaluator } from "../behavior3/src/behavior3"; import { ExpressionEvaluator } from "../behavior3/src/behavior3";
import { EditNode, EditTree, useWorkspace } from "../contexts/workspace-context"; import { EditNode, EditTree, useWorkspace } from "../contexts/workspace-context";
import { import {
hasArgOptions,
ImportDef, ImportDef,
isBoolType, isBoolType,
isEnumType,
isExprType, isExprType,
isFloatType, isFloatType,
isIntType, isIntType,
@@ -141,49 +140,6 @@ const VarDefItem: FC<VarItemProps> = ({ name, onChange, onRemove, disabled, ...p
); );
}; };
interface GroupDefItemProps {
disabled?: boolean;
value?: string[];
onChange?: (value: string[]) => void;
}
const GroupDefItem: FC<GroupDefItemProps> = ({ disabled, onChange, ...props }) => {
const [value, setValue] = useState<string[]>(props.value ?? []);
const form = useFormInstance();
const workspace = useWorkspace(
useShallow((state) => ({
groupDefs: state.groupDefs,
}))
);
return (
<Flex
style={{
display: "flex",
flexDirection: "row",
gap: 10,
rowGap: 2,
flexWrap: "wrap",
}}
>
{workspace.groupDefs.map((g) => (
<Checkbox
key={g}
disabled={disabled}
checked={value.includes(g)}
onChange={(e) => {
const arr = e.target.checked ? [...value, g] : value.filter((v) => v !== g);
setValue(arr);
onChange?.(arr);
form.submit();
}}
>
{g}
</Checkbox>
))}
</Flex>
);
};
const TreeInspector: FC = () => { const TreeInspector: FC = () => {
const workspace = useWorkspace( const workspace = useWorkspace(
useShallow((state) => ({ useShallow((state) => ({
@@ -360,6 +316,7 @@ const TreeInspector: FC = () => {
mode="multiple" mode="multiple"
suffixIcon={null} suffixIcon={null}
onChange={form.submit} onChange={form.submit}
placeholder={t("tree.group.placeholder")}
options={workspace.groupDefs.map((g) => ({ label: g, value: g }))} options={workspace.groupDefs.map((g) => ({ label: g, value: g }))}
/> />
</Form.Item> </Form.Item>
@@ -1160,29 +1117,29 @@ const NodeInspector: FC = () => {
}), }),
]} ]}
> >
{isStringType(type) && ( {!hasArgOptions(arg) && isStringType(type) && (
<TextArea autoSize disabled={disabled} onBlur={form.submit} /> <TextArea autoSize disabled={disabled} onBlur={form.submit} />
)} )}
{isJsonType(type) && ( {!hasArgOptions(arg) && isJsonType(type) && (
<TextArea autoSize disabled={disabled} onBlur={form.submit} /> <TextArea autoSize disabled={disabled} onBlur={form.submit} />
)} )}
{isIntType(type) && ( {!hasArgOptions(arg) && isIntType(type) && (
<InputNumber <InputNumber
disabled={disabled} disabled={disabled}
onBlur={form.submit} onBlur={form.submit}
precision={0} precision={0}
/> />
)} )}
{isFloatType(type) && ( {!hasArgOptions(arg) && isFloatType(type) && (
<InputNumber disabled={disabled} onBlur={form.submit} /> <InputNumber disabled={disabled} onBlur={form.submit} />
)} )}
{isBoolType(type) && ( {!hasArgOptions(arg) && isBoolType(type) && (
<Switch disabled={disabled} onChange={form.submit} /> <Switch disabled={disabled} onChange={form.submit} />
)} )}
{isExprType(type) && ( {!hasArgOptions(arg) && isExprType(type) && (
<Input disabled={disabled} onBlur={form.submit} /> <Input disabled={disabled} onBlur={form.submit} />
)} )}
{isEnumType(type) && ( {hasArgOptions(arg) && (
<Select <Select
showSearch showSearch
disabled={disabled} disabled={disabled}
@@ -1289,21 +1246,25 @@ const NodeInspector: FC = () => {
}), }),
]} ]}
> >
{isStringType(type) && ( {!hasArgOptions(arg) && isStringType(type) && (
<TextArea autoSize disabled={disabled} onBlur={form.submit} /> <TextArea autoSize disabled={disabled} onBlur={form.submit} />
)} )}
{isJsonType(type) && ( {!hasArgOptions(arg) && isJsonType(type) && (
<TextArea autoSize disabled={disabled} onBlur={form.submit} /> <TextArea autoSize disabled={disabled} onBlur={form.submit} />
)} )}
{isIntType(type) && ( {!hasArgOptions(arg) && isIntType(type) && (
<InputNumber disabled={disabled} onBlur={form.submit} precision={0} /> <InputNumber disabled={disabled} onBlur={form.submit} precision={0} />
)} )}
{isFloatType(type) && ( {!hasArgOptions(arg) && isFloatType(type) && (
<InputNumber disabled={disabled} onBlur={form.submit} /> <InputNumber disabled={disabled} onBlur={form.submit} />
)} )}
{isBoolType(type) && <Switch disabled={disabled} onChange={form.submit} />} {!hasArgOptions(arg) && isBoolType(type) && (
{isExprType(type) && <Input disabled={disabled} onBlur={form.submit} />} <Switch disabled={disabled} onChange={form.submit} />
{isEnumType(type) && ( )}
{!hasArgOptions(arg) && isExprType(type) && (
<Input disabled={disabled} onBlur={form.submit} />
)}
{hasArgOptions(arg) && (
<Select <Select
showSearch showSearch
disabled={disabled} disabled={disabled}
@@ -1584,7 +1545,7 @@ const NodeDefInspector: FC = () => {
rules={[{ required }]} rules={[{ required }]}
> >
<Select disabled={true}> <Select disabled={true}>
{["float", "int", "string", "code", "enum", "boolean"].map((value) => { {["float", "int", "string", "code", "boolean"].map((value) => {
return ( return (
<Select.Option key={value} value={value}> <Select.Option key={value} value={value}>
{value} {value}
+2 -2
View File
@@ -291,7 +291,7 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
const workspace = get(); const workspace = get();
let buildScript = dialog.showOpenDialogSync({ let buildScript = dialog.showOpenDialogSync({
properties: ["openFile"], properties: ["openFile"],
defaultPath: workspace.workdir, defaultPath: workspace.workdir.replaceAll("/", Path.sep),
filters: [{ name: "Javascript", extensions: ["js"] }], filters: [{ name: "Javascript", extensions: ["js"] }],
})?.[0]; })?.[0];
if (buildScript) { if (buildScript) {
@@ -412,7 +412,7 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
const workspace = get(); const workspace = get();
const scriptPath = dialog.showOpenDialogSync({ const scriptPath = dialog.showOpenDialogSync({
properties: ["openFile"], properties: ["openFile"],
defaultPath: workspace.workdir, defaultPath: workspace.workdir.replaceAll("/", Path.sep),
filters: [{ name: "Javascript", extensions: ["js"] }], filters: [{ name: "Javascript", extensions: ["js"] }],
})?.[0]; })?.[0];
if (scriptPath) { if (scriptPath) {
+2 -8
View File
@@ -1,13 +1,7 @@
import type { TreeGraphData as G6TreeGraphData } from "@antv/g6"; import type { TreeGraphData as G6TreeGraphData } from "@antv/g6";
import { NodeDef } from "../behavior3/src/behavior3"; import { NodeDef } from "../behavior3/src/behavior3";
declare module "../behavior3/src/behavior3" { export const VERSION = "1.8.1";
interface NodeDef {
group?: string[];
}
}
export const VERSION = "1.8.0";
export type NodeType = NodeDef["type"] | "Other" | "Error"; export type NodeType = NodeDef["type"] | "Other" | "Error";
export type NodeArg = Exclude<NodeDef["args"], undefined>[number]; export type NodeArg = Exclude<NodeDef["args"], undefined>[number];
@@ -16,9 +10,9 @@ export const isIntType = (type: string) => type.startsWith("int");
export const isFloatType = (type: string) => type.startsWith("float"); export const isFloatType = (type: string) => type.startsWith("float");
export const isStringType = (type: string) => type.startsWith("string"); export const isStringType = (type: string) => type.startsWith("string");
export const isBoolType = (type: string) => type.startsWith("bool"); export const isBoolType = (type: string) => type.startsWith("bool");
export const isEnumType = (type: string) => type.startsWith("enum");
export const isExprType = (type: string) => type.startsWith("expr") || type.startsWith("code"); export const isExprType = (type: string) => type.startsWith("expr") || type.startsWith("code");
export const isJsonType = (type: string) => type.startsWith("json"); export const isJsonType = (type: string) => type.startsWith("json");
export const hasArgOptions = (arg: NodeArg) => arg.options !== undefined;
export interface NodeModel { export interface NodeModel {
id: number; id: number;
+13 -17
View File
@@ -2,9 +2,9 @@ import * as fs from "fs";
import { ExpressionEvaluator, NodeDef } from "../behavior3/src/behavior3"; import { ExpressionEvaluator, NodeDef } from "../behavior3/src/behavior3";
import { import {
FileVarDecl, FileVarDecl,
hasArgOptions,
ImportDef, ImportDef,
isBoolType, isBoolType,
isEnumType,
isExprType, isExprType,
isFloatType, isFloatType,
isIntType, isIntType,
@@ -67,13 +67,7 @@ export const initWorkdir = (path: string, handler: typeof alertError) => {
nodeDefs = new NodeDefs(); nodeDefs = new NodeDefs();
for (const v of nodeDefData) { for (const v of nodeDefData) {
nodeDefs.set(v.name, v); nodeDefs.set(v.name, v);
v.group?.forEach((g) => groups.add(g));
const group = v.type.match(/\(([\w|]+)\)/)?.[1];
if (group) {
v.type = v.type.match(/^\w+/)![0] as NodeDef["type"];
v.group = group.split("|");
v.group.forEach((g) => groups.add(g));
}
} }
groupDefs = Array.from(groups).sort(); groupDefs = Array.from(groups).sort();
}; };
@@ -218,15 +212,6 @@ export const checkNodeArgValue = (
} }
hasError = true; hasError = true;
} }
} else if (isEnumType(type)) {
const isEnum = !!arg.options?.find((option) => option.value === value);
const isOptional = value === undefined && isNodeArgOptional(arg);
if (!(isEnum || isOptional)) {
if (verbose) {
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a one of the option values`);
}
hasError = true;
}
} else if (isExprType(type)) { } else if (isExprType(type)) {
const isExpr = typeof value === "string" && value; const isExpr = typeof value === "string" && value;
const isOptional = (value === undefined || value === "") && isNodeArgOptional(arg); const isOptional = (value === undefined || value === "") && isNodeArgOptional(arg);
@@ -257,6 +242,17 @@ export const checkNodeArgValue = (
error(data, `unknown arg type '${arg.type}'`); error(data, `unknown arg type '${arg.type}'`);
} }
if (hasArgOptions(arg)) {
const found = !!arg.options?.find((option) => option.value === value);
const isOptional = value === undefined && isNodeArgOptional(arg);
if (!(found || isOptional)) {
if (verbose) {
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a one of the option values`);
}
hasError = true;
}
}
return !hasError; return !hasError;
}; };