Skip to content

Commit 916af8a

Browse files
committed
v3 - scripting language
1 parent 5579d04 commit 916af8a

10 files changed

+2483
-52
lines changed

LevelEditor.cs

+34-1
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,18 @@ private enum WindowId
7272
private static Rect[] wir;
7373

7474
public static bool editorMode { get; private set; } = false;
75+
static Coroutine fileWatcher = null;
7576
public static void StartEdit()
7677
{
7778
_init();
7879
editorMode = true;
7980
levelName = "";
8081
SceneManager.sceneLoaded += InitEditor;
8182
SceneManager.LoadScene(6);
83+
if (File.Exists(Path.Combine(Main.directory, "_temp.amta")))
84+
File.Delete(Path.Combine(Main.directory, "_temp.amta"));
85+
if(fileWatcher != null)
86+
Coroutines.StopCoroutine(fileWatcher);
8287
}
8388
const float clickGizmoScaleFactor = 0.1f;
8489
static List<GameObject> clickGizmo;
@@ -336,6 +341,10 @@ void recurence(string name)
336341
dd_level = true;
337342
Time.timeScale = 0f;
338343
SelectedObject.Deselect();
344+
345+
File.WriteAllText(Path.Combine(Main.directory, "_temp.amta"), "# Any code written below will be executed every time the level is (re)started.\n# Be sure to use LF instead of CRLF line endings.\n# Any modifications to this file will be reflected by marking the level as modified (unsaved).\n# Saving the level will also save this script.\n# Automata documentation: https://github.com/devilExE3/automataV2/blob/master/automata.md\n# KME API: https://github.com/karlsonmodding/KarlsonMapEditor/wiki/Scripting-API\n$:print(\"Hello, world!\")");
346+
fileWatcher = Coroutines.StartCoroutine(FileWatcher());
347+
//Process.Start(Path.Combine(Main.directory, "_temp.amta"));
339348
}
340349
}
341350

@@ -377,6 +386,7 @@ void recurence(string file)
377386
if (GUI.Button(new Rect(200, 0, 100, 20), "Tex Browser")) tex_browser_enabled = !tex_browser_enabled;
378387
if (GUI.Button(new Rect(300, 0, 100, 20), "KMP Export")) KMPExporter.Export(levelName, globalObject, textures);
379388
GUI.Label(new Rect(405, 0, 1000, 20), $"<b>Karlson Map Editor</b> v2.0 | Current map: <b>{(unsaved ? (levelName + '*') : levelName)}</b> | Object count: <b>{_countAll.Item1 + _countAll.Item2}</b> | Hold <b>right click</b> down to move and look around | Select an object by <b>middle clicking</b> it");
389+
if (GUI.Button(new Rect(Screen.width - 100, 0, 100, 20), "Open Script")) Process.Start(Path.Combine(Main.directory, "_temp.amta"));
380390

381391
if (dd_file)
382392
{
@@ -1123,6 +1133,8 @@ private static void ExitEditor()
11231133
tex_browser_enabled = false;
11241134
editorMode = false;
11251135
Game.Instance.MainMenu();
1136+
if(fileWatcher != null)
1137+
Coroutines.StopCoroutine(fileWatcher);
11261138
}
11271139

11281140
public static byte[] MakeScreenshot()
@@ -1198,7 +1210,7 @@ private static byte[] SaveLevelData()
11981210
using (MemoryStream ms = new MemoryStream())
11991211
using (BinaryWriter bw = new BinaryWriter(ms))
12001212
{
1201-
bw.Write(3);
1213+
bw.Write(4);
12021214
bw.Write(gridAlign);
12031215
bw.Write(startingGun);
12041216
bw.Write(startPosition);
@@ -1211,6 +1223,7 @@ private static byte[] SaveLevelData()
12111223
bw.Write(data.Length);
12121224
bw.Write(data);
12131225
}
1226+
bw.Write(File.ReadAllText(Path.Combine(Main.directory, "_temp.amta")));
12141227
bw.WriteByteArray(SaveObjectGroup(globalObject));
12151228
bw.Flush();
12161229
return SevenZipHelper.Compress(ms.ToArray());
@@ -1295,6 +1308,26 @@ IEnumerator markAsSaved()
12951308
unsaved = false;
12961309
}
12971310
Coroutines.StartCoroutine(markAsSaved());
1311+
1312+
// scripting
1313+
File.WriteAllText(Path.Combine(Main.directory, "_temp.amta"), data.AutomataScript.Trim().Length > 0 ? data.AutomataScript : "# Any code written below will be executed every time the level is (re)started.\n# Be sure to use LF instead of CRLF line endings.\n# Any modifications to this file will be reflected by marking the level as modified (unsaved).\n# Saving the level will also save this script.\n# Automata documentation: https://github.com/devilExE3/automataV2/blob/master/automata.md\n# KME API: https://github.com/karlsonmodding/KarlsonMapEditor/wiki/Scripting-API\n$:print(\"Hello, world!\")");
1314+
fileWatcher = Coroutines.StartCoroutine(FileWatcher());
1315+
//Process.Start(Path.Combine(Main.directory, "_temp.amta"));
1316+
}
1317+
1318+
static IEnumerator FileWatcher()
1319+
{
1320+
DateTime lastMod = new FileInfo(Path.Combine(Main.directory, "_temp.amta")).LastWriteTime;
1321+
while(true)
1322+
{
1323+
yield return new WaitForSecondsRealtime(1f);
1324+
var modNow = new FileInfo(Path.Combine(Main.directory, "_temp.amta")).LastWriteTime;
1325+
if (modNow != lastMod)
1326+
{
1327+
lastMod = modNow;
1328+
MarkAsModified();
1329+
}
1330+
}
12981331
}
12991332

13001333
private static IEnumerator identifyObject(GameObject go)

LevelPlayer.cs

+140-50
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using HarmonyLib;
2+
using KarlsonMapEditor.Scripting_API;
23
using Loadson;
34
using LoadsonAPI;
45
using SevenZip.Compression.LZMA;
@@ -23,11 +24,19 @@ public static class LevelPlayer
2324
public static string currentLevel { get; private set; } = "";
2425
public static void ExitedLevel() => currentLevel = "";
2526
private static LevelData levelData;
27+
private static Automata.Backbone.FunctionRunner mainFunction = null;
28+
public static ScriptRunner currentScript { get; private set; } = null;
2629

2730
public static void LoadLevel(string levelPath)
2831
{
2932
currentLevel = Path.GetFileName(levelPath);
3033
levelData = new LevelData(File.ReadAllBytes(levelPath));
34+
if(levelData.AutomataScript.Trim().Length > 0)
35+
{ // load level script
36+
var tokens = Automata.Parser.Tokenizer.Tokenize(Automata.Parser.ProgramCleaner.CleanProgram(levelData.AutomataScript));
37+
var program = new Automata.Parser.ProgramParser(tokens).ParseProgram();
38+
mainFunction = new Automata.Backbone.FunctionRunner(new List<(Automata.Backbone.VarResolver, Automata.Backbone.BaseValue.ValueType)> { }, program);
39+
}
3140
SceneManager.sceneLoaded += LoadLevelData;
3241
UnityEngine.Object.FindObjectOfType<Lobby>().LoadMap("4Escape0");
3342
}
@@ -69,6 +78,7 @@ void ReplicateObjectGroup(LevelData.ObjectGroup group, GameObject parentObject)
6978
if (obj.IsPrefab)
7079
{
7180
go = LevelData.MakePrefab(obj.PrefabId);
81+
go.name = obj.Name;
7282
go.transform.parent = objGroup.transform;
7383
go.transform.localPosition = obj.Position;
7484
go.transform.localRotation = Quaternion.Euler(obj.Rotation);
@@ -96,11 +106,11 @@ void ReplicateObjectGroup(LevelData.ObjectGroup group, GameObject parentObject)
96106
if (obj.Glass)
97107
{
98108
go = LoadsonAPI.PrefabManager.NewGlass();
109+
// fix collider
110+
go.GetComponent<BoxCollider>().size = Vector3.one;
99111
if (obj.DisableTrigger)
100112
{
101-
// fix collider
102113
go.GetComponent<BoxCollider>().isTrigger = false;
103-
go.GetComponent<BoxCollider>().size = Vector3.one;
104114
UnityEngine.Object.Destroy(go.GetComponent<Glass>());
105115
}
106116
}
@@ -122,10 +132,22 @@ void ReplicateObjectGroup(LevelData.ObjectGroup group, GameObject parentObject)
122132
go.GetComponent<MeshRenderer>().material.color = obj._Color;
123133
if (obj.Bounce)
124134
go.GetComponent<BoxCollider>().material = LoadsonAPI.PrefabManager.BounceMaterial();
135+
go.name = obj.Name;
125136
go.transform.parent = objGroup.transform;
126137
go.transform.localPosition = obj.Position;
127138
go.transform.localRotation = Quaternion.Euler(obj.Rotation);
128139
go.transform.localScale = obj.Scale;
140+
if(obj.Glass)
141+
{
142+
// fix particle system
143+
var ps = go.GetComponent<Glass>().glass.GetComponent<ParticleSystem>();
144+
var shape = ps.shape;
145+
shape.scale = go.transform.lossyScale;
146+
var volume = (shape.scale.x * shape.scale.y * shape.scale.z);
147+
var main = ps.main;
148+
main.maxParticles = Math.Max((int)(1000f * volume / 160f), 1);
149+
main.startSpeed = 0.5f;
150+
}
129151
}
130152
foreach (var grp in group.Groups)
131153
ReplicateObjectGroup(grp, objGroup);
@@ -155,6 +177,12 @@ IEnumerator enemyFix()
155177
}
156178
}
157179
Coroutines.StartCoroutine(enemyFix());
180+
181+
// load script
182+
if (mainFunction != null)
183+
currentScript = new ScriptRunner(mainFunction);
184+
else
185+
currentScript = null;
158186
}
159187

160188
void LoadLevelData_v2()
@@ -251,33 +279,6 @@ public class LevelData
251279

252280
public LevelData(byte[] _data)
253281
{
254-
ObjectGroup ReadObjectGroup(byte[] group)
255-
{
256-
ObjectGroup objGroup = new ObjectGroup();
257-
using(BinaryReader br = new BinaryReader(new MemoryStream(group)))
258-
{
259-
objGroup.Name = br.ReadString();
260-
objGroup.Position = br.ReadVector3();
261-
objGroup.Rotation = br.ReadVector3();
262-
objGroup.Scale = br.ReadVector3();
263-
int count = br.ReadInt32();
264-
while (count-- > 0)
265-
{
266-
bool prefab = br.ReadBoolean();
267-
string name = br.ReadString();
268-
if (prefab)
269-
objGroup.Objects.Add(new LevelObject(br.ReadInt32(), br.ReadVector3(), br.ReadVector3(), br.ReadVector3(), name, br.ReadInt32()));
270-
else
271-
objGroup.Objects.Add(new LevelObject(br.ReadVector3(), br.ReadVector3(), br.ReadVector3(), br.ReadInt32(), br.ReadColor(), name, br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean()));
272-
Loadson.Console.Log(objGroup.Objects.Last().ToString());
273-
}
274-
count = br.ReadInt32();
275-
while (count-- > 0)
276-
objGroup.Groups.Add(ReadObjectGroup(br.ReadByteArray()));
277-
return objGroup;
278-
}
279-
}
280-
281282
// decompress
282283
byte[] data = SevenZipHelper.Decompress(_data);
283284
using(BinaryReader br = new BinaryReader(new MemoryStream(data)))
@@ -288,27 +289,10 @@ ObjectGroup ReadObjectGroup(byte[] group)
288289
LoadLevel_Version1(br);
289290
else if (version == 2)
290291
LoadLevel_Version2(br);
291-
else if(version == 3)
292-
{
293-
isKMEv2 = true;
294-
gridAlign = br.ReadSingle();
295-
startingGun = br.ReadInt32();
296-
startPosition = br.ReadVector3();
297-
startOrientation = br.ReadSingle();
298-
int _len;
299-
List<Texture2D> list = new List<Texture2D>();
300-
int _texl = br.ReadInt32();
301-
while (_texl-- > 0)
302-
{
303-
string _name = br.ReadString();
304-
_len = br.ReadInt32();
305-
list.Add(new Texture2D(1, 1));
306-
list.Last().LoadImage(br.ReadBytes(_len));
307-
list.Last().name = _name;
308-
}
309-
Textures = list.ToArray();
310-
GlobalObject = ReadObjectGroup(br.ReadByteArray());
311-
}
292+
else if (version == 3)
293+
LoadLevel_Version3(br);
294+
else if (version == 4)
295+
LoadLevel_Version4(br);
312296
else
313297
{
314298
Loadson.Console.Log("<color=red>Unknown level version " + version + "</color>");
@@ -317,6 +301,33 @@ ObjectGroup ReadObjectGroup(byte[] group)
317301
}
318302
}
319303

304+
ObjectGroup ReadObjectGroup_v3(byte[] group)
305+
{
306+
ObjectGroup objGroup = new ObjectGroup();
307+
using (BinaryReader br = new BinaryReader(new MemoryStream(group)))
308+
{
309+
objGroup.Name = br.ReadString();
310+
objGroup.Position = br.ReadVector3();
311+
objGroup.Rotation = br.ReadVector3();
312+
objGroup.Scale = br.ReadVector3();
313+
int count = br.ReadInt32();
314+
while (count-- > 0)
315+
{
316+
bool prefab = br.ReadBoolean();
317+
string name = br.ReadString();
318+
if (prefab)
319+
objGroup.Objects.Add(new LevelObject(br.ReadInt32(), br.ReadVector3(), br.ReadVector3(), br.ReadVector3(), name, br.ReadInt32()));
320+
else
321+
objGroup.Objects.Add(new LevelObject(br.ReadVector3(), br.ReadVector3(), br.ReadVector3(), br.ReadInt32(), br.ReadColor(), name, br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean()));
322+
Loadson.Console.Log(objGroup.Objects.Last().ToString());
323+
}
324+
count = br.ReadInt32();
325+
while (count-- > 0)
326+
objGroup.Groups.Add(ReadObjectGroup_v3(br.ReadByteArray()));
327+
return objGroup;
328+
}
329+
}
330+
320331
private void LoadLevel_Version1(BinaryReader br)
321332
{
322333
isKMEv2 = false;
@@ -349,6 +360,7 @@ private void LoadLevel_Version1(BinaryReader br)
349360
objects.Add(new LevelObject(br.ReadVector3(), br.ReadVector3(), br.ReadVector3(), br.ReadInt32(), br.ReadColor(), name, group, br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean(), false));
350361
}
351362
Objects = objects.ToArray();
363+
AutomataScript = "";
352364
}
353365

354366
private void LoadLevel_Version2(BinaryReader br)
@@ -383,6 +395,53 @@ private void LoadLevel_Version2(BinaryReader br)
383395
objects.Add(new LevelObject(br.ReadVector3(), br.ReadVector3(), br.ReadVector3(), br.ReadInt32(), br.ReadColor(), name, group, br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean(), br.ReadBoolean()));
384396
}
385397
Objects = objects.ToArray();
398+
AutomataScript = "";
399+
}
400+
401+
private void LoadLevel_Version3(BinaryReader br)
402+
{
403+
isKMEv2 = true;
404+
gridAlign = br.ReadSingle();
405+
startingGun = br.ReadInt32();
406+
startPosition = br.ReadVector3();
407+
startOrientation = br.ReadSingle();
408+
int _len;
409+
List<Texture2D> list = new List<Texture2D>();
410+
int _texl = br.ReadInt32();
411+
while (_texl-- > 0)
412+
{
413+
string _name = br.ReadString();
414+
_len = br.ReadInt32();
415+
list.Add(new Texture2D(1, 1));
416+
list.Last().LoadImage(br.ReadBytes(_len));
417+
list.Last().name = _name;
418+
}
419+
Textures = list.ToArray();
420+
GlobalObject = ReadObjectGroup_v3(br.ReadByteArray());
421+
AutomataScript = "";
422+
}
423+
424+
private void LoadLevel_Version4(BinaryReader br)
425+
{
426+
isKMEv2 = true;
427+
gridAlign = br.ReadSingle();
428+
startingGun = br.ReadInt32();
429+
startPosition = br.ReadVector3();
430+
startOrientation = br.ReadSingle();
431+
int _len;
432+
List<Texture2D> list = new List<Texture2D>();
433+
int _texl = br.ReadInt32();
434+
while (_texl-- > 0)
435+
{
436+
string _name = br.ReadString();
437+
_len = br.ReadInt32();
438+
list.Add(new Texture2D(1, 1));
439+
list.Last().LoadImage(br.ReadBytes(_len));
440+
list.Last().name = _name;
441+
}
442+
Textures = list.ToArray();
443+
AutomataScript = br.ReadString();
444+
GlobalObject = ReadObjectGroup_v3(br.ReadByteArray());
386445
}
387446

388447
public bool isKMEv2;
@@ -395,6 +454,8 @@ private void LoadLevel_Version2(BinaryReader br)
395454
public LevelObject[] Objects;
396455
public ObjectGroup GlobalObject;
397456

457+
public string AutomataScript;
458+
398459
public class LevelObject
399460
{
400461
// kme v2 removed group names
@@ -586,4 +647,33 @@ static void Postfix()
586647
SceneManager.sceneLoaded -= LevelPlayer.LoadLevelData;
587648
}
588649
}
650+
651+
[HarmonyPatch(typeof(Glass), "OnTriggerEnter")]
652+
class Hook_Glass_OnTriggerEnter
653+
{
654+
static bool Prefix(Glass __instance, Collider other)
655+
{
656+
if (LevelPlayer.currentLevel == "") return true;
657+
if (LevelPlayer.currentScript == null) return true;
658+
// determine break reason
659+
if (other.gameObject.layer == LayerMask.NameToLayer("Ground"))
660+
return false; // collide with ground, ignore
661+
int reason = 0;
662+
if (other.gameObject.layer == LayerMask.NameToLayer("Player"))
663+
reason = 1;
664+
if (other.gameObject.layer == LayerMask.NameToLayer("Bullet"))
665+
{
666+
if (other.gameObject.name == "Damage")
667+
reason = 3;
668+
else
669+
reason = 2;
670+
}
671+
var ret = LevelPlayer.currentScript.InvokeFunction("onbreak", __instance, other, reason);
672+
if (!ret.HoldsTrue() || ret.Type != Automata.Backbone.BaseValue.ValueType.Number) return true; // continue normal execution
673+
var retN = (double)ret.Value;
674+
if(retN > 0) // insta-break
675+
UnityEngine.Object.Destroy(__instance.gameObject);
676+
return false;
677+
}
678+
}
589679
}

LevelTimeDB.cs

+5
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ public class Hook_Game_Win
5454
public static bool Prefix(Game __instance)
5555
{
5656
if (LevelPlayer.currentLevel == "") return true;
57+
if(LevelPlayer.currentScript != null)
58+
{
59+
var ret = LevelPlayer.currentScript.InvokeFunction("onwin");
60+
if (ret.HoldsTrue()) return false;
61+
}
5762
__instance.playing = false;
5863
Timer.Instance.Stop();
5964
Time.timeScale = 0.05f;

0 commit comments

Comments
 (0)