Skip to content

Commit 885c6cd

Browse files
v0.2.5 Release: P3DES Logic-Script Engine, Light Safety Guard, and Unicode support
1 parent a7b25ec commit 885c6cd

8 files changed

Lines changed: 394 additions & 22 deletions

File tree

demo/2026-3-22 2-19-35.jpg

339 KB
Loading

p3deditor/CommandInterpreter.pde

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ class CommandInterpreter {
77
HashMap<String, String> aliases = new HashMap<String, String>();
88
int recursionDepth = 0;
99
final int MAX_RECURSION = 10;
10+
ScriptManager scriptManager;
1011

1112
CommandInterpreter(SceneManager s) {
1213
this.scene = s;
1314
}
1415

16+
void setScriptManager(ScriptManager sm) {
17+
this.scriptManager = sm;
18+
}
19+
1520
String execute(String cmdLine) {
1621
if (cmdLine == null || cmdLine.trim().isEmpty()) return "";
1722

@@ -97,10 +102,24 @@ class CommandInterpreter {
97102
if (type.equals("cube")) { scene.addEntity("Cube", "Cube"); }
98103
else if (type.equals("sphere")) { scene.addEntity("Sphere", "Sphere"); }
99104
else if (type.equals("plane")) { scene.addEntity("Plane", "Plane"); }
100-
else if (type.equals("light") || type.equals("pointlight")) { scene.addEntity("Point Light", "PointLight"); }
105+
else if (type.equals("light") || type.equals("pointlight")) {
106+
// Safety Check: Total PointLights limited to 5
107+
int existingLights = 0;
108+
for (Entity e : scene.entities) if (e.type.equals("PointLight")) existingLights++;
109+
if (existingLights >= 5) return "Error: Maximum 5 point lights reached (Stability Guard)";
110+
111+
scene.addEntity("Light", "PointLight");
112+
}
101113
else return "Error: Unknown type: " + type;
102114
return "SUCCESS: Created " + type;
103115
}
116+
else if (rawCmd.equals("rename") || rawCmd.equals("name")) {
117+
if (parts.size() < 3) return "Error: rename <oldName> <newName>";
118+
Entity e = findEntity(parts.get(1));
119+
if (e == null) return "Error: Entity not found: " + parts.get(1);
120+
e.name = parts.get(2);
121+
return "SUCCESS: Renamed '" + parts.get(1) + "' to '" + parts.get(2) + "'";
122+
}
104123
else if (rawCmd.equals("alias")) {
105124
if (parts.size() == 1) {
106125
if (aliases.isEmpty()) return "No aliases registered.";
@@ -121,12 +140,42 @@ class CommandInterpreter {
121140
aliases.remove(parts.get(1).toLowerCase());
122141
return "SUCCESS: Removed alias '" + parts.get(1) + "'";
123142
}
124-
else if (rawCmd.equals("exec") || rawCmd.equals("run")) {
143+
else if (rawCmd.equals("clear") || rawCmd.equals("deleteall")) {
144+
scene.entities.clear();
145+
scene.selectedEntities.clear();
146+
scene.nextEntityId = 1;
147+
return "SUCCESS: Cleared entire scene";
148+
}
149+
else if (rawCmd.equals("run")) {
150+
if (parts.size() < 2) return "Error: run <filename.p3des>";
151+
String filename = parts.get(1);
152+
if (!filename.endsWith(".p3des")) filename += ".p3des";
153+
String[] lines = p3deditor.this.loadStrings(filename);
154+
if (lines == null) return "Error: Script not found: " + filename;
155+
if (scriptManager != null) {
156+
scriptManager.runScript(filename, String.join("\n", lines));
157+
return "SUCCESS: Started logic-script " + filename;
158+
}
159+
return "Error: ScriptManager not initialized";
160+
}
161+
else if (rawCmd.equals("stop")) {
162+
if (scriptManager != null) {
163+
scriptManager.stopAll();
164+
return "SUCCESS: Stopped all scripts";
165+
}
166+
return "Error: ScriptManager not initialized";
167+
}
168+
else if (rawCmd.equals("exec") || rawCmd.equals("script")) {
125169
if (parts.size() < 2) return "Error: exec <filename>";
126170
return executeScript(parts.get(1));
127171
}
172+
else if (rawCmd.equals("echo") || rawCmd.equals("print") || rawCmd.equals("say")) {
173+
StringBuilder sb = new StringBuilder();
174+
for (int i=1; i<parts.size(); i++) sb.append(parts.get(i)).append(i == parts.size()-1 ? "" : " ");
175+
return sb.toString();
176+
}
128177
else if (rawCmd.equals("help")) {
129-
return "CMDS: move, tp, color, scale, delete, alias, unalias, exec, help";
178+
return "CMDS: move, tp, color, scale, delete, rename, clear, echo, alias, unalias, exec, run, stop, help";
130179
}
131180
} catch (Exception ex) {
132181
return "Error: " + ex.getMessage();
@@ -162,6 +211,7 @@ class CommandInterpreter {
162211
char c = line.charAt(i);
163212
if (c == '\"') {
164213
inQuotes = !inQuotes;
214+
// DO NOT append the quote character itself
165215
} else if (c == ' ' && !inQuotes) {
166216
if (currentArg.length() > 0) {
167217
args.add(currentArg.toString());
@@ -178,7 +228,9 @@ class CommandInterpreter {
178228
}
179229

180230
Entity findEntity(String nameOrId) {
181-
for (Entity e : scene.entities) {
231+
// Search backwards to prioritize the NEWEST entity if multiple share a name
232+
for (int i = scene.entities.size() - 1; i >= 0; i--) {
233+
Entity e = scene.entities.get(i);
182234
if (e.name.equalsIgnoreCase(nameOrId)) return e;
183235
if (str(e.id).equals(nameOrId)) return e;
184236
}

p3deditor/SceneManager.pde

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,7 @@ class SceneManager {
2727
}
2828

2929
void render(PApplet app) {
30-
// Global Base Illumination
31-
app.ambientLight(50, 50, 50);
32-
app.lightSpecular(180, 180, 180);
33-
34-
// Note: The main lighting is now handled in p3deditor.draw() via PointLight entities.
35-
// We keep a very weak fill here just to prevent total blackness on backfaces.
36-
app.directionalLight(40, 45, 50, 0.5f, -0.2f, 0.5f);
30+
// Note: Global lighting is now handled in p3deditor.draw() to respect the 8-light limit.
3731

3832
// ONLY start rendering from root entities (hierarchical recursion handles children)
3933
for(Entity e : entities) {

p3deditor/ScriptEngine.pde

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import java.util.*;
2+
3+
/**
4+
* P3DE Logic-Script (P3DES) Engine
5+
* Supports multi-threaded, frame-by-frame script execution with wait, goto, and variables.
6+
*/
7+
8+
class ScriptContext {
9+
String scriptName;
10+
String[] lines;
11+
int pc = 0; // Program Counter
12+
13+
HashMap<String, Float> variables = new HashMap<String, Float>();
14+
HashMap<String, Integer> labels = new HashMap<String, Integer>();
15+
Stack<Integer> loopStack = new Stack<Integer>();
16+
17+
long waitUntil = 0;
18+
boolean terminated = false;
19+
Entity targetEntity = null; // "self"
20+
21+
ScriptContext(String name, String[] rawLines) {
22+
this.scriptName = name;
23+
this.lines = rawLines;
24+
preScanLabels();
25+
}
26+
27+
void preScanLabels() {
28+
for (int i = 0; i < lines.length; i++) {
29+
String line = lines[i].trim();
30+
if (line.startsWith(":")) {
31+
labels.put(line.substring(1).toLowerCase(), i);
32+
}
33+
}
34+
}
35+
36+
void update(CommandInterpreter interpreter) {
37+
if (terminated || pc >= lines.length) {
38+
terminated = true;
39+
return;
40+
}
41+
42+
if (p3deditor.this.millis() < waitUntil) return;
43+
44+
// Process one or more lines per frame until a WAIT or script end
45+
// To prevent infinite loops, we cap executions per frame
46+
int burstCap = 100;
47+
while (pc < lines.length && p3deditor.this.millis() >= waitUntil && burstCap > 0) {
48+
String rawLine = lines[pc].trim();
49+
String line = substituteVariables(rawLine);
50+
pc++;
51+
burstCap--;
52+
53+
if (line.isEmpty() || line.startsWith("#") || line.startsWith(":")) continue;
54+
55+
// Logic Commands
56+
String[] parts = line.split("\\s+");
57+
if (parts.length == 0) continue;
58+
String cmd = parts[0].toLowerCase();
59+
60+
if (cmd.equals("if")) {
61+
if (parts.length < 4) continue;
62+
boolean condition = evaluateCondition(parts[1], parts[2], parts[3]);
63+
if (!condition) {
64+
int depth = 1;
65+
while (pc < lines.length && depth > 0) {
66+
String l = lines[pc].trim().toLowerCase();
67+
if (l.startsWith("if")) depth++;
68+
if (l.startsWith("endif")) depth--;
69+
if (depth == 1 && l.startsWith("else")) { pc++; break; }
70+
pc++;
71+
}
72+
}
73+
} else if (cmd.equals("else")) {
74+
int depth = 1;
75+
while (pc < lines.length && depth > 0) {
76+
String l = lines[pc].trim().toLowerCase();
77+
if (l.startsWith("if")) depth++;
78+
if (l.startsWith("endif")) depth--;
79+
pc++;
80+
}
81+
} else if (cmd.equals("endif")) {
82+
// Just a marker
83+
} else if (cmd.equals("while")) {
84+
if (parts.length < 4) continue;
85+
loopStack.push(pc - 1); // Record the WHILE line
86+
boolean condition = evaluateCondition(parts[1], parts[2], parts[3]);
87+
if (!condition) {
88+
loopStack.pop();
89+
int depth = 1;
90+
while (pc < lines.length && depth > 0) {
91+
String l = lines[pc].trim().toLowerCase();
92+
if (l.startsWith("while")) depth++;
93+
if (l.startsWith("endwhile")) depth--;
94+
pc++;
95+
}
96+
}
97+
} else if (cmd.equals("endwhile")) {
98+
if (!loopStack.isEmpty()) {
99+
pc = loopStack.pop(); // Jump back to WHILE line
100+
}
101+
} else if (cmd.equals("for")) {
102+
if (parts.length < 5) continue;
103+
String vName = parts[1];
104+
float startV = Float.parseFloat(parts[2]);
105+
float endV = Float.parseFloat(parts[3]);
106+
float stepV = Float.parseFloat(parts[4]);
107+
108+
if (!variables.containsKey(vName)) variables.put(vName, startV);
109+
float curV = variables.get(vName);
110+
111+
loopStack.push(pc - 1);
112+
if ((stepV > 0 && curV >= endV) || (stepV < 0 && curV <= endV)) {
113+
loopStack.pop();
114+
variables.remove(vName); // Cleanup
115+
int depth = 1;
116+
while (pc < lines.length && depth > 0) {
117+
String l = lines[pc].trim().toLowerCase();
118+
if (l.startsWith("for")) depth++;
119+
if (l.startsWith("endfor")) depth--;
120+
pc++;
121+
}
122+
}
123+
} else if (cmd.equals("endfor")) {
124+
if (!loopStack.isEmpty()) {
125+
int forPc = loopStack.pop();
126+
String forLine = substituteVariables(lines[forPc].trim());
127+
String[] fParts = forLine.split("\\s+");
128+
String vName = fParts[1];
129+
float stepV = Float.parseFloat(fParts[4]);
130+
variables.put(vName, variables.get(vName) + stepV);
131+
pc = forPc; // Jump back to FOR line for condition check
132+
}
133+
} else if (cmd.equals("wait")) {
134+
if (parts.length > 1) {
135+
waitUntil = p3deditor.this.millis() + (long)Float.parseFloat(parts[1]);
136+
return;
137+
}
138+
} else if (cmd.equals("goto")) {
139+
if (parts.length > 1) {
140+
String lab = parts[1].toLowerCase();
141+
if (labels.containsKey(lab)) pc = labels.get(lab);
142+
}
143+
} else if (cmd.equals("set")) {
144+
if (parts.length > 2) {
145+
variables.put(parts[1], Float.parseFloat(parts[2]));
146+
}
147+
} else if (cmd.equals("add") || cmd.equals("sub") || cmd.equals("mul") || cmd.equals("div")) {
148+
if (parts.length > 2) {
149+
float cur = variables.getOrDefault(parts[1], 0f);
150+
float val = Float.parseFloat(parts[2]);
151+
if (cmd.equals("add")) cur += val;
152+
if (cmd.equals("sub")) cur -= val;
153+
if (cmd.equals("mul")) cur *= val;
154+
if (cmd.equals("div") && val != 0) cur /= val;
155+
variables.put(parts[1], cur);
156+
}
157+
} else {
158+
try {
159+
String result = interpreter.execute(line);
160+
if (result.startsWith("Error")) {
161+
terminated = true;
162+
p3deditor.this.ui.debugConsole.addLog("Script Terminated: " + result, 3);
163+
} else if (line.trim().toLowerCase().startsWith("echo") || line.trim().toLowerCase().startsWith("print")) {
164+
// Explicitly show echo messages in the terminal
165+
p3deditor.this.ui.debugConsole.addLog(result, 1);
166+
}
167+
} catch (Exception e) {
168+
terminated = true;
169+
p3deditor.this.ui.debugConsole.addLog("Script Crash: " + e.getMessage(), 3);
170+
}
171+
}
172+
}
173+
}
174+
175+
boolean evaluateCondition(String left, String op, String right) {
176+
try {
177+
float l = Float.parseFloat(left);
178+
float r = Float.parseFloat(right);
179+
if (op.equals("==")) return l == r;
180+
if (op.equals("!=")) return l != r;
181+
if (op.equals(">")) return l > r;
182+
if (op.equals("<")) return l < r;
183+
if (op.equals(">=")) return l >= r;
184+
if (op.equals("<=")) return l <= r;
185+
} catch (Exception e) {}
186+
return false;
187+
}
188+
189+
String substituteVariables(String text) {
190+
if (!text.contains("$")) return text;
191+
// Sort keys by length descending to prevent partial matches ($xPos vs $x)
192+
String[] keys = variables.keySet().toArray(new String[0]);
193+
Arrays.sort(keys, (a, b) -> Integer.compare(b.length(), a.length()));
194+
195+
for (String key : keys) {
196+
float val = variables.get(key);
197+
String valStr = (val == (long)val) ? String.valueOf((long)val) : String.valueOf(val);
198+
text = text.replace("$" + key, valStr);
199+
}
200+
return text;
201+
}
202+
}
203+
204+
class ScriptManager {
205+
ArrayList<ScriptContext> activeScripts = new ArrayList<ScriptContext>();
206+
CommandInterpreter interpreter;
207+
208+
ScriptManager(CommandInterpreter interpreter) {
209+
this.interpreter = interpreter;
210+
}
211+
212+
void runScript(String name, String content) {
213+
String[] lines = content.split("\\r?\\n");
214+
activeScripts.add(new ScriptContext(name, lines));
215+
System.out.println("P3DES: Started script " + name);
216+
}
217+
218+
void update() {
219+
for (int i = activeScripts.size() - 1; i >= 0; i--) {
220+
ScriptContext ctx = activeScripts.get(i);
221+
ctx.update(interpreter);
222+
if (ctx.terminated) {
223+
activeScripts.remove(i);
224+
System.out.println("P3DES: Finished script " + ctx.scriptName);
225+
}
226+
}
227+
}
228+
229+
void stopAll() {
230+
activeScripts.clear();
231+
}
232+
}

p3deditor/UIManager.pde

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class UIManager {
132132
}
133133

134134
void render() {
135+
p3deditor.this.textFont(p3deditor.this.mainFont);
135136
textAlign(LEFT, BASELINE);
136137
if (!showUI) return;
137138

@@ -769,7 +770,11 @@ class UIManager {
769770

770771
// Fallback: If console is not active and not editing inspector, allow shortcuts like Delete
771772
if (!debugConsole.active && !isEditingText()) {
772-
if (key == BACKSPACE || keyCode == DELETE) deleteSelection();
773+
boolean isDelete = (keyCode == DELETE || key == DELETE || key == 127);
774+
boolean isBackspace = (key == BACKSPACE || key == 8);
775+
if (isDelete || isBackspace) {
776+
deleteSelection();
777+
}
773778
}
774779
}
775780

0 commit comments

Comments
 (0)